Skip to content

How to run a one-way ANOVA in SciPy (with Code)

The Short Answer (TL;DR)

Call scipy.stats.f_oneway with the independent groups as separate arguments and equal_var=True for the classical equal-variance ANOVA. Report the F statistic and its p-value as an omnibus comparison of population means, without treating it as a test of every pair.

from scipy import stats
a, b, c = [4.8, 5.1, 5.4, 4.9, 5.6, 5.2], [6.0, 6.3, 6.2, 5.9, 6.4, 6.1], [5.4, 5.7, 5.5, 5.8, 5.6, 5.9]
result = stats.f_oneway(a, b, c, equal_var=True, nan_policy="raise"); print(result)

Detailed Implementation

This example uses synthetic data and does not need a network connection. Install the recorded package versions in a separate Python environment before running it.

python -m pip install numpy==2.5.4 scipy==1.18.1
from pathlib import Path
import numpy as np
from scipy import stats

# Three independent synthetic groups measured on the same scale.
groups = [
    np.array([4.8, 5.1, 5.4, 4.9, 5.6, 5.2]),
    np.array([6.0, 6.3, 6.2, 5.9, 6.4, 6.1]),
    np.array([5.4, 5.7, 5.5, 5.8, 5.6, 5.9]),
]
if any(g.ndim != 1 or g.size < 2 or not np.isfinite(g).all() for g in groups):
    raise ValueError("Use at least two finite observations per group.")
if sum(np.sum((g-g.mean())**2) for g in groups) == 0:
    raise ValueError("The pooled within-group variance must be nonzero.")
result = stats.f_oneway(*groups, equal_var=True, nan_policy="raise")
group_count = len(groups)
total_size = sum(g.size for g in groups)
df_between, df_within = group_count-1, total_size-group_count
means = ", ".join(f"{g.mean():.6f}" for g in groups)
report = (
    f"Group sizes: {[g.size for g in groups]}\n"
    f"Group means: {means}\n"
    f"F statistic: {result.statistic:.6f}\n"
    f"Numerator and denominator df: {df_between}, {df_within}\n"
    f"Omnibus p-value: {result.pvalue:.8g}\n"
)
print(report)
Path("one-way-anova-output.txt").write_text(report, encoding="utf-8")

The text reports three groups of six observations, eighteen observations overall, and numerator and denominator degrees of freedom 2 and 15. The F statistic and p-value agree with an independent sums-of-squares calculation. Reordering the groups or applying a common nonzero linear scale and offset preserves this classical omnibus result.

Which assumptions belong to the classical test?

The model uses independent observations, normally distributed errors within each group and a common population variance. These are properties of the design and populations; similar observed sample variances do not prove them. Inspect distributions, outliers and the sampling process. This example sets equal_var=True explicitly rather than claiming those assumptions follow from the synthetic values.

What if variances differ or observations repeat?

SciPy also offers Welch ANOVA through equal_var=False, which changes the variance model and reference calculation. Repeated measurements of the same units require a suitable repeated-measures or mixed-model analysis instead of this independent-groups calculation. Dropping missing measurements silently can change group composition; the example rejects them for an explicit decision.

Which groups differ when the p-value is small?

The omnibus null states that every population mean is equal. Evidence against it does not identify a particular pair and does not mean all pairs differ. Prespecify suitable contrasts or a multiple-comparison procedure for follow-up questions, and report group estimates and practical magnitudes. The p-value is not a probability that the null hypothesis is true or evidence of causation by itself.

Mathematical Context

For k groups and N total observations, the between-group sum of squares adds n_j times the squared distance from group mean j to the grand mean. The within-group sum of squares adds the squared residuals around each observation's own group mean. Their degrees of freedom are k minus one and N minus k. Dividing each sum by its degrees of freedom produces the two mean squares, whose ratio is F. Under the independent normal equal-variance null model, F follows an F distribution with those numerator and denominator degrees of freedom. The p-value is the upper-tail probability beyond the observed F statistic. The example checks that the total corrected sum of squares equals the sum of the between and within components, so the arithmetic decomposition is explicit.

\[ F=\frac{SS_{\mathrm{between}}/(k-1)}{SS_{\mathrm{within}}/(N-k)} \]

Reproducibility

Executed with Python 3.12.14; numpy 2.5.4, scipy 1.18.1. Automated checks confirm the stated output properties.

Primary documentation

Related guides