Random Slopes and Model Building Strategy

PSY 8XXX: Multilevel Modeling for Organizational Research — Week 4

Author

Instructor Name

Published

March 31, 2026

1 Introduction: When Relationships Differ Across Groups

So far, we’ve assumed that the relationship between a predictor and an outcome is identical across all groups. For example, we assumed that autonomy has the same effect on job satisfaction in every team. But organizational reality is messier: in some teams, autonomy might strongly drive satisfaction; in others, it might matter little.

This variation in slopes across groups is the domain of random slopes modeling. A random slopes model allows both the intercept and the slope to vary across groups. It’s more flexible and realistic than the random intercept model, but it comes with costs: additional parameters to estimate, more potential for convergence issues, and lower power when sample sizes are small.

This week, we’ll develop intuition for random slopes, learn when to use them, grapple with model-building strategy, and navigate the thorny practical issues that arise in real data analysis.

2 The Random Slope Model: Equations

Recall the random intercept model allowed \(\beta_{0j}\) to vary across groups but assumed \(\beta_{1j}\) was constant:

\[Y_{ij} = \beta_{0j} + \beta_{1} X_{ij} + r_{ij}\]

The random slope model relaxes this assumption:

\[Y_{ij} = \beta_{0j} + \beta_{1j} X_{ij} + r_{ij}\]

Now both \(\beta_{0j}\) and \(\beta_{1j}\) vary across groups. At Level 2, we model these:

\[\beta_{0j} = \gamma_{00} + \gamma_{01} W_j + u_{0j}\] \[\beta_{1j} = \gamma_{10} + \gamma_{11} W_j + u_{1j}\]

The random deviations \(u_{0j}\) and \(u_{1j}\) are allowed to correlate. This correlation can be substantively interesting: do teams with higher baseline satisfaction also show stronger autonomy-satisfaction relationships?

Substituting into the Level-1 model:

\[Y_{ij} = \gamma_{00} + \gamma_{10} X_{ij} + \gamma_{01} W_j + \gamma_{11} W_j X_{ij} + u_{0j} + u_{1j} X_{ij} + r_{ij}\]

This combined model includes:

  • \(\gamma_{00}\): Grand mean intercept
  • \(\gamma_{10}\): Average effect of \(X\) across groups
  • \(\gamma_{01}\): Effect of group-level predictor on intercepts (between-group effect)
  • \(\gamma_{11}\): Cross-level interaction (does the effect of \(X\) depend on \(W\)?)
  • \(u_{0j}\), \(u_{1j}\): Random deviations of intercept and slope in group \(j\)

3 Setup

Code
set.seed(1234)
library(tidyverse)
library(lme4)
library(lmerTest)
library(performance)
library(broom.mixed)
library(magrittr)

# Recreate the employee dataset
n_teams <- 50
teams <- tibble(
  team_id = 1:n_teams,
  team_size = sample(8:15, n_teams, replace = TRUE),
  team_climate = rnorm(n_teams, mean = 5, sd = 0.8))

 teams %<>% mutate(
  team_climate = pmin(pmax(team_climate, 1), 7)
)

employees <- teams %>%
  slice(rep(1:n_teams, teams$team_size)) %>%
  arrange(team_id) %>%
  group_by(team_id) %>%
  mutate(
    emp_id = row_number(),
    employee_id = paste0("T", team_id, "E", emp_id),
    autonomy = rnorm(n(), mean = 4.5, sd = 1.2),
    autonomy = pmin(pmax(autonomy, 1), 7),
    performance = rnorm(n(), mean = 6, sd = 1.5),
    performance = pmin(pmax(performance, 1), 10),
    tenure = rgamma(n(), shape = 3, rate = 0.8)
  ) %>%
  ungroup()

employees <- employees %>%
  group_by(team_id) %>%
  mutate(
    u0j = rnorm(1, mean = 0, sd = sqrt(0.6)),
    job_satisfaction = 3.0 +
                       0.5 * scale(autonomy)[,1] +
                       0.4 * team_climate +
                       u0j +
                       rnorm(n(), mean = 0, sd = sqrt(0.8)),
    job_satisfaction = pmin(pmax(job_satisfaction, 1), 7)
  ) %>%
  ungroup() %>%
  select(-u0j, -emp_id)

cat("Dataset ready:", nrow(employees), "employees in", n_distinct(employees$team_id), "teams\n")
Dataset ready: 604 employees in 50 teams

4 Fitting Random Slopes in lme4

The syntax for a random slopes model in lmer() is straightforward. The formula (predictor | group) means: allow the slope of predictor to vary randomly across groups.

Code
# Model 1: Random intercept only (baseline)
model_ri <- lmer(job_satisfaction ~ autonomy + (1 | team_id), data = employees)

# Model 2: Random slope for autonomy
model_rs <- lmer(job_satisfaction ~ autonomy + (1 + autonomy | team_id), 
                 data = employees)

# Display summaries
cat("=== RANDOM INTERCEPT MODEL ===\n")
=== RANDOM INTERCEPT MODEL ===
Code
print(summary(model_ri))
Linear mixed model fit by REML. t-tests use Satterthwaite's method [
lmerModLmerTest]
Formula: job_satisfaction ~ autonomy + (1 | team_id)
   Data: employees

REML criterion at convergence: 1648.2

Scaled residuals: 
     Min       1Q   Median       3Q      Max 
-2.88819 -0.67410 -0.01095  0.65901  2.55164 

Random effects:
 Groups   Name        Variance Std.Dev.
 team_id  (Intercept) 0.7340   0.8567  
 Residual             0.7178   0.8472  
Number of obs: 604, groups:  team_id, 50

Fixed effects:
             Estimate Std. Error        df t value Pr(>|t|)    
(Intercept)   3.41907    0.18650 205.60821   18.33   <2e-16 ***
autonomy      0.32900    0.03057 559.89725   10.76   <2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Correlation of Fixed Effects:
         (Intr)
autonomy -0.737
Code
cat("\n\n=== RANDOM SLOPE MODEL ===\n")


=== RANDOM SLOPE MODEL ===
Code
print(summary(model_rs))
Linear mixed model fit by REML. t-tests use Satterthwaite's method [
lmerModLmerTest]
Formula: job_satisfaction ~ autonomy + (1 + autonomy | team_id)
   Data: employees

REML criterion at convergence: 1647.6

Scaled residuals: 
     Min       1Q   Median       3Q      Max 
-2.90216 -0.68072 -0.01651  0.65492  2.56658 

Random effects:
 Groups   Name        Variance Std.Dev. Corr  
 team_id  (Intercept) 0.963532 0.98160        
          autonomy    0.004275 0.06538  -0.54 
 Residual             0.711761 0.84366        
Number of obs: 604, groups:  team_id, 50

Fixed effects:
            Estimate Std. Error      df t value Pr(>|t|)    
(Intercept)   3.4009     0.1990 46.9689   17.09  < 2e-16 ***
autonomy      0.3329     0.0321 47.2030   10.37  9.3e-14 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Correlation of Fixed Effects:
         (Intr)
autonomy -0.774

Key differences in the output:

In the random intercept model, we see: - Intercept | team_id: Variance 0.469 - Residual: 0.768

In the random slope model, we see: - Intercept | team_id: Variance 0.491 - autonomy | team_id: Variance (this is \(\tau_{11}^2\), the variance of slopes) - Residual: 0.755

The variance components look similar, but the slope variance is crucial to interpret.

5 Interpreting Random Slope Output

Let’s extract and examine the random effects more carefully:

Code
# Extract random intercepts and slopes
ranef_rs <- ranef(model_rs)$team_id
head(ranef_rs, 10)
   (Intercept)     autonomy
1    0.3331217 -0.001786358
2   -1.3389744  0.035476373
3   -1.2924974  0.034915911
4    0.5847730 -0.017938819
5   -0.9344387  0.013962372
6    0.1141152 -0.031912175
7    0.2993860  0.016420909
8   -0.3984467  0.030504297
9   -0.2485891  0.008377055
10  -0.7593336 -0.023107917
Code
# Calculate team-specific intercepts and slopes
team_effects <- ranef_rs %>%
  rownames_to_column(var = "team_id") %>%
  mutate(
    team_id = as.numeric(team_id),
    intercept = `(Intercept)` + fixef(model_rs)[1],  # Add fixed intercept
    slope = autonomy + fixef(model_rs)[2]            # Add fixed slope
  ) %>%
  arrange(slope)

# Show teams with strongest and weakest autonomy effects
cat("Teams with STRONGEST autonomy effects:\n")
Teams with STRONGEST autonomy effects:
Code
print(head(team_effects[, c("team_id", "intercept", "slope")], 5))
  team_id intercept     slope
1      37  5.520581 0.2654619
2      34  3.862754 0.2820287
3      31  4.550979 0.2966340
4       6  3.515040 0.3009538
5      50  4.839092 0.3029917
Code
cat("\n\nTeams with WEAKEST autonomy effects:\n")


Teams with WEAKEST autonomy effects:
Code
print(tail(team_effects[, c("team_id", "intercept", "slope")], 5))
   team_id intercept     slope
46       3  2.108427 0.3677819
47       2  2.061950 0.3683424
48      18  1.786514 0.3732781
49      38  2.211585 0.3733242
50      23  1.696487 0.4005733

Notice that the slope of autonomy varies substantially across teams. Some teams show strong autonomy effects (e.g., slope near 0.6), others weak effects (e.g., slope near 0.2). This heterogeneity was hidden in the random intercept model, which forced all teams to have the same slope.

6 Visualizing Random Slopes

The best way to understand random slopes is through visualization:

Code
# Create predicted values for each team
pred_data <- expand.grid(
  autonomy = seq(1, 7, by = 0.3),
  team_id = unique(employees$team_id)
) %>%
  left_join(
    employees %>% 
      group_by(team_id) %>% 
      summarize(team_climate = first(team_climate), .groups = 'drop'),
    by = "team_id"
  )

# Predictions with random slopes model
pred_data$pred_rs <- predict(model_rs, newdata = pred_data)
# Fixed effects only (overall trend)
pred_data$pred_fixed <- predict(model_rs, newdata = pred_data, re.form = NA)

# Plot
ggplot(employees, aes(x = autonomy, y = job_satisfaction)) +
  geom_point(alpha = 0.15, size = 0.8) +
  geom_line(data = pred_data, aes(y = pred_rs, group = team_id, color = team_climate),
            alpha = 0.5, size = 0.7) +
  geom_line(data = pred_data %>% distinct(autonomy, pred_fixed), 
            aes(y = pred_fixed, group = NA), color = "black", size = 1.3) +
  scale_color_gradient(low = "blue", high = "red", name = "Team Climate") +
  labs(
    title = "Random Slopes Model: Autonomy-Satisfaction Relationship Varies by Team",
    subtitle = "Black line = fixed effect (average slope); colored lines = team-specific slopes",
    x = "Autonomy (1-7 scale)",
    y = "Job Satisfaction (1-7 scale)"
  ) +
  theme_minimal() +
  theme(legend.position = "bottom")

Now the colored lines converge and diverge—they’re not parallel! Some teams show steep relationships (autonomy matters a lot), others show shallow relationships (autonomy matters less). This is the payoff of random slopes: a more realistic depiction of organizational heterogeneity.

7 Likelihood Ratio Tests: Does the Model Improve?

Are random slopes necessary? We can test this formally by comparing the random intercept model to the random slope model using a likelihood ratio test:

Code
# Compare models
cat("=== LIKELIHOOD RATIO TEST ===\n")
=== LIKELIHOOD RATIO TEST ===
Code
cat("Null: Random intercept model\n")
Null: Random intercept model
Code
cat("Alternative: Random slope model\n\n")
Alternative: Random slope model
Code
anova(model_ri, model_rs)
Data: employees
Models:
model_ri: job_satisfaction ~ autonomy + (1 | team_id)
model_rs: job_satisfaction ~ autonomy + (1 + autonomy | team_id)
         npar    AIC    BIC  logLik -2*log(L)  Chisq Df Pr(>Chisq)
model_ri    4 1648.7 1666.3 -820.36    1640.7                     
model_rs    6 1652.2 1678.7 -820.13    1640.2 0.4686  2     0.7911

The p-value tests whether allowing the slope to vary improves model fit. If p < 0.05, we have evidence that slopes genuinely vary across teams. If p > 0.05, the random intercept model is simpler and sufficient (principle of parsimony).

In real applications, this test is often non-significant because: 1. We have modest team sample sizes (10 employees per team on average), limiting power 2. True slope heterogeneity may be small in many real datasets 3. The computational cost of estimating slope variance is high relative to the information gain

This is why many researchers use random slopes conservatively and examine whether results differ meaningfully between RI and RS models.

8 Correlation of Random Effects

An important output from the random slope model is the correlation between random intercepts and slopes. Extract this:

Code
# Extract variance-covariance matrix of random effects
vcov_re <- VarCorr(model_rs)
print(vcov_re)
 Groups   Name        Std.Dev. Corr   
 team_id  (Intercept) 0.981597        
          autonomy    0.065385 -0.543 
 Residual             0.843659        
Code
# The correlation appears in the output (if it's printed)
# You can also extract it programmatically
sd_intercept <- sqrt(as.numeric(vcov_re$team_id[1, 1]))
sd_slope <- sqrt(as.numeric(vcov_re$team_id[2, 2]))
cov_is <- as.numeric(vcov_re$team_id[1, 2])
corr_is <- cov_is / (sd_intercept * sd_slope)

cat("\n\nCorrelation between random intercept and slope:", round(corr_is, 3), "\n")


Correlation between random intercept and slope: -0.543 
Code
cat("Interpretation: If two teams have different baseline satisfaction,\n")
Interpretation: If two teams have different baseline satisfaction,
Code
cat("do they also differ in how much autonomy matters?\n")
do they also differ in how much autonomy matters?

A positive correlation means teams with higher intercepts (more satisfied) also show steeper slopes (autonomy matters more). A negative correlation means the opposite. This correlation often has substantive meaning—it can reveal whether high-satisfaction teams are “stronger” (more sensitive to predictors) or whether there’s a compensatory relationship.

9 Model Building Strategies

When should you use random slopes? The field has evolved through several perspectives:

9.1 Strategy 1: “Keep It Maximal” (Barr et al., 2013)

Barr, Levy, Scheepers, and Tily (2013) argued that you should include random slopes for every Level-1 predictor (keeping the model “maximal”) to avoid anti-conservative bias. However, this strategy:

Pros: - Reduces Type I error inflation in some scenarios - More accurately reflects true sampling variability

Cons: - Models often fail to converge with realistic sample sizes - Leads to overparameterization and poor estimation of random effects - Computationally expensive

9.2 Strategy 2: Bottom-Up Model Building

Start with a random intercept model, then test whether adding random slopes improves fit:

  1. Fit unconditional model (baseline)
  2. Add fixed effects of main interest
  3. Test random slopes with LRT
  4. Retain slopes only if they improve fit significantly and estimation is stable

Pros: - Parsimonious; avoids overparameterization - Guided by data and fit statistics - Easier interpretation

Cons: - Sequential testing inflates Type I error slightly - Misses effects that are real but weak

9.3 Strategy 3: Theory-Driven Selection

Select random slopes based on theoretical rationale: “Does this variable’s effect likely vary across groups?” If yes theoretically, include it; if no, leave it out.

Pros: - Aligned with your research questions - Computationally stable - Interpretable

Cons: - Requires strong theory upfront - May miss surprising heterogeneity

9.4 Practical Recommendation

For a doctoral-level analysis with ~50 groups and ~10 observations per group:

  1. Fit random intercept model first
  2. Test a small number of theoretically motivated random slopes (e.g., your main predictor of interest)
  3. Use model comparison and convergence as guides
  4. If the model fails to converge, simplify: remove correlation between intercept and slope with || syntax

10 Dealing with Convergence Issues

Random slope models sometimes fail to converge or show warnings like “singular fit” or “failed to converge.” These warnings signal estimation trouble. Here are practical solutions:

Code
# Solution 1: Simplify random effects structure
# Remove correlation between intercept and slope
model_rs_uncorr <- lmer(job_satisfaction ~ autonomy + (1 + autonomy || team_id), 
                        data = employees)
cat("Model with uncorrelated random effects:\n")
Model with uncorrelated random effects:
Code
print(summary(model_rs_uncorr))
Linear mixed model fit by REML. t-tests use Satterthwaite's method [
lmerModLmerTest]
Formula: job_satisfaction ~ autonomy + (1 + autonomy || team_id)
   Data: employees

REML criterion at convergence: 1648.2

Scaled residuals: 
     Min       1Q   Median       3Q      Max 
-2.88819 -0.67410 -0.01095  0.65901  2.55164 

Random effects:
 Groups    Name        Variance Std.Dev.
 team_id   (Intercept) 0.7340   0.8567  
 team_id.1 autonomy    0.0000   0.0000  
 Residual              0.7178   0.8472  
Number of obs: 604, groups:  team_id, 50

Fixed effects:
             Estimate Std. Error        df t value Pr(>|t|)    
(Intercept)   3.41907    0.18650 205.60706   18.33   <2e-16 ***
autonomy      0.32900    0.03057 559.89730   10.76   <2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Correlation of Fixed Effects:
         (Intr)
autonomy -0.737
optimizer (nloptwrap) convergence code: 0 (OK)
boundary (singular) fit: see help('isSingular')
Code
# Solution 2: Change optimizer
model_rs_opt <- lmer(job_satisfaction ~ autonomy + (1 + autonomy | team_id),
                     data = employees,
                     control = lmerControl(optimizer = "bobyqa"))
cat("\n\nModel with alternative optimizer:\n")


Model with alternative optimizer:
Code
print(summary(model_rs_opt))
Linear mixed model fit by REML. t-tests use Satterthwaite's method [
lmerModLmerTest]
Formula: job_satisfaction ~ autonomy + (1 + autonomy | team_id)
   Data: employees
Control: lmerControl(optimizer = "bobyqa")

REML criterion at convergence: 1647.6

Scaled residuals: 
    Min      1Q  Median      3Q     Max 
-2.9022 -0.6807 -0.0165  0.6549  2.5666 

Random effects:
 Groups   Name        Variance Std.Dev. Corr  
 team_id  (Intercept) 0.963537 0.98160        
          autonomy    0.004275 0.06538  -0.54 
 Residual             0.711761 0.84366        
Number of obs: 604, groups:  team_id, 50

Fixed effects:
            Estimate Std. Error      df t value Pr(>|t|)    
(Intercept)   3.4009     0.1990 46.9678   17.09  < 2e-16 ***
autonomy      0.3329     0.0321 47.2013   10.37 9.31e-14 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Correlation of Fixed Effects:
         (Intr)
autonomy -0.774
Code
# Solution 3: Scale predictors
employees_scaled <- employees %>%
  mutate(autonomy_scaled = scale(autonomy)[,1])

model_rs_scaled <- lmer(job_satisfaction ~ autonomy_scaled + (1 + autonomy_scaled | team_id),
                        data = employees_scaled)
cat("\n\nModel with scaled predictor:\n")


Model with scaled predictor:
Code
print(summary(model_rs_scaled))
Linear mixed model fit by REML. t-tests use Satterthwaite's method [
lmerModLmerTest]
Formula: job_satisfaction ~ autonomy_scaled + (1 + autonomy_scaled | team_id)
   Data: employees_scaled

REML criterion at convergence: 1647.3

Scaled residuals: 
     Min       1Q   Median       3Q      Max 
-2.90217 -0.68072 -0.01651  0.65493  2.56659 

Random effects:
 Groups   Name            Variance Std.Dev. Corr  
 team_id  (Intercept)     0.736648 0.85828        
          autonomy_scaled 0.005859 0.07654  -0.28 
 Residual                 0.711757 0.84366        
Number of obs: 604, groups:  team_id, 50

Fixed effects:
                Estimate Std. Error       df t value Pr(>|t|)    
(Intercept)      4.89541    0.12631 48.87080   38.76  < 2e-16 ***
autonomy_scaled  0.38971    0.03758 47.19956   10.37 9.31e-14 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Correlation of Fixed Effects:
            (Intr)
autnmy_scld -0.079

The || syntax (double pipe) means: allow random slopes but assume they’re uncorrelated with random intercepts. This reduces parameters and often resolves convergence issues.

11 Comparing Multiple Random Slopes Specifications

Let’s compare several models systematically:

Code
# Fit models with different random structures
models <- list(
  "RI only" = lmer(job_satisfaction ~ autonomy + (1 | team_id), data = employees),
  "RS autonomy" = lmer(job_satisfaction ~ autonomy + (1 + autonomy | team_id), 
                       data = employees),
  "Uncorr slopes" = lmer(job_satisfaction ~ autonomy + (1 + autonomy || team_id), 
                         data = employees),
  "With team_climate" = lmer(job_satisfaction ~ autonomy + team_climate + 
                             (1 + autonomy | team_id), data = employees)
)

# Compare via AIC and BIC
comparison <- tibble(
  Model = names(models),
  AIC = sapply(models, AIC),
  BIC = sapply(models, BIC),
  LogLik = sapply(models, logLik)
) %>%
  mutate(
    dAIC = AIC - min(AIC),
    dBIC = BIC - min(BIC)
  )

print(comparison)
# A tibble: 4 × 6
  Model               AIC   BIC LogLik  dAIC  dBIC
  <chr>             <dbl> <dbl>  <dbl> <dbl> <dbl>
1 RI only           1656. 1674.  -824. 0.597  0   
2 RS autonomy       1660. 1686.  -824. 4.04  12.3 
3 Uncorr slopes     1658. 1680.  -824. 2.60   6.40
4 With team_climate 1656. 1686.  -821. 0     12.6 
Code
cat("\n\nLower AIC/BIC is better. dAIC/dBIC shows difference from best model.\n")


Lower AIC/BIC is better. dAIC/dBIC shows difference from best model.

Use this table to guide model selection. Models within 2-4 AIC points are roughly equivalent; choose the simpler one (fewer parameters).

12 A Comprehensive Example: Building a Realistic Analysis

Let’s work through a complete random slopes analysis from start to finish:

Code
cat("=== COMPREHENSIVE RANDOM SLOPES EXAMPLE ===\n\n")
=== COMPREHENSIVE RANDOM SLOPES EXAMPLE ===
Code
# Start: Unconditional model
cat("STEP 1: Unconditional Model\n")
STEP 1: Unconditional Model
Code
m0 <- lmer(job_satisfaction ~ 1 + (1 | team_id), data = employees)
cat("  ICC:", round({ vc <- as.data.frame(VarCorr(m0)); vc$vcov[1]/(vc$vcov[1]+vc$vcov[nrow(vc)]) }, 3), "\n")
  ICC: 0.443 
Code
# Step 2: Add main predictor with random intercept
cat("\nSTEP 2: Add Autonomy (Random Intercept)\n")

STEP 2: Add Autonomy (Random Intercept)
Code
m1 <- lmer(job_satisfaction ~ autonomy + (1 | team_id), data = employees)
cat("  Autonomy coef:", round(fixef(m1)[2], 3), "\n")
  Autonomy coef: 0.329 
Code
cat("  Variance reduction (within):", 
    round((1 - as.numeric(VarCorr(m1))[2] / as.numeric(VarCorr(m0))[2]) * 100, 1), "%\n")
  Variance reduction (within): NA %
Code
# Step 3: Add random slope
cat("\nSTEP 3: Add Random Slope\n")

STEP 3: Add Random Slope
Code
m2 <- lmer(job_satisfaction ~ autonomy + (1 + autonomy | team_id), 
           data = employees)
cat("  Slope variance (tau11):", round(as.numeric(VarCorr(m2)$team_id[2, 2]), 3), "\n")
  Slope variance (tau11): 0.004 
Code
# Step 4: Test necessity of random slope
cat("\nSTEP 4: Likelihood Ratio Test\n")

STEP 4: Likelihood Ratio Test
Code
test_result <- anova(m1, m2)
cat("  p-value:", round(test_result$`Pr(>Chisq)`[2], 4), "\n")
  p-value: 0.7911 
Code
if (test_result$`Pr(>Chisq)`[2] < 0.05) {
  cat("  --> Random slope is justified (p < 0.05)\n")
} else {
  cat("  --> Random intercept alone is sufficient (p >= 0.05)\n")
}
  --> Random intercept alone is sufficient (p >= 0.05)
Code
# Step 5: Add team-level predictor
cat("\nSTEP 5: Add Team Climate (Between-Level Effect)\n")

STEP 5: Add Team Climate (Between-Level Effect)
Code
m3 <- lmer(job_satisfaction ~ autonomy + team_climate + (1 + autonomy | team_id),
           data = employees)
cat("  Team climate coef:", round(fixef(m3)[3], 3), "\n")
  Team climate coef: 0.414 
Code
# Step 6: Check for interaction
cat("\nSTEP 6: Cross-Level Interaction (Optional)\n")

STEP 6: Cross-Level Interaction (Optional)
Code
cat("  Does team climate moderate the autonomy effect?\n")
  Does team climate moderate the autonomy effect?
Code
m4 <- lmer(job_satisfaction ~ autonomy * team_climate + (1 + autonomy | team_id),
           data = employees)
test_int <- anova(m3, m4)
cat("  p-value for interaction:", round(test_int$`Pr(>Chisq)`[2], 4), "\n")
  p-value for interaction: 0.1213 
Code
# Final comparison
cat("\nFINAL MODEL COMPARISON (AIC):\n")

FINAL MODEL COMPARISON (AIC):
Code
final_comp <- tibble(
  Model = c("RI", "RS", "RS + team_climate", "RS + climate + interaction"),
  AIC = c(AIC(m1), AIC(m2), AIC(m3), AIC(m4))
) %>%
  mutate(dAIC = AIC - min(AIC))
print(final_comp)
# A tibble: 4 × 3
  Model                        AIC  dAIC
  <chr>                      <dbl> <dbl>
1 RI                         1656. 0.597
2 RS                         1660. 4.04 
3 RS + team_climate          1656. 0    
4 RS + climate + interaction 1660. 4.57 

This step-by-step approach shows how model building progresses: start simple, test complexity, let the data guide decisions.

13 Try It Yourself Exercises

13.1 Exercise 1: Random Slopes for Multiple Predictors

Fit a random slopes model allowing both autonomy and tenure to have random slopes. Does the correlation between their random slopes make substantive sense? (High-autonomy teams—are they also teams where tenure matters more?)

13.2 Exercise 2: Slope Heterogeneity Quantification

For the random slopes model, calculate the range of slopes across teams (max - min). How much do autonomy effects truly vary? Calculate the ratio of slope SD to fixed slope to quantify the relative heterogeneity.

13.3 Exercise 3: Convergence Troubleshooting

Simulate a scenario with fewer teams (25 instead of 50) and refit the random slopes model. Does it converge? If not, try the solutions provided (uncorrelated slopes, alternative optimizer, scaling). Document which approach works and why.

13.4 Exercise 4: Theory-Driven Model Selection

Hypothesize which Level-1 predictors should have random slopes: - Autonomy (should affect everyone, right?) - Tenure (effect might differ by team) - Performance (maybe doesn’t vary by team?)

Fit models with each random slope and compare. Did the data align with your theory?

13.5 Exercise 5: Cross-Level Interactions

Fit a model where autonomy has both a fixed and random slope, and team_climate moderates this relationship. Interpret the cross-level interaction term and visualize predictions at high and low climate.


Key Takeaways:

  • Random slopes models allow relationships between predictors and outcomes to vary across groups, capturing organizational heterogeneity.

  • The (1 + predictor | group) syntax in lmer() estimates both random intercepts and slopes.

  • Likelihood ratio tests compare random intercept versus random slope models; use these to decide whether slope heterogeneity is real.

  • The correlation between random intercepts and slopes is often substantively interesting, revealing whether high-intercept groups also have steep slopes.

  • Model building strategies vary: “keep it maximal,” “test incrementally,” or “theory-driven.” For typical organizational data with ~50 groups and ~10 per group, cautious incrementalism works best.

  • Convergence issues are common with random slopes and small samples. Solutions include uncorrelated slopes, alternative optimizers, and scaling predictors.

  • Always visualize random slopes to see heterogeneity; summary tables alone miss the important patterns.