Randomization (Permutation) Test for Two Independent Groups
Source:R/randomization_test.R
randomization_test.RdCompares two independent groups by referring the observed statistic to the distribution of that same statistic over reassignments of the observed scores to the two groups. That reference distribution, not a normal or a t distribution, supplies the p-value, so the test needs no assumption about the shape of the population. Alongside the test, the function reports the effect sizes that answer the question the test only screens: the mean difference and its randomization-based interval, the standardized mean difference with a noncentral t interval, the common language effect size, and Cliff's delta.
Arguments
- x
A formula of the form
y ~ group, or a numeric response vector to be paired withgroup. LeaveNULLwhen the two samples are supplied throughgroup_1andgroup_2.- group
A grouping variable the same length as
x, with exactly two levels after unused levels are dropped. Ignored whenxis a formula.- data
An optional
data.framein which to find the variables named in the formula.- group_1, group_2
The two samples supplied directly as numeric vectors, an alternative to the formula and response-plus-grouping interfaces. Lengths need not be equal.
- statistic
One of
"mean"(default) or"t"."mean"uses the difference in group means."t"uses the studentized (Welch) statistic, which divides that difference by its separate-variances standard error and is the better choice when the groups may differ in variance (see Details).- alternative
One of
"two_sided"(default; the base-R spelling"two.sided"is accepted as an alias),"less", or"greater". The direction refers to the first group minus the second, the same orientationt.testuses.- exact
Logical. If
NULL(default), the test enumerates every reassignment whenchoose(N, n_1)is at most 50,000 and samples reassignments otherwise. IfTRUE, enumeration is forced (refused above 1,000,000 reassignments). IfFALSE, Monte Carlo is forced.- n_resamples
Number of randomly drawn reassignments when enumeration is not used. Default
10000L.- seed
Optional integer seed for the Monte Carlo branch. Default
NULL, which leaves the user's current RNG state intact; supply an integer for reproducibility. When a seed is supplied the RNG state in place before the call is restored on exit.- conf_level
Confidence level for every interval reported, the inverted randomization interval included. Default
0.95.- shift_ci
Logical. Compute the randomization-based interval for the shift by inverting the test? Default
TRUE. Setting it toFALSEreports the two endpoints asNAand skips the inversion, which is the expensive part of the call.
Value
A data.frame with a term column and a numeric
value column, in three blocks.
The test: mean_difference (first group minus second),
statistic (the statistic actually referred to the reference
distribution), p_value, and p_value_se (the Monte Carlo
standard error of the p-value, NA under exact
enumeration, which has no Monte Carlo error).
The intervals and effect sizes: shift_lower_limit and
shift_upper_limit (the randomization interval for the shift,
obtained by inverting the test); normal_theory_lower_limit and
normal_theory_upper_limit (Welch's t interval on the same
mean difference, reported for contrast); smd with
smd_lower_limit and smd_upper_limit from
ci_smd; cles with cles_lower_limit and
cles_upper_limit from cles; and
cliff_delta with cliff_delta_lower_limit and
cliff_delta_upper_limit from cliff_delta.
The design: n_1, n_2, N, n_evaluated (how
many reassignments were actually used), and exact (1 if every
reassignment was enumerated, 0 if they were sampled).
Non-numeric information travels on attributes rather than in the
value column: statistic_name, method
(“exact enumeration” or “Monte Carlo”),
alternative, group_labels, response_name,
group_name, seed, observed_statistic, and
reference_distribution, the vector of statistics over the
reassignments that plot_randomization_test draws.
Details
What the randomization distribution is. Suppose N participants were randomly assigned, \(n_1\) to one condition and \(n_2\) to the other. Under the null hypothesis that the condition a participant received made no difference to that participant's score, each score would have been the same number no matter which group the participant landed in. The assignment actually used was one draw from the \(\binom{N}{n_1}\) assignments the randomization could equally well have produced, so every one of those assignments was equally likely, and each of them yields a value of the test statistic. Those values are the randomization distribution. The p-value is the proportion of them at least as extreme as the value the experiment actually produced.
Why there is no normality assumption. Nothing in that argument mentions a population, a normal curve, or a sampling model. The probability comes from the coin flips the experimenter performed, which are known exactly because the experimenter performed them. This is the inferential logic Fisher (1935) used to introduce experimental design, and it is where Chapter 1 of Maxwell, Delaney, and Kelley (2027) starts, for the same reason: the validity of the test rests on the randomization rather than on assumptions a data analyst cannot check.
What the test does and does not license. A small p-value says the observed separation between the groups would rarely arise from reassignment alone, which is evidence that the assignment mattered. It does not say how much it mattered, and with a large N an uninteresting difference will produce a small p-value. It also does not, by itself, license generalization beyond the participants at hand: randomization licenses a causal claim about these units, while generalization to a population is a separate argument that rests on how the units were recruited. That is why this function reports effect sizes with intervals rather than a p-value alone.
Why the studentized statistic. With \(n_1 = n_2\) and equal
population variances the two statistics give the same p-value to
within the discreteness of the reference distribution, because the
denominator of the studentized statistic is then nearly constant across
reassignments. When the variances differ and the groups are unbalanced
they part company. Reassigning scores between groups of unequal size
mixes the two variances in proportions that the observed assignment does
not have, so the reference distribution for the raw mean difference is
built under a null that is false in a second way, and the test's actual
Type I error rate drifts away from the nominal level. The studentized
statistic rescales each reassignment by its own separate-variances
standard error, which removes most of that drift and remains
asymptotically valid under heteroscedasticity (Janssen, 1997; Neuhaus,
1993). Use statistic = "t" whenever unequal variances are
plausible, which for unbalanced designs is nearly always.
Exact or Monte Carlo. When choose(N, n_1) is at most
50,000 every reassignment is enumerated and the p-value is exact:
it is a count divided by a known total, with no approximation anywhere.
Above that threshold n_resamples reassignments are drawn at
random and the p-value is
\((r + 1) / (m + 1)\), where r counts the sampled reassignments
at least as extreme as the observed one and m is
n_resamples. Adding one to each part counts the observed
assignment, which is itself a legitimate reassignment; without it a
p-value of exactly zero could be reported for a hypothesis the
data cannot rule out, and the test would be anticonservative (Phipson &
Smyth, 2010). The reported p_value_se is
\(\sqrt{\hat p (1 - \hat p) / m}\), the standard error of the
resampling itself. It describes how much the p-value would move
if the reassignments were drawn again, not how much it would move in a
new experiment. Raising n_resamples shrinks it at the usual
\(1/\sqrt{m}\) rate.
The randomization interval, and how it differs from the normal
theory one. Suppose the treatment adds a constant \(\delta\) to every
score it touches. Subtracting \(\delta\) from each first-group score
should then leave scores that are exchangeable across groups, so the
randomization test applied to the subtracted data is a test of
\(H_0\!: \mathrm{shift} = \delta\). The set of \(\delta\) for which
that test does not reject at level \(1 - \) conf_level is a
confidence interval for the shift, and it is reported as
shift_lower_limit and shift_upper_limit. Inverting a test
this way is the general recipe (Ernst, 2004); the endpoints are located
by bisection on the p-value, using the same reassignments
throughout so the interval and the test agree.
The contrast with normal_theory_lower_limit and
normal_theory_upper_limit, which are Welch's t limits on
the same mean difference, is worth reading whenever both are printed.
The randomization interval is exactly the set of shifts the test being
run does not reject, so the test and the interval can never disagree.
The normal theory interval instead assumes the sampling distribution of
the mean difference has a known shape; it is smooth, symmetric about the
point estimate, and can extend past the range the data can support.
The randomization interval is discrete, need not be symmetric, and in a
very small design is unbounded, a correct statement of how little
information the design carries rather than a defect:
with three observations per group the smallest attainable two-sided
p-value is 2/20 = 0.10, so no shift can be rejected at the 5%
level and the 95% interval is the whole real line. The randomization
interval also inherits the shift model, so it answers a narrower
question than the test does: the test needs only exchangeability, while
the interval needs the treatment to move every score by the same amount.
Effect sizes. Every effect size reported here comes from the
package function that owns it, so the numbers match a direct call.
smd and ci_smd supply the standardized mean
difference and its noncentral t interval; cles
supplies the common language effect size, the probability that a
randomly drawn score from the first group exceeds one from the second,
by transforming those limits through \(\Phi(\cdot/\sqrt 2)\); and
cliff_delta supplies Cliff's delta with its consistent
interval. The standardized mean difference and the common language
effect size are normal theory quantities, so their intervals lean on the
assumption the test itself avoids. Cliff's delta does not: it is a
function of the ordering of the observations alone, which makes it the
natural effect size companion to a randomization test. Reporting all
three lets a reader see whether the distribution-free and normal theory
summaries tell the same story.
Ranks give the Wilcoxon test. Replacing the scores by their
ranks and running this test with statistic = "mean" reproduces
the exact Wilcoxon rank sum test, since the rank sum is a monotone
function of the difference in mean ranks. That equivalence is a useful
check and a reminder of what the rank test is: a randomization test on
transformed data.
References
Edgington, E. S., & Onghena, P. (2007). Randomization tests (4th ed.). Chapman & Hall/CRC.
Ernst, M. D. (2004). Permutation methods: A basis for exact inference. Statistical Science, 19(4), 676–685. doi:10.1214/088342304000000396
Fisher, R. A. (1935). The design of experiments. Oliver & Boyd.
Janssen, A. (1997). Studentized permutation tests for non-i.i.d. hypotheses and the generalized Behrens-Fisher problem. Statistics & Probability Letters, 36(1), 9–21. doi:10.1016/S0167-7152(97)00043-6
Maxwell, S. E., Delaney, H. D., & Kelley, K. (2027). Designing experiments and analyzing data: A model comparison perspective (4th ed.). Routledge. (See Chapter 1 on the logic of randomization and the randomization test.)
Neuhaus, G. (1993). Conditional rank tests for the two-sample problem under random censorship. The Annals of Statistics, 21(4), 1760–1779. doi:10.1214/aos/1176349396
Phipson, B., & Smyth, G. K. (2010). Permutation p-values should never be zero: Calculating exact p-values when permutations are randomly drawn. Statistical Applications in Genetics and Molecular Biology, 9(1), Article 39. doi:10.2202/1544-6115.1585
Pitman, E. J. G. (1937). Significance tests which may be applied to samples from any populations. Supplement to the Journal of the Royal Statistical Society, 4(1), 119–130.
See also
plot_randomization_test for the figure that shows
the reference distribution, the observed statistic, and the rejection
region; randomization_test_paired for the sign-flip
sibling used with paired observations;
t.test and wilcox.test for
the parametric and rank-based alternatives; smd,
ci_smd, cles, and
cliff_delta for the effect sizes reported here.
Other hypothesis tests:
adjusted_means(),
ancova(),
anova_within(),
ci_dunnett(),
ci_scheffe(),
ci_tukey_kramer(),
compare_cov_structures(),
contrast_test(),
correlations_test(),
equivalence_r(),
equivalence_smd(),
factorial_anova(),
manova_split_plot(),
mauchly_test(),
mixed_anova(),
obrien_test(),
pairwise_within(),
randomization_test_paired(),
regions_of_significance(),
simple_effects_AB(),
summary_t_test(),
welch_t()
Author
Ken Kelley kkelley@nd.edu
Examples
# 1. Ten observations, so every one of the choose(10, 5) = 252
# reassignments is enumerated and the p-value is exact.
treatment <- c(80, 84, 79, 88, 83)
control <- c(72, 75, 68, 81, 74)
randomization_test(group_1 = treatment, group_2 = control)
#> term value
#> mean_difference 8.8
#> statistic 8.8
#> p_value 0.0238
#> p_value_se <NA>
#> shift_lower_limit 3
#> shift_upper_limit 15
#> normal_theory_lower_limit 2.6
#> normal_theory_upper_limit 15
#> smd 2.1
#> smd_lower_limit 0.461
#> smd_upper_limit 3.66
#> cles 0.931
#> cles_lower_limit 0.628
#> cles_upper_limit 0.995
#> cliff_delta 0.84
#> cliff_delta_lower_limit -0.117
#> cliff_delta_upper_limit 0.988
#> n_1 5
#> n_2 5
#> N 10
#> n_evaluated 252
#> exact 1
#>
#> Confidence level: 95%
# 2. The studentized statistic, preferable when the groups may differ
# in variance.
randomization_test(group_1 = treatment, group_2 = control,
statistic = "t")
#> term value
#> mean_difference 8.8
#> statistic 3.32
#> p_value 0.0238
#> p_value_se <NA>
#> shift_lower_limit 3
#> shift_upper_limit 15
#> normal_theory_lower_limit 2.6
#> normal_theory_upper_limit 15
#> smd 2.1
#> smd_lower_limit 0.461
#> smd_upper_limit 3.66
#> cles 0.931
#> cles_lower_limit 0.628
#> cles_upper_limit 0.995
#> cliff_delta 0.84
#> cliff_delta_lower_limit -0.117
#> cliff_delta_upper_limit 0.988
#> n_1 5
#> n_2 5
#> N 10
#> n_evaluated 252
#> exact 1
#>
#> Confidence level: 95%
# 3. Formula interface: weekly drinking in the two comparable arms of
# the drinks_trial data, a right-skewed outcome, which is exactly
# where a distribution-free test earns its keep. With 37 and 32
# participants there are far too many reassignments to enumerate, so
# 10,000 are drawn and the p-value carries a Monte Carlo standard
# error.
cra <- droplevels(subset(drinks_trial, treatment != "CRA + Disulfiram"))
set.seed(113)
randomization_test(drinks_per_week ~ treatment, data = cra, seed = 113)
#> term value
#> mean_difference 24.5
#> statistic 24.5
#> p_value 0.2784
#> p_value_se 0.00448
#> shift_lower_limit -13
#> shift_upper_limit 63.1
#> normal_theory_lower_limit -15.8
#> normal_theory_upper_limit 64.7
#> smd 0.282
#> smd_lower_limit -0.195
#> smd_upper_limit 0.756
#> cles 0.579
#> cles_lower_limit 0.445
#> cles_upper_limit 0.704
#> cliff_delta 0.3
#> cliff_delta_lower_limit 0.0213
#> cliff_delta_upper_limit 0.535
#> n_1 37
#> n_2 32
#> N 69
#> n_evaluated 10000
#> exact 0
#>
#> Confidence level: 95%
# 4. A one-sided test, and the one-sided interval that goes with it.
randomization_test(group_1 = treatment, group_2 = control,
alternative = "greater")
#> term value
#> mean_difference 8.8
#> statistic 8.8
#> p_value 0.0119
#> p_value_se <NA>
#> shift_lower_limit 4
#> shift_upper_limit Inf
#> normal_theory_lower_limit 3.82
#> normal_theory_upper_limit Inf
#> smd 2.1
#> smd_lower_limit 0.461
#> smd_upper_limit 3.66
#> cles 0.931
#> cles_lower_limit 0.628
#> cles_upper_limit 0.995
#> cliff_delta 0.84
#> cliff_delta_lower_limit -0.117
#> cliff_delta_upper_limit 0.988
#> n_1 5
#> n_2 5
#> N 10
#> n_evaluated 252
#> exact 1
#>
#> Confidence level: 95%
# 5. On ranks, the test is the exact Wilcoxon rank sum test.
y <- c(treatment, control)
g <- rep(c("treatment", "control"), each = 5)
res <- randomization_test(rank(y), g)
res$value[res$term == "p_value"]
#> [1] 0.03174603
wilcox.test(treatment, control, exact = TRUE)$p.value
#> [1] 0.03174603