Answer

Why does a t test use the t distribution and ANOVA the F distribution?

Inspired by a question on Cross Validated ·

hypothesis testingdistributionsanovat test

The short answer

The distribution is derived, not chosen: statisticians work out what values a test statistic takes when the null and the test's assumptions hold. A mean difference over an estimated standard error follows a t distribution; a ratio of two independent variance estimates follows an F distribution. In 10,000 simulated null data sets with 5 per group, using the normal cutoff instead of t gave 8.1% false positives instead of 4.8%.

The short answer

A hypothesis test needs to know how its statistic behaves when nothing is going on, so that it can say whether the observed value is unusual. That behaviour is called the null distribution (or reference distribution) of the statistic. It is not chosen; it is derived. You write down the statistic, write down the assumptions (for example, independent observations from normal distributions with equal variances), assume the null is true, and use probability theory to find the distribution of the statistic. The t, F and chi-square distributions are simply the names of the answers that keep coming up.

So the t test uses a t distribution because its statistic is built in a way that produces one, and the ANOVA uses an F distribution because its statistic is a ratio of two variance estimates. When the maths has no exact answer, statisticians fall back on large-sample approximations or on simulating the null distribution directly.

Where the common distributions come from

Almost every classical test is built from a few pieces, and each piece has a known distribution when the data are normal:

These pieces are connected. A t statistic squared is an F statistic with 1 numerator degree of freedom, which is why a two-group ANOVA and a pooled two-sample t test always give the same p-value (see t test vs ANOVA with two groups). And the degrees of freedom are not decoration: they tell you which member of the family to use, because each one describes how much information went into a variance estimate (see what degrees of freedom are).

See it in R and Python

The code checks each derivation by brute force. It generates 10,000 data sets in which the null hypothesis is true and compares the simulated statistics with the theoretical distribution: a pooled two-sample t test with 5 per group (8 degrees of freedom), a one-way ANOVA with 3 groups of 5 (2 and 12 degrees of freedom), the t² = F identity, a chi-square goodness-of-fit test for 60 rolls of a fair die, and finally the variance-ratio F test applied to data that break its normality assumption.

R

set.seed(70820)
reps <- 10000

# 1. Two-sample t statistic (5 per group) when the null is true
t_stat <- replicate(reps, t.test(rnorm(5), rnorm(5), var.equal = TRUE)$statistic)
probs <- c(0.90, 0.95, 0.975, 0.995)
round(rbind(simulated = quantile(t_stat, probs),
            t_8 = qt(probs, df = 8), normal = qnorm(probs)), 3)
c(reject_with_normal = mean(abs(t_stat) > qnorm(0.975)),
  reject_with_t8 = mean(abs(t_stat) > qt(0.975, df = 8)))

# 2. One-way ANOVA F statistic (3 groups of 5) when the null is true
g <- factor(rep(1:3, each = 5))
F_stat <- replicate(reps, oneway.test(rnorm(15) ~ g, var.equal = TRUE)$statistic)
round(rbind(simulated = quantile(F_stat, c(0.90, 0.95, 0.99)),
            F_2_12 = qf(c(0.90, 0.95, 0.99), 2, 12)), 3)
mean(F_stat > qf(0.95, 2, 12))

# 3. With two groups, F is exactly t squared
y <- rnorm(10); g2 <- factor(rep(1:2, each = 5))
c(t_squared = unname(t.test(y ~ g2, var.equal = TRUE)$statistic^2),
  F = unname(oneway.test(y ~ g2, var.equal = TRUE)$statistic))

# 4. Pearson chi-square for 60 rolls of a fair die: chi-square(5) is an approximation
chi_stat <- replicate(reps, {
  obs <- tabulate(sample(6, 60, replace = TRUE), 6)
  sum((obs - 10)^2 / 10)
})
c(reject_rate = mean(chi_stat > qchisq(0.95, 5)))

# 5. Same statistic, broken assumption: variance-ratio F test, 20 per group
p_normal <- replicate(reps, var.test(rnorm(20), rnorm(20))$p.value)
p_skewed <- replicate(reps, var.test(rexp(20), rexp(20))$p.value)
c(normal_data = mean(p_normal < 0.05), exponential_data = mean(p_skewed < 0.05))

Python

import numpy as np
from scipy import stats

rng = np.random.default_rng(70820)
reps = 10000

# 1. Two-sample t statistic (5 per group) when the null is true
t_stat = stats.ttest_ind(rng.normal(size=(reps, 5)), rng.normal(size=(reps, 5)), axis=1).statistic
probs = [0.90, 0.95, 0.975, 0.995]
print("simulated", np.round(np.quantile(t_stat, probs), 3))
print("t_8      ", np.round(stats.t.ppf(probs, 8), 3))
print("normal   ", np.round(stats.norm.ppf(probs), 3))
print("reject_with_normal", np.mean(np.abs(t_stat) > stats.norm.ppf(0.975)),
      "reject_with_t8", np.mean(np.abs(t_stat) > stats.t.ppf(0.975, 8)))

# 2. One-way ANOVA F statistic (3 groups of 5) when the null is true
y = rng.normal(size=(reps, 3, 5))
F_stat = stats.f_oneway(y[:, 0], y[:, 1], y[:, 2], axis=1).statistic
print("simulated", np.round(np.quantile(F_stat, [0.90, 0.95, 0.99]), 3))
print("F_2_12   ", np.round(stats.f.ppf([0.90, 0.95, 0.99], 2, 12), 3))
print("reject", np.mean(F_stat > stats.f.ppf(0.95, 2, 12)))

# 3. With two groups, F is exactly t squared
y2 = rng.normal(size=10)
print("t_squared", stats.ttest_ind(y2[:5], y2[5:]).statistic ** 2,
      "F", stats.f_oneway(y2[:5], y2[5:]).statistic)

# 4. Pearson chi-square for 60 rolls of a fair die: chi-square(5) is an approximation
obs = np.array([np.bincount(r, minlength=6) for r in rng.integers(0, 6, size=(reps, 60))])
chi_stat = ((obs - 10) ** 2 / 10).sum(axis=1)
print("reject_rate", np.mean(chi_stat > stats.chi2.ppf(0.95, 5)))

# 5. Same statistic, broken assumption: variance-ratio F test, 20 per group
def var_test_p(a, b):
    ratio = a.var(axis=1, ddof=1) / b.var(axis=1, ddof=1)
    p = stats.f.cdf(ratio, 19, 19)
    return np.minimum(1, 2 * np.minimum(p, 1 - p))   # two-sided, as in R's var.test

p_normal = var_test_p(rng.normal(size=(reps, 20)), rng.normal(size=(reps, 20)))
p_skewed = var_test_p(rng.exponential(size=(reps, 20)), rng.exponential(size=(reps, 20)))
print("normal_data", np.mean(p_normal < 0.05), "exponential_data", np.mean(p_skewed < 0.05))
            90%   95% 97.5% 99.5%
simulated 1.387 1.844 2.299 3.370
t_8       1.397 1.860 2.306 3.355
normal    1.282 1.645 1.960 2.576
reject_with_normal     reject_with_t8 
            0.0807             0.0484 
            90%   95%   99%
simulated 2.792 3.840 6.964
F_2_12    2.807 3.885 6.927
[1] 0.0485
t_squared         F 
0.6648932 0.6648932 
reject_rate 
     0.0477 
     normal_data exponential_data 
          0.0549           0.2720

All figures come from one seeded run of the R code. The theoretical quantiles involve no randomness, and Python reproduces them exactly. Python's random numbers differ from R's, so its simulated values differ slightly (for example 8.5% false positives with the normal cutoff instead of 8.1%, and 27.4% instead of 27.2% for the variance test on skewed data), but they show the same pattern.

What the numbers show

When there is no exact formula

Exact results like Gosset's are the exception. For most modern methods statisticians use one of three routes:

Whichever route a test takes, the practical lesson is the same: the reference distribution is part of the test's assumptions. When the assumptions clearly fail, choose a test whose null distribution does not depend on them. Our test chooser and the DASS blog can help you pick one.

How to report t and F statistics in APA style (7th edition)

The degrees of freedom in parentheses tell the reader which reference distribution you used, so always include them, along with the exact p-value and an effect size. With placeholders for your own results:

"Scores were higher in the treatment group (M = x.xx, SD = x.xx) than in the control group (M = x.xx, SD = x.xx), t(8) = x.xx, p = .xxx, d = x.xx."

"Mean scores differed across the three conditions, F(2, 12) = x.xx, p = .xxx, η² = .xx." If you used a permutation or bootstrap test instead, say so in the method section and give the number of resamples, for example "p values were obtained from 10,000 random permutations of the group labels".

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.