Mixed ANOVA
The engine for Longitudinal Discovery. Mixed ANOVA combines Between-Subjects groupings with Within-Subjects repeated measures to audit recovery trajectories.
What is it?
Mixed ANOVA (also Split-Plot ANOVA) incorporates one Between-Subjects factor and one Within-Subjects repeated measure to trace longitudinal recovery profiles.
When to use it
- Between Factor: Independent treatment groups (e.g. Active vs Control).
- Within Factor: Chronological repeated tests (e.g. Pre, Mid, Post).
- Longitudinal Study: Track subjects over time.
Core Idea
It maps the divergence of trajectories. If the treatment works, the active group should show a steeper recovery slope than control:
The Group × Time Interaction evaluates if the slopes diverge. A significant interaction indicates the rate of change differs between groups.
Hypotheses
How it works
Splits variance into two compartments:
1. **Between-Subjects**: Group vs Error(Subjects)
2. **Within-Subjects**: Time, Group×Time vs Error(Within)
Assumptions
Important Note
If sphericity fails repeatedly, we adjust using the Greenhouse-Geisser (GG) corrections to reduce degrees of freedom and prevent false-positive inflation.
Quick Example
| Group | Pre | Mid | Post |
|---|---|---|---|
| Treatment | 48.2 | 62.4 | 78.1 |
| Control | 49.0 | 51.2 | 52.4 |
Mixed ANOVA Live Laboratory
Adjust Group shifts, Time trends, and Interactions to see how the Within-Between split partitions F-statistics.
| Source | SS | df | MS | F | p-value |
|---|---|---|---|---|---|
| Between-Subjects Effects | |||||
| Group | 1484.4 | 1 | 1484.4 | 6.87 | 0.0194 |
| Error (Subj/Group) | 3027.1 | 14 | 216.2 | - | - |
| Within-Subjects Effects | |||||
| Time | 98.5 | 2 | 49.2 | 1.82 | 0.1805 |
| Group × Time | 101.7 | 2 | 50.9 | 1.88 | 0.1711 |
| Error (Within) | 757.0 | 28 | 27.0 | - | - |
Hypotheses
Pragmatic null and alternative hypotheses defined in mathematical notation.
H₀: (1) All group means are equal (between-subjects main effect), (2) All repeated measure means are equal (within-subjects main effect), (3) No Group × Time interaction
Hₐ: At least one main effect or interaction is significant
Tests three hypotheses: between-subjects main effect, within-subjects main effect, and interaction. The interaction is often the primary hypothesis in treatment studies: Does the pattern of change over time differ between groups? If interaction is significant (p<α), follow up with simple effects analysis.
Assumptions
The core mathematical criteria needed to ensure that statistical testing remains unbiased and valid.
Diagnostics
Checking residual plots and indices to examine model deviations and ensure standard error integrity.
- Mauchly's test of sphericity for within-subjects factor
- Levene's test for homogeneity of variance across between-subjects groups
- Q-Q plots of residuals to assess normality
- Interaction plot (Group × Time) to visualize interaction pattern
- Greenhouse-Geisser or Huynh-Feldt epsilon (ε) if sphericity violated
- Boxplots by group and time to identify outliers
- Residuals vs. fitted values plot to check homoscedasticity
- Profile plots showing mean trajectory for each group
- Descriptive statistics (M, SD, n) for each group×time cell
- Cook's distance to identify influential cases
- Simple effects analysis if interaction significant
- Box's M test for homogeneity of covariance matrices
- Effect size plots (means with 95% CI by group and time)
- Within-subjects correlation matrix (check compound symmetry)
Applied Minds
Review concrete study examples, data layout guidelines, and copy executable syntax scripts.
Treatment Efficacy Over Time (2×3 Mixed Design)
Research question: Does CBT reduce anxiety more than waitlist control over 8 weeks? Design: 2 (Group: CBT vs. Control, between) × 3 (Time: Baseline, Week 4, Week 8, within). Outcome: State-Trait Anxiety Inventory (STAI) score (continuous, 20-80, higher=more anxiety). Sample: 80 participants (40 CBT, 40 Control). Hypothesis: Significant Group × Time interaction (CBT shows greater reduction than Control over time).
# Mixed-design ANOVA: Group (between) × Time (within)
# Clinical trial: CBT vs. Control for anxiety reduction
library(ez) # ezANOVA for mixed designs
library(emmeans) # Marginal means and contrasts
library(ggplot2)
library(dplyr)
library(tidyr)
library(car) # Levene's test
# Simulate realistic clinical trial data
set.seed(2025)
n_per_group <- 40
time_points <- c("Baseline", "Week4", "Week8")
# Create between-subjects design
data_between <- data.frame(
subject_id = 1:80,
group = rep(c("CBT", "Control"), each=n_per_group),
baseline_anxiety = rnorm(80, mean=52, sd=10) # Individual differences
) %>%
mutate(baseline_anxiety = pmax(25, pmin(75, baseline_anxiety)))
# Generate longitudinal data (repeated measures)
data_long <- data_between %>%
crossing(time = factor(time_points, levels=time_points)) %>%
mutate(
time_numeric = as.numeric(time) - 1, # 0, 1, 2
# True effects:
# CBT reduces anxiety by -4 pts per timepoint
# Control reduces by -1 pt (placebo/natural improvement)
# Within-subject correlation ~0.65
group_effect = ifelse(group == "CBT", -4, -1),
# STAI = Baseline + Group×Time + error
stai_score = baseline_anxiety + group_effect * time_numeric +
rnorm(n(), mean=0, sd=4), # Within-subject error
stai_score = pmax(20, pmin(80, stai_score))
) %>%
select(subject_id, group, time, stai_score)
# Convert to factors
data_long$subject_id <- factor(data_long$subject_id)
data_long$group <- factor(data_long$group)
data_long$time <- factor(data_long$time, levels=time_points)
cat("=== Data Structure ===")
cat("\nTotal participants:", length(unique(data_long$subject_id)))
cat("\nTotal observations:", nrow(data_long))
cat("\nDesign: 2 (Group) × 3 (Time) mixed ANOVA\n")
# Descriptive statistics
cat("\n=== Descriptive Statistics ===")
desc_stats <- data_long %>%
group_by(group, time) %>%
summarise(n = n(), M = mean(stai_score), SD = sd(stai_score), .groups='drop')
print(desc_stats)
# === STEP 1: Check Assumptions ===
cat("\n\n=== ASSUMPTION CHECKS ===")
# 1. Normality of residuals
model_prelim <- lm(stai_score ~ group * time, data=data_long)
residuals_all <- residuals(model_prelim)
cat("\n1. Normality of Residuals")
shapiro_test <- shapiro.test(sample(residuals_all, min(5000, length(residuals_all))))
cat("\n Shapiro-Wilk: W=", round(shapiro_test$statistic, 4),
", p=", round(shapiro_test$p.value, 3))
cat(ifelse(shapiro_test$p.value > 0.05, " ✓", " ⚠"), "\n")
# Q-Q plot and histogram
par(mfrow=c(1,2))
qqnorm(residuals_all, main="Q-Q Plot: Residuals")
qqline(residuals_all, col="red", lwd=2)
hist(residuals_all, breaks=30, main="Histogram: Residuals",
xlab="Residuals", col="lightblue")
# 2. Homogeneity of variance (between-subjects)
cat("\n2. Homogeneity of Variance(Between-Subjects)")
levene_test <- leveneTest(stai_score ~ group,
data=filter(data_long, time=="Baseline"))
cat("\n Levene's test: F=", round(levene_test$`F value`[1], 3),
", p=", round(levene_test$`Pr(>F)`[1], 3))
cat(ifelse(levene_test$`Pr(>F)`[1] > 0.05, " ✓", " ⚠"), "\n")
# Boxplots by group
par(mfrow=c(1,1))
ggplot(data_long, aes(x=time, y=stai_score, fill=group)) +
geom_boxplot() +
labs(title="STAI Anxiety by Group and Time",
x="Time Point", y="STAI Anxiety Score",
fill="Group") +
theme_classic() +
scale_fill_manual(values=c("CBT"="#66c2a5", "Control"="#fc8d62"))
# 3. Sphericity (within-subjects factor: Time)
cat("\n3. Sphericity(Within-Subjects Factor: Time)")
cat("\n Note: Sphericity tested automatically in ezANOVA output below")
cat("\n If Mauchly's p<.05 → use Greenhouse-Geisser or Huynh-Feldt correction\n")
# === STEP 2: Run Mixed-design ANOVA ===
cat("\n\n=== MIXED-DESIGN ANOVA ===")
# Using ezANOVA (automatically tests sphericity and provides corrections)
anova_result <- ezANOVA(
data = data_long,
dv = stai_score,
wid = subject_id,
within = time,
between = group,
type = 3,
detailed = TRUE,
return_aov = TRUE
)
print(anova_result$ANOVA)
cat("\n=== Sphericity Tests(Mauchly's) ===")
if(!is.null(anova_result$`Mauchly's Test for Sphericity`)) {
print(anova_result$`Mauchly's Test for Sphericity`)
cat("\nIf p<.05, sphericity violated → use corrections below\n")
}
cat("\n=== Sphericity Corrections ===")
if(!is.null(anova_result$`Sphericity Corrections`)) {
print(anova_result$`Sphericity Corrections`)
cat("\nGreenhouse-Geisser(GGe): Conservative(use if ε<.75)")
cat("\nHuynh-Feldt(HFe): Less conservative(use if ε>.75)\n")
}
# === STEP 3: Interpret Results ===
cat("\n\n=== INTERPRETATION ===")
cat("\n1. BETWEEN-SUBJECTS MAIN EFFECT(Group):")
cat("\n Tests if CBT differs from Control averaged across all time points")
cat("\n If p<.05 → CBT and Control differ overall\n")
cat("\n2. WITHIN-SUBJECTS MAIN EFFECT(Time):")
cat("\n Tests if anxiety changes over time(averaged across groups)")
cat("\n If p<.05 → Significant change from Baseline to Week 4 to Week 8")
cat("\n CHECK SPHERICITY! Use GG or HF p-value if Mauchly's p<.05\n")
cat("\n3. GROUP × TIME INTERACTION(PRIMARY HYPOTHESIS):")
cat("\n Tests if rate of change over time differs between CBT and Control")
cat("\n If p<.05 → CBT shows different trajectory than Control(EXPECTED!)")
cat("\n This is the KEY test for treatment efficacy in clinical trials\n")
# === STEP 4: Visualize Interaction ===
cat("\n\n=== VISUALIZATION ===")
# Interaction plot: Group × Time
interaction_means <- data_long %>%
group_by(group, time) %>%
summarise(M = mean(stai_score),
SE = sd(stai_score)/sqrt(n()),
.groups='drop')
ggplot(interaction_means, aes(x=time, y=M, color=group, group=group)) +
geom_line(size=1.5) +
geom_point(size=4) +
geom_errorbar(aes(ymin=M-1.96*SE, ymax=M+1.96*SE), width=0.15) +
labs(title="Group × Time Interaction: CBT vs. Control",
subtitle="Mean STAI Anxiety Score ± 95% CI",
x="Time Point", y="Anxiety(STAI)",
color="Group") +
theme_classic(base_size=14) +
theme(legend.position="bottom") +
scale_color_manual(values=c("CBT"="#2E86AB", "Control"="#A23B72")) +
ylim(30, 60)
# Profile plot showing individual trajectories (sample)
data_sample <- data_long %>%
filter(subject_id %in% sample(unique(subject_id), 20))
ggplot(data_sample, aes(x=time, y=stai_score, group=subject_id, color=group)) +
geom_line(alpha=0.4) +
geom_point(alpha=0.4, size=2) +
facet_wrap(~group) +
labs(title="Individual Trajectories(Sample of 10 per Group)",
x="Time Point", y="STAI Anxiety Score") +
theme_classic()
# === STEP 5: Post-hoc Tests (if interaction significant) ===
cat("\n\n=== POST-HOC: Simple Effects Analysis ===")
# If Group×Time interaction is significant, test simple effects:
# (1) Effect of Group at each Time point
cat("\nSimple effects: CBT vs. Control at each time point\n")
emm_time <- emmeans(anova_result$aov, ~ group | time)
pairs_by_time <- pairs(emm_time, adjust="bonferroni")
print(pairs_by_time)
cat("\nInterpretation: Compare CBT vs. Control at Baseline, Week 4, Week 8")
cat("\nExpected: No difference at Baseline(randomization);")
cat("\n CBT < Control at Week 4 and Week 8 (treatment effect)\n")
# (2) Effect of Time within each Group
cat("\n\nSimple effects: Time effect within each group\n")
emm_group <- emmeans(anova_result$aov, ~ time | group)
pairs_by_group <- pairs(emm_group, adjust="bonferroni")
print(pairs_by_group)
cat("\nInterpretation: Test if anxiety changes over time within CBT and Control")
cat("\nExpected: Large decline in CBT; small/no decline in Control\n")
# === STEP 6: Effect Sizes ===
cat("\n\n=== EFFECT SIZES ===")
# Partial eta-squared from ezANOVA output
cat("\nPartial η² for each effect(from ANOVA table above):")
cat("\n - Group(between): Variance explained by group differences")
cat("\n - Time(within): Variance explained by time trend")
cat("\n - Group×Time: Variance explained by differential trajectories")
cat("\nInterpretation: .01=small, .06=medium, .14=large(Cohen, 1988)\n")
# Calculate Cohen's d for key comparison: CBT vs. Control at Week 8
week8_data <- filter(data_long, time=="Week8")
cbt_8 <- filter(week8_data, group=="CBT")$stai_score
control_8 <- filter(week8_data, group=="Control")$stai_score
cohens_d <- (mean(cbt_8) - mean(control_8)) /
sqrt((var(cbt_8) + var(control_8)) / 2)
cat("\nCohen's d(CBT vs. Control at Week 8):", round(cohens_d, 2))
cat("\nInterpretation: .2=small, .5=medium, .8=large")
cat("\nExpected d ~ -0.6 to -0.8 (negative = CBT lower anxiety)\n")
# === STEP 7: Bootstrap Confidence Intervals ===
cat("\n\n=== BOOTSTRAP CI for Group Difference at Week 8 ===")
library(boot)
# Bootstrap function
boot_diff <- function(data, indices) {
d <- data[indices, ]
cbt_mean <- mean(d$stai_score[d$group=="CBT"])
control_mean <- mean(d$stai_score[d$group=="Control"])
return(cbt_mean - control_mean)
}
boot_results <- boot(data=week8_data, statistic=boot_diff, R=2000)
boot_ci <- boot.ci(boot_results, type="bca") # Bias-corrected accelerated
cat("\nMean difference(CBT - Control):", round(boot_results$t0, 2), "points")
cat("\n95% BCa Bootstrap CI: [",
round(boot_ci$bca[4], 2), ",",
round(boot_ci$bca[5], 2), "]")
cat("\nInterpretation: We are 95% confident the true difference is in this range\n")
# === APA-Style Reporting ===
cat("\n\n=== APA-STYLE REPORT ===")
cat("
A 2 (Group: CBT vs. Control, between-subjects) × 3 (Time: Baseline, Week 4,
Week 8, within-subjects) mixed-design ANOVA was conducted to examine the
effectiveness of CBT for anxiety reduction. The sample included 80 participants
(40 CBT, 40 Control) measured at three time points(N=240 observations).
Assumptions were evaluated: Residuals were approximately normally distributed
(Shapiro-Wilk W=.XX, p=.XX). Levene's test indicated homogeneity of variance
across groups(F(1,78)=X.XX, p=.XX). Mauchly's test of sphericity was
[non-significant / significant] for the Time factor(W=.XX, p=.XX);
[no correction was needed / Greenhouse-Geisser correction was applied with ε=.XX].
Results revealed a significant Group×Time interaction, F(2, 156)=XX.XX, p<.001,
partial η²=.XX(large effect), indicating that CBT and Control groups showed
different trajectories over time. Simple effects analysis showed that at baseline,
CBT(M=52.1, SD=10.2) and Control(M=51.8, SD=9.8) did not differ(p=.89),
confirming successful randomization. At Week 4, CBT showed significantly lower
anxiety(M=48.2, SD=9.5) than Control(M=50.7, SD=9.2), p=.02, d=-0.27 (small
effect). By Week 8, this difference increased substantially: CBT(M=44.3, SD=9.1)
versus Control(M=50.1, SD=8.9), p<.001, d=-0.64 (medium-large effect). Bootstrap
analysis confirmed the Week 8 difference: mean=-5.8 points, 95% CI [-8.2, -3.4].
The main effect of Time was significant, F(2, 156)=XX.XX, p<.001, partial η²=.XX,
indicating overall anxiety reduction across time points. The main effect of Group
was [significant/non-significant], F(1, 78)=X.XX, p=.XX, partial η²=.XX.
These findings demonstrate that CBT is effective for anxiety reduction, with
therapeutic benefits emerging by Week 4 and strengthening through Week 8. The
medium-large effect size at Week 8 (d=-0.64) is consistent with meta-analytic
estimates of CBT efficacy for anxiety disorders(Hofmann et al., 2012).
")
cat("\n\n=== KEY REPORTING ELEMENTS ===")
cat("
✓ Design clearly specified(2×3 mixed)
✓ Sample size and group sizes reported(n=40 per group)
✓ Assumption checks reported(normality, homogeneity, sphericity)
✓ Sphericity corrections applied if needed(GG or HF)
✓ All effects reported: Group×Time, Time, Group with F, df, p, partial η²
✓ Simple effects analysis for significant interaction
✓ Effect sizes for key comparisons(Cohen's d)
✓ Bootstrap CI for robustness
✓ Means, SDs, and CIs for each cell
✓ Interpretation linked to research question and literature
")Group×Time interaction F(2,156)=28.4, p<.001, partial η²=.27 (large effect): CBT shows significantly steeper decline (−4 pts/timepoint) than Control (−1 pt/timepoint). At Week 8, Cohen's d=−0.64 (medium-large effect), with CBT participants scoring 5.8 points lower (95% CI [−8.2, −3.4]). Main effect of Time: F(2,156)=45.2, p<.001, ε=.92 (sphericity satisfied). Findings demonstrate robust CBT efficacy for anxiety reduction, with therapeutic benefits emerging by Week 4 and strengthening through Week 8, consistent with meta-analytic evidence (Hofmann et al., 2012).
Alternatives
Structured fallback pathways for choosing alternative tests when normality or slopes requirements fail.
- Greenhouse-Geisser Shield — Conservative correction for unequal variances of differences.
- Huynh-Feldt Pivot — A more liberal correction if epsilon is near 1.0.
- MANOVA Path — Abandon sphericity entirely by treating timepoints as a multivariate vector.
- Robust Standard Errors — Protect group comparisons from heterogeneous spread across sites.
- GLS Variance Modeling — Explicitly model the variance structure per group.
Post-hoc
Group mean comparisons and correction controls (e.g. Tukey HSD, Bonferroni) to protect against Family-Wise Error Rates.
Post-hoc pairwise tests defined for this model.
For significant Group×Time interaction, conduct simple effects: (1) Group effect at each time point; (2) Time effect within each group. Apply appropriate multiple comparison corrections to control familywise error rate.
Effect Size
Understanding effect sizes (e.g., Cohen's d, Partial Eta-Squared) and clinical impact benchmarks.
Proportion of variance in DV attributable to effect, excluding variance from other effects. Small: .01, Medium: .06, Large: .14 (Cohen, 1988). Most commonly reported in mixed ANOVA.
Proportion of total variance including between-subjects variance. More appropriate for mixed designs. Provides comparable effect sizes across different designs.
Standardized mean difference for pairwise comparisons. Small: .2, Medium: .5, Large: .8. Calculate for key simple effects (e.g., treatment vs. control at final timepoint).
Less biased than η²; estimates population effect size accounting for sampling variability. Interpretation same as η² but more conservative.
0.2
0.5
0.8
Sample Size
Guidelines for minimum sample requirements and power analysis parameters.
Longitudinal Mandate: A minimum of 25 participants per between-subjects group is required to stabilize the trajectory audit, assuming at least 3 temporal measurements.
| Effect Size | Parameters | Required n |
|---|---|---|
| Small Effect | f=.10 (Small) | n ≈ 158 total |
| Medium Effect | f=.25 (Medium) | n ≈ 34 total |
| Large Effect | f=.40 (Large) | n ≈ 18 total |
Missing data is the 'Cancer' of Mixed ANOVA. A 20% attrition buffer is non-negotiable. If correlation between timepoints is low (r < .30), the sample size must double to maintain the same discovery threshold.
Reporting
How to compile statistical results into publication prose matching APA and journal style guides.
A k (Between-factor: levels, between-subjects) × j (Within-factor: levels, within-subjects) mixed-design ANOVA was conducted to examine research question. The sample included N participants (n per group per between-factor group) measured at j time points (N=total observations observations). Assumptions were evaluated: Report normality (Shapiro-Wilk), homogeneity of variance for between-subjects factor (Levene's test), sphericity for within-subjects factor (Mauchly's test). If sphericity violated: Mauchly's test indicated sphericity was violated for factor/interaction (W=.XX, p=.XX), so Greenhouse-Geisser/Huynh-Feldt correction was applied (ε=.XX). Results revealed report interaction first if significant, then main effects. For significant interaction: The Between × Within interaction was significant, F(df1, df2)=X.XX, p=.XXX, partial η²=.XX interpret effect size, indicating that describe pattern. Simple effects analysis showed describe key patterns with specific comparisons, p-values, and effect sizes. Main effects: The main effect of Between-factor was significant/non-significant, F(df1, df2)=X.XX, p=.XX, partial η²=.XX. The main effect of Within-factor was significant/non-significant, F(df1, df2)=X.XX, p=.XX, partial η²=.XX if sphericity corrected, report corrected df and p. Conclude with interpretation in context of research question and literature.
- F-statistic for each main effect and interaction
- Degrees of freedom (numerator and denominator)
- p-value
- Effect size (partial η² for omnibus, Cohen's d for pairwise)
- Descriptive statistics per cell (M, SD, n)
- Sphericity test (Mauchly's W, p-value, ε) and corrections applied
- Levene's test for between-subjects homogeneity
- Post-hoc test results with adjusted p-values for significant effects
- Confidence intervals for key comparisons
- Statement about assumption checks
Manuscript Lab
Copy standard summary tables and forensic reporting grids to outline analysis details.
| Source | Type III SS | df | MS | F | p | ηp² |
|---|---|---|---|---|---|---|
| Group (Between) | 45.2 | 1 | 45.2 | 8.12 | .005 | .06 |
| Time (Within) | 112.8 | 1.6 | 70.5 | 22.45 | < .001 | .16 |
| Group × Time | 38.4 | 1.6 | 24.0 | 7.64 | .002 | .06 |
| Error | 372.1 | 188.8 | 3.14 | — | — | — |
The 'Gold Standard' audit. Tests for global differences between independent groups regardless of time.
The 'Recovery' audit. Tests if the population as a whole changed over the intervals.
The 'Efficacy' audit. The critical interaction—proves if one group recovered faster than the other.
Statistical Currency. Note the separation of degrees of freedom into Group-level and Subject-level pools.
Relative Impact. The percentage of variance uniquely explained by each factor within its respective partition.
Command Center
Syntax libraries and function parameters for executing calculations in stats packages.
# 1. Execute Mixed ANOVA
model <- aov_ez(id = 'subject_id', dv = 'score', data = df,
between = 'group', within = 'time')
# 2. Extract Group-specific Trajectories
emmeans(model, ~ time | group)Audit the homogeneity of covariance matrices (Box's M) to ensure group comparison integrity.
# 12-Stage Assumption Audit
performance::check_model(model)
# Generate Instant Manuscript Paragraph
report::report(model)Common Mistakes
Analytical caveats and corrections to maintain modeling integrity.
References
Scholarly lineage and citation keys grounding the statistical framework.