My Interaction Is Significant—What Do I Report?
Translate the interaction into planned slopes and contrasts, with uncertainty on the scale your research question needs.
By Data Analysis & Statistical Solutions (DASS) · Published · Presentation updated
Symptoms
The interaction term is significant, but you are unsure which groups differ, where they differ, or how to explain the lower-order coefficients. A table of coefficients does not directly answer the substantive question.
What it means
In a linear model with a continuous predictor and a binary group coded 0 and 1, the interaction coefficient is the difference in predictor slopes between groups. The continuous predictor’s coefficient is its slope in group 0. The group coefficient is the group difference when the continuous predictor equals zero.
Run these checks
- Verify predictor units, group coding, centering, and whether zero is meaningful.
- Define the slopes or contrasts needed for the research question before searching across many values.
- Inspect the observed range within each group and avoid unsupported extrapolation.
- Check functional form, residual patterns, and dependence. Use appropriate inference for clustering or repeated observations.
What not to do
Do not conclude that slopes differ because one is significant and another is not. Test their difference directly. Do not interpret a group coefficient as a constant group difference when an interaction is present. Do not report a selected contrast without acknowledging exploratory selection or multiplicity.
Reporting options
Report the interaction estimate and its uncertainty, then relevant conditional slopes or group contrasts. Plot predicted outcomes over supported values with uncertainty and enough information about the data range. For a multi-category moderator, an overall interaction test and individual contrasts answer distinct questions.
For logistic models, a product term concerns log odds; probability-scale contrasts require a separate calculation. This worked example is ordinary linear regression, not a demonstration of logistic interaction inference.
Worked example
The scripts construct 16 synthetic observations with predictor values 0 through 7 in each group. The residual pattern is deliberately made orthogonal to the model matrix, yielding coefficients 40, 2, 5, and −1 for the intercept, predictor, group, and interaction. This controlled illustration is not client evidence or a power simulation.
| Contrast | Estimate | 95% CI |
|---|---|---|
| Slope in group 0 | 2.000 | 1.477 to 2.523 |
| Slope in group 1 | 1.000 | 0.477 to 1.523 |
| Slope difference, group 1 minus group 0 | −1.000 | −1.739 to −0.261 |
| Group difference at predictor 2 | 3.000 | 0.975 to 5.025 |
| Group difference at predictor 6 | −1.000 | −3.508 to 1.508 |
The slope difference excludes zero, while the group difference at predictor 6 is imprecise and includes zero. A detectable interaction does not imply detectable group differences at every value. The scripts calculate uncertainty using the full coefficient covariance matrix and 12 residual degrees of freedom. The selected values illustrate interpretation; they are not a recommended universal probing rule.
What to tell the reviewer
For this synthetic example: With group coded 0/1, the estimated predictor slope was 2.00 in group 0 and 1.00 in group 1. Their difference was −1.00 (95% CI −1.739 to −0.261). We report conditional group contrasts at specified predictor values and distinguish these from the slope-difference test. These model associations do not establish a causal mechanism.
For your study, replace the illustrative values with justified contrasts and estimates. See the interaction guide and the troubleshooting worksheet.
See it in R and Python
R and Python construct the same deterministic synthetic observations and reproduce OLS coefficients and covariance-based confidence intervals.
Python dependencies: NumPy and SciPy. Install with python -m pip install numpy scipy.
# Deterministic synthetic linear-model illustration; base R.
x <- rep(0:7, 2); g <- rep(0:1, each=8)
X <- cbind(1,x,g,x*g)
z <- sin(seq_along(x))
e <- qr.resid(qr(X),z)*2
response <- as.vector(X %*% c(40,2,5,-1)+e)
fit <- lm(response ~ x*g)
print(coef(fit))
contrast <- function(L,label) {
est <- sum(L*coef(fit)); se <- sqrt(drop(t(L)%*%vcov(fit)%*%L))
ci <- est+c(-1,1)*qt(.975,df.residual(fit))*se
cat(sprintf("%s estimate=%.6f SE=%.6f CI=[%.6f,%.6f]\n",label,est,se,ci[1],ci[2]))
}
contrast(c(0,1,0,0),"slope g=0")
contrast(c(0,1,0,1),"slope g=1")
contrast(c(0,0,0,1),"slope difference")
for(h in c(2,6)) contrast(c(0,0,1,h),paste("group difference x=",h))
# Same deterministic synthetic data and OLS contrasts as R.
import numpy as np
from scipy.stats import t
x=np.tile(np.arange(8.),2); g=np.repeat([0.,1.],8)
X=np.column_stack([np.ones(16),x,g,x*g])
z=np.sin(np.arange(1.,17.))
e=(z-X@np.linalg.lstsq(X,z,rcond=None)[0])*2
y=X@np.array([40.,2.,5.,-1.])+e
b=np.linalg.lstsq(X,y,rcond=None)[0]; df=12
v=(np.sum((y-X@b)**2)/df)*np.linalg.inv(X.T@X)
print(b)
for label,L in [('slope g=0',[0,1,0,0]),('slope g=1',[0,1,0,1]),('slope difference',[0,0,0,1]),('group difference x= 2',[0,0,1,2]),('group difference x= 6',[0,0,1,6])]:
L=np.array(L); est=L@b; se=np.sqrt(L@v@L); ci=est+np.array([-1,1])*t.ppf(.975,df)*se
print(f'{label} estimate={est:.6f} SE={se:.6f} CI=[{ci[0]:.6f},{ci[1]:.6f}]')
assert np.allclose(b,[40,2,5,-1])
Base R only. Python requires NumPy and SciPy. Conventional OLS confidence intervals assume independent errors and constant variance in this illustration.
Download case-011-interaction.R →