Fits a repeated measures ANOVA to data in long format using
aov_ez, reports Mauchly's test of sphericity with the
Greenhouse-Geisser and Huynh-Feldt corrections, generalised and partial eta
squared, residual diagnostics, and estimated marginal means.
Usage
anova_rm(
data,
response,
subject,
within,
between = NULL,
factorize = TRUE,
emm_specs = NULL,
adjust = "tukey",
conf_level = 0.95,
correction = c("GG", "HF", "none"),
posthoc = TRUE,
plots = TRUE,
verbose = FALSE,
...
)Arguments
- data
A data frame in long format: one row per subject per cell.
- response
Character. Name of the numeric response column.
- subject
Character. Name of the subject identifier column.
- within
Character vector. One or more within-subject factors.
- between
Character vector or
NULL. Between-subject factors, each constant within a subject.- factorize
Logical. Convert the response to numeric through its labels. Default
TRUE; withFALSEa non-numeric response is an error. The subject, within- and between-subject columns are always coerced to factors, whatever this is set to.- emm_specs
Character vector or
NULL. Factors to compute estimated marginal means over. Defaults to all within-subject factors; when it leaves out a factor of the model, the means are averaged over that factor's levels and$notessays so.- adjust
Character. Multiplicity adjustment for the pairwise comparisons, applied by emmeans. One of
"tukey","sidak","scheffe","dunnettx","bonferroni","holm","hochberg","hommel","BH","BY","fdr"or"none". Default"tukey".- conf_level
Numeric in (0, 1). Level for every interval returned. Default
0.95.- correction
Character. Sphericity correction applied to the ANOVA table:
"GG"(default),"HF"or"none". The correction actually applied is recorded inattr(fit$anova, "correction")and printed:"none"when every within-subject factor has two levels, where there is nothing to correct.- posthoc
Logical. Compute pairwise comparisons. There are
choose(k, 2)of them, so this is worth turning off when the number of cells is large;$notesrecords that they were skipped. DefaultTRUE.- plots
Logical. Build ggplot2 objects. They are returned in
$plots, never drawn. DefaultTRUE.- verbose
Logical. Emit progress through
message. DefaultFALSE.- ...
Only these arguments of
aov_ezare accepted; anything else is an error rather than silently ignored.fun_aggregateA function combining the rows of a subject that share a within-subject cell into one number. Default
mean.observedCharacter. Factors that were observed (measured) rather than manipulated. Generalised eta squared depends on this (Olejnik and Algina, 2003).
typeSums of squares:
3(or"III", the default) or2("II").anova_tableA list holding only
p_adjust_method, one ofp.adjust.methods, which adjusts the p-values of$anovaacross its terms. Thep_ggandp_hfcolumns of$sphericitystay unadjusted.
covariateis refused:anova_rm()does not fit covariates.id,dv,return,include_aovandprint.formulaare set by the function itself, andtransformationis refused (transform the response column instead).
Value
An anovakit_fit object. $anova has the
corrected degrees of freedom, the MSE, F, partial eta squared and the
p-value for each term; $effect_sizes has partial and generalised
eta squared, the latter honouring observed. The function adds
$sphericityMauchly's test (
mauchly_w,p_value,NAwhere it is undefined), the Greenhouse-Geisser and Huynh-Feldt epsilons (gg_epsilon,hf_epsilon, the latter capped at 1), the p-values under each correction (p_gg,p_hf) and the uncapped Huynh-Feldt estimate (hf_epsilon_raw), one row per within-subject term with more than one degree of freedom.$residualsThe within-subject residuals (see Details), one per row of
$data_usedand in the same order, so$residuals[i]belongs to$data_used[i, ].$subjects_droppedThe subjects removed for incomplete within-subject cells.
$internal_namesA data frame with the
role, yournameand theinternalname each column has in$model.$n_removed_missing,$n_removed_unbalanced,$n_removed_aggregated$n_removedbroken into its three causes.
$data_used has one row per subject and within-subject cell, in the
order the rows came in (a combined row takes the place of its first
row). $assumptions$normality has one Shapiro-Wilk test per
within-subject cell, with a note giving the reason when a cell
could not be tested.
Details
What is fixed rather than inherited. The sums of squares are Type
III unless type = 2 is given through ...; the between-subject
factors are coded with sum-to-zero contrasts, which Type III needs; and the
marginal means and comparisons use afex's multivariate model, whose
standard errors and degrees of freedom come from each within-subject cell's
own variance rather than a pooled error term. None of these is read from
afex::afex_options(), so a global setting made for some other
analysis cannot change the result. The type is recorded in
attr(fit$anova, "ss_type") (and printed), the emmeans model in
attr(fit$emmeans, "emmeans_model") and
attr(fit$posthoc, "emmeans_model").
Sphericity. Mauchly's test and both epsilon estimates are read
from the multivariate model afex fitted (through
car::summary.Anova.mlm()). Each term's error matrix is first
rescaled to unit average variance, so a response measured in small units
does not fall below car's absolute singularity tolerance and
lose its corrections. When the error matrix is genuinely singular – fewer
subjects than the term has contrasts – car cannot give the
corrections and Mauchly's test is undefined. The Greenhouse-Geisser
epsilon, \(tr(S)^2 / ((k-1) tr(S^2))\), is still defined, where \(S\)
is the covariance matrix of the orthonormal within-subject contrasts; it
and the Huynh-Feldt epsilon (with Lecoutre's correction) are then computed
directly, the requested correction is applied with them, and
$notes says so. The Huynh-Feldt estimate can exceed 1; it is used,
and reported in $sphericity$hf_epsilon, capped at 1, with the
uncapped value in $sphericity$hf_epsilon_raw. The corrected degrees
of freedom are the uncorrected ones multiplied by epsilon, as in
afex.
Unbalanced subjects. A repeated measures ANOVA needs every subject
to appear in every within-subject cell. Subjects who do not are removed
before fitting, and both the count and the identifiers (in the order of the
subject column's levels) are reported in $subjects_dropped and in
$notes.
Repeated rows. A subject with more than one row in the same
within-subject cell has those rows combined into one value before fitting,
with fun_aggregate (the mean unless another function is given). The
note says how many subject-by-cell combinations were affected and how many
extra rows were combined away; those rows are counted in $n_removed.
Column names and labels. afex builds a model formula from
the column names and wide-format column names from the within-subject
levels, so a column name that is not a syntactic R name ("subject
id"), or level labels such as 0, 4, 12, would be mangled or parsed
as code. afex is therefore given internal names (Y, ID,
W1, W2, ..., B1, ...) and level labels (L01,
L02, ... in level order), and every table, $data_used,
$emmeans_object and the plots are mapped back to your names and
labels. $model is the afex fit itself and keeps the internal
names; $internal_names gives the correspondence.
Normality. The F tests for within-subject effects depend on the
within-subject errors: what is left of each value after its subject's mean
and its cell's mean (within each between-subject group) are removed,
\(y_{ij} - \bar{y}_{i\cdot} - \bar{y}_{g(i)\cdot j} +
\bar{y}_{g(i)\cdot\cdot}\). These are $residuals, and Shapiro-Wilk
is run on them separately in each within-subject cell
($assumptions$normality), as anova_welch tests each
group: the cells' variances usually differ, and pooling values of
different spread gives a mixture that fails a normality test even when
every cell is normal. With a single two-level within-subject factor the
residuals are plus and minus half of each subject's centred difference, so
both rows test the normality of the differences, which is exactly the
assumption of the equivalent paired t-test. The Q-Q plot shows the same
residuals standardised within each cell. Normality of the subject means,
which the between-subject tests rely on, is not tested.
A factor response. With factorize = TRUE, a response stored
as a factor is converted with as.numeric(as.character(x)), which
recovers the numbers. A bare as.numeric() on a factor returns the
level indices, so a response of 11, 17, 21 would silently become
1, 2, 3.
Requires the afex package.
References
Olejnik, S., & Algina, J. (2003). Generalized eta and omega squared statistics: measures of effect size for some common research designs. Psychological Methods, 8(4), 434-447.
Lecoutre, B. (1991). A correction for the epsilon-tilde approximate test in repeated measures designs with two or more independent groups. Journal of Educational Statistics, 16(4), 371-372.
See also
anova_welch for independent groups,
aov_ez for the fit itself.
Examples
if (requireNamespace("afex", quietly = TRUE)) {
set.seed(1)
d <- expand.grid(id = factor(1:24), time = factor(c("t1", "t2", "t3")))
d$arm <- factor(rep(rep(c("ctrl", "trt"), each = 12), 3))
d$score <- 10 + 2 * as.numeric(d$time) +
1.5 * (d$arm == "trt") + rnorm(nrow(d), 0, 2)
fit <- anova_rm(d, "score", subject = "id",
within = "time", between = "arm")
print(fit)
# Mauchly's test with both epsilon corrections, read off the fitted model
print(fit$sphericity)
# Generalised eta squared alongside partial eta squared, and the marginal
# means over the within-subject factor (averaged over arm, as $notes says)
print(fit$effect_sizes)
print(fit$emmeans)
# A factor that was measured rather than assigned (an age group, say) is
# declared with `observed`, which changes generalised eta squared
d$age_grp <- factor(rep(rep(c("younger", "older"), 12), 3))
anova_rm(d, "score", subject = "id", within = "time",
between = c("arm", "age_grp"), observed = "age_grp",
plots = FALSE)$effect_sizes
}
#> Repeated measures analysis of variance
#> --------------------------------------
#> Call: anova_rm(data = d, response = "score", subject = "id", within = "time", between = "arm")
#> Observations used: 72
#>
#> Omnibus test (Type III, F, GG-corrected degrees of freedom)
#> term num_df den_df mse statistic partial_eta_sq p_value
#> 1 arm 1.000 22.00 3.274 14.8000 0.4022 0.0008754
#> 2 time 1.994 43.87 3.672 29.7600 0.5750 7.013e-09
#> 3 arm:time 1.994 43.87 3.672 0.4833 0.0215 0.6194033
#>
#> Notes
#> - The Huynh-Feldt epsilon estimate exceeds 1 for time, arm:time (1.096, 1.096). Epsilon cannot exceed 1, so it is capped at 1 in $sphericity$hf_epsilon and wherever it is used (p_hf); the uncapped estimate is in $sphericity$hf_epsilon_raw.
#> - The marginal means in $emmeans and the comparisons in $posthoc are averaged over the levels of: arm.
#>
#> Plots available: residuals, qq, index, emmeans
#> (use plot(x, which = "residuals"))
#> term mauchly_w p_value gg_epsilon hf_epsilon p_gg p_hf
#> 1 time 0.9969409 0.9683427 0.9969503 1 7.013415e-09 6.686437e-09
#> 2 arm:time 0.9969409 0.9683427 0.9969503 1 6.194033e-01 6.199669e-01
#> hf_epsilon_raw
#> 1 1.096159
#> 2 1.096159
#> term partial_eta_sq generalised_eta_sq
#> 1 arm 0.4021595 0.17208342
#> 2 time 0.5749716 0.48314858
#> 3 arm:time 0.0214969 0.01495398
#> time estimate se df conf_low conf_high
#> 1 t1 13.04973 0.3999464 22 12.22030 13.87917
#> 2 t2 14.80474 0.2902370 22 14.20282 15.40665
#> 3 t3 17.29008 0.4441658 22 16.36894 18.21123
#> term partial_eta_sq generalised_eta_sq
#> 1 arm 0.410002321 0.1720834176
#> 2 age_grp 0.029364143 0.0090485174
#> 3 arm:age_grp 0.002793737 0.0008379477
#> 4 time 0.587583344 0.4831485847
#> 5 arm:time 0.022614367 0.0149539780
#> 6 age_grp:time 0.006697070 0.0044236914
#> 7 arm:age_grp:time 0.044381926 0.0304721791