Skip to content
Statistical RethinkingStatistical Rethinking

7.3 Continuous interactions

When both interacting variables are continuous, the interaction is a product term in the linear model. Interpretation requires care because the effect of one variable is a linear function of the other.

7.3.1 The challenge of continuous interactions

With two continuous predictors, the interaction creates a twisted plane — the expected outcome is a bilinear function of both predictors:

μi=α+βxxi+βzzi+βxzxizi\mu_i = \alpha + \beta_x x_i + \beta_z z_i + \beta_{xz} x_i z_i

The slope of xx is βx+βxzz\beta_x + \beta_{xz} z, which varies continuously with zz. This makes the coefficients hard to interpret directly — you need to evaluate the slope at specific values of zz.

# Continuous interaction: the effect of temperature depends on rainfall
set.seed(42)
n <- 200

# Temperature (Celsius) and rainfall (mm)

temp <- rnorm(n, mean = 20, sd = 5)
rain <- rnorm(n, mean = 50, sd = 15)

# Crop yield: positive effect of temp, but only when rain is high

yield <- 100 + 2 _ temp + 0.5 _ rain + 0.05 _ temp _ rain + rnorm(n, sd = 10)

# Standardize for easier interpretation

temp_s <- (temp - mean(temp)) / sd(temp)
rain_s <- (rain - mean(rain)) / sd(rain)

m_int <- lm(yield ~ temp_s * rain_s)

cat("Continuous interaction model:\n")
cat(" beta_temp:", round(coef(m_int)[2], 3), "\n")
cat(" beta_rain:", round(coef(m_int)[3], 3), "\n")
cat(" beta_temp:rain:", round(coef(m_int)[4], 3), "\n")

cat("\nSlope of temp at different rain levels:\n")
cat(" Rain = -1 SD:", round(coef(m*int)[2] + coef(m_int)[4] * (-1), 3), "\n")
cat(" Rain = 0 (mean):", round(coef(m*int)[2], 3), "\n")
cat(" Rain = +1 SD:", round(coef(m_int)[2] + coef(m_int)[4] * 1, 3), "\n")
Click Run to execute this code in your browser with WebR

7.3.2 Visualizing continuous interactions

The best way to understand a continuous interaction is to plot the predicted outcome at several values of the moderator.

# Visualize the continuous interaction
set.seed(42)
n <- 200
temp <- rnorm(n, mean = 20, sd = 5)
rain <- rnorm(n, mean = 50, sd = 15)
yield <- 100 + 2 * temp + 0.5 * rain + 0.05 * temp * rain + rnorm(n, sd = 10)

m_int <- lm(yield ~ temp * rain)

# Plot predicted yield vs temp at different rain levels

temp_seq <- seq(10, 30, length.out = 100)
rain_vals <- c(30, 50, 70)
colors <- c("firebrick", "steelblue", "darkgreen")

plot(NULL, xlim = c(10, 30), ylim = c(80, 200),
xlab = "Temperature (C)", ylab = "Crop yield",
main = "Continuous interaction: temp x rain")
for (i in 1:3) {
pred <- coef(m*int)[1] + coef(m_int)[2] * temp*seq +
coef(m_int)[3] * rain*vals[i] +
coef(m_int)[4] * temp*seq * rain_vals[i]
lines(temp_seq, pred, lwd = 2, col = colors[i])
}
legend("topleft", legend = paste("Rain =", rain_vals, "mm"), col = colors, lwd = 2)

cat("At low rain (30mm), temp has a smaller effect\n")
cat("At high rain (70mm), temp has a larger effect\n")
cat("The interaction (0.05) quantifies this difference\n")
Click Run to execute this code in your browser with WebR

7.3.3 Centering for interpretation

Centering both predictors makes the main effects interpretable as the effect at the average value of the other predictor, not at zero.

# Centering for interpretation
set.seed(42)
n <- 200
temp <- rnorm(n, mean = 20, sd = 5)
rain <- rnorm(n, mean = 50, sd = 15)
yield <- 100 + 2 * temp + 0.5 * rain + 0.05 * temp * rain + rnorm(n, sd = 10)

# Uncentered

m_raw <- lm(yield ~ temp * rain)
cat("Uncentered:\n")
cat(" beta_temp:", round(coef(m_raw)[2], 3),
"(effect of temp when rain = 0, which is meaningless)\n")
cat(" beta_rain:", round(coef(m_raw)[3], 3),
"(effect of rain when temp = 0, which is meaningless)\n")

# Centered

temp_c <- temp - mean(temp)
rain_c <- rain - mean(rain)
m_cen <- lm(yield ~ temp_c * rain_c)
cat("\nCentered:\n")
cat(" beta_temp:", round(coef(m_cen)[2], 3),
"(effect of temp at average rain)\n")
cat(" beta_rain:", round(coef(m_cen)[3], 3),
"(effect of rain at average temp)\n")
cat(" beta_interaction:", round(coef(m_cen)[4], 3),
"(same as uncentered)\n")
Click Run to execute this code in your browser with WebR
Rethinking

Plot, don't just read coefficients. Continuous interactions are nearly impossible to interpret from coefficients alone. The main effects depend on where zero is, and the interaction effect depends on the scale of both variables. The only reliable way to understand a continuous interaction is to plot the predicted outcome at several meaningful values of the moderator. This is not a shortcut — it is the correct approach. The coefficients are parameters of a model; the plots are the meaning.

Summary of Section 7.3

  • Continuous interactions create a twisted plane where the slope of one variable varies continuously with the other.
  • The slope of xx is βx+βxzz\beta_x + \beta_{xz} z — evaluate it at meaningful values of zz.
  • Centering makes main effects interpretable as the effect at the average of the other predictor.
  • Plot predictions at several values of the moderator — coefficients alone are not enough.
  • The interaction coefficient is the same whether or not you center, but the main effects change.