Repeated Measures (Within-Subjects) ANOVA

Author

Dr. Anisha Singh

The dataset for this week is mind.csv. Ten participants had their blood pressure (BP) measured at four time points: Before an intervention, at One Month, Three Months, and Six Months post-intervention. Because the same participants were measured at every time point, this is a within-subjects (repeated measures) design.

Note

Between-subjects vs. within-subjects

In a between-subjects design (Script 5), different people are in each group — the groups are independent. In a within-subjects design, the same people contribute data to every condition. This has two key consequences: (1) individual differences are removed from the error term, giving the test more power; (2) the data structure changes — each person’s scores across time points are correlated, which must be accounted for in the model.

Packages

# install.packages("reshape2")    # run once if needed
# install.packages("psych")
# install.packages("emmeans")
# install.packages("effectsize")

library(reshape2)
library(psych)
library(emmeans)
library(effectsize)

Load and Prepare the Data

The data arrive in wide format — one row per person, one column per time point.

mind <- read.csv("mind.csv")
       Person Before OneMonth ThreeMonth SixMonth
1       Harry    116      112        109      106
2    Hermione    119      121        118      114
3         Ron    128      118        110      106
4      George    103      101         98       96
5        Fred    122      118        113      109
6     Parvati     74       71         68       63
7      Hagrid     85       84         85       84
8  Dumbledore    115      115        113      110
9       Draco    138      133        127      123
10       Luna    104      102         97       93

R’s repeated measures aov() requires long format — one row per observation, with separate columns for Person, Time, and BP. We reshape with melt().

mind.melt <- melt(mind, id = "Person")
colnames(mind.melt) <- c("Person", "Time", "BP")

mind.melt$Person <- as.factor(mind.melt$Person)
mind.melt$Time   <- as.factor(mind.melt$Time)
      Person   Time  BP
1      Harry Before 116
2   Hermione Before 119
3        Ron Before 128
4     George Before 103
5       Fred Before 122
6    Parvati Before  74
...

Exploring the Data

describeBy(mind.melt$BP, mind.melt$Time)
group: Before
   vars  n  mean    sd median trimmed   mad min max range  skew kurtosis   se
X1    1 10 110.4 19.43  115.5  111.50 17.79  74 138    64 -0.50    -0.98 6.14
------------------------------------------------------------
group: OneMonth
   vars  n  mean    sd median trimmed   mad min max range  skew kurtosis   se
X1    1 10 107.5 18.52  113.5  108.88 14.08  71 133    62 -0.63    -0.82 5.86
------------------------------------------------------------
group: ThreeMonth
   vars  n  mean    sd median trimmed   mad min max range  skew kurtosis   se
X1    1 10 103.8 17.29  109.5  105.38 14.83  68 127    59 -0.69    -0.63 5.47
------------------------------------------------------------
group: SixMonth
   vars  n  mean    sd median trimmed   mad min max range  skew kurtosis   se
X1    1 10 100.4 17.21  106.0  102.25 13.34  63 123    60 -0.81    -0.33 5.44

Mean BP decreased at each time point: Before (M = 110.4), One Month (M = 107.5), Three Months (M = 103.8), Six Months (M = 100.4).

Checking Assumptions

The key normality assumption for repeated measures ANOVA is that the difference scores between pairs of time points are normally distributed — not the raw scores themselves.

# Compute difference scores relative to baseline
d_1mo  <- mind$Before - mind$OneMonth
d_3mo  <- mind$Before - mind$ThreeMonth
d_6mo  <- mind$Before - mind$SixMonth

shapiro.test(d_1mo)
shapiro.test(d_3mo)
shapiro.test(d_6mo)
Before - OneMonth:    W = 0.94264, p-value = 0.5827
Before - ThreeMonth:  W = 0.93256, p-value = 0.4735
Before - SixMonth:    W = 0.96621, p-value = 0.8537

All three tests are non-significant, so there is no evidence against normality of the difference scores.

Note

Sphericity

Repeated measures ANOVA also assumes sphericity — that the variances of all pairwise difference scores are equal. This is tested with Mauchly’s test. If sphericity is violated, apply a Greenhouse-Geisser or Huynh-Feldt correction (both are available in SPSS; in R, the ez package provides them). For this small dataset we proceed with the standard model.

Visualizing Residuals

These plots help check the independence and constant-variance assumptions visually. Run them and look for random scatter (no patterns or fans).

fit_check <- lm(BP ~ Time, data = mind.melt)
N <- length(fit_check$residuals)

# Independence: residuals in observation order
plot(1:N, fit_check$residuals,
     xlab = "Observation order", ylab = "Residual",
     main = "Residuals vs. Order")
abline(h = 0, lty = 2)

# Homoscedasticity: residuals vs. fitted values
plot(fit_check$fitted.values, fit_check$residuals,
     xlab = "Fitted values", ylab = "Residuals",
     main = "Residuals vs. Fitted")
abline(h = 0, lty = 2)

# Normality: Q-Q plot
qqnorm(fit_check$residuals, main = "Normal Q-Q Plot")
qqline(fit_check$residuals)

Fitting the Repeated Measures Model

The Error(Person/Time) term tells R that Time is nested within Person — each person contributes one score at each time point.

fit.ws <- aov(BP ~ Time + Error(Person/Time), data = mind.melt)
summary(fit.ws)
Error: Person
          Df Sum Sq Mean Sq F value Pr(>F)
Residuals  9  11632    1292

Error: Person:Time
          Df Sum Sq Mean Sq F value  Pr(>F)
Time       3  569.1   189.7   24.48 7.3e-08 ***
Residuals 27  209.2     7.8

Time has a statistically significant effect on blood pressure, F(3, 27) = 24.48, p < .001.

Note

Reading the output

The ANOVA table is split into two error strata. The Person stratum captures between-person variability (individual differences in average BP). The Person:Time stratum is where the test lives: it compares variance attributable to Time against the Person × Time interaction (the within-person error). Because individual differences are absorbed into the Person stratum rather than the error term, the test is more powerful than a between-subjects design would be.

Overall Effect Size: Partial Eta Squared

eta_squared(fit.ws)
# Effect Size for ANOVA (Type I)

Group       | Parameter | Eta2 (partial) |       95% CI
-------------------------------------------------------
Person:Time |      Time |           0.73 | [0.56, 1.00]

Partial η² = .73 — time accounts for 73% of the within-person variance in blood pressure. By Cohen’s conventions (small ≈ .01, medium ≈ .06, large ≈ .14), this is a very large effect.

Note

Partial eta squared in repeated measures

eta_squared() returns partial η² for repeated measures models, because it partitions variance relative to the within-person error (Person:Time) rather than total variance. This is the standard convention for within-subjects effects.

Post-hoc Tests: Tukey’s HSD

A significant omnibus F tells us that BP changed over time, but not when the changes occurred. We use Tukey’s HSD via emmeans to make all pairwise comparisons.

emm <- emmeans(fit.ws, ~ Time)
pairs(emm, adjust = "tukey")
 contrast              estimate   SE df t.ratio p.value
 Before - OneMonth          2.9 1.24 27   2.330  0.1162
 Before - ThreeMonth        6.6 1.24 27   5.302  <.0001
 Before - SixMonth         10.0 1.24 27   8.034  <.0001
 OneMonth - ThreeMonth      3.7 1.24 27   2.972  0.0295
 OneMonth - SixMonth        7.1 1.24 27   5.704  <.0001
 ThreeMonth - SixMonth      3.4 1.24 27   2.731  0.0506

P value adjustment: tukey method for comparing a family of 4 estimates

BP did not significantly change from Before to One Month (p = .116), but all comparisons involving Three Month or Six Month against an earlier time point were significant or near-significant. By Six Months, BP had dropped 10 points from baseline (p < .001).

Confidence Intervals for Pairwise Differences

confint(pairs(emm, adjust = "tukey"))
 contrast              estimate   SE df lower.CL upper.CL
 Before - OneMonth          2.9 1.24 27  -0.506    6.306
 Before - ThreeMonth        6.6 1.24 27   3.194   10.006
 Before - SixMonth         10.0 1.24 27   6.594   13.406
 OneMonth - ThreeMonth      3.7 1.24 27   0.294    7.106
 OneMonth - SixMonth        7.1 1.24 27   3.694   10.506
 ThreeMonth - SixMonth      3.4 1.24 27  -0.006    6.806

Confidence level used: 0.95
Conf-level adjustment: tukey method for comparing a family of 4 estimates

Confidence intervals that do not include 0 indicate a significant difference. The Before – Six Month interval [6.59, 13.41] is the widest and clearest, reflecting the largest and most certain reduction.

Pairwise Effect Sizes: Cohen’s d

cohens_d() with paired = TRUE computes the standardized mean difference using the standard deviation of the difference scores.

cohens_d(mind$Before, mind$OneMonth,    paired = TRUE)
cohens_d(mind$Before, mind$ThreeMonth,  paired = TRUE)
cohens_d(mind$Before, mind$SixMonth,    paired = TRUE)
Before vs. One Month:
  Cohen's d |       95% CI
  0.89      | [0.13, 1.62]

Before vs. Three Months:
  Cohen's d |       95% CI
  1.24      | [0.38, 2.06]

Before vs. Six Months:
  Cohen's d |       95% CI
  1.68      | [0.68, 2.64]

Effect sizes grow steadily across time: One Month d = 0.89 (large), Three Months d = 1.24 (very large), Six Months d = 1.68 (very large). Even the comparison that was not statistically significant (Before vs. One Month, p = .116) showed a large effect — a reminder that significance depends on sample size, while effect size does not.