PSY 771 — Script 8: Assumption Testing in ANOVA

Overview

ANOVA models rest on a set of statistical assumptions. When these assumptions hold, the F test behaves as expected. When they are violated, p-values and confidence intervals may be misleading. This script walks through how to check each assumption in R and how to respond when a violation is detected.

We will work with the mind2 dataset — a mindfulness program study that tracked blood pressure (BP) in 10 participants across four time points: Before, One Month, Three Months, and Six Months.


Setup

library(readr)
library(nlme)
library(car)
Loading required package: carData
mind2 <- read_csv("mind2.csv")
New names:
Rows: 40 Columns: 4
── Column specification
──────────────────────────────────────────────────────── Delimiter: "," chr
(2): Person, Time dbl (2): ...1, BP
ℹ Use `spec()` to retrieve the full column specification for this data. ℹ
Specify the column types or set `show_col_types = FALSE` to quiet this message.
• `` -> `...1`
mind2$Person <- as.factor(mind2$Person)
mind2$Time   <- as.factor(mind2$Time)

The Assumptions

Three assumptions apply to all ANOVA designs (one-way, two-way between-subjects, and repeated-measures):

  1. Independence of observations — scores from different people must not influence one another
  2. Homogeneity of variance (homoscedasticity) — the variance of the residuals should be roughly equal across groups or conditions
  3. Normality — the residuals (or population scores) should be approximately normally distributed

Repeated-measures ANOVA adds a fourth:

  1. Sphericity — the variances of the difference scores between all pairs of conditions must be equal

We test these using residuals from the fitted model. We start by fitting the model using lme() from the nlme package, which makes it easy to extract residuals from a repeated-measures design.

fit_lme <- lme(BP ~ Time, random = ~1|Person, data = mind2)
anova(fit_lme)
            numDF denDF  F-value p-value
(Intercept)     1    27 344.6427  <.0001
Time            3    27  24.4851  <.0001

The model confirms what Script 6 showed: there is a significant effect of time, F(3, 27) = 24.49, p < .001. Now we check whether the assumptions of this model are satisfied.


Assumption 1: Independence

model.res    <- residuals(fit_lme)
model.fitted <- fitted(fit_lme)

plot(model.res,
     ylab = "Residuals",
     xlab = "Observation Index",
     main = "Residuals by Observation Order")
abline(h = 0, lty = 2, col = "gray50")

Note

In a repeated-measures design, the residuals will not look completely random. Because the same person contributes multiple observations, there is built-in correlation — and that is expected. If the residuals looked perfectly independent and scattered, it would actually suggest the within-subject correlation is not being captured by the model. What you are looking for is the absence of systematic drift or clustering across the plot.


Assumption 2: Homogeneity of Variance (Homoscedasticity)

A residuals vs. fitted values plot is the standard diagnostic for homoscedasticity. If the assumption holds, the spread of residuals should be roughly consistent across the range of fitted values — a horizontal band with no fanning or narrowing.

plot(model.fitted, model.res,
     xlab = "Fitted Values",
     ylab = "Residuals",
     main = "Residuals vs. Fitted Values")
abline(h = 0, lty = 2, col = "gray50")

In this plot, the spread of the residuals appears to change across fitted values, which suggests unequal variance. This is not unusual in repeated-measures data, but it should be addressed.

Formal Test: Likelihood Ratio Test

We can formally test whether allowing condition-specific residual variances improves the model fit. We fit a second model that explicitly allows each time point to have its own residual variance, then compare the two models using a likelihood ratio test.

fit_lme_unequal <- lme(
  BP ~ Time,
  random  = ~1|Person,
  data    = mind2,
  weights = varIdent(form = ~1|Time)
)

anova(fit_lme, fit_lme_unequal)
                Model df      AIC      BIC    logLik   Test  L.Ratio p-value
fit_lme             1  6 243.1303 252.6315 -115.5652                        
fit_lme_unequal     2  9 229.7751 244.0268 -105.8876 1 vs 2 19.35524   2e-04

The likelihood ratio test shows a significant improvement when allowing unequal variances across time points, χ²(3) = 19.36, p < .001. The model with unequal variances also has lower AIC (229.78 vs. 243.13) and lower BIC (244.03 vs. 252.63).

Tip

Lower AIC and BIC indicate a better-fitting model. Both criteria favor the heterogeneous-variance model here, confirming that the equal-variance assumption is violated. For final reporting, results should be based on fit_lme_unequal rather than the original model.


Assumption 3: Normality

A Q-Q (quantile–quantile) plot compares the distribution of the residuals to what we would expect if they came from a normal distribution. Points that fall closely along the diagonal line indicate approximate normality.

qqnorm(model.res, main = "Normal Q-Q Plot of Residuals")
qqline(model.res, col = "steelblue", lwd = 2)

In this plot, several departures from normality are visible:

  • Heavy tails at both ends: points deviate from the line at the extremes
  • Lower tail deviation: the most pronounced departure is in the lower tail (around −2 theoretical quantiles), where points fall below the line
  • S-shaped pattern: suggests possible skewness in the distribution

These patterns indicate that the residuals have heavier tails than a normal distribution and may be slightly negatively skewed. With only 10 participants, the Q-Q plot has limited resolution, and ANOVA is reasonably robust to mild normality violations when sample sizes are equal across conditions.


Assumption 4: Sphericity (Repeated-Measures Only)

Sphericity requires that the variances of the difference scores between all pairs of time points are equal. For example, the variance of (Before − OneMonth) should equal the variance of (Before − SixMonth) and the variance of (OneMonth − SixMonth), and so on.

If sphericity is violated, the F test from a standard repeated-measures ANOVA is too liberal — it rejects the null hypothesis more often than it should.

Note

The aov() function used in Script 6 does not test sphericity or apply corrections for it. To test and correct for sphericity violations, we use the Anova() function from the car package.

Restructuring the Data

The Anova() function from car requires the data in wide format — one row per participant, one column per time point.

shortmind <- with(mind2, cbind(
  BP[Time == "Before"],
  BP[Time == "OneMonth"],
  BP[Time == "ThreeMonth"],
  BP[Time == "SixMonth"]
))

mind.model <- lm(shortmind ~ 1)
TimePoint  <- factor(1:4)

Running the Test

fit_sph <- Anova(mind.model,
                 idata   = data.frame(TimePoint),
                 idesign = ~TimePoint,
                 type    = "III")

summary(fit_sph, multivariate = FALSE)

Univariate Type III Repeated-Measures ANOVA Assuming Sphericity

            Sum Sq num Df Error SS den Df F value    Pr(>F)    
(Intercept) 445421      1  11631.7      9 344.643 1.749e-08 ***
TimePoint      569      3    209.2     27  24.485 7.305e-08 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1


Mauchly Tests for Sphericity

          Test statistic    p-value
TimePoint      0.0098901 1.4697e-06


Greenhouse-Geisser and Huynh-Feldt Corrections
 for Departure from Sphericity

           GG eps Pr(>F[GG])    
TimePoint 0.38075  0.0004042 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

             HF eps   Pr(>F[HF])
TimePoint 0.3997112 0.0003089785

Reading the Output

The output has three parts:

Univariate Type III Repeated-Measures ANOVA Assuming Sphericity This is the standard F test, computed under the assumption that sphericity holds. Use these values only if the Mauchly test is non-significant.

Mauchly Tests for Sphericity The null hypothesis is that sphericity holds. We want p > .05 to retain the assumption. Here, p < .001, which means sphericity is violated. We should not rely on the uncorrected F test.

Greenhouse-Geisser and Huynh-Feldt Corrections When sphericity is violated, these corrections adjust the degrees of freedom to produce a more conservative F test. Both corrections still yield p < .001, so the main effect of time remains significant even after accounting for the sphericity violation.

Tip

Which correction to report? The Greenhouse-Geisser (GG) correction is more conservative and is generally preferred when the GG epsilon (ε) is below 0.75. Here, GG ε = 0.38, so report the Greenhouse-Geisser corrected values.


Summary

Assumption Test / Diagnostic Result
Independence Residuals plot No systematic pattern detected
Homoscedasticity Residuals vs. fitted plot; likelihood ratio test Violated — unequal variance across time points
Normality Q-Q plot Mild deviation (heavy tails); proceed with caution
Sphericity Mauchly’s test Violated — use GG-corrected F values