Understanding categorical predictors in a mixed-effects model

Categorical predictors
Mixed-effects models
R
Author

Nora Wickelmaier

Published

November 5, 2024

This post is meant to give you a better understanding how to interpret parameters for categorical predictors in a mixed-effects model. Understanding what the parameters mean is helpful when simulating data, e.g., for power simulations.

Example

This is an example where three different media are compared to each other: A Multi-Touch Table (MTT) and two different Virtual Reality conditions (VR1 and VR2). These are all presented to every subject (within-factor mediatype). Additionally, we have a between-factor interaction with levels passive and active. Subjects can either actively interact for all media types or they can just passively look at things presented to them with the three different media types. The dependent varible is immersion. Subjects should feel more immersed when actively interacting and Virtual Reality should also lead to more immersion than a Multi-Touch Table.

library(lme4)

# Data simulation for a 2 (between) x 3 (within) design
set.seed(1046)

n   <- 100
dat <- data.frame(id          = factor(rep(1:n, each = 3)),
                  mediatype   = factor(c("MTT", "VR1", "VR2")),
                  interaction = factor(rep(c("active", "passive"), each = 3),
                                       levels = c("passive", "active")))

## Set parameters
beta  <- c(.5, .2, .3, .4, .7, .4)
sigma <- .8
spi   <- .5
theta <- spi / sigma

## Simulate data
dat$y <- simulate(~ 1 + mediatype + interaction + mediatype:interaction +
                  (1 | id),
                  newparam = list(beta = beta, theta = theta, sigma = sigma),
                  newdata = dat)$sim_1

## Visualize simulated data
datm <- aggregate(y ~ mediatype + interaction, data = dat, FUN = mean)

colors <- c("#3CB4DC", "#91C86E")

par(mai = c(.8, .8, .1, .1), mgp = c(2.4, 1, 0))
plot(y ~ I(1:3), data = datm[datm$interaction == "passive", ],
     xlab = "Media type",
     ylab = "Immersion",
     xlim = c(0.5, 3.5),
     ylim = c(-2, 4),
     axes = FALSE,
     type = "n")
axis(1, at = 1:3, labels = c("MTT", "VR1", "VR2"))
axis(2)
box()
points(y ~ jitter(as.numeric(mediatype)), data = dat,
       col = colors[dat$interaction])
points(y ~ I(1:3), datm[datm$interaction == "passive", ], col = colors[1],
       type = "b", pch = 16, cex = 2)
points(y ~ I(1:3), datm[datm$interaction == "active", ], col = colors[2],
       type = "b", pch = 16, cex = 2)
text(1.45, 0.4, "passive", col = colors[1])
text(1.4, 1.6, "active", col = colors[2])

Linear mixed-effects model

The following model will be fitted to the data.

\[\begin{align*} y = & \beta_0 + \beta_1 mediatype_{VR1} + \beta_2 mediatype_{VR2} + \beta_3 interaction_{active} + \beta_4 (mediatype_{VR1} \times interaction_{active}) + \\ & \beta_5 (mediatype_{VR2} \times interaction_{active}) + \upsilon_0 + \varepsilon \end{align*}\]

with \(\upsilon_0 \sim N(0, \sigma^2_{\upsilon_0})\) and \(\varepsilon \sim N(0, \sigma^2_{\varepsilon})\)

## Fit linear mixed-effects model
m1 <- lmer(y ~ 1 + mediatype + interaction + mediatype:interaction + (1| id),
           data = dat)

tab <- summary(m1)$coef
ci  <- confint(m1)
Estimate Std. Error t value 2.5 % 97.5 %
(Intercept) 0.421 0.128 3.295 0.172 0.670
mediatypeVR1 0.439 0.146 3.014 0.155 0.723
mediatypeVR2 0.393 0.146 2.702 0.110 0.677
interactionactive 0.499 0.181 2.763 0.147 0.852
mediatypeVR1:interactionactive 0.474 0.206 2.304 0.073 0.876
mediatypeVR2:interactionactive 0.280 0.206 1.357 -0.122 0.681

Implied model formulae

The two categorical predictors are dummy coded. For interaction the level passive is set as reference category. The dummy variable \(interaction_{active}\) is therefore 0 for passive and 1 for active.

contrasts(dat$interaction)
        active
passive      0
active       1

The three level factor mediatype is represented by two dummy variables, namely \(mediatype_{VR1}\) and \(mediatype_{VR2}\). Since MTT is the reference category, the coding scheme is as folows.

contrasts(dat$mediatype)
    VR1 VR2
MTT   0   0
VR1   1   0
VR2   0   1

When we plug these dummy variables into the model formula from above, we get the following equations for each data point of the aggregated data set.

\[\begin{align*} y_{11} & = \beta_0 + \upsilon_0 + \varepsilon & \text{value for an average person for the reference categories (MTT and passive)}\\ y_{12} & = \beta_0 + \beta_1 + \upsilon_0 + \varepsilon & \text{effect when going from MTT to VR1 for passive}\\ y_{13} & = \beta_0 + \beta_2 + \upsilon_0 + \varepsilon & \text{effect when going from MTT to VR2 for passive}\\ y_{21} & = \beta_0 + \beta_3 + \upsilon_0 + \varepsilon & \text{effect when going from passive to active for MTT}\\ y_{22} & = \beta_0 + \beta_1 + \beta_3 + \beta_4 + \upsilon_0 + \varepsilon & \text{effect when going from MTT to VR1 for active}\\ y_{23} & = \beta_0 + \beta_2 + \beta_3 + \beta_5 + \upsilon_0 + \varepsilon & \text{effect when going from MTT to VR2 for active}\\ \end{align*}\]

Compare to plot

Session info

sessionInfo()
R version 4.6.1 (2026-06-24 ucrt)
Platform: x86_64-w64-mingw32/x64
Running under: Windows 11 x64 (build 26100)

Matrix products: default
  LAPACK version 3.12.1

locale:
[1] LC_COLLATE=German_Germany.utf8  LC_CTYPE=German_Germany.utf8   
[3] LC_MONETARY=German_Germany.utf8 LC_NUMERIC=C                   
[5] LC_TIME=German_Germany.utf8    

time zone: Europe/Berlin
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] lme4_2.1-0   Matrix_1.7-6

loaded via a namespace (and not attached):
 [1] digest_0.6.39     fastmap_1.2.0     xfun_0.62         lattice_0.23-1   
 [5] splines_4.6.1     knitr_1.52        htmltools_0.5.9   rmarkdown_2.32   
 [9] Rdpack_2.6.6      cli_3.6.6         nloptr_2.2.1      grid_4.6.1       
[13] reformulas_0.4.4  compiler_4.6.1    rbibutils_2.4.1   boot_1.3-32      
[17] tools_4.6.1       nlme_3.1-171      minqa_1.2.8       evaluate_1.0.5   
[21] Rcpp_1.1.2        yaml_2.3.12       otel_0.2.0        rlang_1.3.0      
[25] jsonlite_2.0.0    htmlwidgets_1.6.4 MASS_7.3-66