Interactions

When the effect of one predictor depends on another

Dr Massimiliano Canzi

What an interaction is

Did you know: the greater honeyguide, an African bird, leads people to wild bees’ nests. People take the honey, and the bird gets the leftover honeycomb.

Sources: Spottiswoode et al. (2016), Science; National Geographic

Today

  • What an interaction is, and why lines stop being parallel
  • Categorical × continuous interactions
  • Categorical × categorical interactions
  • Continuous × continuous interactions and curved regression planes
  • Why centering and sum coding matter so much here
  • A word on higher-order interactions

So far

Lectures 6 and 7: several predictors, each with one effect.

An interaction: the effect of one predictor depends on the value of another. The lines are no longer parallel.

Interactions in R

* adds both predictors and their interaction:

lm(y ~ x * group)            # is the same as
lm(y ~ x + group + x:group)

x and group are often called the main effects, although with an interaction they are really simple effects (more below). x:group is the interaction term: how much the effect of x changes from one group to the other.

Discussion

Interactions are common in linguistics: a frequency effect can be larger for L2 speakers, or a vowel can lengthen more before voiced consonants in one dialect than in another.

  • Think of a study in your own field where the effect of one variable could depend on another.
  • Sketch the plot you would expect with an interaction, and without one.

Categorical × continuous

Did you know: Yao honey hunters in Mozambique call honeyguides with a special brrr-hm. In experiments, honeyguides were about twice as likely to start guiding when they heard it than when they heard other sounds.

Sources: Spottiswoode et al. (2016), Science; National Geographic

The data

A simulated lexical decision task with first-language (L1) and second-language (L2) speakers:

set.seed(10)
lex <- tibble(group    = rep(c("L1", "L2"), each = 200),
              log_freq = runif(400, 1, 6)) |>
  mutate(rt = if_else(group == "L1", 700 - 25 * log_freq,
                                     900 - 55 * log_freq) + rnorm(400, 0, 40))

In this simulation, frequency matters more for L2 speakers: −55 ms per log unit, against −25 ms for L1 speakers.

The data

Two different slopes. Can last week’s model capture that?

Without an interaction

m_add <- lm(rt ~ log_freq + group, data = lex)
coef(m_add)
(Intercept)    log_freq     groupL2 
  768.58134   -44.00400    92.00191 

One slope for both groups: too shallow for L2, too steep for L1.

With an interaction

m_int <- lm(rt ~ log_freq * group, data = lex)
coef(m_int)
     (Intercept)         log_freq          groupL2 log_freq:groupL2 
       711.46630        -27.05222        201.53411        -31.43419 
Coefficient What it is
(Intercept) the L1 reaction time at log_freq = 0
log_freq the frequency slope for the reference group, L1
groupL2 the L2 minus L1 difference, at log_freq = 0
log_freq:groupL2 how much steeper the frequency slope is for L2

L2 slope = -27.1 + (-31.4) = -58.5 ms per log unit.

The “main effect” trap

groupL2 = 202 ms: is that the L1–L2 difference?

Only at log_freq = 0, a frequency that does not exist in our data (1 to 6).

With an interaction, each lower-order coefficient is a simple effect: the effect of one predictor when the other is 0 (or at its reference level).

Centering rescues the coefficients

From lecture 5: centre the continuous predictor, so that 0 means “average frequency”.

lex <- lex |> mutate(log_freq_c = log_freq - mean(log_freq))
coef(lm(rt ~ log_freq_c * group, data = lex))
       (Intercept)         log_freq_c            groupL2 
         616.93726          -27.05222           91.69311 
log_freq_c:groupL2 
         -31.43419 

groupL2 is now the L1–L2 difference for a word of average frequency, which is what most readers expect it to mean. The slope and the interaction do not change.

Did the interaction help?

c(additive    = glance(m_add)$adj.r.squared,
  interaction = glance(m_int)$adj.r.squared)
   additive interaction 
  0.7078173   0.7709118 

Adjusted R² goes up: separate slopes describe these data better. Whether the interaction is reliable beyond this sample is a question for inference, later in the course.

Categorical × categorical

Did you know: Hadza hunters in Tanzania whistle instead. Honeyguides in Tanzania were over three times more likely to cooperate with the Hadza whistle than with the Yao call, and in Mozambique almost twice as likely with the Yao call: the effect of the call depends on the place.

Source: Spottiswoode & Wood (2023), Science

The data

Vowel duration by voicing of the following consonant and speaking style, simulated:

set.seed(11)
cells <- tribble(
  ~style,   ~voicing,    ~mu,
  "casual", "voiceless", 110,
  "casual", "voiced",    145,
  "clear",  "voiceless", 125,
  "clear",  "voiced",    180
)
vs <- cells |>
  uncount(40) |>                                    # 40 tokens per cell
  mutate(duration = round(rnorm(n(), mu, 18)),
         voicing  = fct_relevel(voicing, "voiceless")) |>
  select(-mu)

Four cell means

vs |> summarise(mean = mean(duration), .by = c(style, voicing))
# A tibble: 4 × 3
  style  voicing    mean
  <chr>  <fct>     <dbl>
1 casual voiceless  104.
2 casual voiced     146.
3 clear  voiceless  125.
4 clear  voiced     182.

An interaction plot

Clear speech lengthens vowels, more so before voiced consonants: the lines are not parallel.

Treatment-coded interaction

m_vs <- lm(duration ~ voicing * style, data = vs)
coef(m_vs)
             (Intercept)            voicingvoiced 
                 104.175                   41.525 
              styleclear voicingvoiced:styleclear 
                  20.675                   15.425 
Coefficient Meaning
(Intercept) mean of voiceless, casual (both reference levels)
voicingvoiced voicing effect in casual speech only
styleclear style effect for voiceless only
voicingvoiced:styleclear how much bigger the voicing effect is in clear speech

Rebuilding the cell means

b <- coef(m_vs)
c(voiceless_casual = b[[1]],
  voiced_casual    = b[[1]] + b[[2]],
  voiceless_clear  = b[[1]] + b[[3]],
  voiced_clear     = b[[1]] + b[[2]] + b[[3]] + b[[4]])
voiceless_casual    voiced_casual  voiceless_clear     voiced_clear 
         104.175          145.700          124.850          181.800 

The interaction coefficient is added only in the cell where both non-reference levels meet.

Sum coding, as promised

vs_sum <- vs |> mutate(style = factor(style))
contrasts(vs_sum$voicing) <- contr.sum(2)
contrasts(vs_sum$style)   <- contr.sum(2)
coef(lm(duration ~ voicing * style, data = vs_sum))
    (Intercept)        voicing1          style1 voicing1:style1 
      139.13125       -24.61875       -14.19375         3.85625 
  • Intercept: the grand mean of the four cells
  • voicing1, style1: effects averaged over the other predictor, the classic main effects
  • The fit and the predictions are unchanged; only the coefficients mean something different

Which coding?

With treatment coding, each effect describes the reference level of the other predictor: the voicing effect in casual speech. With sum coding, each effect is averaged over the levels of the other predictor: the voicing effect across both styles.

With interactions, always check which coding you used before interpreting a “main effect”.

Continuous × continuous

Did you know: honeyguides lay their eggs in other birds’ nests. The chick hatches with a hooked bill, which it uses to kill its foster nest-mates.

Source: University of Cape Town, FitzPatrick Institute

The data

Back to word frequency and length, simulated, now with an interaction:

set.seed(12)
w <- tibble(log_freq = runif(400, 1, 6),
            length   = round(runif(400, 3, 12))) |>
  mutate(lf_c  = log_freq - mean(log_freq),
         len_c = length - mean(length),
         rt    = 760 - 30 * lf_c + 14 * len_c - 5 * lf_c * len_c + rnorm(400, 0, 40))

The interaction term multiplies the two predictors: here, length matters less for frequent words.

Uncentred: a surprise

coef(lm(rt ~ log_freq * length, data = w))
    (Intercept)        log_freq          length log_freq:length 
     620.941597        9.734164       31.430518       -5.103751 

The frequency slope is positive? It is the effect of frequency for a word of length 0.

coef(lm(rt ~ lf_c * len_c, data = w))
(Intercept)        lf_c       len_c  lf_c:len_c 
 756.910198  -28.288779   13.715687   -5.103751 

Centred, the simple effects describe a word of average frequency and length. The interaction is the same in both models.

Simple slopes

The effect of length at three frequencies. Compare lecture 6: the lines now fan out.

A twisted plane

Without an interaction, the contour lines were straight and parallel (lecture 6). With one, they bend: the plane is twisted.

Higher-order interactions

Did you know: in Niassa National Reserve, Mozambique, 75% of the bees’ nests that Yao honey hunters found were found with the help of a honeyguide.

Source: Spottiswoode, Begg & Begg (2016), Science

Three-way interactions

lm(y ~ a * b * c)

expands to 7 coefficients beyond the intercept:

  • three simple effects: a, b and c
  • three two-way interactions: a:b, a:c and b:c
  • and the three-way interaction a:b:c: the interaction between a and b changes with c

Three-way interactions are hard to interpret and need a lot of data. Include them only when your research question asks for one.

Reading interactions: a checklist

  1. Plot the data: do the lines or groups look non-parallel?
  2. Centre continuous predictors and choose your coding before fitting
  3. Read the interaction term as a change in an effect
  4. Read lower-order terms as simple effects, at 0 or the reference level
  5. Plot the model predictions as well as the coefficients

Your turn

Exercises

With palmerpenguins::penguins, after drop_na():

  1. Model body_mass_g by flipper_length_mm * species. Plot the fitted lines.
  2. What is the flipper slope for Gentoo penguins?
  3. Centre flipper length and refit. Which coefficients changed?
  4. Model body_mass_g by species * sex, first with treatment coding, then with sum coding. Interpret the intercept of each.

Solutions

pengs <- palmerpenguins::penguins |>
  drop_na(body_mass_g, flipper_length_mm, sex) |>
  mutate(flipper_c = flipper_length_mm - mean(flipper_length_mm))

b_p <- coef(lm(body_mass_g ~ flipper_length_mm * species, data = pengs))
b_p
                       (Intercept)                  flipper_length_mm 
                       -2508.08774                           32.68891 
                  speciesChinstrap                      speciesGentoo 
                        -529.10803                        -4166.11653 
flipper_length_mm:speciesChinstrap    flipper_length_mm:speciesGentoo 
                           1.88448                           21.47651 
b_p[["flipper_length_mm"]] + b_p[["flipper_length_mm:speciesGentoo"]]   # Gentoo slope
[1] 54.16542
coef(lm(body_mass_g ~ flipper_c * species, data = pengs))
               (Intercept)                  flipper_c 
                4061.30417                   32.68891 
          speciesChinstrap              speciesGentoo 
                -150.38985                  149.95200 
flipper_c:speciesChinstrap    flipper_c:speciesGentoo 
                   1.88448                   21.47651 

Solutions

coef(lm(body_mass_g ~ species * sex, data = pengs))
             (Intercept)         speciesChinstrap 
               3368.8356                 158.3703 
           speciesGentoo                  sexmale 
               1310.9058                 674.6575 
speciesChinstrap:sexmale    speciesGentoo:sexmale 
               -262.8928                 130.4372 
pengs_sum <- pengs
contrasts(pengs_sum$species) <- contr.sum(3)
contrasts(pengs_sum$sex)     <- contr.sum(2)
coef(lm(body_mass_g ~ species * sex, data = pengs_sum))
  (Intercept)      species1      species2          sex1 species1:sex1 
   4173.84711    -467.68273    -440.75888    -315.25282     -22.07595 
species2:sex1 
    109.37047 

Treatment: the intercept is the mean of female Adelie penguins. Sum: the grand mean of the six species × sex cells.

Summary

  • An interaction: the effect of one predictor depends on another; lines stop being parallel
  • y ~ x * z = x + z + x:z
  • The interaction term is a change in an effect
  • Lower-order terms become simple effects: centre continuous predictors, choose your coding
  • Continuous × continuous interactions twist the regression plane
  • Higher-order interactions: only when the research question needs them

Slides and code

QR code linking to these slides

These slides

Code, data and all lectures