36  Interactions in regression: one categorical and one numeric predictor

In Chapter 35, you learned how to include and interpret interactions between two categorical predictors: we modelled mean f0 in female vs male speakers in informal vs polite attitude. In this chapter, we will look at a model with an interaction between a categorical and a numeric predictor instead. Interactions between categorical and numeric predictors work on the same principles of interactions between two categorical predictors, but it is worth going through an example. We will revisit the model of reaction times from Chapter 31, but this time we will model RTs based on both mean phoneme-level Levenshtein distance and word type (real vs non-real).

36.1 The data

As usual, let’s attach the necessary packages and read the data. We filter RTs to include only values above 34 ms, as we have done in Chapter 31.

library(tidyverse)
theme_set(theme_light())
library(brms)
library(posterior)
library(tidybayes)
library(ggdist)
mald_filt <- readRDS("data/tucker2019/mald_1_1.rds") |> 
  filter(RT > 34)

We want to answer the following research question:

Does the effect of Levenshtein distance on RTs differ in real and non-real words?

To address this question we will need to model an interaction between distance and word type. Before we move onto modelling, let’s plot the data. Figure 36.1 shows a scatter plot with mean phoneme-level Levenshtein distance on the x-axis and logged reaction times on the y-axis. The plot also includes two regression lines: one for real words (in green) and one for non-real words (in orange).

Code
mald_filt |> 
  ggplot(aes(PhonLev, RT_log)) +
  geom_point(alpha = 0.05) +
  geom_smooth(aes(colour = IsWord), method = "lm") +
  scale_color_brewer(palette = "Dark2") +
  labs(x = "Levenshtein distance", y = "RT (ms)")
Figure 36.1: Scatter plot of Levenshtein distance vs RTs in real and non-real words.

From glancing at the plot, it looks like the regression line is steeper in real words relative to non-real words, although they do end up overlapping at higher Levenshtein distance, thus potentially indicating a ceiling effect of Levenshtein distance. Independent of the reason behind this pattern (which statistics per se cannot answer), we can run a regression model to quantify differences.

36.2 One categorical and one numeric predictor (no interaction)

Let’s start by figuring out what the model mathematical formula looks like. First, let’s build the formula without the interaction term.

\[\begin{aligned} RT_i & \sim LogNormal(\mu_i, \sigma)\\ \mu_i & = \beta_0 + \beta_1 \cdot l_i + \beta_2 \cdot w_i\\ \end{aligned}\]

  • \(l\) is Levenshtein distance and \(w\) is the indicator variable for IsWord. \(w=0\) is TRUE and \(w=1\) is FALSE. (The reference level of IsWord is TRUE.)

  • \(\beta_0\) is the intercept, i.e. the mean logged RTs (logged because it is a log-normal model) when PhonLev is 0 and IsWord is the reference level, TRUE.

  • \(\beta_1\) is the difference in outcome (i.e. mean logged RTs) for each unit increase of PhonLev, while controlling for IsWord.

  • \(\beta_2\) is the difference in outcome when the word is a non-real word compared to when it is a real word, while controlling for PhonLev.

Since there is no interaction between distance and word type, the effect of distance on RTs is estimated to be the same independent of which word type the observation comes from and vice versa, the effect of word type on RTs is estimated to be the same independent of the value of distance. Let’s run this model so we can inspect the estimates of the regression coefficients and plot the results to see what the estimated effects look like.

rt_lw <- brm(
  RT ~ PhonLev + IsWord,
  family = lognormal,
  data = mald_filt,
  cores = 4,
  seed = 1927,
  file = "cache/ch-regression-cat-num-rt_lw"
)

The model has three regression coefficients, as you would expect (in the following, the 80% CrI are shown).

fixef(rt_lw, probs = c(0.1, 0.9)) |> round(2)
            Estimate Est.Error  Q10  Q90
Intercept       6.58      0.02 6.55 6.61
PhonLev         0.03      0.00 0.03 0.04
IsWordFALSE     0.11      0.01 0.10 0.12
  • The Intercept corresponds to the logged RTs when PhonLev is 0 and IsWord is TRUE. At 80% probability, the logged RTs in these conditions are between 6.55 and 6.61.

  • According to the PhonLev estimate, for each unit increase of PhonLev the logged RTs increase by 0.03 to 0.04 (when controlling for word type) at 80% confidence.

  • IsWordFalse indicates that the logged RTs are 0.1 to 0.12 higher in non-words than in real words (when controlling for distance).

The estimated effect of PhonLev is the same in both real words (the reference level of IsWord) and in non-words. Vice versa, the estimated effect of IsWord is the same independent of the value of PhonLev. We can confirm this visually by plotting the expected values. For convenience, we use conditional_effects(). This time we use the method argument to tell the function to plot the linear predictor (method = "posterior_linpred") rather than the expected values (method = "posterior_epred"). We do this so that we can see the effects on the linear scale of logged RTs, rather than on the original scale (since we are using a log-normal model, which makes effects on the original scale log-linear rather than simply linear).

conditional_effects(rt_lw, "PhonLev:IsWord", method = "posterior_linpred")
conditional_effects(rt_lw, "IsWord:PhonLev", method = "posterior_linpred")
(a) Effect of mean phoneme-level distance in real words and non-words.
(b) Effect of word type at three values of mean distance.
Figure 36.2: Posterior draws of the linear predictor from a model without interactions (mean and 95% CrI).

In Figure 36.2 (a), logged RT increase with greater mean phoneme-level Levenshtein distance in both real and non-words. If you look closely, you will also notice that the two regression lines are parallel. In other words, the slope of the lines is the same: this is because the model estimates a single effect of mean distance for both real and non-words. There is no other way this can be, because the model does not include an interaction between word type and distance. Conversely, if we look at Figure 36.2 (b), we can see that the effect of mean distance within real and non-words is the same in both word types. Note that conditional_effects() automatically picks “representative” values of the numeric predictor in this plot: by default, the representative values are the mean and mean \(\pm\) 1 SD of the observed values in the data for that predictor. If you compare, within each word type, the distances between the posterior logged RTs for the three values of distance, their distances are the same in both word types.

However, we have reasons to believe that the effects of word type and mean phoneme-level distance are not totally independent from each other. Why? Because this is the default assumption: from a linguistic point of view, we know that non-words behave differently from real words, so it makes sense to expect that the effect of phoneme-level distance might also be different. So what we really need is a model with an interaction between the two predictors. Note that we only fitted a model without an interaction for pedagogical reasons, but you should not fit a model without interactions if you intend to fit one with interactions.

36.3 One categorical and one numeric predictor in interaction

The model with an interaction between phoneme-level distance and word type can be represented with the following formula:

\[\begin{aligned} RT_i & \sim LogNormal(\mu_i, \sigma)\\ \mu_i & = \beta_0 + \beta_1 \cdot l_i + \beta_2 \cdot w_i + \beta_3 \cdot l_i \cdot w_i\\ \end{aligned}\]

Because of the interaction term \(\beta_3 \cdot l_i \cdot w_i\), now the effect of the each predictor depends on the value of the other. When distance \(l = 0\) and word type is non-word, the regression formula simplifies to \(\beta_0 + \beta_2\) (all terms with \(l\) are dropped). When distance \(l \neq 0\) and word type is non-word, then all terms are preserved and whatever value \(l\) takes is plugged in: \(\beta_0 + \beta_1 \cdot l_i + \beta_2 + \beta_3 \cdot l_i\).

The R syntax for interactions between predictors of different type is the same as with two categorical predictors: you include an interaction term by adding the two predictors separated by a colon, like PhonLev:IsWord. We have also seen the syntactic sugar * which automatically includes the main coefficients and the interaction term, but for clarity we use the full syntax here.

rt_lw_int <- brm(
  # Equivalent: RT ~ PhonLev * IsWord
  RT ~ PhonLev + IsWord + PhonLev:IsWord,
  family = lognormal,
  data = mald_filt,
  cores = 4,
  seed = 1927,
  file = "cache/ch-regression-cat-num-rt_lw_int"
)

Now that we have included an interaction, the interpretation of the PhonLev and IsWordFALSE coefficients changes.

fixef(rt_lw_int, probs = c(0.1, 0.9)) |> round(2)
                    Estimate Est.Error   Q10   Q90
Intercept               6.52      0.03  6.48  6.56
PhonLev                 0.04      0.00  0.04  0.05
IsWordFALSE             0.23      0.04  0.18  0.29
PhonLev:IsWordFALSE    -0.02      0.01 -0.02 -0.01
  • The Intercept is still the logged RTs when phoneme-level distance is 0 and the word is real, but you will notice that the estimate is slightly different now. This is common when including an interaction between predictors. Since the effects of each predictor are allowed to differ depending on the value of the other predictor, the intercept might be affected.

  • Now, the PhonLev coefficient is the effect of PhonLev when the word is real. In other words, the estimate of this coefficient applies exclusively to real words. Why real words? Because that’s the reference level of IsWord. According to the model, for each unit increase of phoneme-level distance in real words, logged RTs increase by 0.04 to 0.05 (at 80% probability). This is a slightly larger difference than that estimated by the model without the interaction.

  • IsWordFALSE tells us the difference between non-words and real words when phoneme-level distance is 0. Why 0? Because that’s the “default” value of a numeric predictor. Non-words elicit logged RTs that are 0.18 to 0.29 longer than real words, when distance is 0. This difference is much larger than the one suggested by the model without an interaction.

  • PhonLev:IsWordFALSE is the coefficient of the interaction term. This coefficient tells us how the effect of PhonLev differs in non-words vs real words. In other words, it tells us what we need to add or subtract to the phoneme-level distance effect of real words to get that effect in non-words. According to the model, the effect of distance in non-words on logged RTs is between 0.01 and 0.02 units smaller than the effect in real words, at 80% confidence. This is because the 80% CrI of the posterior distribution of the coefficient is [-0.02, -0.01].

So what is the effect of phoneme-level distance in non-words? Easy, we can compute that using the posterior draws of the PhonLev and PhonLev:IsWordFALSE coefficients. We simply sum them to obtain the effect of mean distance on logged RTs when the word is a non-word.

rt_lw_int_draws <- as_draws_df(rt_lw_int) |> 
  mutate(
    # Remember to use backticks `` with column names that have colons.
    PhonLev_false = b_PhonLev + `b_PhonLev:IsWordFALSE`
  )

quantile2(rt_lw_int_draws$PhonLev_false, probs = c(0.1, 0.9)) |> round(2)
 q10  q90 
0.02 0.03 

The 80% CrI of the posterior draws of the effect of distance in non-words is between 0.02 and 0.03. Compare it with the effect in real words, [0.04, 0.05]. The slope of the regression line of the effect of phoneme-level distance on logged RTs is thus less steep in non-words than in real words. How less steep is quantified by the PhonLev:IsWordFALSE coefficient. How does all this look like? Let’s plot the posterior draws of the linear predictor using conditional_effects().

conditional_effects(rt_lw_int, "PhonLev:IsWord", method = "posterior_linpred")
Figure 36.3: Posterior draws of the linear predictor from a model with an interaction (mean and 95% CrI).

Figure 36.3 shows the regression lines for real and non-words. The line is steeper in real words than in non-words. In other words, for each unit increase of phoneme-level distance, the logged RTs increase more in real than in non-words. Let’s plot the expected values now, but instead of using conditional_effects(), let’s use epred_draws(). First, we need the grid:

epred_grid <- expand_grid(
  PhonLev = seq(min(mald_filt$PhonLev), max(mald_filt$PhonLev), by = 0.1),
  IsWord = c("TRUE", "FALSE")
) |> 
  mutate(
    # We convert IsWord to factor so we can specify the order of the levels.
    IsWord = factor(IsWord, levels = c("TRUE", "FALSE"))
  )

Now, we can calculate the expected values.

rt_lw_int_epred <- epred_draws(
  rt_lw_int,
  epred_grid
)

rt_lw_int_epred

For plotting effects of numeric predictors, we need to calculate the mean and lower and upper limits of the credible interval.

rt_lw_int_epred_cri <- rt_lw_int_epred |> 
  summarise(
    mean = mean(.epred),
    lower = quantile2(.epred, probs = 0.025),
    upper = quantile2(.epred, probs = 0.975)
  )

Finally, we can plot the expected values.

rt_lw_int_epred_cri |> 
  ggplot(aes(PhonLev, mean)) +
  geom_ribbon(aes(ymin = lower, ymax = upper, fill = IsWord), alpha = 0.2) +
  geom_line(aes(colour = IsWord), linewidth = 1) +
  scale_fill_brewer(palette = "Dark2") +
  scale_colour_brewer(palette = "Dark2") +
  labs(x = "Phoneme-level distance", y = "Expected RTs (ms)", colour = "Real word?", fill = "Real word?")
Figure 36.4: Expected values of RT from a model with a phoneme-level distance/word type interaction.
WarningExercise 1

Reproduce Figure 36.3 using linpred_draws() to calculate the posterior draws of the linear predictor and then plot them with ggplot2 (like we did now with the expected values).

36.4 Calculating posterior draws at specific predictor values

With categorical/numeric interactions, it is often useful to calculate and report differences (aka comparisons) between the levels of the categorical predictor at different values of the numeric predictor. There isn’t a specific recipe to follow when it comes to calculating specific comparisons: it depends on the research question/hypothesis. Here, I illustrate how to calculate specific comparisons using three values of mean phoneme-level distance based on the range of values in the data. We can use 7, 10 and 12. From Figure 36.4, we should see a decreasing difference in logged RTs between non- and real words from distance 7 to 10 to 12.

We can calculate this difference using the posterior draws (and plugging in 7, 10, 12 in the regression formula) or use linpred_draws(). I will show you the former and you will do the latter as an exercise below. Before we get to the code, answer the following question.

NoteQuiz 1
Which is the correct regression formula to get the linear predictor of non-words when phoneme-level distance is 10?

The following code calculates the posterior draws of the linear predictor when distance is 7, 10, 12. Then, it gets the posterior draws of the difference between non-words and real words at those values of phoneme-level distance.

rt_lw_int_draws <- rt_lw_int_draws |> 
  mutate(
    # Posterior draws of the linear predictor
    rt_log_7_true = b_Intercept + b_PhonLev * 7,
    rt_log_7_false = b_Intercept + b_PhonLev * 7 + b_IsWordFALSE + `b_PhonLev:IsWordFALSE` * 7,
    rt_log_10_true = b_Intercept + b_PhonLev * 10,
    rt_log_10_false = b_Intercept + b_PhonLev * 10 + b_IsWordFALSE + `b_PhonLev:IsWordFALSE` * 10,
    rt_log_12_true = b_Intercept + b_PhonLev * 12,
    rt_log_12_false = b_Intercept + b_PhonLev * 12 + b_IsWordFALSE + `b_PhonLev:IsWordFALSE` * 12,
    
    # Posterior draws of the difference
    rt_log_7 = rt_log_7_false - rt_log_7_true,
    rt_log_10 = rt_log_10_false - rt_log_10_true,
    rt_log_12 = rt_log_12_false - rt_log_12_true,
  )

# Get 90% CrI of the differences
quantile2(rt_lw_int_draws$rt_log_7, probs = c(0.1, 0.9)) |> round(2)
 q10  q90 
0.10 0.12 
quantile2(rt_lw_int_draws$rt_log_10, probs = c(0.1, 0.9)) |> round(2)
 q10  q90 
0.03 0.08 
quantile2(rt_lw_int_draws$rt_log_12, probs = c(0.1, 0.9)) |> round(2)
  q10   q90 
-0.02  0.06 

The just calculated 80% CrIs of the posterior draws indicate that, at 80% probability, the logged RTs of non-words are 0.1 to 0.12 longer than those of real words when mean phoneme-level distance is 7, 0.03 to 0.08 longer when mean distance is 10 and they are -0.02 shorter to 0.06 longer when mean distance is 12. Indeed, the difference between real and non-words decreases with increasing mean distance, to the point where with mean distance 12 it is even possible that logged RTs are slightly faster in non-words than in real words (note however that the CrI spans both negative and positive values, with the majority of the CrI being along positive values).

WarningExercise 2

Calculate the posterior draws of the linear predictor at phoneme-level distance 7, 10 and 12 for real and non-words using linpred_draws(). Then calculate the difference between real and non-words.

You should set up a prediction grid as usual, but this time you list the specific values of distance you want.

36.5 Ratio effects

Let’s go back to our initial research question, Does the effect of Levenshtein distance on RTs differ in real and non-real words? Since the model uses a log-normal family, it is more straightforward to answer the question using ratios rather than logged milliseconds (on the linear predictor scale, i.e. logged RTs) or milliseconds (on the response scale, i.e. RTs). As we’ve done in Chapter 31, ratios can be calculated by exponentiating the log differences.

# Real words
quantile2(exp(rt_lw_int_draws$b_PhonLev), probs = c(0.1, 0.9)) |> round(2)
 q10  q90 
1.04 1.05 
# Non-words
quantile2(exp(rt_lw_int_draws$b_PhonLev + rt_lw_int_draws$`b_PhonLev:IsWordFALSE`), probs = c(0.1, 0.9)) |> round(2)
 q10  q90 
1.02 1.03 

At 80% probability, RTs increase by a factor of 1.04-1.05 for each unit increase of mean phoneme-level Levenshtein distance in real words, while in non-words they increase by a factor of 1.02-1.03. In percentages, that is a 4-5% increase in real words and a 2-3% increase in non-words. We can also calculate ratios for more than one unit increase: let’s say for example we want the ratio between RTs when distance is 12 and when it is 7, in real and non-words. First, we need the number of units: \(12-7=5\). So between distance 7 and 12 there is an increase of 5 units. Now we can obtain the ratio of RTs at Levenshtein distance 12 to RTs at Levenshtein distance 5 by multiplying the posterior draws of the relevant coefficients by 5. Note that for non-words we need to multiply both b_PhonLev and b_PhonLev:IsWordFalse.

# Real words
quantile2(exp(rt_lw_int_draws$b_PhonLev * 5), probs = c(0.1, 0.9)) |> round(2)
 q10  q90 
1.21 1.27 
# Non-words
quantile2(exp(rt_lw_int_draws$b_PhonLev * 5 + rt_lw_int_draws$`b_PhonLev:IsWordFALSE` * 5), probs = c(0.1, 0.9)) |> round(2)
 q10  q90 
1.11 1.17 

The RTs increase by 21-27% from distance 7 to distance 12 in real words, but only by 11-17% in non-words, at 80% confidence.

36.6 Summary

  • An interaction between a categorical and numeric predictor can be included in a model with the same syntax as a categorical/categorical interaction: cat + num + cat:num or equivalently cat * num.

  • The interaction coefficient indicates the adjustment to the main coefficient for each unit increase of the numeric predictor, at the other level of the categorical predictor.

  • Estimates for more than one unit increase of the numeric predictor can be calculated by multiply the posterior draws of both the main coefficient of the numeric predictor and the interaction coefficient by the desired value.

  • Categorical/numeric interactions are easily extended to categorical predictors with more than one level. There will be \(N - 1\) interaction terms, where \(N\) is the number of levels in the categorical predictor.