Answer

How many participants do you need when comparing several treatment arms?

Inspired by a question on Cross Validated ·

power analysisanovamultiple comparisons

The short answer

Plan around the comparisons you will report. To compare each treatment with a control, run a two-group power analysis for one comparison, at a corrected alpha if you adjust for multiple tests, and give every group that many participants. The omnibus ANOVA calculation answers a different question and can suggest too few. Power to detect every true effect at once is also lower than power for any one comparison.

The short answer

A study with several arms (say a control group and two treatments, or three competing treatments) is usually analysed with a set of specific comparisons, not only the overall one-way ANOVA. The F test tells you that the group means are not all equal; it does not tell you which treatment beat the control. So the sample size should be driven by the comparisons that answer your research question.

See it in R and Python

The example assumes a control group with mean 0 and two treatments with mean 0.5, all with a standard deviation of 1, so each treatment differs from the control by d = 0.5. It finds the per-group size for 80% power from the omnibus ANOVA, then from a single treatment-versus-control t test at α = .05 and at Bonferroni-corrected levels for two and three comparisons. Finally it simulates 10,000 studies to see how often one, at least one, and both treatments come out significant against the shared control. Both calculations use only base R (power.anova.test and power.t.test).

R

set.seed(467595)
means <- c(control = 0, t1 = 0.5, t2 = 0.5)  # assumed group means; within-group SD = 1

# 1. Omnibus one-way ANOVA: n per group for 80% power at alpha = .05
f <- sqrt(mean((means - mean(means))^2)) / 1  # Cohen's f
aov_n <- power.anova.test(groups = 3, between.var = var(means),
                          within.var = 1, power = 0.8)$n
round(c(cohens_f = f, anova_n_per_group = aov_n), 3)

# 2. One treatment vs control (d = 0.5): n per group for 80% power
n_for <- function(alpha) ceiling(power.t.test(delta = 0.5, sd = 1,
                                 power = 0.8, sig.level = alpha)$n)
c(alpha_.05 = n_for(0.05), alpha_.025 = n_for(0.05 / 2),
  alpha_.0167 = n_for(0.05 / 3))

# 3. Simulate both treatment-vs-control tests with a shared control group
sim <- function(n, alpha, reps = 10000) {
  res <- replicate(reps, {
    ctl <- rnorm(n, 0); y1 <- rnorm(n, 0.5); y2 <- rnorm(n, 0.5)
    p1 <- t.test(y1, ctl, var.equal = TRUE)$p.value
    p2 <- t.test(y2, ctl, var.equal = TRUE)$p.value
    c(t1 = p1 < alpha, at_least_one = p1 < alpha | p2 < alpha,
      both = p1 < alpha & p2 < alpha)
  })
  round(rowMeans(res), 3)
}
rbind(n64_alpha.05 = sim(64, 0.05), n78_alpha.025 = sim(78, 0.025))

Python

import numpy as np
from scipy import stats, optimize

rng = np.random.default_rng(467595)
means = np.array([0, 0.5, 0.5])  # control, t1, t2; within-group SD = 1

# 1. Omnibus one-way ANOVA: n per group for 80% power (same formula as power.anova.test)
f = np.sqrt(np.mean((means - means.mean()) ** 2)) / 1  # Cohen's f
def anova_power(n, k=3, alpha=0.05):
    df1, df2 = k - 1, k * (n - 1)
    ncp = (k - 1) * n * np.var(means, ddof=1) / 1
    return stats.ncf.sf(stats.f.ppf(1 - alpha, df1, df2), df1, df2, ncp)
aov_n = optimize.brentq(lambda n: anova_power(n) - 0.8, 2, 1000)
print("cohens_f", round(f, 3), "anova_n_per_group", round(aov_n, 3))

# 2. One treatment vs control (d = 0.5): n per group for 80% power (as power.t.test)
def t_power(n, alpha, d=0.5):
    df = 2 * n - 2
    return stats.nct.sf(stats.t.ppf(1 - alpha / 2, df), df, d * np.sqrt(n / 2))
for alpha in (0.05, 0.05 / 2, 0.05 / 3):
    n = optimize.brentq(lambda n: t_power(n, alpha) - 0.8, 2, 1000)
    print(f"alpha {alpha:.4f}: n = {int(np.ceil(n))}")

# 3. Simulate both treatment-vs-control tests with a shared control group
def sim(n, alpha, reps=10000):
    ctl = rng.normal(0, 1, (reps, n))
    y1, y2 = rng.normal(0.5, 1, (reps, n)), rng.normal(0.5, 1, (reps, n))
    s1 = stats.ttest_ind(y1, ctl, axis=1).pvalue < alpha
    s2 = stats.ttest_ind(y2, ctl, axis=1).pvalue < alpha
    return {"t1": s1.mean(), "at_least_one": (s1 | s2).mean(), "both": (s1 & s2).mean()}
for n, alpha in ((64, 0.05), (78, 0.025)):
    print(f"n{n}_alpha{alpha}", {k: round(float(v), 3) for k, v in sim(n, alpha).items()})
         cohens_f anova_n_per_group 
            0.236            58.818 
  alpha_.05  alpha_.025 alpha_.0167 
         64          78          86 
                 t1 at_least_one  both
n64_alpha.05  0.806        0.915 0.693
n78_alpha.025 0.799        0.920 0.690

The figures come from one seeded run of the R code. The sample-size calculations involve no randomness, and Python reproduces them exactly. Python's random numbers differ from R's, so its simulated rates differ slightly (for example 69.7% instead of 69.3% for both treatments at 64 per group), but they show the same pattern.

What the numbers show

The joint power is a little higher than you would get by multiplying 0.806 by itself (about 0.65), because the two comparisons share a control group: when the control mean happens to come out low, both treatments look better at the same time. This correlation is also why an exact many-to-one method such as Dunnett's test is slightly less conservative than Bonferroni for treatment-versus-control comparisons, so a Bonferroni-based sample size errs on the safe side.

Choices that change the answer

This page is about planning a study with several arms. For the separate question of whether a two-group t test has a minimum sample size, see Does a t test need a minimum sample size?. To read the F test itself, see How to interpret the F statistic in ANOVA. For more plain-language guides to planning and reporting analyses, see the DASS blog.

How to report a power analysis in APA style (7th edition)

Report an a priori power analysis in the method section, naming every input so a reader can repeat it. Using the example above:

"An a priori power analysis was conducted for the two planned comparisons of each treatment with the control. To detect a difference of d = 0.50 with 80% power in a two-sided independent-samples t test at a Bonferroni-adjusted α = .025, 78 participants were required per group (N = 234). A simulation of 10,000 studies with this design indicated a power of .69 to detect both treatment effects."

Name the software or function you used (here, power.t.test in R), state where the assumed effect size came from (earlier studies, a pilot, or the smallest difference of practical importance), and say whether the planned sample was reached.

Related tools and guides

More answered questions

Working with your own data?

General answers only go so far. Send us your situation and we'll reply by email within two business days.

Ask your question

Written by AskStats with AI assistance. This is general information, not advice for your specific data or study. When the results matter, check your approach with a qualified statistician.