In Lesson 1 you knew nothing about each person. Here you get one extra fact about each — and a choice about what to do with it.
Every spoken-aloud quote in a film adaptation maps to a page in the source book. Plot minute vs. page, drag a line by hand, read what the intercept and slope mean.
Each point is a quote from Peter Jackson's Fellowship of the Ring theatrical cut. The x-coordinate is what minute it appears in the film. The y-coordinate is what page it appears on in the book.
Drag the α (intercept) and β (slope) sliders to fit a line by eye. Then read what each parameter is:
"Pages per minute" is a quantity you can imagine without learning new vocabulary. The same arithmetic — α, β, residual, fit-to-minimize-SSR — will appear with body weight and finch beaks for the rest of the lesson. The math is the new thing in the room; the data should not also be new.
Same setup as Lesson 1 Stage B, with weight instead of height. Move the slider, watch the errors.
NHANES adult weights. The slider is your constant guess. Gray = the truth, red = your signed errors. Move the slider and watch the two distributions move relative to each other.
nh <- read.csv("data/clean/nhanes_adults.csv")y <- nh$Weightmean(y); median(y); sd(y)guess <- 81.0err <- guess - ymean(err^2) # MSE -- minimized by guess = meanmean(abs(err)) # MAE -- minimized by guess = median
Same dataset. One new piece of information per person.
Each NHANES adult has a height and a weight. Blue line: a sloped fit of weight on height. Dashed gray: the flat guess from Stage A.
Toggle the leftover bars to see each vertical miss, and watch how far they spread compared with Stage A's.
lm(weight ~ height). What is that number actually predicting?nh <- read.csv("data/clean/nhanes_adults.csv")set.seed(42)idx <- sample(nrow(nh), 400)d <- nh[idx, ]fit <- lm(Weight ~ Height, data = d)summary(fit)sd(residuals(fit)) # residual SD -- smaller than sd(d$Weight)plot(d$Height, d$Weight, pch = 16, col = "#444", xlab = "height (cm)", ylab = "weight (kg)")abline(fit, col = "#2f6b8f", lwd = 2)abline(h = mean(d$Weight), col = "gray60", lty = 2)
Five scatters. Each has a straight-line fit. Some are wrong on purpose. Pick the residual pattern. Move on.
Each round: a scatter with a fitted line (top) and a residuals plot (bottom). Pick the pattern. Five rounds, real data each time.
# Round 1: mammal mass vs gestation, log-log (clean)m <- read.csv("data/clean/pantheria_mammals.csv")fit <- lm(log(AdultBodyMass_g) ~ log(GestationLen_d), data = m)plot(fitted(fit), residuals(fit), pch = 16)abline(h = 0, col = "#b23a48")# Same plot, linear-linear: curvature appearsplot(lm(AdultBodyMass_g ~ GestationLen_d, data = m))
Real growth data for both kids. One measurement is hidden per round. Click where you think it goes; the reveal tells you what was actually there.
One kid's mass-by-age trajectory with a single point hidden. WHO median in gray. Click where you think the missing point belongs.
The reveal color tells you something about that point. Six rounds.
k <- read.csv("data/clean/kids_growth.csv")b <- subset(k, kid == "beren" & measure == "mass")fit <- lm(value ~ poly(age_years, 2), data = b)b$residual <- residuals(fit)# compare residuals by sick_proxy flagaggregate(residual ~ sick_proxy, data = b, mean)boxplot(residual ~ sick_proxy, data = b, col = c("#6f8a4a", "#a86a1a"))
The downloaded .R for Stage D shows the boxplot move: pull residuals from the smooth "mass = f(age)" fit on Beren, then split them by sick_proxy. Do it. Are the sick-day residuals systematically more negative? Refit including sick_proxy as a predictor and report the new residual SD.
Every quote from The Lord of the Rings film adaptations, plotted as book page against film minute. Fit one line to all of them — read R². Then color by which film the quote came from.
Three film adaptations of Tolkien's trilogy. Each colored point is one quote spoken on screen: the x coordinate is what minute it appears in the film, the y coordinate is what page it appears on in the source book.
If the films were perfect linear adaptations, every point would sit on one line. They don't. The residuals from a pooled fit look like noise — until you color them.
lotr <- read.csv("data/clean/lotr_quotes.csv")# Pooled fit ignores 'film'pooled <- lm(page ~ minute, data = lotr)# Stratified fit lets each film have its own slopestrat <- lm(page ~ minute * film, data = lotr)summary(pooled)$r.squared; summary(strat)$r.squared
The pooled fit ignored "film". In a DAG, "film" is a categorical node that affects both minute (when does this film's runtime end?) and page (which book is this film adapting?). Build that DAG below and watch the scatter behave: pooled vs colored-by-film. This is the same picture you saw above, generated from a causal model you control.
No prediction, no controls. The same fit-and-leftover move, applied to the anatomy example from lecture.
Ten mammals. x = the direct distance from the brainstem to the larynx (cm) — the path a sensible engineer would route the nerve down. y = the actual length of the recurrent laryngeal nerve (cm) — the path evolution actually took, looping under the aortic arch at the heart and back up.
The line is fit to the nine non-giraffe mammals. The giraffe is plotted in red — and the residual panel below shows how far off the line it sits.
Approximate values from comparative-anatomy references (Wedel 2012 for giraffe; standard texts for the rest). Treat as pedagogical rather than research-grade.
The nine-mammal fit says: for every 1 cm of direct brain-to-larynx distance, expect roughly 2–3 cm of nerve. For a giraffe, with a ~25 cm direct distance, the line predicts about 60 cm.
The giraffe's actual nerve is roughly 270 cm — it runs the full length of the neck, loops under an artery at the heart, and comes back up. That gap between predicted and actual is the leftover you have been drawing all lesson, now at its most extreme.
Same machine you used on Beren's sick-day weights. So ask yourself: is a leftover this large the kind of scatter a sharper measurement would shrink — or is it the "what should a nerve do?" line running up against something it was never built to see? Hold your answer. You will meet leftovers like this one at every scale of life, and each time the same question decides what they mean.
v <- read.csv("data/clean/vagus_nerve.csv")non_g <- subset(v, species != "giraffe")fit <- lm(rln_length_cm ~ direct_cm, data = non_g)# predict the giraffe from the non-giraffe regressiongiraffe <- subset(v, species == "giraffe")pred <- predict(fit, newdata = giraffe)giraffe$rln_length_cm - pred # the residual: ~200 cm