Example 11: Repeated Measure ANOVA

Author
Affiliation

Jihong Zhang*, Ph.D

Educational Statistics and Research Methods (ESRM) Program*

University of Arkansas

Research Scenario

  • Strength Measure: CrossFit evaluated strength using the front squat across three event types:
    • RM1: One-repetition maximum
    • RM4: Four-repetition maximum
    • RM21: Twenty-one-repetition maximum
  • Study Context:
    • RM1, RM4, and RM21 were measured for athletes in the CrossFit Games quarterfinals
  • DV: Average pounds for each type of RM trial using the front squat
  • Within-subject factor: Repetition maximum (RM) type (RM1, RM4, RM21)
  • Research question: Are there differences in average pounds across RM types?

H_0: \mu_{RM1} = \mu_{RM4} = \mu_{RM21}


Data Preparation (R code)

⌘+C
set.seed(123)

library(tidyverse)

# Simulate 54 participants: 27 males, 27 females
n <- 54
gender <- rep(c("M", "F"), each = n / 2)

# Mean and SDs from the slides (approximate)
means <- list(
  M = c(RM1 = 403.65, RM4 = 378.04, RM21 = 247.08),
  F = c(RM1 = 262.32, RM4 = 246.18, RM21 = 162.25)
)
sds <- list(
  M = c(30, 28, 25),
  F = c(25, 23, 20)
)

# Simulate repeated measures data per subject
simulate_subject <- function(gender, id) {
  mu <- means[[gender]]
  sd <- sds[[gender]]
  data.frame(
    subject = id,
    gender = gender,
    RM1 = rnorm(1, mu[1], sd[1]),
    RM4 = rnorm(1, mu[2], sd[2]),
    RM21 = rnorm(1, mu[3], sd[3])
  )
}

# Generate the full dataset
sim_data <- map2_dfr(gender, 1:n, simulate_subject)

# Convert to long format
sim_data_long <- sim_data %>%
  pivot_longer(cols = starts_with("RM"),
               names_to = "RM_type",
               values_to = "weight") %>%
  mutate(
    subject = factor(subject),
    gender = factor(gender),
    RM_type = factor(RM_type, levels = c("RM1", "RM4", "RM21"))
  )

# Display head
head(sim_data_long)
# A tibble: 6 × 4
  subject gender RM_type weight
  <fct>   <fct>  <fct>    <dbl>
1 1       M      RM1       387.
2 1       M      RM4       372.
3 1       M      RM21      286.
4 2       M      RM1       406.
5 2       M      RM4       382.
6 2       M      RM21      290.
library(tidyverse)
library(rstatix)

# Sample format
cf_long <- sim_data_long %>%
  mutate(RM_type = factor(RM_type, levels = c("RM1", "RM4", "RM21")))

Assumption Checks

  • Normality: Robust due to CLT
  • Independence: Not required (repeated measures)
  • Sphericity: Use Mauchly’s test
# Sphericity test
anova_res <- cf_long %>% anova_test(dv = weight, wid = subject, within = RM_type)
anova_res$`Mauchly's Test for Sphericity`
   Effect     W     p p<.05
1 RM_type 0.914 0.095      

Sphericity is not violated (W = .914, p = .095). We can use standard F-test.

anova_res$`Sphericity Corrections`
   Effect  GGe      DF[GG]    p[GG] p[GG]<.05   HFe      DF[HF]    p[HF]
1 RM_type 0.92 1.84, 97.57 7.07e-45         * 0.952 1.9, 100.92 2.51e-46
  p[HF]<.05
1         *
  • If sphericity violated, use the following instead of F-test:
    • GGe: Greenhouse-Geisser correction
    • HFe: Huynh-Feldt correction

ANOVA Results

get_anova_table(anova_res)
ANOVA Table (type III tests)

   Effect DFn DFd       F        p p<.05   ges
1 RM_type   2 106 369.808 1.59e-48     * 0.447

The RM Type has significant effect on outcome.


Post-hoc Tests (if needed)

cf_long %>%
  pairwise_t_test(weight ~ RM_type, paired = TRUE, p.adjust.method = "bonferroni")
# A tibble: 3 × 10
  .y.   group1 group2    n1    n2 statistic    df        p    p.adj p.adj.signif
* <chr> <chr>  <chr>  <int> <int>     <dbl> <dbl>    <dbl>    <dbl> <chr>       
1 weig… RM1    RM4       54    54      5.94    53 2.23e- 7 6.69e- 7 ****        
2 weig… RM1    RM21      54    54     22.8     53 5   e-29 1.5 e-28 ****        
3 weig… RM4    RM21      54    54     21.1     53 1.89e-27 5.67e-27 ****        

To obtain sum of squares (SS) for between-subject and within-subject effects using the lme4 package in R, you can follow these steps:


Mixed ANOVA: RQ2

  • Between-subjects factor: Gender
  • Within-subject factor: RM type
  • RQ: Is there a gender × RM type interaction?

Mixed ANOVA (R code)

mixed_res <- anova_test(
  data = cf_long, dv = weight, wid = subject,
  within = RM_type, between = gender
)
mixed_res
ANOVA Table (type II tests)

$ANOVA
          Effect DFn DFd        F        p p<.05   ges
1         gender   1  52 1250.938 4.77e-38     * 0.875
2        RM_type   2 104  487.055 1.54e-53     * 0.869
3 gender:RM_type   2 104   17.803 2.24e-07     * 0.196

$`Mauchly's Test for Sphericity`
          Effect     W     p p<.05
1        RM_type 0.996 0.906      
2 gender:RM_type 0.996 0.906      

$`Sphericity Corrections`
          Effect   GGe      DF[GG]    p[GG] p[GG]<.05   HFe       DF[HF]
1        RM_type 0.996 1.99, 103.6 2.42e-53         * 1.036 2.07, 107.72
2 gender:RM_type 0.996 1.99, 103.6 2.35e-07         * 1.036 2.07, 107.72
     p[HF] p[HF]<.05
1 1.54e-53         *
2 2.24e-07         *

Follow-up Contrasts

cf_long %>%
  group_by(RM_type, gender) %>%
  summarise(mean = mean(weight), .groups = "drop")
# A tibble: 6 × 3
  RM_type gender  mean
  <fct>   <fct>  <dbl>
1 RM1     F       266.
2 RM1     M       410.
3 RM4     F       247.
4 RM4     M       374.
5 RM21    F       156.
6 RM21    M       247.
cf_long %>%
  pairwise_t_test(weight ~ gender, paired = FALSE, 
                  p.adjust.method = "bonferroni")
# A tibble: 1 × 9
  .y.    group1 group2    n1    n2        p p.signif    p.adj p.adj.signif
* <chr>  <chr>  <chr>  <int> <int>    <dbl> <chr>       <dbl> <chr>       
1 weight F      M         81    81 9.34e-24 ****     9.34e-24 ****        

Interpretation Summary

  • RM1 > RM4 > RM21
  • Significant main effect of RM type
  • Interaction between RM type and gender significant
  • Males lift significantly more than females across all RM types

Sphericity Explained

  • Assumption: Variance of the differences between RM levels are equal
  • Test: Mauchly’s Test
  • If violated:
    • Use Greenhouse-Geisser correction (default conservative choice)
anova_res$`Mauchly's Test for Sphericity`
   Effect     W     p p<.05
1 RM_type 0.914 0.095      

Conclusion

  • Repeated measures ANOVA is powerful when assumptions are met or adjusted
  • Mixed ANOVA enables testing interactions with between-subject factors
  • Use corrected tests (e.g., GG) when sphericity is violated

Step-by-step Procedure using lme4

We will assume the data is in long format (as simulated previously), with variables: - subject: participant ID - gender: between-subjects factor (optional) - RM_type: within-subject factor (e.g., RM1, RM4, RM21) - weight: outcome variable

1. Load required packages

library(lme4)
library(lmerTest)  # for p-values and ANOVA

2. Fit the linear mixed model

# Main repeated measures model (random intercept for subject)
model <- lmer(weight ~ gender + RM_type + (1 | subject), data = sim_data_long)
summary(model)
Linear mixed model fit by REML. t-tests use Satterthwaite's method [
lmerModLmerTest]
Formula: weight ~ gender + RM_type + (1 | subject)
   Data: sim_data_long

REML criterion at convergence: 1491.4

Scaled residuals: 
     Min       1Q   Median       3Q      Max 
-2.80588 -0.74290 -0.01438  0.67387  2.64020 

Random effects:
 Groups   Name        Variance Std.Dev.
 subject  (Intercept)   0.0     0.00   
 Residual             666.5    25.82   
Number of obs: 162, groups:  subject, 54

Fixed effects:
            Estimate Std. Error       df t value Pr(>|t|)    
(Intercept)  277.716      4.057  158.000  68.461  < 2e-16 ***
genderM      120.757      4.057  158.000  29.768  < 2e-16 ***
RM_typeRM4   -27.861      4.968  158.000  -5.608 8.94e-08 ***
RM_typeRM21 -136.686      4.968  158.000 -27.512  < 2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Correlation of Fixed Effects:
            (Intr) gendrM RM_RM4
genderM     -0.500              
RM_typeRM4  -0.612  0.000       
RM_typeRM21 -0.612  0.000  0.500
optimizer (nloptwrap) convergence code: 0 (OK)
boundary (singular) fit: see help('isSingular')
  • RM_type: fixed effect (within-subject factor)
  • (1 | subject): random intercept for subjects to account for repeated measures

3. Obtain the ANOVA table with Type III SS

anova_results <- anova(model, type = 3)
print(anova_results)
Type III Analysis of Variance Table with Satterthwaite's method
        Sum Sq Mean Sq NumDF DenDF F value    Pr(>F)    
gender  590578  590578     1   158  886.15 < 2.2e-16 ***
RM_type 563438  281719     2   158  422.71 < 2.2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

This will provide: - Sum of Squares for the within-subject effect (RM_type) - Mean Squares, F-statistics, and p-values


4. Estimate between-subject variability (subject-level random effect)

The between-subjects SS is not shown directly in the ANOVA table but can be extracted from the model’s random effects:

# Extract random effects variance (between-subjects variability)
VarCorr(model)
 Groups   Name        Std.Dev.
 subject  (Intercept)  0.000  
 Residual             25.816  

You will see something like:

 Groups   Name        Variance Std.Dev.
 subject  (Intercept)  XXX      YYY
 Residual              ZZZ      ...
  • subject (Intercept) reflects the between-subject variability
  • Residual is the within-subject (error) variance

To calculate the sum of squares for these components, you can multiply the variances by their degrees of freedom (or number of units):

# Degrees of freedom
n_subjects <- length(unique(sim_data_long$subject))
n_within   <- length(unique(sim_data_long$RM_type))

# Estimated variances
var_components <- as.data.frame(VarCorr(model))
between_var <- var_components$vcov[1] # random intercept
residual_var <- var_components$vcov[2] # residuals

# Approximate SS
SS_between <- between_var * (n_subjects - 1)
SS_within <- residual_var * (n_subjects * (n_within - 1))
SS_acrossRM <- as.data.frame(anova_results)[["Sum Sq"]]

SS_total <- sum((sim_data_long$weight - mean(sim_data_long$weight))^2)

SS_between + SS_within + SS_acrossRM
[1] 662555.7 635415.8
SS_total
[1] 1259317

✅ Summary Output

  • anova(model) gives SS for fixed effects (within-subject factor)
  • VarCorr(model) provides random effects variance
  • Manual calculation gives approximate SS for between- and within-subject variability
Back to top