Chapter 3
Bayesian linear models
Lectures 5–6 · the Gaussian model, the regression line, and what a coefficient means once the graph is drawn
More published science rests on the linear model than on any other statistical tool. It is the first model taught and the last one abandoned, and it is almost always the right place to begin. Its form is simple: the average of an outcome is a straight-line function of some predictors, and the outcomes scatter around that average with a bell-shaped spread. Every model in the rest of these notes, from logistic regression to neural networks, is a linear model with something changed.
The linear model's simplicity is also its trap. Fitting a line is easy; knowing what the fitted numbers mean is not. A regression of weight on height returns a slope of about 0.9 kg per centimetre. Is that how much heavier you would become if you grew a centimetre? How much heavier a taller person is, on average, among people like those in the sample? How much of the difference between men and women it explains? These are different questions, and the same coefficient answers at most one of them. Which one depends on the causal graph, not on the regression output. This chapter teaches the linear model and its Bayesian estimation, but its central lesson is the one in the cartoon: a coefficient is an answer, and the graph tells you which question it answers.
- Lecture 5 builds the model. We will see why bell curves turn up everywhere, estimate the average height of American women from a national health survey (finding a trap in the data on the way), fit a line relating weight to height, separate the two kinds of uncertainty every prediction carries, and use a residual check to discover that the 19th-century formula for the body mass index is not quite right.
- Lecture 6 asks what the coefficients mean. We will add a categorical predictor, separate a total effect from a direct one, watch a striking association between college majors and earnings shrink by half when we account for field of study, learn which "control variables" help and which do harm, and bend the line into a curve to measure when children grow fastest.
Think About It: Before You Start
Men in the United States are, on average, about 12 kg heavier than women. Men are also about 14 cm taller. Suppose you fit a regression of weight on sex and height. What do you expect the coefficient on sex to be, and what question would that number answer? Write down your guess; Section 3.7 gives the answer from data on 4,600 adults.
Lecture 5 — Lines, and the uncertainty around them
3.1 Why the bell curve keeps appearing
Measure the heights of a few thousand women and draw a histogram. You will get a bell: a single hump, roughly symmetric, falling away on both sides at a characteristic rate. Do the same for the lengths of beetles, the errors of a careful surveyor, the daily changes of a temperature reading, or the scores on a well-designed test, and you will keep getting bells. The normal (or Gaussian) distribution is so common that it is easy to forget it needs explaining. There are two explanations, and both matter for modelling.
The first is mechanical. A woman's adult height is the end result of many small influences: dozens of genetic variants each adding or subtracting a millimetre or two, nutrition in each year of childhood, illnesses, sleep, and luck. When an outcome is a sum of many small, roughly independent contributions, the contributions in each direction tend to cancel, the most common totals pile up in the middle, and extreme totals (which need nearly everything to push the same way) are rare. Whatever the shape of the individual contributions, the shape of the sum approaches the normal curve. This is the central limit theorem, and you can watch it work below.
Start with one flat influence: the histogram is flat, nothing like a bell. With two, it is a triangle. By five or six it is hard to tell from the curve, even when each influence is a coin flip or badly skewed. Now switch to multiply with many influences of ±5%. The result is a bell that leans to the right, with a long upper tail. That shape is the log-normal: when effects multiply, their logarithms add, so the logarithm of the outcome is normal. Body weight, incomes, and city sizes all grow multiplicatively and all have long right tails. Keep that in mind; it will matter in Section 3.5 and again in Section 3.8.
The second explanation is epistemic, and you met it in Section 2.8. If all you are prepared to say about a quantity is its mean and its variance, the normal distribution is the maximum-entropy distribution with that mean and variance: the one that assumes nothing else. Even when you have no idea what mechanism produced a variable, using a normal model for it commits you to nothing beyond a centre and a spread. That is why the normal is a sensible default likelihood for a continuous outcome, and also why it can fail: if the data have structure beyond a centre and a spread (two humps, a hard boundary, a long tail), the normal model will not see it. Section 3.5 shows how to look.
History: Galton's Board and the Origin of "Regression"
In the 1870s Francis Galton built a board studded with pins, the quincunx, and poured lead shot through it. Each ball bounced left or right at every pin, and the balls piled up at the bottom in a bell: a sum of coin flips, exactly as in the widget. Galton was fascinated by heredity, and in 1886 he published the heights of 928 adult children and their parents. Tall parents had tall children, but on average less extremely tall; short parents had children less extremely short. He called this "regression towards mediocrity", and the line he drew through the data became the first regression line. The phenomenon has nothing to do with heredity in particular: whenever two measurements are imperfectly correlated, extreme values of one are paired, on average, with less extreme values of the other. The name stuck to the whole method. Much earlier, in 1809, Carl Friedrich Gauss had derived the normal curve by asking which distribution of measurement errors would make the ordinary average the best estimate of a true value, an argument that was Bayesian in all but name, and had used it to justify least squares in astronomy.
Check Yourself
A sum of many small additive effects gives a normal; is growth additive here?
Constant percentage growth is not the same as a fixed upper limit.
Percentage growth means the influences multiply. Logarithms turn products into sums, so log mass is a sum of many small terms and is approximately normal. Mass itself is log-normal: skewed, with a long right tail.
3.2 A model of one variable: how tall are American women?
Our data come from the National Health and Nutrition Examination Survey (NHANES), which the US Centers for Disease Control and Prevention runs continuously. Each year about 5,000 people are selected from across the country, interviewed at home, and then examined in a mobile clinic where trained staff measure height, weight, blood pressure and much more. We use the 2009–2012 cycles as packaged in the NHANES R package. First, the setup for the chapter.
library(tidyverse)
library(brms)
library(NHANES) # US national health survey, 2009-2012
library(dagitty)
options(mc.cores = parallel::detectCores()) # run Markov chains in parallel
harbour <- c(ink = "#10191d", muted = "#4a5f68", grid = "#d5e3e6",
sky = "#1d5187", amber = "#805907", rose = "#ad3b28",
moss = "#1d6b54", lilac = "#5b3a94")
theme_harbour <- function(base_size = 11) {
theme_minimal(base_size = base_size) +
theme(text = element_text(colour = harbour[["ink"]]),
axis.text = element_text(colour = harbour[["muted"]]),
panel.grid.major = element_line(colour = harbour[["grid"]]),
panel.grid.minor = element_blank(),
strip.text = element_text(colour = harbour[["ink"]], hjust = 0),
legend.position = "top")
}
theme_set(theme_harbour())
Before modelling anything, look at how the data were made. Here is the first surprise.
nh <- NHANES |> select(ID, SurveyYr, Gender, Age, AgeMonths, Height, Weight)
c(rows = nrow(nh), people = n_distinct(nh$ID))
count(nh, ID, name = "copies") |> count(copies, name = "people") # how often people repeat
adults <- nh |>
distinct(ID, .keep_all = TRUE) |> # keep one row per person
filter(Age >= 20) |>
drop_na(Height, Weight)
women <- filter(adults, Gender == "female")
c(adults = nrow(adults), women = nrow(women))
rows people
10000 6779
copies people
1 1 4646
2 2 1413
3 3 461
4 4 177
5 5 60
6 6 18
7 7 3
8 8 1
adults women
4609 2373
The package's data frame has 10,000 rows but only 6,779 different people: 2,133 people appear more than once, one of them eight times. This is not a mistake. NHANES deliberately oversamples some groups (older people, and Black, Hispanic and low-income Americans) so that it can estimate their health accurately, and every respondent carries a survey weight saying how many Americans they stand for. The package's authors turned the weighted survey into something that looks like a simple random sample by drawing 10,000 rows from it with replacement, in proportion to the weights. Heavily weighted people were drawn several times.
That is fine for drawing histograms, but it is poison for inference. A model that treats the rows as independent people believes it has 10,000 independent observations when it has 6,779, and its posterior will be too narrow. Repeating every person twice would make every standard deviation of the posterior too small by a factor of . We keep one row per person, which leaves 4,609 adults with both height and weight measured, 2,373 of them women.
Research Practice: Know How Your Data Were Made
Reviewers of applied papers ask, before anything else, where the data came from and how the units were selected. Before you fit a model:
- Count your units, not your rows. Check for duplicates (
n_distinct(id)againstnrow()), repeated measures, and multiple records per unit. Report the number of distinct units in the paper. - Read the documentation of every derived dataset. A teaching version of a survey, a Kaggle upload, or a colleague's "cleaned" file may differ from the source in ways that matter.
- Know the design. National surveys such as NHANES, the American Community Survey and the British Household Panel use oversampling, clustering and weights. A population estimate that ignores them can be badly biased. The full answer for NHANES is to model the raw data with its weights and design, or to use multilevel regression and poststratification (Chapter 6). We will treat our de-duplicated sample as if it were a simple random sample of US adults, which is good enough to learn the methods and not good enough for a publication about the US population.
Check Yourself
A dataset accidentally contains every person exactly three times. By roughly what factor are the posterior standard deviations of a mean too small, if the duplicates are treated as independent people?
A posterior standard deviation for a mean shrinks like . What happens to it when is multiplied by 3?
The posterior standard deviation of a mean is about . Tripling divides it by , so the reported uncertainty is about 1.73 times too small, although the duplicates contain no new information at all.
Now look at the heights. Adult heights of men and women together form a lumpy distribution, because they are a mixture of two groups with different averages. Within each sex, the bell shape is clear.
stats <- adults |> group_by(Gender) |> summarise(mean = mean(Height), sd = sd(Height))
stats
curves <- reframe(stats, Height = seq(135, 205, by = 0.5),
density = dnorm(Height, mean, sd), .by = Gender)
ggplot(adults, aes(Height)) +
geom_histogram(aes(y = after_stat(density)), binwidth = 1,
fill = harbour[["sky"]], alpha = 0.55) +
geom_line(data = curves, aes(y = density), colour = harbour[["amber"]], linewidth = 0.9) +
facet_wrap(~ Gender) +
labs(x = "height (cm)", y = "density")
# A tibble: 2 x 3
Gender mean sd
<fct> <dbl> <dbl>
1 female 162. 7.36
2 male 175. 7.58
We will start with the women. The question is simple: what is the average height of adult women in the United States, and how sure can we be? The model says that each woman's height is a draw from a normal distribution with unknown mean and unknown standard deviation :
Read the priors as statements of knowledge you had before seeing these data. The prior for is centred at 170 cm with a standard deviation of 20 cm, so it considers average heights from about 130 cm to 210 cm plausible: wide enough to include every human population ever measured, but not absurd. The prior for is the maximum-entropy distribution for a positive quantity with a known mean (Section 2.8); its mean is cm, which says "people differ in height by something like ten centimetres", and it gives very little weight to spreads above 40 cm. With 2,373 women, neither prior will matter much. In the small subgroups you meet in research, they will.
Definition: The Gaussian Model
A Gaussian model for an outcome says that each observation is drawn independently from a normal distribution, . Its two parameters are the mean , the centre of the distribution, and the standard deviation , the typical distance of an observation from the centre. About 68% of observations fall within one of and about 95% within two. A Bayesian analysis adds priors for and and computes their joint posterior.
The posterior, computed by brute force
With two parameters, we can compute the posterior by the method of Chapter 1 with no cleverness at all. Lay a fine grid over all plausible pairs ; at each pair, compute the log-likelihood (the sum of the log normal densities of all 2,373 heights) and add the log priors; then exponentiate and normalise. This is grid approximation.
loglik <- function(mu, sigma, y) sum(dnorm(y, mu, sigma, log = TRUE))
grid <- expand_grid(mu = seq(160.9, 162.3, length.out = 150),
sigma = seq(6.9, 7.85, length.out = 150)) |>
mutate(loglik = map2_dbl(mu, sigma, loglik, y = women$Height),
logpost = loglik + dnorm(mu, 170, 20, log = TRUE) + dexp(sigma, 0.1, log = TRUE),
post = exp(logpost - max(logpost)), # subtract the max first, to avoid underflow
post = post / sum(post))
set.seed(5)
grid_draws <- slice_sample(grid, n = 1e4, weight_by = post, replace = TRUE)
grid_draws |>
summarise(across(c(mu, sigma), list(mean = mean,
lo = ~ quantile(.x, 0.025), hi = ~ quantile(.x, 0.975))))
# A tibble: 1 x 6
mu_mean mu_lo mu_hi sigma_mean sigma_lo sigma_hi
<dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 162. 161. 162. 7.36 7.16 7.57
Two things in that code are worth copying into every grid you write. The likelihood of 2,373 observations is a product of 2,373 small numbers, about , far below the smallest number a computer can store, so we work with log-likelihoods and subtract the maximum before exponentiating. And instead of summarising the grid directly, we sample 10,000 parameter pairs from it in proportion to their posterior probability. From then on, every question about the posterior is a question about a table of samples, exactly as in Chapter 1.
ggplot(grid, aes(mu, sigma)) +
geom_point(data = slice_head(grid_draws, n = 600), colour = harbour[["sky"]],
alpha = 0.25, size = 0.9,
position = position_jitter(width = 0.004, height = 0.003, seed = 1)) +
geom_contour(aes(z = post), colour = harbour[["amber"]], bins = 7, linewidth = 0.6) +
labs(x = "mean height μ (cm)", y = "standard deviation σ (cm)")
The posterior is a small, almost circular hill. The average height of US women is between about 161.3 and 161.9 cm with 95% probability, and the standard deviation between about 7.16 and 7.57 cm. The hill is round rather than tilted, which says that what we learn about the centre tells us almost nothing about the spread: and are nearly uncorrelated in the posterior. The widget lets you watch the hill form as the data arrive.
With one woman measured, the posterior for is still essentially the prior (you cannot learn about spread from one observation), and is pinned only loosely near her height. By thirty, both are well located. Notice also that with few observations the hill is lopsided, with a longer tail toward large : small samples cannot rule out a large spread. The normal approximation to a posterior, which many software packages report, is only trustworthy once the hill is round.
The same model in brms
Grids are perfect for two parameters and hopeless for twenty: a grid with 150 points per parameter needs points. Every serious model in this course is therefore fitted with brms, which translates a model formula into the Stan language and draws posterior samples by Hamiltonian Monte Carlo (Chapter 5 explains how). The formula Height ~ 1 means "height has a mean and nothing predicts it".
m_h <- brm(Height ~ 1, data = women,
prior = c(prior(normal(170, 20), class = Intercept),
prior(exponential(0.1), class = sigma)),
seed = 3, refresh = 0, file = "fits/ch3_height")
summary(m_h)
Family: gaussian
Links: mu = identity; sigma = identity
Formula: Height ~ 1
Data: women (Number of observations: 2373)
Draws: 4 chains, each with iter = 2000; warmup = 1000; thin = 1;
total post-warmup draws = 4000
Population-Level Effects:
Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
Intercept 161.58 0.15 161.28 161.87 1.00 4056 2702
Family Specific Parameters:
Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
sigma 7.36 0.11 7.16 7.58 1.00 3598 2797
Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
and Tail_ESS are effective sample size measures, and Rhat is the potential
scale reduction factor on split chains (at convergence, Rhat = 1).
The two methods agree to within a few hundredths of a centimetre. In the brms summary, Estimate is the posterior mean, Est.Error the posterior standard deviation, and l-95% CI and u-95% CI the ends of the 95% credible interval. Rhat and the two ESS columns diagnose the sampler: an Rhat of 1.00 and effective sample sizes in the thousands mean the chains agree with each other and the summaries are precise. You met these in Section 1.8, and Chapter 5 explains them properly.
3.3 Drawing a line: weight and height
A mean is a model with no predictors. The linear model lets the mean depend on something. Taller people tend to be heavier; how much heavier? Before writing a model, draw the graph.
The first model is the simplest one that can answer "how does average weight change with height?":
The first line is the likelihood, as before, except that the mean now carries an index : each person has their own expected weight . The second line is the linear model: is a straight-line function of height. The equals sign is deliberate. The relationship between and is deterministic; all the randomness lives in the first line.
Each prior is a statement you can defend before seeing the data:
- is the expected weight of a person exactly 170 cm tall, because we centred height at 170 cm. A 170 cm adult weighs perhaps 60 to 100 kg, and Normal(80, 20) covers that comfortably.
- is the change in expected weight, in kilograms, per centimetre of height. Normal(0, 2) allows anything from about −4 to 4 kg per cm. It does not even assume the slope is positive, and it rules out only the absurd (a slope of 10 would make a 190 cm person 200 kg heavier than a 170 cm one).
- is the typical deviation of a person's weight from the line, again with an exponential prior of mean 10 kg.
Definition: The Linear Model
A linear model for an outcome with predictors is
"Linear" means linear in the parameters and . The predictors can be any fixed functions of the data: logarithms, squares, indicators of categories, or the spline basis functions of Section 3.10. A coefficient is the difference in the expected outcome between two units whose differ by one and whose other predictors are equal. Whether that difference is a causal effect depends on the graph.
Why centre the predictor?
Without centring, would be the expected weight of a person of height zero, a quantity no one has prior knowledge about, and whose posterior is strongly correlated with (raise the slope a little and the intercept must fall a lot to keep the line through the cloud of data). Centring at a typical value makes interpretable, makes its prior easy to set, and makes and nearly independent in the posterior, which also helps the sampler. Centre at a round, meaningful number (170 cm, age 40, the year 2000) rather than at the sample mean, so that the intercept means the same thing in every analysis and every paper. Exercise 4 asks you to fit the model without centring and see what happens.
adults <- mutate(adults, h = Height - 170) # centimetres above 170
m_wh <- brm(Weight ~ 1 + h, data = adults,
prior = c(prior(normal(80, 20), class = Intercept),
prior(normal(0, 2), class = b),
prior(exponential(0.1), class = sigma)),
seed = 3, refresh = 0, file = "fits/ch3_wh")
fixef(m_wh) |> round(3)
posterior_summary(m_wh, variable = "sigma") |> round(2)
Estimate Est.Error Q2.5 Q97.5
Intercept 83.602 0.285 83.03 84.166
h 0.903 0.027 0.85 0.958
Estimate Est.Error Q2.5 Q97.5
sigma 19.14 0.2 18.76 19.52
The posterior says: a 170 cm adult weighs 83.6 kg on average (95% interval 83.0 to 84.2), and each extra centimetre of height goes with 0.90 kg more weight on average (0.85 to 0.96). Individual weights scatter around the line with a standard deviation of about 19 kg, which is large: height explains only part of why people differ in weight.
Read the slope carefully. It compares the average weights of different people whose heights differ by a centimetre. It does not say what happens to one person who grows a centimetre, and, as the graph warns, it mixes the effect of height with the effect of sex, because men are both taller and heavier. Section 3.7 untangles this.
3.4 Two kinds of uncertainty
A fitted line is not one line but a posterior distribution of lines, one for each posterior sample of . Any question about the line is answered by asking it of every sample. There are two questions people ask most often, and they have very different answers.
- Where is the average? For a given height, where is the mean weight of people of that height? Compute for each posterior sample and summarise. This is the credible interval for the mean, and it reflects uncertainty about the parameters only.
- Where is a person? For a given height, where will a new individual's weight fall? For each posterior sample, also draw a weight from Normal. This is the posterior predictive interval, and it adds the scatter of individuals around the mean.
grid_h <- tibble(h = seq(-30, 30, by = 1))
mu_band <- fitted(m_wh, newdata = grid_h) |> as_tibble() |> bind_cols(grid_h) # the mean
pred_band <- predict(m_wh, newdata = grid_h, seed = 4) |> as_tibble() |> bind_cols(grid_h) # a person
set.seed(2)
ggplot(slice_sample(adults, n = 1000), aes(h + 170, Weight)) + # plot 1,000 of the 4,609
geom_point(colour = harbour[["muted"]], alpha = 0.25, size = 0.8) +
geom_ribbon(data = pred_band, aes(x = h + 170, ymin = Q2.5, ymax = Q97.5),
inherit.aes = FALSE, fill = harbour[["sky"]], alpha = 0.18) +
geom_ribbon(data = mu_band, aes(x = h + 170, ymin = Q2.5, ymax = Q97.5),
inherit.aes = FALSE, fill = harbour[["amber"]], alpha = 0.6) +
geom_line(data = mu_band, aes(h + 170, Estimate), colour = harbour[["amber"]]) +
labs(x = "height (cm)", y = "weight (kg)")
The amber band is barely wider than the line: with 4,609 people, we know the average weight at each height to within a kilogram or so. The blue band is about 75 kg wide: we cannot predict an individual's weight from height to better than that. Both statements are correct, and confusing them is one of the commonest errors in applied work. A paper that reports only the narrow band can make a weak predictor look precise; a paper that reports only the wide one can hide a well-estimated relationship.
Slide upward. The posterior lines converge and the amber band shrinks toward the line: more data always buys more certainty about the mean, roughly in proportion to . The blue band moves around at first, while is poorly known (with a handful of people it can even be too narrow, if they happen to sit close to the line), and then settles: it can never be narrower than the spread of real people around the line. No amount of data makes height a good predictor of a particular person's weight. With very few points, notice how wide both bands flare at the edges: a line is pinned down best near the middle of the data and least well at the extremes, so extrapolation is where uncertainty grows fastest.
Research Practice: Say Which Interval You Mean
Every interval in a paper should say what it is an interval for: a parameter, the mean outcome at given predictor values, or a new observation. In captions write, for example, "shaded band: 95% credible interval for the expected weight" or "95% posterior predictive interval". When the purpose is prediction (forecasting response times, flagging a risky login, predicting a student's grade), report predictive intervals and their calibration (do 95% intervals contain about 95% of held-out cases?). When the purpose is explanation, report intervals for the parameters or contrasts that answer the scientific question.
Check Yourself
Which part of the predictive interval comes from the parameters, and which from individuals?
More data never makes a correctly specified model less certain on average.
With 4,609 people the parameters are already pinned down; the predictive interval is dominated by , the scatter of individuals around the line (about 19 kg), which more data cannot reduce. The credible interval for the mean would shrink by a factor of about .
The model assumes the same scatter at every height.
Centring changes what the intercept means, not where the line is uncertain.
Every posterior line passes near the point (mean height, mean weight) of the data, and the lines differ mainly in slope. Lines with different slopes that cross in the middle are far apart at the ends, so uncertainty about the mean grows with distance from the centre of the data. That is why extrapolation is dangerous.
3.5 Checking the model, and the shape of the human body
A posterior is a statement about the parameters of the model you wrote down. If the model is wrong in an important way, the posterior can be precise and misleading at once. Section 1.8 introduced the remedy: simulate data from the fitted model and compare them with the real data. For the weight model, compare the shape of the distribution of weights.
set.seed(6)
yrep <- posterior_predict(m_wh, ndraws = 200) # 200 simulated datasets
skew <- function(x) mean((x - mean(x))^3) / sd(x)^3
c(observed = skew(adults$Weight), simulated = mean(apply(yrep, 1, skew)),
share_simulated_as_skewed = mean(apply(yrep, 1, skew) >= skew(adults$Weight)))
sims <- tibble(draw = rep(1:25, each = nrow(adults)), Weight = as.vector(t(yrep[1:25, ])))
ggplot() +
geom_density(data = sims, aes(Weight, group = draw), colour = harbour[["sky"]],
alpha = 0.3, linewidth = 0.3, n = 128) +
geom_density(data = adults, aes(Weight), colour = harbour[["ink"]], linewidth = 1.1, n = 256) +
labs(x = "weight (kg)", y = "density")
observed simulated share_simulated_as_skewed
1.106532 0.007017 0.000000
The check fails clearly. The real weights are skewed: there are more people far above the average weight than far below it. Simulated datasets from the model are symmetric, with a skewness near 0, while the real data have a skewness of about 1.1, and not one of 200 simulated datasets comes close. The line itself may still be a fine summary of the mean, but the model's statements about individuals (such as the predictive band in Section 3.4) are wrong in the tails: it predicts too many very light people and too few very heavy ones.
Section 3.1 suggests the fix. Weight is built up multiplicatively: tissue grows in proportion to the tissue already there, and a 10% change in diet changes weight by a percentage, not by a fixed number of kilograms. So model log weight. If we also take the logarithm of height, the model becomes
On the original scale this says : weight is proportional to height raised to the power . Logarithms on both sides turn a power law into a straight line, and the slope becomes an elasticity: a 1% difference in height goes with a % difference in weight.
History: Quetelet's Index and the Exponent of Two
In 1835 the Belgian astronomer and statistician Adolphe Quetelet measured Belgian conscripts and claimed that adult weight increases roughly as the square of height. Dividing weight by height squared should therefore remove the effect of height, leaving a measure of how heavy someone is "for their size". In 1972 the physiologist Ancel Keys compared candidate indices and recommended Quetelet's, naming it the body mass index, BMI . It now decides medical categories for billions of people. But why a power of two? If people were scaled-up copies of each other, weight would grow as the cube of height. In 2013 the Oxford mathematician Nick Trefethen argued in a letter to The Economist that an exponent of 2.5 fits real populations better, so that BMI misclassifies tall people as fatter, and short people as thinner, than they are. The exponent is an empirical question, and a linear model on the log scale answers it directly.
A model of on puts Quetelet's claim to the test: if BMI is the right adjustment, then . We fit one model to all adults, and a second that allows the exponent to differ between women and men. The prior for is centred halfway between Quetelet's 2 and the 3 of geometric scaling, Normal(2.5, 1), so that it favours neither hypothesis.
adults <- mutate(adults, log_w = log(Weight), log_h = log(Height / 170))
m_log <- brm(log_w ~ 1 + log_h, data = adults,
prior = c(prior(normal(4.4, 0.5), class = Intercept), # log(80 kg) is 4.38
prior(normal(2.5, 1), class = b),
prior(exponential(5), class = sigma)),
seed = 3, refresh = 0, file = "fits/ch3_log")
m_logs <- brm(log_w ~ 0 + Gender + Gender:log_h, data = adults, # separate lines by sex
prior = c(prior(normal(4.4, 0.5), class = b),
prior(normal(2.5, 1), class = b, coef = Genderfemale:log_h),
prior(normal(2.5, 1), class = b, coef = Gendermale:log_h),
prior(exponential(5), class = sigma)),
seed = 3, refresh = 0, file = "fits/ch3_logs")
expo <- as_draws_df(m_logs) |>
transmute(women = `b_Genderfemale:log_h`, men = `b_Gendermale:log_h`,
`men - women` = men - women) |>
mutate(`all adults` = as_draws_df(m_log)$b_log_h)
expo |>
pivot_longer(everything(), names_to = "exponent") |>
group_by(exponent) |>
summarise(mean = mean(value), lo = quantile(value, 0.025), hi = quantile(value, 0.975),
`Pr(< 2)` = mean(value < 2))
# A tibble: 4 x 5
exponent mean lo hi `Pr(< 2)`
<chr> <dbl> <dbl> <dbl> <dbl>
1 all adults 1.90 1.79 2.00 0.971
2 men 2.08 1.87 2.28 0.238
3 men - women 0.364 0.0777 0.648 1
4 women 1.71 1.52 1.91 0.998
expo |>
select(-`men - women`) |>
pivot_longer(everything(), names_to = "group") |>
ggplot(aes(value, colour = group)) +
geom_density(linewidth = 1) +
geom_vline(xintercept = c(2, 2.5), linetype = "dashed", colour = harbour[["muted"]]) +
scale_colour_manual(values = c(women = harbour[["rose"]], men = harbour[["sky"]],
`all adults` = harbour[["amber"]])) +
labs(x = "exponent β (weight ∝ height^β)", y = "density", colour = NULL)
The answer is more interesting than either side of the argument. For all adults together, the exponent is about 1.90 (95% interval 1.79 to 2.00), slightly below Quetelet's 2, with posterior probability 0.97. But that pooled number hides a real difference: among women the exponent is about 1.71 (95% interval 1.52 to 1.91), among men about 2.08 (1.87 to 2.28), and the difference, 0.36 (0.08 to 0.65), is clearly positive. Neither sex is close to Trefethen's 2.5 in these data. A BMI built for men would be about right; for women, dividing by over-corrects, so that among women of the same build, taller women get slightly lower BMIs. (Whether that matters clinically is a question about how BMI predicts health, not one this model can answer.)
Notice also how the pooled exponent, 1.90, is not the average of the two sex-specific exponents in any simple way. Pooling men and women puts two clouds of points with different centres into one regression, and the line through the combined cloud reflects both the within-sex slopes and the difference between the groups. This is a gentle version of the confounding problem that dominates the next lecture.
Does the log scale fix the skew? The skewness of the residuals (each person's distance from the fitted line) falls from 1.35 on the kilogram scale to about 0.45 on the log scale. Not perfect (weight has a longer right tail than even a log-normal), but the model is now much closer to the data, and Exercise 5 asks you to run the posterior predictive check yourself. A model does not need to be perfect; it needs to be good enough for the question, and you need to know where it fails.
Research Practice: Report the Checks You Ran
Include at least one posterior predictive check for each main model in the paper or its supplement, chosen to probe the assumptions that matter for your conclusions: tails for risk predictions, group means for comparisons, skewness or zeros for counts. Say what failed and what you did about it. "The Gaussian model under-predicted heavy weights (posterior predictive skewness 0.0 against 1.1 observed), so we modelled log weight" is a sentence that builds a reviewer's trust. Choosing the outcome scale after looking at the data is a modelling decision like any other; report it, and if it could change the conclusion, report both analyses.
Check Yourself
In the model for men, the exponent is about 2.08. By about what percentage is the average weight of men who are 1% taller higher? (Use the elasticity interpretation.)
On the log–log scale, a 1% difference in height corresponds to a % difference in average weight.
An elasticity of 2.08 means a 1% difference in height goes with about a 2.08% difference in average weight (exactly, ). Under Quetelet's assumption it would be 2%, and under geometric scaling 3%.
A posterior probability near 0.2 is not evidence of equality.
What does Pr(β < 2) of about 1 say?
For women nearly all the posterior mass is below 2 (around 1.71). For men the posterior is centred just above 2 with about a quarter of its mass below 2, so 2 is plausible but slightly larger values are favoured. Reporting the full posterior (or its interval) is better than any yes/no verdict.
Key Takeaways: Lecture 5
- Sums of many small effects are approximately normal; products are log-normal. The normal is also the maximum-entropy choice given a mean and a variance.
- Count units, not rows, and know how the data were sampled before modelling.
- A linear model lets the mean depend on predictors. Centre predictors at meaningful values and set priors you can defend on that scale.
- The credible interval for the mean shrinks with data; the predictive interval for an individual does not. Say which one you report.
- Check the model with posterior predictive checks. Logarithms turn multiplicative relationships and power laws into lines, and their slopes are elasticities.
Lecture 6 — What a coefficient means
3.6 Categories as predictors
Not every predictor is a number. Sex, field of study, treatment arm, borough and survey year are categorical. There are two ways to put a category into a linear model. The traditional way uses an indicator (or "dummy") variable: for men and for women, and . Then is the women's mean and is the difference between men and women.
We will use the second way, an index variable: give each category its own parameter, and let the data's category label pick which one applies,
The two codings fit the same data equally well, but the index version is easier to think about. With indicators, the prior on is a prior on a difference, so the men's mean automatically gets a wider prior than the women's (it is the sum of two uncertain quantities), which is an assumption no one intended. With an index variable each category gets the same prior, the parameters mean what they say, and the extension to 16 categories (Section 3.8) needs no new ideas. In brms, the formula Weight ~ 0 + Gender asks for one parameter per category and no common intercept.
m_total <- brm(Weight ~ 0 + Gender, data = adults,
prior = c(prior(normal(80, 20), class = b),
prior(exponential(0.1), class = sigma)),
seed = 3, refresh = 0, file = "fits/ch3_total")
fixef(m_total) |> round(2)
Estimate Est.Error Q2.5 Q97.5
Genderfemale 76.21 0.41 75.40 77.03
Gendermale 88.21 0.44 87.35 89.09
How much heavier are men than women, on average? The model has no parameter for that, and it does not need one. A difference between categories is a function of the parameters, and as always we compute it from the posterior samples: subtract, sample by sample. This is called a contrast.
post <- as_draws_df(m_total)
diff_total <- post$b_Gendermale - post$b_Genderfemale # one difference per posterior sample
c(mean = mean(diff_total), quantile(diff_total, c(0.025, 0.975)))
mean 2.5% 97.5%
12.01 10.79 13.17
Research Practice: Compare Groups with Contrasts, Not by Eye
A very common error is to compare two groups by checking whether their separate intervals overlap, or whether one estimate is "significant" and the other is not. Overlapping intervals do not mean there is no difference, and "the difference between significant and not significant is not itself statistically significant" (Gelman and Stern, 2006). The correct summary of a comparison is the posterior distribution of the contrast itself, computed draw by draw as above, with its interval. Every comparison your paper's argument depends on should appear as a contrast.
3.7 Total and direct effects
Men weigh about 12 kg more than women on average. Now return to the question in the opening box: what happens when we add height to the model? Look at the graph again, keeping only the parts we need.
Sex affects weight in two ways: through height (men are taller, and taller bodies are heavier), and directly, by any other route (differences in muscle mass, fat distribution and bone density at the same height). These give two different questions, and two different estimands:
- The total effect of sex on weight: how different are the average weights of men and women? It includes both paths. The graph has no back-door path from to (nothing causes sex in this graph), so the model with sex alone estimates it. That was
m_total. - The direct effect: how different are the average weights of men and women of the same height? To estimate it, we block the path through by conditioning on height.
m_direct <- brm(Weight ~ 0 + Gender + h, data = adults,
prior = c(prior(normal(80, 20), class = b),
prior(normal(0, 2), class = b, coef = h),
prior(exponential(0.1), class = sigma)),
seed = 3, refresh = 0, file = "fits/ch3_direct")
post_d <- as_draws_df(m_direct)
diff_direct <- post_d$b_Gendermale - post_d$b_Genderfemale # men minus women, same height
tibble(total = diff_total, direct = diff_direct) |>
pivot_longer(everything(), names_to = "estimand") |>
group_by(estimand) |>
summarise(mean = mean(value), lo = quantile(value, 0.025), hi = quantile(value, 0.975))
# A tibble: 2 x 4
estimand mean lo hi
<chr> <dbl> <dbl> <dbl>
1 direct -0.790 -2.32 0.718
2 total 12.0 10.8 13.2
tibble(total = diff_total, direct = diff_direct) |>
pivot_longer(everything(), names_to = "estimand") |>
ggplot(aes(value, fill = estimand)) +
geom_density(colour = NA, alpha = 0.7) +
geom_vline(xintercept = 0, linetype = "dashed", colour = harbour[["muted"]]) +
scale_fill_manual(values = c(total = harbour[["sky"]], direct = harbour[["amber"]])) +
labs(x = "men minus women, average weight (kg)", y = "density", fill = NULL)
The total effect is 12.0 kg. The direct effect, comparing men and women of the same height, is about −0.8 kg, with an interval comfortably including zero. In these data, a man and a woman of the same height weigh, on average, about the same. All of the average weight difference between the sexes is accounted for by the difference in height.
Both numbers are correct. They answer different questions, and the question comes first. If you are a public-health planner estimating how much more food a male population needs, the total effect is the relevant one. If you are asking whether men are heavier "for their size", the direct effect is. What you must not do is fit the model with height, read off the sex coefficient, and report that "sex has no effect on weight". The coefficient in m_direct is a direct effect, and that is not what the sentence claims.
Warning: Direct Effects Need More Than a Graph Without Back Doors
The total effect needs only that nothing confounds and . The direct effect needs more: nothing may confound the mediator and the outcome. In the graph, unmeasured influences (genes, childhood nutrition) affect both height and weight. Conditioning on height then makes it a collider on the path , and opens that path. Among people of the same height, a man is, on average, someone whose pushed his height less (he is "short for a man") and a woman someone whose pushed hers more; if the same also affects weight, the comparison is biased. The near-zero direct effect is therefore an estimate of a controlled comparison at equal height, which is a perfectly good descriptive quantity, but its causal reading depends on an assumption about . Chapter 13 develops the tools (mediation analysis and sensitivity analysis) for doing better.
Check Yourself
Hours studied lies on a path from tutor use to scores.
Is hours studied a cause of tutor use, or a consequence?
Hours studied is a mediator: tutor → hours → scores. Conditioning on it blocks that path, so the coefficient becomes a direct effect. If the question is "does giving students the tutor raise their scores?", hours should not be controlled for. (And, as the warning explains, the direct effect also needs no unmeasured common causes of hours and scores, such as motivation.)
3.8 Confounding in the wild: majors and money
Now a harder example, with real stakes. In 2014 the data journalism site FiveThirtyEight published earnings data for 173 college majors, taken from the American Community Survey for 2010–2012: for each major, the median annual earnings of recent graduates working full time, and the share of graduates who are women. The question that leaps out is: do majors with more women pay less, and why?
url <- "https://raw.githubusercontent.com/fivethirtyeight/data/master/college-majors/recent-grads.csv"
majors <- read_csv(url, show_col_types = FALSE) |>
drop_na(ShareWomen) |> # one major has no breakdown by sex
mutate(log_earn = log(Median), field = Major_category)
nrow(majors)
majors |> select(Major, field, ShareWomen, Median, Sample_size) |> slice(c(1, 40, 90, 160))
[1] 172
# A tibble: 4 x 5
Major field ShareWomen Median Sample_size
<chr> <chr> <dbl> <dbl> <dbl>
1 PETROLEUM ENGINEERING Engineering 0.121 110000 36
2 ACCOUNTING Business 0.524 45000 2042
3 GEOSCIENCES Physical S~ 0.591 36000 18
4 COSMETOLOGY SERVICES AND CULINARY ARTS Industrial~ 0.585 29000 117
The raw association is strong: across majors, the correlation between the share of women and median earnings is −0.62. Petroleum engineering (12% women) pays $110,000; early childhood education (97% women) pays $28,000. We model the logarithm of earnings, because earnings are multiplicative and skewed (Section 3.5), and a coefficient on the log scale converts to a percentage difference.
Before fitting, the graph. Majors belong to broad fields (engineering, health, education, the arts, and so on). Field plausibly affects both the share of women (through decades of gendered sorting into fields, and the different histories of nursing and engineering) and earnings (through the jobs each field leads to). That makes field a common cause, a fork, and the raw association mixes any link between share of women and pay with the differences between fields.
We fit two models: one ignoring field, and one with a separate intercept for each of the 16 fields (an index variable, as in Section 3.6) and a common slope for the share of women. Formally, the second is
with Normal(10.6, 1) priors for the field intercepts ( $40,000, and one unit on the log scale is a factor of 2.7, so this allows field averages from about $5,000 to $300,000) and a Normal(0, 1) prior for , which allows majors with no women and majors with only women to differ in earnings by up to a factor of about 7 in either direction.
m_maj1 <- brm(log_earn ~ 1 + ShareWomen, data = majors,
prior = c(prior(normal(10.6, 1), class = Intercept), # log($40,000)
prior(normal(0, 1), class = b),
prior(exponential(2), class = sigma)),
seed = 3, refresh = 0, file = "fits/ch3_maj1")
m_maj2 <- brm(log_earn ~ 0 + field + ShareWomen, data = majors,
prior = c(prior(normal(10.6, 1), class = b),
prior(normal(0, 1), class = b, coef = ShareWomen),
prior(exponential(2), class = sigma)),
seed = 3, refresh = 0, file = "fits/ch3_maj2")
# percentage difference in median earnings, all-women major vs all-men major
pct <- function(fit) 100 * (exp(as_draws_df(fit)$b_ShareWomen) - 1)
tibble(`ignoring field` = pct(m_maj1), `within field` = pct(m_maj2)) |>
pivot_longer(everything(), names_to = "model") |>
group_by(model) |>
summarise(mean = mean(value), lo = quantile(value, 0.025), hi = quantile(value, 0.975))
# A tibble: 2 x 4
model mean lo hi
<chr> <dbl> <dbl> <dbl>
1 ignoring field -50.3 -56.0 -44.0
2 within field -30.5 -40.3 -18.7
Ignoring field, a major with only women graduates is predicted to pay about half (50% less, 95% interval 44% to 56%) what a major with only men pays. Within the same field, the gap is about 30% (95% interval 19% to 40%): still large, and with an interval well away from zero, but only about half as large on the log scale (about −0.36 against −0.70). Roughly half of the raw association is a difference between fields; the rest is a difference between majors within a field. The figure shows where each part comes from.
fe <- fitted(m_maj2) |> as_tibble() |> bind_cols(majors) # within-field fits
overall <- tibble(ShareWomen = c(0, 1)) |>
bind_cols(as_tibble(fitted(m_maj1, newdata = tibble(ShareWomen = c(0, 1)))))
centres <- majors |> group_by(field) |>
summarise(ShareWomen = mean(ShareWomen), log_earn = mean(log_earn), n = n())
ggplot(majors, aes(ShareWomen, exp(log_earn))) +
geom_point(colour = harbour[["muted"]], alpha = 0.5, size = 1.4) +
geom_line(data = fe, aes(ShareWomen, exp(Estimate), group = field),
colour = harbour[["sky"]], linewidth = 0.8) +
geom_line(data = overall, aes(ShareWomen, exp(Estimate)),
colour = harbour[["rose"]], linewidth = 1.2) +
geom_point(data = centres, aes(size = n), colour = harbour[["amber"]], alpha = 0.9) +
ggrepel::geom_text_repel(data = filter(centres, field %in% c("Engineering", "Education", "Health", "Arts")),
aes(label = field), nudge_y = 0.12, size = 3, colour = harbour[["ink"]], min.segment.length = 0,
segment.colour = harbour[["muted"]], seed = 1) +
scale_y_log10(labels = scales::label_dollar(accuracy = 1)) +
scale_x_continuous(labels = scales::percent) +
scale_size_area(max_size = 5, guide = "none") +
labs(x = "share of graduates who are women", y = "median earnings (log scale)")
The steep red line is mostly drawn by a few clusters: engineering (few women, high pay) at the upper left, and education, psychology and health (many women, lower pay) at the lower right. Within each field, the blue lines still slope downward, but more gently. Conditioning on field has closed the fork .
What "controlling for" a variable actually does
A coefficient in a multiple regression has a precise and illuminating meaning, discovered by Ragnar Frisch and Frederick Waugh in 1933. The coefficient on in a model that also includes field equals the slope you get by (1) removing from log earnings everything that field predicts, (2) removing from the share of women everything that field predicts, and (3) regressing the first set of leftovers on the second. Multiple regression asks: once I know a major's field, does knowing its share of women tell me anything more about its earnings? You can check this numerically (with ordinary least squares, which matches the posterior mean closely here):
r_earn <- resid(lm(log_earn ~ field, data = majors)) # earnings not predicted by field
r_women <- resid(lm(ShareWomen ~ field, data = majors)) # share of women not predicted by field
c(by_hand = coef(lm(r_earn ~ r_women))[["r_women"]],
multiple_regression = coef(lm(log_earn ~ field + ShareWomen, data = majors))[["ShareWomen"]])
by_hand multiple_regression
-0.3784 -0.3784
This is also why a regression coefficient can only be as good as the variables you condition on. If field is measured coarsely (16 broad categories), the leftovers in step (2) still contain differences between, say, nursing and health administration, and any confounding they carry remains.
So is the remaining within-field gap a causal effect of women's presence on pay? The data cannot say, and this is where the graph earns its keep. Two very different mechanisms produce exactly this pattern. In the sorting explanation, men and women choose different majors within each field, for many reasons, and the majors men choose happen to lead to better-paid occupations (the dashed and arrows: unmeasured features of each major affect both). In the devaluation explanation, work becomes paid less because women do it (the arrow itself). Both predict a negative within-field slope. A cross-section of 172 majors cannot separate them, because it cannot block the unmeasured .
History: Does Pay Fall When Women Arrive?
Sociologists have argued about sorting versus devaluation for decades, and the debate is a model of how to make progress on a causal question with observational data. In 2009 Asaf Levanon, Paula England and Paul Allison used US census data from 1950 to 2000 to follow the same occupations over time. If devaluation is real, an occupation's pay should fall after women enter it; if sorting explains everything, women's entry should follow, not precede, changes in pay. Using models that compared each occupation with itself across decades (removing stable unmeasured differences between occupations), they found evidence that occupations' pay declined after the share of women rose, consistent with devaluation, though other researchers dispute the size of the effect. The design lesson is general: when a cross-section cannot block an unmeasured confounder, change the data: follow units over time, find a natural experiment, or measure the confounder. Chapters 8 and 13 develop these designs.
Research Practice: State the Estimand Before the Model
Lundberg, Johnson and Stewart (2021) argue that the most important sentence in a quantitative paper is the one that says what quantity, for which population, the analysis estimates: the estimand. "The association between share of women and earnings" is not an estimand; it does not say whether field is held fixed. Better: "the difference in median log earnings between majors in the same broad field whose shares of women differ by ten percentage points, among US college majors in 2010–2012". Writing the estimand first forces you to decide which variables to condition on, and the graph then tells you whether the model identifies it. Then state plainly what the estimate does not show: here, whether women's presence causes lower pay.
Two more lessons from these data. First, the Bayesian R² (the share of variance explained; Gelman et al., 2019) rises from about 0.42 to about 0.65 when field is added, but a high R² is not evidence that a model is causally correct: the model with field explains earnings better and still cannot tell sorting from devaluation. Second, the medians are themselves estimates: 10% of majors have fewer than 15 graduates in the survey sample, and Library Science's median is based on two people. A model that treats every median as exact gives these majors too much influence. Exercise 6 asks you to check whether this changes the conclusion; Chapters 6 and 7 show how to model measurement uncertainty directly.
rbind(`ignoring field` = bayes_R2(m_maj1), `with field` = bayes_R2(m_maj2)) |> round(2)
Estimate Est.Error Q2.5 Q97.5
R2 0.42 0.04 0.33 0.50
R2 0.65 0.03 0.59 0.69
Check Yourself
The interval for the within-field estimate does not include zero.
The model was fitted on the log scale and converted to percentages.
Field is one measured common cause. The within-field association still mixes any causal effect of share of women on pay () with sorting on unmeasured characteristics of majors (). The data are consistent with both stories.
R² measures fit, not identification.
Higher R² does not mean less confounding of a particular coefficient.
R² measures how much variation the model explains. Whether a coefficient answers a causal question depends on the graph, and a model can fit very well while every coefficient in it is confounded.
3.9 Good controls and bad controls
The last two sections conditioned on a variable and changed the answer: height turned a total effect into a direct one, and field removed a fork. It is tempting to conclude that adding variables makes estimates more trustworthy, and much applied research behaves as if it did, routinely "controlling for" every available variable. The graph says otherwise. A control variable is good or bad depending on where it sits relative to the exposure and the outcome , and there are only a handful of basic positions. The widget simulates each one.
Go through the six settings. Each corresponds to a rule.
- Confounder (fork): without the estimate is biased; with it is centred on the truth. Good control.
- Mediator (pipe): without the estimate is centred on the total effect; with it estimates only the direct part. Bad control for the total effect, good for the direct effect, as in Section 3.7.
- Collider: without the estimate is fine; with it is biased, even though is "just another variable". Bad control.
- Cause of only: both estimates are centred on the truth, but adding makes the amber histogram narrower, because explains some of the noise in . Good control, for precision.
- Cause of only: both are unbiased, but adding makes the estimate less precise, because it removes variation in that was useful. Neutral to harmful.
- Effect of : conditioning on a consequence of the outcome biases the estimate, toward zero here. Bad control, and a common one: think of controlling for a diagnosis that the outcome itself triggers.
Research Practice: Choose Controls with the Graph, and Leave Their Coefficients Alone
Cinelli, Forney and Pearl (2022) catalogue these cases as "a crash course in good and bad controls", and reviewers increasingly expect authors to justify each covariate from a causal diagram rather than by habit. Two habits to adopt:
- Justify every control. List each covariate with its role (confounder, precision variable, or otherwise) in a table or in the DAG figure. Never include post-treatment variables in an analysis of a total effect.
- Avoid the "Table 2 fallacy" (Westreich and Greenland, 2013). A model built to estimate the effect of adjusts for the confounders of ; it does not, in general, estimate the effects of those confounders. In
m_maj2, the field intercepts are not the causal effects of field on earnings, because nothing in the design removes the confounders of field. Report the coefficient you designed the model for, and leave the others in the supplement or out of the discussion.
Project Connection: AI and University Mathematics
A study of whether students' use of AI tools affects their grades in mathematics courses will be judged largely on its choice of controls. Prior attainment (earlier GPA, placement scores) plausibly affects both AI use and grades: a confounder, so adjust for it. Hours spent on homework or assignment completion may be affected by AI use: mediators, so do not adjust for them if the question is the total effect. Attending tutoring may be a collider if both struggling grades and AI enthusiasm lead students to it. Course section or instructor may affect grades only: precision variables, worth including. Draw the graph before collecting the data, so that you collect the confounders, and pre-register the adjustment set (Chapter 1).
Check Yourself
Is there any path from X to Y through Z in this structure?
The amber histogram is the one with Z included.
When affects only , it opens no path and closes none, so including it leaves the estimate unbiased. But it absorbs some of the variation in , reducing and therefore the uncertainty of every other coefficient. Randomised trials often adjust for strong baseline predictors of the outcome for exactly this reason.
If cameras change officers' behaviour, including how many arrests they make, then arrests are a mediator (X → Z → Y) and adjusting for them removes part of the cameras' effect, estimating only a direct effect at a fixed number of arrests. Arrests might also be a collider, if complaints and cameras both affect arrest records (for example, if complaints trigger reviews that lead to fewer arrests), in which case adjusting for them creates bias. The total effect should be estimated without adjusting for arrests; if a direct effect is wanted, it needs its own justification.
3.10 Curves from lines: how fast do children grow?
Many relationships are not straight. Children grow quickly when they are small, slow down, speed up again at puberty, and stop. A linear model can still describe this, because "linear" refers to the parameters, not to the shape. The trick is to replace the single predictor "age" with several fixed functions of age, called basis functions, and let the model choose how much of each to use:
Each is a smooth bump that is non-zero only over a short range of ages. The weights are ordinary regression coefficients, so the model is still linear and fitted exactly like the others. Adding up the bumps, each scaled by its weight, produces a smooth curve. The bumps used in practice are B-splines: pieces of cubic polynomials joined smoothly at points called knots. The widget fits girls' heights from NHANES this way.
With no interior knots, the basis is a single cubic polynomial, which cannot both rise steeply in childhood and level off sharply after puberty. With three or four knots, the curve follows the data well. With fifteen and a generous prior, it starts to wiggle, chasing noise in particular age ranges: that is overfitting. Shrink and the wiggles disappear, because a tighter prior on the weights pulls them toward zero and the curve toward smoothness. A prior on spline weights is a smoothness assumption.
That observation leads to the method used in practice. Rather than choosing the number of knots by hand, use many basis functions and estimate how strongly to shrink their weights from the data, by giving its own prior. This is a penalised spline, and in brms it is the s() term, borrowed from the mgcv package. The model below fits a separate smooth curve for girls and boys.
kids <- nh |>
distinct(ID, .keep_all = TRUE) |>
filter(Age >= 2, Age <= 19) |>
drop_na(Height, AgeMonths) |> # age in months was recorded in 2009-10
mutate(age = AgeMonths / 12)
count(kids, Gender)
m_sp <- brm(Height ~ Gender + s(age, by = Gender, k = 10), data = kids,
prior = c(prior(normal(130, 30), class = Intercept),
prior(normal(0, 50), class = b),
prior(exponential(0.2), class = sds), # how wiggly the curves may be
prior(exponential(0.2), class = sigma)),
seed = 3, refresh = 0, control = list(adapt_delta = 0.95),
file = "fits/ch3_spline")
Gender n
1 female 451
2 male 490
The fitted curves are only the beginning. Because we have posterior samples of each whole curve, we can ask questions about features of the curve, with honest uncertainty. When do children grow fastest during puberty? For each posterior sample, compute the curve on a fine grid of ages, take differences to get the growth rate in cm per year, and find the age between 8 and 17 at which it peaks.
ages <- expand_grid(Gender = c("female", "male"), age = seq(2, 19.5, by = 0.1))
curve <- posterior_epred(m_sp, newdata = ages, ndraws = 1000) # 1000 samples x 352 ages
band <- function(m) tibble(est = colMeans(m), lo = apply(m, 2, quantile, 0.025),
hi = apply(m, 2, quantile, 0.975))
heights <- bind_cols(ages, band(curve))
vel <- (curve[, -1] - curve[, -ncol(curve)]) / 0.1 # cm per year
same <- ages$Gender[-1] == ages$Gender[-nrow(ages)] # don't difference across sexes
speed <- bind_cols(ages[-1, ][same, ], band(vel[, same]))
peak_age <- function(g) {
cols <- which(ages$Gender[-1] == g & same & ages$age[-1] >= 8 & ages$age[-1] <= 17)
ages$age[-1][cols][apply(vel[, cols], 1, which.max)]
}
peaks <- tibble(girls = peak_age("female"), boys = peak_age("male"), `boys - girls` = boys - girls)
peaks |> pivot_longer(everything()) |> group_by(name) |>
summarise(mean = mean(value), lo = quantile(value, 0.025), hi = quantile(value, 0.975))
p1 <- ggplot(heights, aes(age, est, colour = Gender, fill = Gender)) +
geom_point(data = kids, aes(age, Height), alpha = 0.25, size = 0.7, inherit.aes = FALSE,
colour = harbour[["muted"]]) +
geom_ribbon(aes(ymin = lo, ymax = hi), colour = NA, alpha = 0.3) + geom_line() +
labs(x = "age (years)", y = "height (cm)", colour = NULL, fill = NULL)
p2 <- ggplot(speed, aes(age, est, colour = Gender, fill = Gender)) +
geom_ribbon(aes(ymin = lo, ymax = hi), colour = NA, alpha = 0.3) + geom_line() +
coord_cartesian(ylim = c(0, 12)) +
labs(x = "age (years)", y = "growth (cm per year)", colour = NULL, fill = NULL)
patchwork::wrap_plots(p1, p2) + patchwork::plot_layout(guides = "collect") &
scale_colour_manual(values = c(female = harbour[["rose"]], male = harbour[["sky"]])) &
scale_fill_manual(values = c(female = harbour[["rose"]], male = harbour[["sky"]])) &
theme(legend.position = "top")
# A tibble: 3 x 4
name mean lo hi
<chr> <dbl> <dbl> <dbl>
1 boys 12.0 9.3 13.2
2 boys - girls 2.73 -0.303 4.8
3 girls 9.26 8 10.5
The left panel shows the familiar picture: girls and boys grow at similar rates until about age 10; girls then stop growing at around 15 and boys continue to 17 or 18, ending about 14 cm taller. The right panel shows something growth charts hide: growth slows through childhood and then speeds up again at puberty, clearly for boys and only faintly for girls. The posterior puts boys' fastest growth after age 8 at about 12 years (95% interval 9.3 to 13.2). For girls it cannot even separate a pubertal peak from the slow decline of childhood growth: the fastest growth after age 8 could be anywhere from age 8 to about 10.5.
Paediatric studies that measure the same children every few months find something much sharper: peak growth of 8 to 10 cm per year (our curves peak at about 7), at about 11.5 years for girls and 13.5 for boys. Is the model wrong? No: it answers a different question. Every child in NHANES was measured once, so our curve is the average height of children at each age, and its slope is the rate at which that average changes. Children enter puberty at different ages, so at any age the average mixes children in their spurt with children before and after it, and the population curve's spurt is lower and more spread out than any individual child's. With only about 25 girls per year of age, the faint hump is also hard to locate. To estimate a typical child's peak growth you need a different estimand and different data: repeated measurements of the same children, modelled with a curve for each child (Chapters 6 and 8). The cross-section was not useless. It said exactly as much as it could, and the posterior said it honestly, with intervals wide enough to show the ambiguity.
Why penalised splines are hierarchical models
In s(age), the wiggly part of the curve has weights , with (reported by brms as sds) estimated from the data. A parameter that sets the spread of a whole family of other parameters is a hyperparameter, and a model with one is a multilevel (hierarchical) model. The data decide how much the curve may bend, just as in Chapter 6 the data decide how much tadpole tanks or survey subgroups should resemble each other. Penalised splines are hierarchical models in disguise, and the shrinkage you saw in the widget is the same partial pooling.
Check Yourself
The number of basis functions does not change when τ changes.
τ is part of the prior, not the likelihood.
A wiggle needs neighbouring weights that differ sharply. A tight prior around zero makes large weights, and therefore large differences between neighbours, improbable, so the posterior curve is smoother. Estimating from the data, as s() does, lets the data decide how smooth the curve should be.
Research Practice: A Complete Results Paragraph
Here is what a publishable results paragraph for the analysis of Section 3.7 looks like. It states the estimands, the model, the priors, the key contrasts with intervals, the checks, and the caveat.
"We estimated the total effect of sex on adult body weight, and the controlled direct effect at equal height, using 4,609 adults aged 20 and over in NHANES 2009–2012 (one record per person). Weight was modelled as normal with sex-specific means and, for the direct effect, a common linear term in height centred at 170 cm, with weakly informative priors (Normal(80, 20) for means in kg, Normal(0, 2) for the slope in kg per cm, Exponential(0.1) for the residual standard deviation). Men weighed 12.0 kg more than women on average (95% credible interval 10.8 to 13.2). At equal height the difference was −0.8 kg (−2.3 to 0.7), so the sex difference in weight is accounted for by height. A posterior predictive check showed that the normal model under-predicts the right tail of weight; a log-scale model gave the same substantive conclusion (Supplement S2). The direct effect assumes no unmeasured common causes of height and weight; Supplement S3 reports a sensitivity analysis. Code and data are available at [repository]."
Key Takeaways: Lecture 6
- Use index variables for categories, and compute every comparison you care about as a contrast of posterior samples.
- Conditioning on a mediator turns a total effect into a direct effect. Decide which you want before choosing the model.
- Conditioning on a common cause removes confounding by that cause only; unmeasured confounders remain, and no fit statistic detects them.
- Controls are good or bad according to their position in the graph: confounders and causes of the outcome help; mediators, colliders and effects of the outcome can hurt. Do not interpret the coefficients of controls.
- Splines make curves from linear models; a prior on their weights is a smoothness assumption, and any feature of the curve has a posterior.
3.11 Exercises
Each exercise has a hint and an answer button.
Exercises
The cost of duplicates. Fit the model
m_hto the women's heights with the duplicated rows kept (usefilter(nh, Age >= 20, Gender == "female") |> drop_na(Height)). Compare the posterior standard deviation of the mean with that ofm_h. Is the ratio what you expected?Use
update(m_h, newdata = ...)and comparefixef()outputs. The expected ratio is the square root of the ratio of the numbers of rows.Keeping the duplicates gives 3,658 rows for 2,373 women. The posterior standard deviation of falls from 0.151 to 0.121 cm, a ratio of 1.25, almost exactly the predicted : the model is 25% more confident with no new information. The posterior mean also moves, from 161.58 to 162.05 cm. The duplicates are not random: they encode the survey weights, so the duplicated data estimate the population average height (weighting by how many Americans each woman represents), while the de-duplicated data over-represent the oversampled groups. Neither is simply right. The duplicated version has the right weights and the wrong uncertainty; a proper survey analysis gets both right.
Men's heights. Fit the Gaussian model of Section 3.2 to the men, with the same priors. Report the posterior mean and 95% interval of their average height, and the posterior of the difference between men's and women's average heights.
Fit
Height ~ 0 + Gendertoadultsand compute the contrast, or fit two separate models and subtract their draws.Men's average height is 175.35 cm (95% interval 175.05 to 175.66), and women's 161.58 cm (161.29 to 161.88). The contrast, men minus women, has posterior mean 13.77 cm (13.34 to 14.21).
Predicting a person. Using
m_wh, give the posterior mean and 95% interval for (a) the average weight of adults who are 185 cm tall, and (b) the weight of one new 185 cm adult. Explain in one sentence why (b) is so much wider than (a).fitted(m_wh, newdata = tibble(h = 15))andpredict(m_wh, newdata = tibble(h = 15)).(a) The average weight of 185 cm adults: 97.1 kg (95% interval 96.1 to 98.2). (b) One new 185 cm adult: about 97 kg (about 60 to 135). The predictive interval is about 75 kg wide, the credible interval about 2 kg, because a single person's weight varies around the average for their height with a standard deviation of about 19 kg, and more data cannot remove that variation.
Forgetting to centre. Refit
m_whwithWeight ~ 1 + Height(height not centred) and the same priors for the slope and , and a Normal(0, 100) prior for the intercept. What is the posterior mean of the intercept, and what does it mean? Compute the posterior correlation between the intercept and the slope, and compare it with the same correlation inm_wh.as_draws_df(fit) |> with(cor(b_Intercept, b_Height)).The intercept is about −69.9 kg (−79.0 to −60.8): the expected weight of a person of height zero, an impossible extrapolation with no meaning. The posterior correlation between intercept and slope is −0.998: almost any increase in the slope must be paid for by a decrease in the intercept to keep the line through the data. In the centred model, the correlation is only 0.17 (not exactly zero, because the centre, 170 cm, is a little above the sample mean of 168 cm). The slope itself is unchanged (0.90), but the intercept is uninterpretable and its prior impossible to set sensibly. (
brmscentres predictors internally for sampling, which is why this model still fits quickly; Stan without that trick would struggle.)Checking the log model. Repeat the posterior predictive check of Section 3.5 for
m_log, comparing the skewness of observed log weights with the skewness of simulated log weights. Does the log model pass this check?Replace
m_whbym_logandadults$Weightbyadults$log_win theppc_whchunk.The observed skewness of log weight is 0.18; simulated datasets have skewness near 0 (mean −0.01), and none of the 200 is as skewed as the data. So, strictly, the log model also fails this check. But the misfit is far smaller than on the kilogram scale (1.1 against 0), and with 4,609 people even a small discrepancy is detectable. Whether it matters depends on the use: for the exponent, which depends on the mean, it does not; for predicting the heaviest few percent of people, a model with a heavier right tail (a skew-normal or Student-t likelihood, Chapter 4) would be better.
Unreliable medians. Refit
m_maj2using only majors with at least 30 graduates in the survey sample (Sample_size >= 30). How many majors remain, and how does the within-field percentage difference change? What does this suggest about the influence of the small majors?Filter, then
update(m_maj2, newdata = ...), and apply thepct()function.140 of the 172 majors have at least 30 graduates in the sample (all 16 fields remain). The within-field difference becomes −25% (95% interval −37% to −11%), against −31% (−40% to −19%) using all majors. The small majors pushed the estimate further from zero, but the conclusion that majors with more women pay less within the same field survives. Dropping data by an arbitrary threshold is crude; Chapter 7 shows how to weight each median by its precision instead.
Is age a confounder?. Age affects adult weight (people tend to gain weight into middle age) and also height (older adults are slightly shorter, partly because they were born earlier and partly because of spinal compression). Draw the graph with (age), and . Is age a confounder of the effect of height on weight? Fit
Weight ~ 1 + h + Ageand compare the slope on height withm_wh.If and , there is a back-door path .
With and , age opens a back-door path , so it is a confounder of the effect of height on weight and should be adjusted for. In practice it matters little here: the slope on height moves from 0.903 to 0.915 kg per cm, because age is only weakly related to height among adults (correlation −0.16) and adds only 0.04 kg per year to weight (0.01 to 0.07). A confounder needs to be associated with both the exposure and the outcome to cause much bias.
Growth spurts. Using the posterior samples in the
growthchunk, compute the posterior distribution of the peak growth velocity (cm per year at the peak, between ages 8 and 17) for girls and boys. Which sex grows faster at its peak, and by how much?Replace
which.maxbymaxin thepeak_age()function.The posterior peak velocity is about 7.0 cm per year for girls (6.3 to 7.9) and 7.3 for boys (6.6 to 8.3), with a difference of 0.3 (−0.9 to 1.5): no clear difference. Longitudinal studies of individual children find peak velocities of 8 to 10 cm per year, with boys' clearly higher. The cross-sectional estimates are lower and similar, which is exactly the smearing described in Section 3.10: averaging growth spurts that happen at different ages for different children flattens them.
Further reading and references
Further Reading
- McElreath, Statistical Rethinking (2nd ed., 2020), Chapters 4–6: Gaussian models, splines, spurious and masked associations, and the "haunted DAG", in a style these notes follow closely.
- Gelman, Hill and Vehtari, Regression and Other Stories (2020): the best practical book on linear regression, with Bayesian fitting throughout and excellent chapters on transformations and causal inference.
- Cinelli, Forney and Pearl, "A crash course in good and bad controls" (2022): the full catalogue of the cases in Section 3.9.
- Lundberg, Johnson and Stewart, "What is your estimand?" (2021): how to state the target of an analysis before choosing the model.
- Wood, Generalized Additive Models: An Introduction with R (2nd ed., 2017): the theory behind
s()and penalised splines.
- Casselman, B. (2014). The economic guide to picking a college major. FiveThirtyEight, 12 September 2014. Data: github.com/fivethirtyeight/data/tree/master/college-majors.
- Centers for Disease Control and Prevention. National Health and Nutrition Examination Survey, 2009–2012. Distributed in the R package
NHANES(Pruim, 2015). - Cinelli, C., Forney, A. and Pearl, J. (2022). A crash course in good and bad controls. Sociological Methods & Research, 53(3), 1071–1104 (print issue 2024).
- Frisch, R. and Waugh, F. V. (1933). Partial time regressions as compared with individual trends. Econometrica, 1(4), 387–401.
- Galton, F. (1886). Regression towards mediocrity in hereditary stature. Journal of the Anthropological Institute, 15, 246–263.
- Gauss, C. F. (1809). Theoria Motus Corporum Coelestium. Hamburg: Perthes and Besser.
- Gelman, A., Goodrich, B., Gabry, J. and Vehtari, A. (2019). R-squared for Bayesian regression models. The American Statistician, 73(3), 307–309.
- Gelman, A. and Stern, H. (2006). The difference between "significant" and "not significant" is not itself statistically significant. The American Statistician, 60(4), 328–331.
- Keys, A., Fidanza, F., Karvonen, M. J., Kimura, N. and Taylor, H. L. (1972). Indices of relative weight and obesity. Journal of Chronic Diseases, 25(6–7), 329–343.
- Levanon, A., England, P. and Allison, P. (2009). Occupational feminization and pay: assessing causal dynamics using 1950–2000 U.S. census data. Social Forces, 88(2), 865–891.
- Lundberg, I., Johnson, R. and Stewart, B. M. (2021). What is your estimand? Defining the target quantity connects statistical evidence to theory. American Sociological Review, 86(3), 532–565.
- Quetelet, A. (1835). Sur l'homme et le développement de ses facultés, ou essai de physique sociale. Paris: Bachelier.
- Trefethen, N. (2013). BMI (body mass index). Letter to The Economist, 5 January 2013.
- Westreich, D. and Greenland, S. (2013). The Table 2 fallacy: presenting and interpreting confounder and modifier coefficients. American Journal of Epidemiology, 177(4), 292–298.
- Wood, S. N. (2017). Generalized Additive Models: An Introduction with R (2nd ed.). Chapman and Hall/CRC.