Writing and reporting · Report a result
How to report ANOVA results: templates for Welch's, classical and two-way ANOVA
Copy-ready sentences for reporting Welch's and classical one-way ANOVA, post hoc comparisons and two-way interactions, with where each number sits in R and Python output.
On this page
Quick answer. For independent groups with one value per animal (repeated measures need a different model), report, in this order: each group’s mean, SD and n; the overall test with both degrees of freedom and the exact p (or “p < 0.001” below 0.001) — for Welch’s ANOVA, which this site recommends by default because it does not assume equal spreads, the second df is a decimal, as in Welch’s F(2, 13.62) = 16.03, p < 0.001; then the comparisons you planned, each as a difference in units with its simultaneous 95% CI and adjusted p, naming the procedure (Games–Howell after Welch, Tukey after a classical ANOVA). In a two-way design, report the interaction first. Templates below.
In five steps. 1. Find each number in the “where to find” table. 2. Pick the template for the analysis you actually ran. 3. Fill the brackets, keeping the order B − A throughout. 4. Check the consistency list. 5. If you ran a classical ANOVA without a stated reason, report it as run and add Welch’s ANOVA as a labelled sensitivity analysis; do not swap one for the other.
Terms used below. F compares the spread between group means with the spread within groups. Degrees of freedom (df): two numbers for an F test — groups minus 1, then the information in the within-group spread (for Welch’s ANOVA, a decimal). SD: the typical spread of individual values. Post hoc comparisons: tests of pairs of groups after the overall test. Adjusted p: a p-value corrected for the number of comparisons. Simultaneous 95% CIs: intervals that together cover all the true differences 95% of the time, matching the adjusted p-values. Games–Howell and Tukey: pairwise procedures that give both; Games–Howell does not assume equal spreads. ω² (omega squared): the share of variation explained by the groups, estimated with less bias than η² (eta squared). Interaction: whether the effect of one factor depends on the level of the other. k is the number of groups and N the total number of animals. Planned comparisons were chosen before seeing the data. Simple effects are the effect of one factor within each level of the other. Type II and Type III sums of squares are two ways of splitting the variation when group sizes differ; with equal group sizes they give the same F. Partial ω² is ω² for one term of a multi-factor ANOVA. ε² (epsilon squared) is another bias-corrected variance index. Main effect: the effect of one factor averaged over the levels of the other. Sum of squares: the part of the variation that a row of the ANOVA table explains. Holm’s adjustment: a stepwise correction of p-values for several comparisons (which correction to use). Sensitivity analysis: a second analysis of the same data, reported beside the main one and labelled as such, to show whether the conclusion depends on a choice. Welch’s ANOVA: the one-way ANOVA that does not assume equal spreads.
The checklist reviewers apply
| Report | Why |
|---|---|
| Which ANOVA: one-way or two-way, Welch’s or classical, and for two-way whether the interaction was included and which sums of squares (Type II or III) | Different ANOVAs give different F and p for the same data |
| n per group and what n counts (animals, cultures, patients) | The reader judges independence and power from it (what counts as n) |
| Mean and SD per group | Lets the reader see the size and spread of each group |
| F with both df and the exact p, for every term | Lets anyone check that F, df and p agree |
| The post hoc procedure by name, adjusted p and simultaneous CIs | “Post hoc tests showed” says neither what was compared nor how it was corrected |
| An effect size: differences in units, and ω² if the journal asks for a variance-explained index | p alone does not say how large the effect was |
| Software and version | Defaults differ between programs |
The gaps are real: in a study of 328 physiology papers published in June 2017, 213 papers — 95% of those that used ANOVA — did not contain the information needed to tell which ANOVA was run, more than 95% of papers with ANOVA did not report the F statistic or degrees of freedom, 77.8% gave ranges such as “p < 0.05” instead of exact p-values, and 26.7% did not say which post hoc test they used (Weissgerber et al. 2018). The SAMPL reporting guidelines likewise ask for each term’s test statistic, degrees of freedom and P value, how interactions were handled, and the software used (Lang & Altman, SAMPL).
Do this: tick each row against your draft before you send it.
Where to find each number
| Number | R | Python |
|---|---|---|
| Welch’s F, df1, df2, p | oneway.test(y ~ group): F =, num df =, denom df =, p-value = (Welch is the default, var.equal = FALSE) |
statsmodels anova_oneway(y, group): .statistic, .df_num, .df_denom, .pvalue (Welch is the default, use_var="unequal"). SciPy f_oneway(*groups, equal_var=False) gives F and p but no df; take the df from anova_oneway |
| Classical F, df, p | summary(aov(y ~ group)): Df, F value, Pr(>F); two-way: car::Anova(fit, type = 2) |
statsmodels anova_lm(ols_fit, typ=2): df, F, PR(>F) |
| Games–Howell pairs | rstatix::games_howell_test(d, y ~ group): estimate, conf.low, conf.high, p.adj (rounded to 3 significant digits) |
SciPy tukey_hsd(*groups, equal_var=False) (SciPy 1.16 or later): .statistic, .confidence_interval(), .pvalue |
| Tukey pairs | TukeyHSD(fit): diff, lwr, upr, p adj |
SciPy tukey_hsd(*groups) |
| ω² with two-sided 95% CI (one-way, classical F) | effectsize::omega_squared(fit, partial = FALSE, alternative = "two.sided") with fit <- aov(y ~ group, data = d) |
Not built in; compute from the classical F and both df with the function under “Effect sizes” below |
| Partial ω² with two-sided 95% CI (two-way) | effectsize::omega_squared(car::Anova(fit, type = 2), partial = TRUE, alternative = "two.sided") |
Point estimate df1(F − 1) / (df1(F − 1) + N); take the CI from R |
| Interaction contrast, simple effects (two-way) | emmeans + contrast(); confint() for the CIs (code under the two-way template) |
statsmodels: fit one mean per group (ols("y ~ 0 + C(group)")) and test the difference of differences with .t_test() |
effectsize 0.8.6 reports a one-sided interval (upper limit 1) unless you set alternative = "two.sided"; say which one you report. If rstatix is also loaded, call effectsize::omega_squared explicitly, because both packages define eta_squared.
Do this: round once, for the paper, from the output: rstatix already rounds adjusted p to three significant digits, which is enough for reporting; round differences and CI limits to one decimal more than the raw data.
Templates
Replace the bracketed parts and delete any bracketed clause that does not apply. Keep the order: groups, overall test, planned comparisons with intervals. “[B] − [A]” means B minus A; keep the same order in text, tables and figures. Pick the template from what you ran: Welch’s ANOVA → the first; classical ANOVA with equal variances stated in advance → the second; classical ANOVA run without a stated reason → the second template reported as run, without claiming a reason, plus Welch’s ANOVA as a labelled sensitivity analysis (step 5); overall p above 0.05 → the third; two factors → the fourth.
Welch’s one-way ANOVA (the recommended default) with Games–Howell
[Outcome] differed among [groups] (Welch’s ANOVA, F([k − 1], [df2, two decimals]) = [F], p = [p]; [group]: mean [x] [units] (SD [s], n = [n]) for each group). Games–Howell comparisons (simultaneous 95% CIs, adjusted p): [B] − [A] [d] [units] ([lower] to [upper]), p = [p]; …
Example: Weight gain over 14 days differed among doses (Welch’s ANOVA, F(2, 13.62) = 16.03, p < 0.001; vehicle 19.7 g (SD 2.0), low dose 23.6 g (SD 2.6), high dose 26.8 g (SD 3.0); n = 8 mice per group). Games–Howell comparisons (simultaneous 95% CIs, adjusted p): low − vehicle 3.9 g (0.9 to 7.0), p = 0.012; high − vehicle 7.1 g (3.7 to 10.5), p < 0.001; high − low 3.2 g (−0.5 to 6.9), p = 0.091.
Report every planned comparison, including those above 0.05: the high − low interval still allows anything from a small decrease to a 6.9 g increase.
Classical one-way ANOVA (equal variances assumed, with the reason stated in advance) with Tukey
… (classical one-way ANOVA, F([k − 1], [N − k]) = [F], p = [p], ω² = [ω²], two-sided 95% CI [lower] to [upper]). Tukey comparisons (simultaneous 95% CIs, adjusted p): …
Example, the same data: classical one-way ANOVA (equal variances assumed in advance), F(2, 21) = 15.53, p < 0.001, ω² = 0.55, two-sided 95% CI 0.21 to 0.72. Tukey: low − vehicle 3.9 g (0.7 to 7.2), p = 0.016; high − vehicle 7.1 g (3.9 to 10.4), p < 0.001; high − low 3.2 g (−0.03 to 6.43), p = 0.052. The last interval keeps two decimals because one decimal would print its lower limit as −0.0 and hide that it lies just below zero.
The two procedures disagree on the same data (high − low: Tukey p = 0.052, Games–Howell p = 0.091), which is why the choice between them is made before the analysis (Welch’s ANOVA vs one-way ANOVA).
A result that is not significant
[Outcome] did not differ clearly among [groups] (Welch’s ANOVA, F([k − 1], [df2]) = [F], p = [p]). [B] − [A] was [d] [units] (Games–Howell simultaneous 95% CI [lower] to [upper]), adjusted p = [p]; the interval is compatible with differences from [lower] to [upper] [units].
Example, from the Welch’s ANOVA guide: Plasma ALT was 100.0 U/L (SD 6.9, n = 10) after vehicle, 104.0 U/L (SD 3.5, n = 9) after the low dose and 115.2 U/L (SD 11.6, n = 5) after the high dose (Welch’s ANOVA, F(2, 8.65) = 3.62, p = 0.072). High dose − vehicle was 15.2 U/L (Games–Howell simultaneous 95% CI −2.6 to 33.0), adjusted p = 0.086; the interval is compatible with no change and with an increase of up to 33 U/L. Describe what the interval allows in place of “there was no difference” (p = 0.06 or “not significant”).
Two-way ANOVA: the interaction first
We analysed [outcome] using a two-way ANOVA with [factor A] ([levels]), [factor B] ([levels]) and their interaction (n = [n] [animals] per group; Type [II] sums of squares). [Interaction sentence: F([df1], [df2]) = [F], p = [p]; difference in [B] effect between [A levels] [d], 95% CI [lower] to [upper].] [Main effect or simple effects.]
Use Type II (car::Anova(fit, type = 2); statsmodels typ=2) unless your plan names Type III; with equal group sizes every type gives the same F (two-way ANOVA guide). For a 2 × 2 design the interaction has df1 = 1; for a 3 × 2 design, 2.
Example, from the two-way ANOVA guide: The data could not show whether the drug effect differed between genotypes (interaction F(1, 28) = 0.50, p = 0.48; difference in drug effect, WT − KO, 1.0 units, 95% CI −1.9 to 3.9). The drug increased grip strength (main effect of treatment F(1, 28) = 12.50, p = 0.0014; partial ω² = 0.26, two-sided 95% CI 0.04 to 0.50).
If the interaction is what your hypothesis is about, follow it with the simple effects you planned and say whether they were adjusted: drug − vehicle in WT 3.0 units (95% CI 0.95 to 5.05), p = 0.0056; in KO 2.0 units (−0.05 to 4.05), p = 0.055; unadjusted, two planned comparisons. Two comparisons named before the data can be reported unadjusted if you say so; more, or any chosen after seeing the data, need an adjustment such as Holm’s. The test of whether the drug effects differ is the interaction line above.
library(emmeans)
g <- read.csv("grip.csv") # from the two-way ANOVA guide: genotype, treatment, y
g$genotype <- factor(g$genotype, levels = c("WT", "KO"))
g$treatment <- factor(g$treatment, levels = c("vehicle", "drug"))
fit2 <- lm(y ~ genotype * treatment, data = g)
car::Anova(fit2, type = 2) # interaction F(1, 28)
em <- emmeans(fit2, ~ treatment | genotype)
summary(contrast(em, "revpairwise"), infer = TRUE) # simple effects with CIs and p: drug - vehicle in WT and KO
confint(contrast(emmeans(fit2, ~ treatment * genotype), # difference in drug effect, WT - KO
list("drug effect WT - KO" = c(-1, 1, 1, -1))))
The contrast prints 1.0 (95% CI −1.9 to 3.9), the interaction estimate in the example; download grip.csv.
Figure legend
Each point is one [mouse]; lines show mean ± SD (n = [n] per group). Groups were compared with Welch’s ANOVA followed by Games–Howell comparisons; [symbols or brackets] show adjusted p-values for the planned comparisons.
Methods paragraph
[Outcome] was compared among [k] groups with Welch’s one-way ANOVA, which does not assume equal variances, followed by Games–Howell comparisons of [all pairs / each dose with vehicle][, as specified before the data were analysed]. The unit of analysis was the [animal]; technical replicates were averaged within each [animal]. Effect sizes are differences between means with simultaneous 95% confidence intervals. Analyses used [R 4.3.3 / Python 3.13 with SciPy 1.18.1 and statsmodels 0.15.0].
Do this: fill each bracket from the “where to find” table, then check the numbers against the next section.
The worked example
The weight-gain data are constructed (one value per mouse, 8 per dose; the seed was fixed before the data were generated and not chosen for the result): download weight_gain.csv. Every number in the templates above comes from this code; R and Python agree to every digit shown.
library(rstatix) # games_howell_test
library(effectsize) # omega_squared
d <- read.csv("weight_gain.csv") # one row per mouse: mouse, dose, gain
d$dose <- factor(d$dose, levels = c("vehicle", "low", "high"))
aggregate(gain ~ dose, d, function(x) c(n = length(x), mean = mean(x), sd = sd(x)))
oneway.test(gain ~ dose, data = d) # Welch's ANOVA: F, num df, denom df, p
games_howell_test(d, gain ~ dose) # pairs: estimate, conf.low, conf.high, p.adj (rounded)
# Only if equal variances were assumed in advance:
fit <- aov(gain ~ dose, data = d)
summary(fit) # classical F(2, 21)
effectsize::omega_squared(fit, partial = FALSE, alternative = "two.sided")
TukeyHSD(fit)
import pandas as pd
from scipy import stats
from statsmodels.stats.oneway import anova_oneway
d = pd.read_csv("weight_gain.csv") # one row per mouse: mouse, dose, gain
order = ["vehicle", "low", "high"]
groups = [d.loc[d.dose == k, "gain"] for k in order]
print(d.groupby("dose")["gain"].agg(["count", "mean", "std"]).loc[order])
w = anova_oneway(d["gain"], d["dose"], use_var="unequal") # Welch's ANOVA
print(f"F({w.df_num:.0f}, {w.df_denom:.2f}) = {w.statistic:.2f}, p = {w.pvalue:.2g}")
gh = stats.tukey_hsd(*groups, equal_var=False) # Games-Howell (SciPy >= 1.16)
ci = gh.confidence_interval(confidence_level=0.95)
for i, j in [(0, 1), (0, 2), (1, 2)]:
print(f"{order[j]} - {order[i]}: {gh.statistic[j, i]:.2f} "
f"({ci.low[j, i]:.2f} to {ci.high[j, i]:.2f}), adjusted p = {gh.pvalue[j, i]:.2g}")
The Python block prints F(2, 13.62) = 16.03, p = 0.00026 and the three Games–Howell lines; R prints the same values. The high − vehicle difference is exactly 7.125 g; both outputs above print 7.12, while rounding half up gives 7.13, so one decimal (7.1 g) avoids the question.
Do this: run the same lines on your own file, replacing the column names.
Effect sizes: which one?
- Differences in units with simultaneous CIs are the primary effect sizes for comparisons of groups: a reader can judge whether 3.9 g matters biologically, and the intervals carry the multiplicity correction. This matches the t-test reporting guide.
- ω² rather than η² when a journal asks for the share of variance explained. η² is an uncorrected estimate that describes the sample, ω² corrects most of its bias (Lakens 2013), and in a 1,000,000-replication simulation of one-way ANOVA η² stayed noticeably biased at small sample sizes while ω² had the smallest overall error and a third index, ε², had the least bias (Okada 2013). In the example, η² = 0.60 and ω² = 0.55.
- Partial ω² for each term of a two-way ANOVA, with its CI: df1(F − 1) / (df1(F − 1) + N), so 1 × 11.5 / (11.5 + 32) = 0.26 for the treatment term (F = 12.50, 32 mice); the CI comes from R’s
effectsize. When F is below 1, as for the interaction in the two-way example (F = 0.50), the estimate is negative andeffectsizereports it as 0; write “ω² = 0 (estimate truncated at zero)” rather than leaving it out.
For a one-way ANOVA, this Python function reproduces effectsize’s ω² and two-sided interval from the classical F and its df (for one-way designs ω² and partial ω² are the same):
from scipy import optimize, stats
def omega2_ci(F, df1, df2, level=0.95):
"""omega^2 with a two-sided CI, computed as R effectsize 0.8.6 does."""
om = max(0.0, (F - 1) * df1 / (F * df1 + df2 + 1))
f = (om / df1) / ((1 - om) / df2) # effectsize's step: treat omega^2 as an eta^2-type ratio and convert it to an F
def ncp(target): # noncentrality giving cdf = target
g = lambda l: stats.ncf.cdf(f, df1, df2, l) - target if l > 0 else stats.f.cdf(f, df1, df2) - target
if g(0) < 0:
return 0.0
hi = 1.0
while g(hi) > 0:
hi *= 2
return optimize.brentq(g, 0, hi)
a = (1 - level) / 2
to_es = lambda lam: lam / (lam + df2) # effectsize's back-conversion; this reproduces the limits it prints
return om, to_es(ncp(1 - a)), to_es(ncp(a))
print("omega2 = %.2f, two-sided 95%% CI %.2f to %.2f" % omega2_ci(15.53004, 2, 21))
It prints omega2 = 0.55, two-sided 95% CI 0.21 to 0.72, the same as R.
Do this: report the differences with intervals; add ω² with its two-sided 95% CI if the journal or reviewer asks for a standardised index.
Numbers reviewers check for consistency
- df must match the design. Classical one-way: k − 1 and N − k (3 groups of 8: 2 and 21). Two-way with interaction, 2 × 2: 1 and N − 4 for every term (32 mice: 28). Welch’s F keeps df1 = k − 1 and estimates a decimal df2 from the group sizes and spreads (Delacre et al. 2019); in the example 13.62 against 21 for the classical test.
- F, df and p must agree. Reviewers and automated checkers recompute p from F and both df.
- Simultaneous CIs and adjusted p must agree. A Tukey or Games–Howell interval excludes zero exactly when the adjusted p from the same procedure is below 0.05 (high − low: Tukey interval −0.03 to 6.43 and p = 0.052).
- Adjusted or unadjusted, every time. Label each p-value and interval; mixing them in one table is the most common inconsistency.
- Journal style. Leading zeros, decimal places and italics vary between journals; follow the journal’s instructions and keep exact p-values, writing “p < 0.001” only below 0.001.
Do this: recompute one F and one adjusted p from your reported numbers before submission.
Check what an AI assistant wrote
AI-drafted results paragraphs can contain each of these; check them against your output:
- Which ANOVA ran: SciPy’s
f_onewayis classical unlessequal_var=False; R’saovis classical,oneway.testis Welch’s. A paragraph that says “one-way ANOVA” must say which. - Both df present and in the right order, F(df1, df2), with Welch’s df2 as a decimal.
- Exact p-values, with “p < 0.001” in place of “p = 0.000” and an exact value in place of “p < 0.05”.
- The post hoc procedure named, and the p-values actually adjusted (pairwise t-tests are not Tukey).
- Differences with intervals alongside any asterisks.
- Two-way: an interaction line, which replaces any comparison of two separate p-values.
- A non-significant result described by its interval.
Do this: paste the paragraph next to the raw output and tick each number.
Rewrite these phrases
Each row turns a phrase reviewers query into one they accept.
| Instead of | Write |
|---|---|
| “Data were analysed by ANOVA” | “Welch’s one-way ANOVA followed by Games–Howell comparisons” |
| “F = 16.03, p < 0.05” | “Welch’s F(2, 13.62) = 16.03, p < 0.001” |
| “Post hoc tests showed significant differences” | Each planned difference with its simultaneous 95% CI and adjusted p, naming the procedure |
| “The drug worked in WT but not in KO” | The interaction F, p and the difference in effects with its CI |
| “There was no difference between doses (p = 0.07)” | The difference with its interval and what the interval allows |
| “η² = 0.60” without a CI | “ω² = 0.55, two-sided 95% CI 0.21 to 0.72” |
References
- Weissgerber TL, Garcia-Valencia O, Garovic VD, Milic NM, Winham SJ (2018). Why we need to report more than “Data were Analyzed by t-tests or ANOVA”. eLife 7:e36163. doi:10.7554/eLife.36163
- Lang TA, Altman DG (2015). Basic statistical reporting for articles published in biomedical journals: the “Statistical Analyses and Methods in the Published Literature” or the SAMPL Guidelines. International Journal of Nursing Studies 52:5–9. doi:10.1016/j.ijnurstu.2014.09.006
- Lakens D (2013). Calculating and reporting effect sizes to facilitate cumulative science: a practical primer for t-tests and ANOVAs. Frontiers in Psychology 4:863. doi:10.3389/fpsyg.2013.00863
- Okada K (2013). Is omega squared less biased? A comparison of three major effect size indices in one-way ANOVA. Behaviormetrika 40(2):129–147. doi:10.2333/bhmk.40.129 (abstract)
- Delacre M, Leys C, Mora YL, Lakens D (2019). Taking parametric assumptions seriously: arguments for the use of Welch’s F-test instead of the classical F-test in one-way ANOVA. International Review of Social Psychology 32(1):13. doi:10.5334/irsp.198
How this page was made
- Written and published by Licklider. Reporting recommendations cite a peer-reviewed meta-research study, the SAMPL reporting guidelines and peer-reviewed methods papers listed under References; cited items were checked against the source text, or against the abstract where stated.
- Every number in the templates comes from stored code (Python with SciPy 1.18.1 and statsmodels 0.15.0; R 4.3.3 with car, emmeans, effectsize and rstatix), and the two languages agree to every digit shown. The example data can be downloaded from this page.
- Separate review passes checked the statistics, the citations and readability before publication.