Getting Started with mlmoderator

Overview

mlmoderator provides a unified workflow for probing, plotting, and interpreting multilevel interaction effects from mixed-effects models fitted with lme4::lmer().

The core workflow is:

mod <- lmer(math ~ ses * climate + (1 + ses | school), data = dat)

mlm_probe(mod, pred = "ses", modx = "climate")   # simple slopes
mlm_jn(mod,    pred = "ses", modx = "climate")   # Johnson-Neyman region
mlm_plot(mod,  pred = "ses", modx = "climate")   # interaction plot
mlm_summary(mod, pred = "ses", modx = "climate") # full report

The built-in dataset

library(mlmoderator)
library(lme4)

data(school_data)
head(school_data)
#>   school student  math    ses climate gender
#> 1      1       1 61.06 -0.005   1.371 female
#> 2      1       2 60.54  0.760   1.371   male
#> 3      1       3 60.47  0.039   1.371   male
#> 4      1       4 59.82  0.735   1.371 female
#> 5      1       5 59.16 -0.146   1.371   male
#> 6      1       6 49.71 -0.058   1.371 female
str(school_data)
#> 'data.frame':    3000 obs. of  6 variables:
#>  $ school : Factor w/ 100 levels "1","2","3","4",..: 1 1 1 1 1 1 1 1 1 1 ...
#>  $ student: int  1 2 3 4 5 6 7 8 9 10 ...
#>  $ math   : num  61.1 60.5 60.5 59.8 59.2 ...
#>  $ ses    : num  -0.005 0.76 0.039 0.735 -0.146 ...
#>  $ climate: num  1.37 1.37 1.37 1.37 1.37 ...
#>  $ gender : Factor w/ 2 levels "female","male": 1 2 2 1 2 1 2 2 1 1 ...

school_data contains 3,000 students nested in 100 schools. The outcome (math) was generated with a true cross-level interaction between student SES (ses) and school climate (climate).

Step 1: Center variables

Before fitting, it is good practice to center predictors. mlm_center() supports grand-mean centering, group-mean (within-cluster) centering, and a full within–between decomposition.

# Group-mean center SES within school
dat <- mlm_center(school_data,
                  vars    = "ses",
                  cluster = "school",
                  type    = "group")

# Grand-mean center school climate (level-2 variable)
dat <- mlm_center(dat,
                  vars = "climate",
                  type = "grand")

head(dat[, c("school", "ses", "ses_c", "climate", "climate_c")])
#>   school    ses       ses_c climate climate_c
#> 1      1 -0.005  0.01513333   1.371   1.33844
#> 2      1  0.760  0.78013333   1.371   1.33844
#> 3      1  0.039  0.05913333   1.371   1.33844
#> 4      1  0.735  0.75513333   1.371   1.33844
#> 5      1 -0.146 -0.12586667   1.371   1.33844
#> 6      1 -0.058 -0.03786667   1.371   1.33844

Step 2: Fit the model

mod <- lmer(math ~ ses_c * climate_c + gender + (1 + ses_c | school),
            data = dat, REML = TRUE)
summary(mod)
#> Linear mixed model fit by REML ['lmerMod']
#> Formula: math ~ ses_c * climate_c + gender + (1 + ses_c | school)
#>    Data: dat
#> 
#> REML criterion at convergence: 18414.9
#> 
#> Scaled residuals: 
#>     Min      1Q  Median      3Q     Max 
#> -3.7607 -0.6591 -0.0070  0.6580  3.1325 
#> 
#> Random effects:
#>  Groups   Name        Variance Std.Dev. Corr  
#>  school   (Intercept)  7.841   2.8002         
#>           ses_c        0.168   0.4099   -0.28 
#>  Residual             24.916   4.9916         
#> Number of obs: 3000, groups:  school, 100
#> 
#> Fixed effects:
#>                 Estimate Std. Error t value
#> (Intercept)     49.84009    0.30871 161.446
#> ses_c            1.42623    0.10133  14.075
#> climate_c        1.01234    0.28421   3.562
#> gendermale       0.07486    0.18608   0.402
#> ses_c:climate_c  0.46554    0.09932   4.687
#> 
#> Correlation of Fixed Effects:
#>             (Intr) ses_c  clmt_c gndrml
#> ses_c       -0.105                     
#> climate_c   -0.002  0.000              
#> gendermale  -0.300  0.012  0.007       
#> ss_c:clmt_c  0.001 -0.009 -0.105 -0.005

Step 3: Probe simple slopes

mlm_probe() computes the simple slope of the focal predictor (ses_c) at selected values of the moderator (climate_c).

probe_out <- mlm_probe(mod, pred = "ses_c", modx = "climate_c")
probe_out
#> 
#> --- Simple Slopes: mlm_probe ---
#> Focal predictor : ses_c 
#> Moderator       : climate_c 
#> Confidence level: 0.95 
#> 
#>  climate_c slope    se      t   df      p ci_lower ci_upper
#>     -1.036 0.944 0.145  6.504 2995 < .001    0.659    1.228
#>      0.000 1.426 0.101 14.075 2995 < .001    1.228    1.625
#>      1.036 1.909 0.144 13.277 2995 < .001    1.627    2.191

By default, moderator values are set to −1 SD, Mean, and +1 SD. Other options include "quartiles", "tertiles", or "custom" values via at.

mlm_probe(mod, pred = "ses_c", modx = "climate_c",
          modx.values = "quartiles")
#> 
#> --- Simple Slopes: mlm_probe ---
#> Focal predictor : ses_c 
#> Moderator       : climate_c 
#> Confidence level: 0.95 
#> 
#>  climate_c slope    se      t   df      p ci_lower ci_upper
#>     -0.649 1.124 0.121  9.318 2995 < .001    0.887    1.360
#>      0.057 1.453 0.101 14.324 2995 < .001    1.254    1.652
#>      0.629 1.719 0.119 14.502 2995 < .001    1.487    1.952

Step 4: Johnson–Neyman interval

The Johnson–Neyman (JN) interval identifies the exact moderator value(s) at which the simple slope of ses_c crosses the significance threshold.

jn_out <- mlm_jn(mod, pred = "ses_c", modx = "climate_c")
jn_out
#> 
#> --- Johnson-Neyman Interval: mlm_jn ---
#> Focal predictor : ses_c 
#> Moderator       : climate_c 
#> Alpha           : 0.05 
#> Moderator range : -3.026 to 2.254 
#> 
#> Johnson-Neyman boundary/boundaries ( climate_c ):
#>    -2.088 
#> 
#> Interpretation: The simple slope of 'ses_c' is statistically
#> significant (p < 0.05) for values of 'climate_c'
#>   between -2.07 and 2.254.

Step 5: Plot the interaction

mlm_plot() returns a ggplot object, so it is fully customisable.

mlm_plot(mod, pred = "ses_c", modx = "climate_c")

Add confidence bands, raw data points, or custom labels:

mlm_plot(mod,
         pred         = "ses_c",
         modx         = "climate_c",
         points       = TRUE,
         point_alpha  = 0.2,
         x_label      = "Student SES (group-mean centred)",
         y_label      = "Mathematics Achievement",
         legend_title = "School Climate")

Step 6: Full summary report

mlm_summary() combines the interaction coefficient, simple slopes, and JN region into a single printable object.

mlm_summary(mod, pred = "ses_c", modx = "climate_c")
#> 
#> ========================================
#>   Multilevel Moderation Summary
#> ========================================
#> Focal predictor : ses_c 
#> Moderator       : climate_c 
#> Confidence level: 0.95 
#> 
#> --- Interaction Term ---
#>   ses_c:climate_c               b =   0.466  SE =  0.099  t(2995) =  4.687  p = < .001  [0.271, 0.66]
#> 
#> --- Simple Slopes ---
#>   climate_c      slope        se         t        df         p  95% CI lo  95% CI hi 
#> ------------------------------------------------------------------------------------ 
#>   -1.036         0.944     0.145     6.504    2995.0    < .001     0.659     1.228
#>   0.000          1.426     0.101    14.075    2995.0    < .001     1.228     1.625
#>   1.036          1.909     0.144    13.277    2995.0    < .001     1.627     2.191
#> 
#> --- Johnson-Neyman Region ---
#>   JN boundary/boundaries for 'climate_c': -2.088
#> 
#> ========================================

Tips