Compares the mean of a numeric response across groups without assuming equal
variances, using oneway.test. Pairwise Welch t-tests
with a multiplicity adjustment, and standardised mean differences, are also
returned for any number of groups from two upwards; with two groups the
single comparison is the omnibus test.
Usage
anova_welch(
data,
response,
groups,
conf_level = 0.95,
adjust = "holm",
hedges_correction = TRUE,
posthoc = TRUE,
plots = TRUE,
verbose = FALSE
)Arguments
- data
A data frame, or anything inheriting from one, such as a
data.tableor a tibble.- response
Character. Name of the numeric response column.
- groups
Character vector. One or more grouping columns.
- conf_level
Numeric in (0, 1). Level for every interval returned: group means, pairwise differences and standardised effect sizes. Default
0.95.- adjust
Character. Multiplicity adjustment for the pairwise comparisons' p-values, passed to
p.adjust. One of"holm","hochberg","hommel","bonferroni","BH","BY","fdr"or"none", spelled out in full. Default"holm". Note that"tukey"is not available here, as it is on the functions whose comparisons come from emmeans: these are Welch t-tests, not linear contrasts on a common error term.- hedges_correction
Logical. Apply the small-sample bias correction to the standardised mean differences (see Details). Default
TRUE, in which case the effect size column is namedhedges_g; otherwisecohens_d. Both use the average-variance standardiser.- posthoc
Logical. Compute the pairwise comparisons. There are
choose(k, 2)of them, so this is worth turning off when the number of groups is large. DefaultTRUE. The standardised mean differences are computed pair by pair alongside them, so$effect_sizesis empty when this isFALSE;$notessays so.- plots
Logical. Build ggplot2 objects. They are returned in
$plots, never drawn. DefaultTRUE.- verbose
Logical. Emit progress through
message. DefaultFALSE.
Value
An anovakit_fit object.
$anova: the Welch test, one row. Itstermis the grouping column, or"A x B cells"when several were combined.$emmeans: the group means with t intervals (raw group summaries; no model is fitted).$posthoc: one row per pair of groups, withgroup1,group2,difference(the mean ofgroup1minus the mean ofgroup2),conf_lowandconf_high(the Welch interval for that difference atconf_level; an unadjusted, per-comparison interval, not a simultaneous one),statistic(Welch's t, same sign asdifference),df,p_value(unadjusted),p_adjusted(adjusted byadjust) andadjustment. This is the same column layout as the functions built on emmeans, whose intervals are however adjusted.$effect_sizes: one row per pair, withgroup1,group2,hedges_g(orcohens_d), the same direction asdifference, its intervalconf_lowandconf_high,magnitude, andstandardiser, which names the standard deviation used.$assumptions: a per-group Shapiro-Wilk table innormality, with anotecolumn giving the reason for any group that was not tested, and the ratio of the largest to the smallest group variance invariance_ratio.$model: thehtestreturned byoneway.test; there is no fitted model object and$emmeans_objectisNULL.
$posthoc and $effect_sizes are NULL when
posthoc = FALSE.
Details
Welch's test is the appropriate default for comparing means: it is barely less powerful than the classical F test when variances are equal, and it keeps its nominal error rate when they are not.
Because the method explicitly permits unequal variances, normality is
assessed within each group rather than on pooled residuals. Pooling
residuals from groups with different spreads produces a mixture distribution
that fails normality tests even when every group is perfectly normal, which
would argue against the very method being used. For the same reason the Q-Q
plot shows each observation's deviation from its group mean divided by its
group's standard deviation, so every group contributes on its own scale.
A group whose Shapiro-Wilk test could not be run (fewer than 3 or more than
5000 observations) is named in $notes, with the reason.
Standardised mean differences. The pairwise effect sizes do not
assume equal variances either. Each difference in means is divided by the
average-variance standard deviation of its two groups,
\(s^* = \sqrt{(s_1^2 + s_2^2)/2}\), rather
than by the pooled standard deviation, whose meaning and sampling variance
both depend on equal variances. The interval is for the population value
\(\delta^* = (\mu_1 - \mu_2) / \sqrt{(\sigma_1^2 + \sigma_2^2)/2}\). It inverts the noncentral
t distribution of Welch's statistic on Welch's degrees of freedom and
rescales the bounds on the noncentrality to the average-variance standard
deviation, which is the interval effectsize::cohens_d(pooled_sd =
FALSE) reports. In simulation (variance ratios from 1/4 to 16 in either
group, group sizes from 2 to 50, true differences of 0 and 0.8) a 95%
interval covered 93-97% whenever the smaller group had at least five
observations, 93-98% for equal groups of two or three, and 86-95% when
a group of two or three sat beside one ten or more times its size, where
no interval does well: with two observations a group's standard deviation
is barely estimated. Bonett's (2008) normal-theory interval for the same
quantity was also examined; it was wider than needed for equal small groups
(99-100% at two per group) and covered less in the unbalanced cases
(81-94%).
With hedges_correction = TRUE the estimate is multiplied by the
small-sample bias correction \(J(\nu) = \Gamma(\nu/2) / (\sqrt{\nu/2}\,
\Gamma((\nu-1)/2))\),
evaluated at the Satterthwaite degrees of freedom of the standardiser,
\(\nu = (s_1^2 + s_2^2)^2 / (s_1^4/(n_1-1) + s_2^4/(n_2-1))\) (Delacre et al., 2021). When the two
sample variances are equal and the groups are the same size this is the
familiar \(n_1 + n_2 - 2\); when the variances differ it
removes the bias that the pooled-variance correction leaves. The correction is a property of the point estimate, so
the interval, which is for \(\delta^*\) itself, is the same with or
without it. With two or three observations per group and a low
conf_level, the corrected estimate can fall outside that interval;
$notes says so when it does.
The standardiser is recorded in the standardiser column of
$effect_sizes. The magnitude labels use Cohen's conventional
thresholds of 0.2, 0.5 and 0.8 on the absolute value.
Several grouping variables. They are combined into a single cell
factor and unused level combinations are dropped, so the number of groups
reported is the number that actually contain data. The result is one
omnibus test across all populated cells, labelled "A x B cells" in
$anova: it cannot separate a main effect of one factor from a main
effect of another, and it cannot test an interaction. A note to that effect
is added to the returned object. For a factorial analysis use
anova_glm with interaction = TRUE.
References
Bonett, D. G. (2008). Confidence intervals for standardized linear contrasts of means. Psychological Methods, 13(2), 99-109.
Delacre, M., Lakens, D., Ley, C., Liu, L., & Leys, C. (2021). Why Hedges' g*s based on the non-pooled standard deviation should be reported with Welch's t-test. PsyArXiv.
Welch, B. L. (1951). On the comparison of several mean values: an alternative approach. Biometrika, 38(3/4), 330-336.
See also
anova_kw for a rank-based alternative,
anova_ancova to adjust for a covariate.
Examples
set.seed(123)
d <- data.frame(
group = rep(c("A", "B", "C"), each = 30),
value = c(rnorm(30, 10, 2), rnorm(30, 12, 2.5), rnorm(30, 9, 1.5))
)
fit <- anova_welch(d, "value", "group")
fit
#> Welch's analysis of variance
#> ----------------------------
#> Call: anova_welch(data = d, response = "value", groups = "group")
#> Observations used: 90
#>
#> Omnibus test
#> term statistic num_df den_df p_value
#> 1 group 28.44 2 55.18 3.24e-09
#>
#> Plots available: means, box, qq
#> (use plot(x, which = "means"))
fit$posthoc
#> group1 group2 difference conf_low conf_high statistic df
#> 1 A B -2.5400534 -3.587216430 -1.492890 -4.855868 57.77758
#> 2 A C 0.8691619 0.005291539 1.733032 2.020414 50.45176
#> 3 B C 3.4092153 2.505770188 4.312660 7.584648 48.65250
#> p_value p_adjusted adjustment
#> 1 9.503682e-06 1.900736e-05 holm
#> 2 4.866484e-02 4.866484e-02 holm
#> 3 8.654470e-10 2.596341e-09 holm
# Standardised mean differences on the average-variance standard deviation,
# with an interval that does not assume equal variances
fit$effect_sizes
#> group1 group2 hedges_g conf_low conf_high magnitude
#> 1 A B -1.2374221 -1.804332292 -0.6941522 large
#> 2 A C 0.5138686 0.003040726 1.0353311 medium
#> 3 B C 1.9279766 1.314162066 2.5895992 large
#> standardiser
#> 1 sqrt((s1^2 + s2^2) / 2)
#> 2 sqrt((s1^2 + s2^2) / 2)
#> 3 sqrt((s1^2 + s2^2) / 2)
# Two grouping variables are combined into one cell factor
d$site <- rep(c("north", "south"), 45)
anova_welch(d, "value", c("group", "site"), plots = FALSE)$anova
#> term statistic num_df den_df p_value
#> 1 group x site cells 11.41081 5 38.34597 8.68667e-07