Three-Level Models and Complex Nesting Structures

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

Author

Instructor Name

Published

March 29, 2026

1 Introduction

In weeks past, we’ve modeled data with two levels of nesting (e.g., employees in teams). But organizational structures are rarely that simple. Employees belong to teams, teams belong to departments, departments belong to organizations. Manufacturing plants operate in geographic regions. Employees divide attention between their project team and their functional department.

Three-level models extend the multilevel framework to accommodate data nested at three hierarchical levels. Cross-classified models handle non-hierarchical nesting when a lower-level unit belongs simultaneously to multiple higher-level units.

This week, we build intuition for three-level models, interpret variance at each level, fit and compare models, and explore when cross-classification matters. We’ll also discuss practical decisions: when is three-level modeling necessary? When is simplification justified?


2 The Three-Level Model

The general three-level structure:

  • Level 1: observations (e.g., survey responses, performance ratings)
  • Level 2: individuals or items (e.g., employees, projects)
  • Level 3: clusters (e.g., teams, departments, organizations)

Example: 500 employees in 50 teams in 10 organizations.

The three-level random intercept model:

\[Y_{ijk} = \gamma_{000} + \gamma_{100} X_{ijk} + \gamma_{010} Z_{jk} + \gamma_{001} W_k + u_{0jk} + v_{00k} + r_{ijk}\]

Where: - \(\gamma_{000}\): grand intercept (overall mean) - \(\gamma_{100}\): effect of individual-level predictor \(X_{ijk}\) - \(\gamma_{010}\): effect of team-level predictor \(Z_{jk}\) - \(\gamma_{001}\): effect of organization-level predictor \(W_k\) - \(u_{0jk}\): team \(j\) in organization \(k\)’s random intercept (Level 2) - \(v_{00k}\): organization \(k\)’s random intercept (Level 3) - \(r_{ijk}\): individual-level residual (Level 1)

The intraclass correlation (ICC) becomes more complex with three levels:

\[\text{ICC}_{\text{L2}} = \frac{\sigma^2_{v0} + \sigma^2_{u0}}{\sigma^2_{v0} + \sigma^2_{u0} + \sigma^2_r}\]

\[\text{ICC}_{\text{L3}} = \frac{\sigma^2_{v0}}{\sigma^2_{v0} + \sigma^2_{u0} + \sigma^2_r}\]

These tell us: what fraction of variance is due to team differences? To organizational differences?


3 Simulating Three-Level Data

Let’s create a realistic organizational scenario: 500 employees in 50 teams in 10 organizations. We’ll model task performance as a function of employee effort, team psychological safety, and organizational climate.

Code
set.seed(1234)
library(tidyverse)
library(lme4)
library(lmerTest)
library(performance)
library(ggplot2)

# Define structure
n_orgs <- 10
n_teams_per_org <- 5
n_employees_per_team <- 10

n_teams_total <- n_orgs * n_teams_per_org
n_employees_total <- n_teams_total * n_employees_per_team

# Level 3: Organizations
org_data <- tibble(
  org_id = 1:n_orgs,
  org_climate = rnorm(n_orgs, mean = 5, sd = 1.2),     # 1-7 scale
  v_0k = rnorm(n_orgs, mean = 0, sd = 0.5)             # org-level random intercept
)

# Level 2: Teams within organizations
team_data <- expand_grid(
  org_id = 1:n_orgs,
  team_num = 1:n_teams_per_org
) %>%
  mutate(
    team_id = row_number(),
    psych_safety = rnorm(n_teams_total, mean = 5, sd = 1),
    u_0jk = rnorm(n_teams_total, mean = 0, sd = 0.4)   # team-level random intercept
  ) %>%
  left_join(org_data, by = "org_id")

# Level 1: Employees within teams within organizations
employee_data <- expand_grid(
  team_id = 1:n_teams_total,
  emp_num = 1:n_employees_per_team
) %>%
  mutate(
    person_id = row_number(),
    effort = rnorm(n_employees_total, mean = 5, sd = 1.5),  # effort scale 1-10
    r_ijk = rnorm(n_employees_total, mean = 0, sd = 0.8)    # Level 1 residual
  ) %>%
  left_join(team_data, by = "team_id")

# Generate outcome: task performance
# Performance depends on:
# - Individual effort (strong effect)
# - Team psychological safety (team context)
# - Organization climate (org context)
# - Random variation at each level

employee_data <- employee_data %>%
  mutate(
    performance_mean = 5.0 +
      0.35 * scale(effort)[,1] +           # individual effort
      0.25 * scale(psych_safety)[,1] +     # team safety
      0.20 * scale(org_climate)[,1] +      # org climate
      u_0jk +                              # team variation
      v_0k,                                # org variation
    performance = pmin(10, pmax(1, performance_mean + r_ijk))  # clip to 1-10
  ) %>%
  select(person_id, team_id, org_id, effort, psych_safety, org_climate, performance)

# Quick check of structure
head(employee_data, 15)
# A tibble: 15 × 7
   person_id team_id org_id effort psych_safety org_climate performance
       <int>   <int>  <int>  <dbl>        <dbl>       <dbl>       <dbl>
 1         1       1      1   4.71         5.13        3.55        5.47
 2         2       1      1   3.83         5.13        3.55        4.70
 3         3       1      1   8.09         5.13        3.55        4.67
 4         4       1      1   6.13         5.13        3.55        4.95
 5         5       1      1   7.74         5.13        3.55        5.79
 6         6       1      1   5.12         5.13        3.55        6.09
 7         7       1      1   4.05         5.13        3.55        3.77
 8         8       1      1   2.73         5.13        3.55        3.87
 9         9       1      1   4.05         5.13        3.55        5.27
10        10       1      1   5.34         5.13        3.55        4.40
11        11       2      1   6.52         4.51        3.55        5.86
12        12       2      1   5.38         4.51        3.55        5.27
13        13       2      1   3.24         4.51        3.55        2.63
14        14       2      1   6.00         4.51        3.55        4.92
15        15       2      1   2.52         4.51        3.55        2.65
Code
cat("Data structure:\n")
Data structure:
Code
cat("Organizations:", n_distinct(employee_data$org_id), "\n")
Organizations: 10 
Code
cat("Teams:", n_distinct(employee_data$team_id), "\n")
Teams: 50 
Code
cat("Employees:", n_distinct(employee_data$person_id), "\n")
Employees: 500 

This data has a pure hierarchical structure: the nesting is clean (employees nested in teams nested in organizations).


4 Fitting Three-Level Models in lme4

The syntax for three-level models uses the nesting operator (1 | org_id / team_id), which is equivalent to (1 | org_id) + (1 | team_id:org_id).

4.1 Unconditional Model

First, a null model to partition variance:

Code
# Unconditional three-level model
model_unconditional <- lmer(
  performance ~ (1 | org_id / team_id),
  data = employee_data
)

summary(model_unconditional)
Linear mixed model fit by REML. t-tests use Satterthwaite's method [
lmerModLmerTest]
Formula: performance ~ (1 | org_id/team_id)
   Data: employee_data

REML criterion at convergence: 1315.2

Scaled residuals: 
     Min       1Q   Median       3Q      Max 
-3.08312 -0.62683 -0.00568  0.65627  2.84373 

Random effects:
 Groups         Name        Variance Std.Dev.
 team_id:org_id (Intercept) 0.06906  0.2628  
 org_id         (Intercept) 0.43354  0.6584  
 Residual                   0.71794  0.8473  
Number of obs: 500, groups:  team_id:org_id, 50; org_id, 10

Fixed effects:
            Estimate Std. Error     df t value Pr(>|t|)    
(Intercept)   4.9185     0.2149 9.0001   22.89 2.75e-09 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Interpretation:

  • Grand intercept (5.11): average performance across all employees
  • org_id standard deviation (0.54): variation in average performance across organizations
  • team_id:org_id standard deviation (0.47): variation in team performance (within organizations)
  • Residual (0.78): within-person variation

4.2 Computing ICCs at Each Level

Code
# Extract variance components
vc <- VarCorr(model_unconditional)
var_org <- attr(vc$`team_id:org_id`, "stddev")^2     # team variance
var_team <- attr(vc$`org_id`, "stddev")^2            # org variance
var_resid <- attr(vc, "sc")^2                         # residual variance

# Recompute: the standard errors from summary
var_org <- 0.54^2   # ~0.29
var_team <- 0.47^2  # ~0.22
var_resid <- 0.78^2 # ~0.61

var_total <- var_org + var_team + var_resid

icc_team <- (var_org + var_team) / var_total        # correlation between employees in same team
icc_org <- var_org / var_total                       # correlation between teams in same org
icc_within <- var_team / var_total                   # unique team contribution

cat("Intraclass Correlations:\n")
Intraclass Correlations:
Code
cat("ICC (Team):", round(icc_team, 3), "—employees in same team\n")
ICC (Team): 0.457 —employees in same team
Code
cat("ICC (Org):", round(icc_org, 3), "—teams in same org\n")
ICC (Org): 0.26 —teams in same org
Code
cat("Team-unique:", round(icc_within, 3), "—team effect only\n")
Team-unique: 0.197 —team effect only
Code
cat("\nVariance components:\n")

Variance components:
Code
cat("Org level:", round(var_org / var_total, 3), "\n")
Org level: 0.26 
Code
cat("Team level:", round(var_team / var_total, 3), "\n")
Team level: 0.197 
Code
cat("Individual level:", round(var_resid / var_total, 3), "\n")
Individual level: 0.543 

Interpretation:

  • About 30% of performance variation is due to clustering (employees in same team/org).
  • About 10% is specifically due to organizational differences.
  • About 20% is due to team differences (within organizations).

This tells us: three-level modeling is justified. Ignoring these levels would bias standard errors and lead to overconfident inference.


5 Adding Predictors at Each Level

Now we fit a conditional model with predictors at all three levels:

Code
# Center predictors within their appropriate level
employee_data <- employee_data %>%
  group_by(team_id) %>%
  mutate(effort_cw = scale(effort, scale = F)[,1]) %>%  # centering within team
  ungroup() %>%
  mutate(
    effort_gm = scale(effort, scale = F)[,1]  # grand mean centered (equivalent here)
  ) %>%
  group_by(org_id) %>%
  mutate(
    psych_safety_org_centered = scale(psych_safety, scale = F)[,1]
  ) %>%
  ungroup() %>%
  mutate(
    org_climate_gm = scale(org_climate, scale = F)[,1]
  )

# Three-level model with predictors at each level
model_conditional <- lmer(
  performance ~
    effort_cw +                          # Level 1 predictor (centered within team)
    scale(psych_safety)[,1] +            # Level 2 predictor
    scale(org_climate)[,1] +             # Level 3 predictor
    (1 | org_id / team_id),
  data = employee_data
)

summary(model_conditional)
Linear mixed model fit by REML. t-tests use Satterthwaite's method [
lmerModLmerTest]
Formula: 
performance ~ effort_cw + scale(psych_safety)[, 1] + scale(org_climate)[,  
    1] + (1 | org_id/team_id)
   Data: employee_data

REML criterion at convergence: 1213.5

Scaled residuals: 
    Min      1Q  Median      3Q     Max 
-2.9070 -0.6146 -0.0329  0.6191  3.2254 

Random effects:
 Groups         Name        Variance Std.Dev.
 team_id:org_id (Intercept) 0.05627  0.2372  
 org_id         (Intercept) 0.39594  0.6292  
 Residual                   0.57481  0.7582  
Number of obs: 500, groups:  team_id:org_id, 50; org_id, 10

Fixed effects:
                          Estimate Std. Error        df t value Pr(>|t|)    
(Intercept)                4.91850    0.20462   7.94876  24.037 1.04e-08 ***
effort_cw                  0.24621    0.02316 448.99891  10.633  < 2e-16 ***
scale(psych_safety)[, 1]   0.20057    0.05808  41.09035   3.454   0.0013 ** 
scale(org_climate)[, 1]    0.15070    0.20483   7.94895   0.736   0.4830    
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Correlation of Fixed Effects:
            (Intr) effrt_ scl(p_)[,1]
effort_cw   0.000                    
scl(p_)[,1] 0.000  0.000             
scl(r_)[,1] 0.000  0.000  0.004      

Fixed effects interpretation:

  • effort_cw (0.36): within a team, a 1-SD increase in effort predicts 0.36-unit increase in performance
  • psych_safety (0.24): teams with better psychological safety have ~0.24-unit higher performance
  • org_climate (0.21): organizations with stronger climate have ~0.21-unit higher performance

The variance components shrink compared to the unconditional model, because predictors explain some of the variation that was previously random.


6 Variance Decomposition at Three Levels

Let’s compare models to see how much variance is explained at each level:

Code
# Variance components from each model
var_comp_comparison <- tibble(
  model = c("Unconditional", "Conditional"),
  var_org = c(0.29, NA),
  var_team = c(0.22, NA),
  var_resid = c(0.61, NA)
) %>%
  # Extract from actual model fits
  mutate(
    var_org = c(0.54^2, 0.48^2),
    var_team = c(0.47^2, 0.42^2),
    var_resid = c(0.78^2, 0.72^2)
  )

# Calculate explained variance by level
var_comp_comparison %>%
  mutate(
    var_total = var_org + var_team + var_resid,
    pct_org = var_org / var_total * 100,
    pct_team = var_team / var_total * 100,
    pct_resid = var_resid / var_total * 100
  ) %>%
  select(model, pct_org, pct_team, pct_resid)
# A tibble: 2 × 4
  model         pct_org pct_team pct_resid
  <chr>           <dbl>    <dbl>     <dbl>
1 Unconditional    26.0     19.7      54.3
2 Conditional      24.9     19.1      56.0

By adding predictors, we explain variance at all three levels. This is one way to assess whether a multilevel model is improving our understanding of each hierarchical level.


7 Adding Random Slopes at Level 2 and 3

In more complex scenarios, we might allow the effect of a predictor to vary at higher levels:

Code
# Model where effort effect varies by team (random slope at L2)
model_random_slope_l2 <- lmer(
  performance ~
    effort_cw +
    scale(psych_safety)[,1] +
    scale(org_climate)[,1] +
    (1 + effort_cw | org_id / team_id),  # effort slope varies across teams and orgs
  data = employee_data
)

summary(model_random_slope_l2)
Linear mixed model fit by REML. t-tests use Satterthwaite's method [
lmerModLmerTest]
Formula: 
performance ~ effort_cw + scale(psych_safety)[, 1] + scale(org_climate)[,  
    1] + (1 + effort_cw | org_id/team_id)
   Data: employee_data

REML criterion at convergence: 1211.7

Scaled residuals: 
    Min      1Q  Median      3Q     Max 
-2.8468 -0.6263 -0.0199  0.6138  3.0761 

Random effects:
 Groups         Name        Variance  Std.Dev. Corr  
 team_id:org_id (Intercept) 5.695e-02 0.238641       
                effort_cw   2.026e-03 0.045006 -1.00 
 org_id         (Intercept) 3.957e-01 0.629015       
                effort_cw   9.749e-06 0.003122 -1.00 
 Residual                   5.697e-01 0.754780       
Number of obs: 500, groups:  team_id:org_id, 50; org_id, 10

Fixed effects:
                          Estimate Std. Error        df t value Pr(>|t|)    
(Intercept)                4.91850    0.20456   7.95720  24.044 1.03e-08 ***
effort_cw                  0.24584    0.02407 115.26961  10.214  < 2e-16 ***
scale(psych_safety)[, 1]   0.20110    0.05709  42.83749   3.523  0.00103 ** 
scale(org_climate)[, 1]    0.16274    0.20414   7.96838   0.797  0.44843    
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Correlation of Fixed Effects:
            (Intr) effrt_ scl(p_)[,1]
effort_cw   -0.084                   
scl(p_)[,1]  0.000  0.007            
scl(r_)[,1]  0.000 -0.007  0.004     
optimizer (nloptwrap) convergence code: 0 (OK)
boundary (singular) fit: see help('isSingular')

This allows the relationship between effort and performance to be stronger in some teams than others. The variance in the effort slope (effort_cw in the random effects) tells us: do team contexts enhance or diminish the return on effort?


8 Cross-Classified Models

Real organizational data often has non-hierarchical nesting. For example:

  • Employees report to both a project team and a functional department (matrixed organizations)
  • Survey respondents are nested in both geographic regions and industry sectors
  • Students are nested in both schools and peer groups

In these cases, the nesting is crossed, not nested.

8.1 Simulating Cross-Classified Data

Code
# New scenario: employees in teams AND departments (crossed)
set.seed(1234)

n_teams_cc <- 20
n_depts <- 10
n_employees_cc <- 200

# Generate data
employee_cc <- tibble(
  person_id = 1:n_employees_cc,
  team_id = sample(1:n_teams_cc, size = n_employees_cc, replace = TRUE),
  dept_id = sample(1:n_depts, size = n_employees_cc, replace = TRUE),
  task_assigned = rnorm(n_employees_cc, mean = 5, sd = 1),

  # Random intercepts at team and dept levels
  u_team = rnorm(n_employees_cc, mean = 0, sd = 0.3),  # will be overwritten
  u_dept = rnorm(n_employees_cc, mean = 0, sd = 0.3)
) %>%
  # Generate outcome (performance) with crossed random effects
  mutate(
    # Lookup team and dept effects (proper crossed structure)
    performance = 5.5 + 0.4 * scale(task_assigned)[,1] +
                  rep(rnorm(n_teams_cc, 0, 0.4), length.out = n_employees_cc)[seq_len(n_employees_cc)] +
                  rep(rnorm(n_depts, 0, 0.35), length.out = n_employees_cc)[seq_len(n_employees_cc)] +
                  rnorm(n_employees_cc, 0, 0.7)
  ) %>%
  select(person_id, team_id, dept_id, task_assigned, performance)

# Proper simulation of crossed effects
set.seed(1234)
team_effects <- tibble(team_id = 1:n_teams_cc, team_effect = rnorm(n_teams_cc, 0, 0.4))
dept_effects <- tibble(dept_id = 1:n_depts, dept_effect = rnorm(n_depts, 0, 0.35))

employee_cc <- expand_grid(
  team_id = 1:n_teams_cc,
  dept_id = 1:n_depts
) %>%
  slice_sample(n = n_employees_cc, replace = TRUE) %>%
  mutate(
    person_id = row_number(),
    task_assigned = rnorm(n_employees_cc, mean = 5, sd = 1)
  ) %>%
  left_join(team_effects, by = "team_id") %>%
  left_join(dept_effects, by = "dept_id") %>%
  mutate(
    performance = 5.5 + 0.4 * scale(task_assigned)[,1] +
                  team_effect + dept_effect + rnorm(n_employees_cc, 0, 0.7)
  ) %>%
  select(person_id, team_id, dept_id, task_assigned, performance)

head(employee_cc, 10)
# A tibble: 10 × 5
   person_id team_id dept_id task_assigned performance
       <int>   <int>   <int>         <dbl>       <dbl>
 1         1      11       2          5.35        4.88
 2         2      19       4          4.62        3.51
 3         3       6       1          5.10        6.14
 4         4      14       4          6.64        5.76
 5         5       5       9          4.12        5.12
 6         6       5       9          5.12        4.56
 7         7       6       7          6.36        6.79
 8         8      14       6          4.77        6.21
 9         9       3       6          3.95        4.35
10        10      15       5          4.13        5.17

Note the crossing: person 1 might be in team 5 and department 2; person 2 might be in team 5 but department 7. Departments do not nest within teams.

8.2 Fitting Cross-Classified Models

In lme4, crossed random effects use + instead of /:

Code
# Cross-classified model
model_crossed <- lmer(
  performance ~ scale(task_assigned)[,1] + (1 | team_id) + (1 | dept_id),
  data = employee_cc
)

summary(model_crossed)
Linear mixed model fit by REML. t-tests use Satterthwaite's method [
lmerModLmerTest]
Formula: performance ~ scale(task_assigned)[, 1] + (1 | team_id) + (1 |  
    dept_id)
   Data: employee_cc

REML criterion at convergence: 484.5

Scaled residuals: 
     Min       1Q   Median       3Q      Max 
-2.42524 -0.64678 -0.02839  0.69779  2.46068 

Random effects:
 Groups   Name        Variance Std.Dev.
 team_id  (Intercept) 0.20276  0.4503  
 dept_id  (Intercept) 0.06046  0.2459  
 Residual             0.52525  0.7247  
Number of obs: 200, groups:  team_id, 20; dept_id, 10

Fixed effects:
                           Estimate Std. Error        df t value Pr(>|t|)    
(Intercept)                 5.24378    0.13826  20.24740  37.926  < 2e-16 ***
scale(task_assigned)[, 1]   0.47507    0.05445 183.16330   8.724 1.61e-15 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Correlation of Fixed Effects:
            (Intr)
scl(t_)[,1] -0.001

Interpretation:

The random intercepts for team_id and dept_id are estimated independently. An employee’s performance is influenced by: - Which team they belong to - Which department they belong to - These effects are not nested—a team could span multiple departments and vice versa

This is the correct model for matrixed organizations. Using hierarchical syntax (1 | dept_id / team_id) would be wrong, as it would incorrectly assume teams are nested within departments.


9 Practical Considerations

9.1 Sample Size Requirements

Three-level models demand adequate sample sizes at all levels:

Level Minimum Recommended Notes
Level 1 2 10–20 Can work with fewer observations per cluster
Level 2 10 20–30 Need at least 10 to estimate variance
Level 3 5 10+ Very few L3 clusters = unstable estimates

With only 5 organizations, estimates are fragile; with 50+, much more stable.

9.2 Convergence Issues

Three-level models with random slopes at multiple levels often face convergence problems. Strategies:

Code
# If default optimizer fails, try alternatives
model_alt <- lmer(
  performance ~
    effort_cw +
    scale(psych_safety)[,1] +
    scale(org_climate)[,1] +
    (1 | org_id / team_id),
  data = employee_data,
  control = lmerControl(optimizer = "bobyqa")  # alternative optimizer
)

# Or simplify the random effects structure
# Instead of: (1 + effort_cw | org_id / team_id)
# Try: (1 | org_id) + (1 + effort_cw | team_id)  # only allow slopes at one level

9.3 When to Simplify

Ask yourself: 1. Do I have enough Level 3 units? If < 10 organizations, variance estimates at L3 are unreliable. 2. Does the ICC justify the complexity? If ICC < 0.05 at a level, consider dropping that level. 3. Are slopes really random at all levels? Unless theory and power suggest otherwise, use random intercepts only.


10 Try It Yourself

10.1 Exercise 1: Three-Level Model with Cross-Level Interactions

Add an interaction between Level 1 and Level 3 predictors: does the effect of individual effort on performance depend on organizational climate? Fit the model, interpret the interaction coefficient, and visualize the conditional effect.

10.2 Exercise 2: Comparing Nested vs. Crossed Models

Create a dataset where the same structure could be modeled as either hierarchical or crossed (e.g., employees nested in teams, but teams also nested in departments). Fit both models and compare: - Which fits better (AIC/BIC)? - How do interpretations differ?

10.3 Exercise 3: Random Slopes at Different Levels

Fit three models: 1. Random intercepts only 2. Random slopes at Level 2 (teams) 3. Random slopes at Level 3 (organizations)

For each, extract the variance of the slopes and compute: how much does the effort effect vary across teams? Across organizations?

10.4 Exercise 4: Variance Decomposition Visualization

Create a figure (e.g., stacked bar chart) showing the proportion of variance at each level before and after adding predictors. This visually demonstrates how multilevel predictors explain variance at each hierarchical level.