# 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.

Canonical: https://www.licklider.ai/guides/how-to-report-anova-results/
Section: Guides › Writing and reporting
Author: Licklider · Published 2026-11-10 · Last reviewed 2026-11-10 (UTC)

> **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](/guides/which-multiple-comparison-correction/)). *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](/guides/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](https://doi.org/10.7554/eLife.36163)). 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](https://doi.org/10.1016/j.ijnurstu.2014.09.006)).

**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](/guides/welch-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](/guides/welch-anova/): *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"](/guides/p-value-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](/guides/two-way-anova-vs-multiple-t-tests/)). For a 2 × 2 design the interaction has df1 = 1; for a 3 × 2 design, 2.

Example, from the [two-way ANOVA guide](/guides/two-way-anova-vs-multiple-t-tests/): *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.

```r
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](/guides/two-way-anova-vs-multiple-t-tests/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](/guides/how-to-report-anova-results/weight_gain.csv). Every number in the templates above comes from this code; R and Python agree to every digit shown.

```r
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)
```

```python
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](/guides/how-to-report-a-t-test/).
- **ω² 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](https://doi.org/10.3389/fpsyg.2013.00863)), 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](https://doi.org/10.2333/bhmk.40.129)). 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 and `effectsize` reports 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):

```python
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](https://doi.org/10.5334/irsp.198)); 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:

1. Which ANOVA ran: SciPy's `f_oneway` is classical unless `equal_var=False`; R's `aov` is classical, `oneway.test` is Welch's. A paragraph that says "one-way ANOVA" must say which.
2. Both df present and in the right order, F(df1, df2), with Welch's df2 as a decimal.
3. Exact p-values, with "p < 0.001" in place of "p = 0.000" and an exact value in place of "p < 0.05".
4. The post hoc procedure named, and the p-values actually adjusted (pairwise t-tests are not Tukey).
5. Differences with intervals alongside any asterisks.
6. Two-way: an interaction line, which replaces any comparison of two separate p-values.
7. 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](https://doi.org/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](https://doi.org/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](https://doi.org/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](https://doi.org/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](https://doi.org/10.5334/irsp.198)

## Related

- [How to report a t-test](https://www.licklider.ai/guides/how-to-report-a-t-test/)
- [Welch's ANOVA vs one-way ANOVA](https://www.licklider.ai/guides/welch-anova/)
- [Which multiple-comparison correction](https://www.licklider.ai/guides/which-multiple-comparison-correction/)
- [Two-way ANOVA vs separate t-tests](https://www.licklider.ai/guides/two-way-anova-vs-multiple-t-tests/)

## 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.
