Did you know: the word pangolin
comes from the Malay pengguling: “one who rolls up”. Threatened
pangolins curl into a ball of scales.
Sources: Born Free Foundation; National Geographic
R² and adjusted R²In lectures 4 and 5, every model had one predictor
(y ~ x). We also saw a warning: with the penguins, leaving
out species reversed a slope.
Outcomes in linguistics rarely depend on one thing. Reaction times depend on word frequency, word length, the speaker, the task…
Today: continuous predictors only. Categorical predictors (like species) come next week.
A simulated lexical decision experiment: one row per word.
set.seed(6)
words <- tibble(
log_freq = runif(400, 1, 6), # log10 frequency
length = pmin(pmax(round(11 - 1.1 * log_freq + # in letters:
rnorm(400, 0, 1.6)), 2), 14), # frequent words are shorter
syllables = pmax(round(length / 3 + rnorm(400, 0, 0.35)), 1),
rt = 760 - 30 * log_freq + 14 * length + rnorm(400, 0, 45)
)
words# A tibble: 400 × 4
log_freq length syllables rt
<dbl> <dbl> <dbl> <dbl>
1 4.03 9 3 778.
2 5.69 4 1 598.
3 2.32 8 2 754.
4 2.90 7 2 766.
# ℹ 396 more rows
We know the truth: frequent words are faster (−30 ms per log unit), longer words are slower (+14 ms per letter). Syllables have no effect of their own.
Frequent words are faster and long words are slower.
[1] -0.7009648
Frequent words tend to be shorter. Each predictor carries part of the other’s information.
(Intercept) log_freq
909.92085 -44.73495
(Intercept) length
549.30028 28.60034
Both slopes are too big compared with the truth (−30 and +14).
On its own, length also picks up the effect of frequency: long words are slow partly because they are rare.
Did you know: pangolin scales are made of keratin, like our fingernails. Scales and skin can
make up about 25% of a pangolin’s body weight.
Sources: National Geographic; Born Free Foundation
b₀ is the predicted
y when both predictors are 0y when its
predictor goes up by 1 while the other stays the
samelm() with two predictors(Intercept) log_freq length
749.78281 -29.13551 14.91379
Both are now close to the true values. The multiple regression separated what the two predictors share.
| predictor | true effect | simple regression | multiple regression |
|---|---|---|---|
| log_freq | -30 | -44.7 | -29.1 |
| length | 14 | 28.6 | 14.9 |
With correlated predictors, a simple regression answers “what goes
with x?”. A multiple regression answers “what goes with
x, over and above the other predictors?”.
Predicted RTs from the model, for words of three fixed frequencies. Each line has the same slope: 14.9 ms per letter.
The lines are parallel because our model says the effect of length is the same at every frequency.
If the effect of length were different for frequent and rare words, the lines would not be parallel. That is an interaction: coming up in two weeks.
With two predictors, the model is a plane, seen here from above. The contour lines are straight and evenly spaced: no curves, no interactions.
summary()
Call:
lm(formula = rt ~ log_freq + length, data = words)
Residuals:
Min 1Q Median 3Q Max
-135.162 -28.989 0.268 29.255 141.362
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 749.783 16.551 45.30 <2e-16 ***
log_freq -29.136 2.146 -13.58 <2e-16 ***
length 14.914 1.438 10.37 <2e-16 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 44.81 on 397 degrees of freedom
Multiple R-squared: 0.7079, Adjusted R-squared: 0.7064
F-statistic: 481.1 on 2 and 397 DF, p-value: < 2.2e-16
{broom} and predictions# A tibble: 3 × 3
term estimate std.error
<chr> <dbl> <dbl>
1 (Intercept) 750. 16.6
2 log_freq -29.1 2.15
3 length 14.9 1.44
Predict for words we choose, giving every predictor a value:
1 2
825.7359 663.7604
A rare, long word versus a frequent, short one: a difference of about 162 ms.
Did you know: a pangolin’s tongue can be up to 70 cm long. Pangolins have no teeth, and can eat up to
70 million insects a year, mostly ants and termites.
Source: Born Free Foundation
log_freq length
-29.13551 14.91379
-29 ms per log unit of frequency, or 15 ms per letter? The units differ, so the numbers cannot be compared directly.
From lecture 5: standardise the predictors, and every slope is “per standard deviation”.
words <- words |>
mutate(across(c(log_freq, length), \(x) as.numeric(scale(x)), .names = "{.col}_z"))
m_z <- lm(rt ~ log_freq_z + length_z, data = words)
coef(m_z)(Intercept) log_freq_z length_z
748.35868 -42.71253 32.62463
The intercept is now the predicted RT for a word of average frequency and average length.
raw standardised
0.7079195 0.7079195
As in lecture 5: standardising changes what the coefficients mean, and the fit stays the same.
Standardised coefficients are useful to compare predictors within a model. Raw coefficients are easier to interpret (“ms per letter”): report the ones your reader needs.
Did you know: Sunda pangolins climb trees to reach ant
nests, holding on to branches with their prehensile
tail. During the day they sleep in tree hollows or burrows.
Source: National Geographic
R² with more predictors[1] 0.6287711
[1] 0.5722565
[1] 0.7079195
Together, the predictors explain more than either alone, but less than the sum of the two: they overlap.
R² never goes downAdd a predictor of pure noise:
set.seed(1)
words <- words |> mutate(noise = rnorm(n()))
r2 <- c(glance(lm(rt ~ log_freq + length, data = words))$r.squared,
glance(lm(rt ~ log_freq + length + noise, data = words))$r.squared)
print(r2, digits = 6)[1] 0.707919 0.707920
R² went up, a tiny bit. Every new predictor fits some of the noise in the sample.
R²Adjusted R² penalises each extra predictor
(as.data.frame() prints more digits):
r.squared adj.r.squared
1 0.7079195 0.706448
r.squared adj.r.squared
1 0.7079199 0.7057072
Use adjusted R² to compare models with different numbers of predictors. A predictor that does not help lowers it.
The same checks as lecture 4, on one set of residuals for the whole model: roughly normal, constant spread.
Did you know: the Sunda pangolin is critically endangered. Pangolins are among the most
trafficked mammals in the world; international commercial trade in them
was banned in 2016.
Source: National Geographic
Length in letters and length in syllables measure almost the same thing:
[1] 0.8443081
When two predictors are highly correlated, the model struggles to tell which one does the work. This is collinearity.
# A tibble: 3 × 3
term estimate std.error
<chr> <dbl> <dbl>
1 (Intercept) 750. 16.6
2 log_freq -29.1 2.15
3 length 14.9 1.44
# A tibble: 4 × 3
term estimate std.error
<chr> <dbl> <dbl>
1 (Intercept) 750. 16.6
2 log_freq -29.2 2.15
3 length 15.7 2.14
4 syllables -2.40 4.93
Adding syllables makes the estimate for length less precise: its standard error grows. Syllables also gets a coefficient of its own, although in our simulation it has no effect, while the overall fit barely changes.
The dotted line is the true effect of length (14 ms per letter). With syllables in the model, both intervals are much wider: the model cannot tell the two overlapping predictors apart.
The variance inflation factor: how well can a predictor be predicted from the other predictors?
r2_len <- glance(lm(length ~ log_freq + syllables, data = words))$r.squared
1 / (1 - r2_len) # length, with syllables in the model[1] 4.346639
r2_len2 <- glance(lm(length ~ log_freq, data = words))$r.squared
1 / (1 - r2_len2) # length, without syllables[1] 1.965995
1 means no overlap. The higher the VIF, the more the standard error is inflated. Common rules of thumb flag values above 5 or 10.
Frequent words tend to be short, so word frequency and word length overlap. You want to know whether frequency affects reading times.
With penguins, after drop_na() on the
columns you use:
body_mass_g from
flipper_length_mm alonebill_length_mm and
bill_depth_mm as predictorsflipper_length_mm in the model from 2pengs <- drop_na(penguins, body_mass_g, flipper_length_mm, bill_length_mm, bill_depth_mm)
m1 <- lm(body_mass_g ~ flipper_length_mm, data = pengs)
m2 <- lm(body_mass_g ~ flipper_length_mm + bill_length_mm + bill_depth_mm, data = pengs)
coef(m1)[2]; coef(m2)[2]flipper_length_mm
49.68557
flipper_length_mm
50.26922
m1 m2
0.7582837 0.7593534
pengs_z <- pengs |>
mutate(across(c(flipper_length_mm, bill_length_mm, bill_depth_mm),
\(x) as.numeric(scale(x))))
coef(lm(body_mass_g ~ flipper_length_mm + bill_length_mm + bill_depth_mm,
data = pengs_z))[-1]flipper_length_mm bill_length_mm bill_depth_mm
706.87140 22.72181 39.59368
r2_flip <- glance(lm(flipper_length_mm ~ bill_length_mm + bill_depth_mm, data = pengs))$r.squared
1 / (1 - r2_flip)[1] 2.673338
y ~ x1 + x2: each slope is the effect
of one predictor, holding the others
constantR² always rises with more predictors;
adjusted R² does notThese slides
mcanzi.de/ws26_statistics/
Code, data and all lectures
github.com/mcanzi/ws26_statistics