Three-Way ANOVA Calculator (3 Factor Calculator)
Test three factors and all their interactions in one run. Paste long-format data or upload a CSV or Excel file, and this free three-way ANOVA calculator returns the full ANOVA table with Type III or Type I sums of squares, F, p, partial eta squared, omega squared, cell and marginal means, Bonferroni post hoc tests, Shapiro-Wilk and Levene checks, interaction plots, and copy-ready APA sentences.
Last updated: 25 September 2026 · Reviewed by the StatsUnlock statistics team · Results verified against R car::Anova and Python statsmodels
⚡ 0. Quick Answer
A three-way ANOVA calculator tests whether three categorical factors, and their interactions, change the mean of one numeric outcome.
It splits the total variation into seven effects: three main effects (A, B, C), three two-way interactions (A×B, A×C, B×C) and one three-way interaction (A×B×C). Each effect gets its own F test: F = MSeffect ÷ MSerror. In the worked example below, Drug gives F(1, 24) = 234.26, p < .001, partial η² = .91. This page works as a 3 factor calculator for any design from 2×2×2 upward.
Rule of thumb: read the three-way interaction first. If A×B×C is significant, interpret the two-way interactions separately at each level of the third factor. If it is not, move down to the two-way interactions, then to the main effects.
Key takeaways
- A three-way ANOVA calculator (also called a 3 factor calculator or factorial ANOVA calculator) returns 7 F tests from one dataset: 3 main effects, 3 two-way interactions and 1 three-way interaction.
- Error degrees of freedom are N − a·b·c. A 2×2×2 design with 4 per cell has 32 − 8 = 24 error df.
- With equal cell sizes, Type I and Type III sums of squares are identical. With unequal cell sizes they differ, and Type III is the SPSS and journal default.
- Report partial η² with every F. Cohen's bands are about .01 small, .06 medium, .14 large.
- You need at least 2 observations in every factor combination, and 10 or more per cell gives you reasonable power for interactions.
📚 1. What Is a Three-Way ANOVA?
A three-way ANOVA is an analysis of variance with three independent categorical factors and one continuous outcome. It asks two kinds of question at once. First, does each factor change the mean on its own? Second, does the effect of one factor depend on the level of another factor? The second kind of question is called an interaction, and it is usually the reason people run a three-way design instead of three separate one-way tests.
The seven effects it tests:
- Main effect of A, for example: does blood pressure differ between Drug and Placebo, averaged over sex and age?
- Main effect of B and main effect of C, the same question for the other two factors.
- A×B, A×C, B×C, the two-way interactions. A×C asks: is the drug effect bigger in older patients than in younger ones?
- A×B×C, the three-way interaction. It asks: does the A×C pattern itself change between males and females?
A worked one-liner: in a 2×2×2 blood pressure trial with 4 patients per cell (N = 32), the error mean square is 7.8125 and the drug mean square is 1830.125, so F = 1830.125 ÷ 7.8125 = 234.26 on 1 and 24 degrees of freedom.
1.1 Is a three-way ANOVA a "factor calculator"?
Yes, in the statistics sense. In ANOVA, a factor is a categorical variable such as treatment, sex or season, and each factor has levels. This page is a 3 factor calculator for experiments. It is not a tool for finding the factors of a number such as 36 (1, 2, 3, 4, 6, 9, 12, 18, 36). That is a different, arithmetic meaning of the same word.
Who uses it: clinical researchers (drug by sex by age group), agronomists (fertilizer by irrigation by crop variety), psychologists (condition by task by time), ecologists (light by soil by water in greenhouse trials), and marketers (channel by region by plan).
| Design | Factors | F tests | Use it when |
|---|---|---|---|
| One-way ANOVA | 1 | 1 | You compare the means of 3 or more groups on one factor |
| Two-way ANOVA | 2 | 3 | You want two main effects and their single interaction |
| Three-way ANOVA | 3 | 7 | You suspect effects depend on a combination of three factors |
| Three separate one-way tests | 3 | 3 | Never as a substitute: it ignores interactions and inflates Type I error |
| MANOVA | 1 or more | varies | You have two or more correlated outcome variables |
🧮 2. Set Up Your Data
Type one observation per row. Column headers are editable and become your factor names. Empty rows are ignored.
📊 3. Results
🧠 4. Interpretation of Results, In Detail
4.1 Your result in one paragraph
Across N = ___ observations in a ___ design, ___ of the seven effects were significant at α = ___. The largest effect was ___ (partial η² = ___).
4.2 Read the table from the bottom up
The ANOVA table lists main effects first, but you should read it in the opposite order. Start with the three-way interaction, then the two-way interactions, then the main effects. The reason is simple. A higher-order interaction changes the meaning of every lower-order effect it contains. If Drug × Age is significant, the phrase "the drug lowers blood pressure by 15 mmHg" is only an average. The true effect might be 12 mmHg in young patients and 18 mmHg in old ones.
4.3 The three-way interaction
The three-way interaction was F(___, ___) = ___, p = ___.
A significant A×B×C means the two-way interaction between two factors is different at different levels of the third factor. To follow it up, split the data by the third factor and run a two-way ANOVA inside each level, using the pooled error term from this analysis. A non-significant three-way interaction lets you interpret the two-way interactions as consistent patterns.
4.4 Two-way interactions and simple effects
A significant two-way interaction, say A×C, means the lines on the interaction plot are not parallel. The follow-up is a simple effects analysis: test the effect of A at each level of C. In the worked example, the drug lowers blood pressure by 12.25 mmHg in young patients and by 18.00 mmHg in old patients. Both simple effects are significant, but the difference between them is what makes the interaction significant.
Look at Chart 1. Lines that cross indicate a disordinal interaction, where the ranking of groups flips. Lines that diverge without crossing indicate an ordinal interaction, where one group is always higher but by different amounts.
4.5 Main effects, and when to trust them
Main effects: ___.
A main effect is the difference between marginal means, averaged over the other two factors. When a factor is not involved in any significant interaction, its main effect is a clean summary. When it is involved in one, report the main effect but describe it as an average and point readers to the simple effects.
4.6 Effect sizes: partial eta squared, eta squared and omega squared
The p-value tells you whether an effect is likely to be real. The effect size tells you how big it is. Partial η² is SSeffect divided by (SSeffect + SSerror), so each effect is judged against error only. This is what SPSS reports. η² divides by the total SS, so all seven values add to at most 1. Partial ω² corrects the upward bias of partial η² and is the better estimate for small samples. With large samples all three give similar answers.
Cohen (1988) bands for partial η² are about .01 small, .06 medium and .14 large. Treat these as a rough guide. In a tightly controlled lab study, .14 may be ordinary. In a field study with noisy measures, .03 may matter.
4.7 Assumption checks for your data
Shapiro-Wilk on the residuals: W = ___, p = ___. Levene's test (median-centred): F(___, ___) = ___, p = ___.
ANOVA is fairly robust to mild non-normality when cells are equal and n is 15 or more per cell. It is much less robust to unequal variances combined with unequal cell sizes. If the largest cell SD is more than about twice the smallest and the cell sizes also differ, the F tests can be badly wrong in either direction.
4.8 Post hoc tests for factors with three or more levels
A significant main effect for a two-level factor needs no post hoc test, because there is only one comparison. For a factor with three or more levels, the F test tells you that some means differ but not which ones. This calculator runs Bonferroni-adjusted pairwise comparisons on the estimated marginal means, using the pooled error term. The adjusted p multiplies each raw p by the number of comparisons.
4.9 Decision guide
| If you see | Then |
|---|---|
| A×B×C significant | Split by one factor and run two-way analyses inside each level. Do not interpret main effects on their own. |
| Only one two-way interaction significant | Run simple effects for that pair. Interpret the third factor's main effect normally. |
| No interactions significant | Interpret the three main effects directly, with post hoc tests for factors with 3+ levels. |
| Nothing significant, small partial η² | Report a null result with effect sizes and confidence in the design's power. |
| Assumptions fail badly | Transform the outcome (log for right skew), or use a robust or aligned rank transform ANOVA. |
4.10 Common mistakes this calculator prevents
- Running three one-way ANOVAs instead of one three-way ANOVA, which misses every interaction.
- Using Type I sums of squares on unbalanced data without saying so. The setting is explicit here, and the output states which type was used.
- Reporting eta squared and calling it partial eta squared. Both are labelled clearly in the table.
- Forgetting a factor combination with no data. The calculator stops and names the problem instead of returning a wrong answer.
- Testing normality on the raw outcome instead of the residuals. The Shapiro-Wilk test here runs on the residuals, as it should.
✍ 5. How to Write Your Results in Research
5.1 APA 7th edition
📌 Key conventions for this style
- Write F in italics with both df in brackets: F(1, 24) = 8.46.
- Report exact p to three decimals, and p < .001 below that. No leading zero on p or η².
- Give an effect size for every F, including non-significant ones.
- Name the sums of squares type if the design is unbalanced.
5.2 Thesis or dissertation
📌 Key conventions for this style
- State the design, the cell sizes and whether the design is balanced.
- Describe how each assumption was checked and the result.
- Put the full ANOVA table in the text or an appendix.
- Name the software and version.
5.3 Plain-language summary
📌 Key conventions for this style
- No symbols: no F, p, df or η².
- Describe an interaction as "the effect of X was bigger when Y".
- Give real means with units.
- Say how many people or units were measured.
5.4 Structured abstract or conference poster
📌 Key conventions for this style
- Keep to the one or two effects that answer your research question.
- Give F, p and partial η² in one compact bracket.
- Put the interaction plot on the poster, not the full table.
- Abstract word limits usually leave room for two statistics at most.
5.5 Replication or pre-registration
📌 Key conventions for this style
- Fix the alpha level, SS type, and follow-up plan before seeing data.
- State the smallest effect size of interest and the planned n per cell.
- Say what you will do if an assumption fails.
- Record every exclusion rule in advance.
5.6 Reporting checklist
- Design stated as a × b × c with factor names and levels.
- Cell sizes, cell means and SDs in a table.
- All seven F tests with both df, exact p and partial η².
- Follow-up tests for every significant interaction.
- Assumption checks and the SS type.
- An interaction plot with error bars that are labelled as SE or 95% CI.
∑ 6. Formulas Used
The formulas below are written for a balanced design with a levels of A, b levels of B, c levels of C and n observations per cell. For unbalanced designs, the calculator gets the same quantities by fitting an effect-coded regression model and comparing the full model with the model that drops each term (Type III) or adds the terms in order (Type I).
🔀 7. Three-Way ANOVA Calculation Flowchart, Step by Step
This flowchart shows how a three-way ANOVA is calculated, step by step, from raw rows to seven F tests. Every box uses the same worked example as the Excel, R and Python sections: systolic blood pressure for 32 patients in a Drug × Sex × Age trial, 4 patients per cell.
Check any box yourself: the Excel sheet in Section 9, the R script in Section 10 and the Python script in Section 11 all return SSerror = 187.5 and F(1, 24) = 8.464 for Drug × Age on this dataset.
📝 8. How to Use This Calculator
- Arrange your data in long format. One row per observation, four columns: Factor A, Factor B, Factor C and the numeric outcome. The header row names the factors, for example
Drug,Sex,Age,SystolicBP. - Paste it into the box. Commas, tabs and semicolons all work. A block copied from Excel or Google Sheets pastes as tab-separated text and is read correctly.
- Or upload a file. On the Upload tab choose a CSV or Excel file, pick the sheet, then click one column for each role: Factor A, Factor B, Factor C and Outcome. Press Load selected columns.
- Or type it in. The Manual Entry tab gives you a four-column table with editable headers. Add rows as needed, then press Use this table.
- Try a sample dataset. Ten built-in datasets cover a clean 2×2×2 trial, 3-level factors with post hoc tests, a real three-way interaction, a null result, a small sample, an unbalanced survey and a skewed dataset that fails the assumptions.
- Choose the significance level. 0.05 is standard. Use 0.01 when you run many analyses or need stronger evidence.
- Choose the sums of squares type. Keep Type III to match SPSS and most journals. Choose Type I to match R's
aov()andanova(). For equal cell sizes both give the same answer. - Press Run Three-Way ANOVA. Nothing is computed until you do. Changing the data or a setting clears old results so you never read stale numbers.
- Read from the bottom of the table up. Check the three-way interaction first, then the two-way interactions, then the main effects, and look at Chart 1 to see the pattern behind any interaction.
- Copy your write-up and export. Section 5 fills five reporting styles with your numbers. Download Doc gives a text report and Download PDF gives a print-ready copy.
📈 9. How to Run a Three-Way ANOVA in Excel
Excel's Analysis ToolPak has "Anova: Single Factor" and "Anova: Two-Factor With Replication", but no three-way ANOVA. You can still get the exact answer with ordinary formulas, because every sum of squares is built from averages. This section builds the full table for the blood pressure example, with the spreadsheet shown at each stage.
AVERAGEIFS gives you the means, DEVSQ and SUMSQ give you the squares.9.1 The functions you need
| You want | Excel function | Note |
|---|---|---|
| Grand mean | =AVERAGE(D2:D33) | Outcome in column D |
| Total SS | =DEVSQ(D2:D33) | Sum of squared deviations from the mean |
| A marginal mean | =AVERAGEIFS(D$2:D$33,A$2:A$33,"Drug") | Mean of every row where A = Drug |
| Cell mean | =AVERAGEIFS(D$2:D$33,A$2:A$33,A2,B$2:B$33,B2,C$2:C$33,C2) | Mean of the row's own cell |
| Error SS | =SUMSQ(E2:E33) | E holds each value minus its cell mean |
| p-value | =F.DIST.RT(F, df1, df2) | Right tail of the F distribution |
| Critical F | =F.INV.RT(0.05, df1, df2) | 4.26 for (1, 24) |
| Check for empty cells | =COUNTIFS(A:A,"Drug",B:B,"Male",C:C,"Young") | Must be at least 1 for every combination |
9.2 Step by step
1 Lay the data out in long format
Put each factor in its own column and the outcome in column D, one patient per row. Row 1 holds the names.
The 32 rows sit in A2:D33. The first eight rows are shown: the Drug, Male, Young cell holds 120, 121, 119 and 117.
2 Grand mean, total SS and error SS
=AVERAGE(D2:D33)=DEVSQ(D2:D33)=D2-AVERAGEIFS(D$2:D$33,A$2:A$33,A2,B$2:B$33,B2,C$2:C$33,C2)Fill the third formula down column E, then total the squares with =SUMSQ(E2:E33).
Grand mean 129.75, SStotal 3174, SSerror 187.5.
3 Main effects from the marginal means
=16*((H2-$G$1)^2+(H3-$G$1)^2)Each Drug level holds 16 patients (n × b × c = 4 × 2 × 2). Put the two Drug means in H2:H3 with AVERAGEIFS and the grand mean in G1.
Drug means 122.1875 and 137.3125 give SSDrug = 1830.125. Sex gives 72 and Age gives 1012.5.
4 Interactions by subtraction
=8*SUMPRODUCT((K2:K5-$G$1)^2)-SSdrug-SSageK2:K5 hold the four Drug × Age means (each averages 8 patients). Subtracting both main effects leaves the interaction. The three-way SS is whatever is left of SScells = SStotal − SSerror after all six other terms.
Drug × Age means 118, 126.375, 130.25 and 144.375 give 2908.75 − 1830.125 − 1012.5 = 66.125.
5 Build the ANOVA table
=B2/C2=D2/$D$9=F.DIST.RT(E2,C2,$C$9)=B2/(B2+$B$9)MS is SS over df, F is MS over MSerror, then the p-value and partial eta squared.
Drug × Age: F = 66.125 ÷ 7.8125 = 8.464, p = 0.0077. The three-way term: F = 0.144, p = 0.708.
9.3 The awkward cases: unequal cell sizes and 3+ levels
The averaging method above is exact only for a balanced design, where every cell has the same n. With unequal n, the subtraction method mixes up the effects and gives neither Type I nor Type III answers. You have two honest options in Excel.
- Regression with effect coding. Code each two-level factor as +1 and −1, build product columns for every interaction, and run Data → Data Analysis → Regression (ToolPak must be enabled first via File → Options → Add-ins). Refit without each term's columns; the rise in residual SS is that term's Type III SS.
- Use this calculator or R or Python for the final numbers, and use Excel only for descriptive tables and charts.
For a factor with 3 levels, the main effect SS formula is the same, with three squared terms instead of two, and df = 2. Effect coding needs 2 columns for that factor instead of 1.
9.4 Why your Excel answer might differ from R, Python or SPSS
| Situation | What happens | Fix |
|---|---|---|
| Unequal cell sizes | R aov() gives Type I (sequential) SS, SPSS and car::Anova(type = 3) give Type III | Choose the matching SS type here. For balanced data they agree |
| Python Type III looks wrong | statsmodels uses treatment contrasts by default, which break Type III tests | Write C(drug, Sum) in the formula, as in Section 11 |
| R Type III looks wrong | R's default contr.treatment has the same problem | Set options(contrasts = c("contr.sum","contr.poly")) before fitting |
| Levene differs from SPSS | SPSS centres on the mean, this page and R's car default centre on the median | Both are valid. The median version is more robust to skew |
| Excel SS for interactions is negative or odd | Unbalanced data used with the averaging method | Use the regression method in 9.3 |
| p shows as 0 in Excel | The value is below display precision | Report p < .001 |
| Tool | Three-way ANOVA command | Default SS type |
|---|---|---|
| Excel | Formulas above, or Regression in the ToolPak | None built in |
| R base | aov(y ~ A * B * C, data) | Type I |
| R car | car::Anova(lm(y ~ A*B*C), type = 3) | Type II unless type = 3 |
| Python | anova_lm(ols("y ~ C(A,Sum)*C(B,Sum)*C(C,Sum)"), typ=3) | Type I unless typ set |
| SPSS | Analyze → General Linear Model → Univariate | Type III |
| jamovi / JASP | ANOVA module, three fixed factors | Type III |
9.5 Charting the interaction in Excel
- Build a small table of cell means: Drug levels down the rows, one column per Age level. Make one table per Sex level.
- Select one table and choose Insert → Charts → Line with Markers.
- Repeat for the second Sex level and place the charts side by side with the same y-axis range (Format Axis → Bounds).
- Add error bars with + → Error Bars → More Options → Custom and point at your SE cells. Say in the caption that the bars are SE.
Chart 1 in Section 3 draws this plot for you, with every B and C combination as its own line.
9.6 Excel errors you will probably hit
| Excel shows | Why | Fix |
|---|---|---|
#DIV/0! from AVERAGEIFS | No rows match that combination: an empty cell or a typo in a level name | Check with COUNTIFS. "Male " with a trailing space is a different level |
#NUM! from F.DIST.RT | A negative F or df of 0 | Check dferror = N − abc is at least 1 |
#VALUE! | Text in the outcome column | Remove units and convert text to numbers |
| SS do not add up to SStotal | Unequal cell sizes | Expected for unbalanced data. Use the regression method |
| F is huge and p is 0 | SSerror is near zero because every value in a cell is identical | Check the data were not copied down by mistake |
| Green triangles in cells | Numbers stored as text | Select the column, click the warning icon, Convert to Number |
📈 10. How to Run a Three-Way ANOVA in R
R fits a three-way ANOVA in one line, aov(y ~ drug * sex * age), but that line gives Type I sums of squares. The script below uses base R only and builds the Type III table directly, checks both assumptions, prints a plain-English verdict and saves one publication-ready interaction plot at 300 dpi.
Rscript. Change only the bp <- data.frame(...) block, or swap in the read.csv() line.10.1 The complete script
R, base only, saves three_way_anova_plot.png
# ============================================================
# Three-way ANOVA with Type III sums of squares and one figure
# StatsUnlock - https://statsunlock.com/three-way-anova-calculator/
# Base R only. No packages to install.
# ============================================================
set.seed(2026) # nothing here is random, but keep runs reproducible
# ---- 1. Your data (long format: one row per patient) -------
# To use a file instead:
# bp <- read.csv("my_data.csv", stringsAsFactors = TRUE)
# bp <- bp[!is.na(bp$y), ] # drop missing outcomes
bp <- data.frame(
drug = rep(c("Drug", "Placebo"), each = 16),
sex = rep(rep(c("Male", "Female"), each = 8), times = 2),
age = rep(rep(c("Young", "Old"), each = 4), times = 4),
y = c(120, 121, 119, 117, 126, 125, 128, 133,
115, 115, 119, 118, 125, 122, 125, 127,
131, 134, 129, 131, 143, 148, 145, 150,
133, 131, 123, 130, 144, 144, 139, 142)
)
bp$drug <- factor(bp$drug, levels = c("Drug", "Placebo"))
bp$sex <- factor(bp$sex, levels = c("Male", "Female"))
bp$age <- factor(bp$age, levels = c("Young", "Old"))
bp <- bp[!is.na(bp$y), ]
# ---- 2. Fit with sum-to-zero contrasts ---------------------
# Sum contrasts are REQUIRED for correct Type III tests.
options(contrasts = c("contr.sum", "contr.poly"))
fit <- lm(y ~ drug * sex * age, data = bp)
# ---- 3. Type III table: drop each term's columns in turn ----
X <- model.matrix(fit)
assign <- attr(X, "assign")
labs <- attr(terms(fit), "term.labels")
sse <- sum(resid(fit)^2)
dfe <- df.residual(fit)
mse <- sse / dfe
tab <- do.call(rbind, lapply(seq_along(labs), function(k) {
reduced <- lm.fit(X[, assign != k, drop = FALSE], bp$y)
ss <- sum(reduced$residuals^2) - sse
df1 <- sum(assign == k)
F <- (ss / df1) / mse
data.frame(term = labs[k], SS = ss, df = df1, F = F,
p = pf(F, df1, dfe, lower.tail = FALSE),
partial_eta_sq = ss / (ss + sse))
}))
tab <- rbind(tab, data.frame(term = "Residuals", SS = sse, df = dfe,
F = NA, p = NA, partial_eta_sq = NA))
print(tab, digits = 4, row.names = FALSE)
# ---- 4. Assumption checks ----------------------------------
sw <- shapiro.test(resid(fit))
cell <- interaction(bp$drug, bp$sex, bp$age)
dev <- abs(bp$y - ave(bp$y, cell, FUN = median))
lev <- anova(lm(dev ~ cell)) # Levene, median-centred
cat(sprintf("\nShapiro-Wilk on residuals: W = %.4f, p = %.4f\n", sw$statistic, sw$p.value))
cat(sprintf("Levene (median): F(%d, %d) = %.4f, p = %.4f\n",
lev$Df[1], lev$Df[2], lev$`F value`[1], lev$`Pr(>F)`[1]))
# ---- 5. Plain-English verdict ------------------------------
alpha <- 0.05
cat("\nVerdict:\n")
for (i in seq_along(labs)) {
cat(sprintf(" %-16s F(%d, %d) = %6.2f, p = %.4f, partial eta^2 = %.3f -> %s\n",
gsub(":", " x ", labs[i]), tab$df[i], dfe, tab$F[i], tab$p[i],
tab$partial_eta_sq[i],
ifelse(tab$p[i] < alpha, "SIGNIFICANT", "not significant")))
}
# ---- 6. One figure: interaction plot, one panel per sex -----
png("three_way_anova_plot.png", width = 2700, height = 1260, res = 300)
par(mfrow = c(1, 2), mar = c(4.5, 4.5, 3, 1))
for (s in levels(bp$sex)) {
d <- bp[bp$sex == s, ]
interaction.plot(d$drug, d$age, d$y, type = "b", pch = 19, lwd = 2,
col = c("#2563eb", "#c2410c"), lty = 1,
ylim = range(tapply(bp$y, cell, mean)) + c(-3, 3),
xlab = "Treatment", ylab = "Systolic BP (mmHg)",
trace.label = "Age", main = s, legend = (s == "Female"))
grid(col = "grey85")
}
invisible(dev.off())
cat("\nSaved three_way_anova_plot.png at 300 dpi\n")
10.2 What each part does
| Line | What it does |
|---|---|
| factor(..., levels = ) | Fixes the level order so tables and plots read Drug before Placebo. |
| options(contrasts = ...) | Switches to sum-to-zero coding. Without this, Type III tests are wrong. |
| lm(y ~ drug * sex * age) | The star expands to all 3 main effects, 3 two-way and 1 three-way interaction. |
| attr(X, "assign") | Tells you which model-matrix columns belong to which term. |
| lm.fit(X[, assign != k]) | Refits without term k. The rise in residual SS is the Type III SS for k. |
| pf(F, df1, dfe, lower.tail = FALSE) | Right-tail p-value from the F distribution. |
| shapiro.test(resid(fit)) | Normality test on residuals, not on the raw outcome. |
| ave(bp$y, cell, FUN = median) | Cell medians for the Brown-Forsythe version of Levene's test. |
| interaction.plot(...) | Draws one line per Age level, one panel per Sex level. |
| png(..., res = 300) | Opens a 300 dpi file. dev.off() closes it; forget it and the file stays empty. |
10.3 What the figure shows
Two panels, male on the left and female on the right. Each panel plots mean blood pressure for Drug and Placebo, with a blue line for young patients and an orange line for old patients. In both panels the orange line is steeper than the blue one: the drug helps older patients more. That is the Drug × Age interaction. The two panels look alike, which is why the three-way interaction is not significant.
10.4 Common R problems and fixes
| Symptom | Cause | Fix |
|---|---|---|
| Main effects differ from SPSS | Default treatment contrasts or Type I SS | Set sum contrasts and use the Type III loop or car::Anova(type = 3) |
summary(aov()) changes when you reorder factors | Type I SS on unbalanced data | Use Type III. Order only matters for Type I |
| Error: contrasts can be applied only to factors with 2 or more levels | A factor has one level after filtering, often from a typo | Run table(bp$drug) and fix level names |
| Coefficients are NA | An empty cell | Check table(bp$drug, bp$sex, bp$age) has no zeros |
| The PNG is empty | dev.off() never ran | Run while (dev.cur() > 1) dev.off() |
| Numbers read as text | read.csv met a stray character | Use as.numeric(gsub(",", "", x)) and check for NAs |
10.5 One-liners
# Type I table (equal to Type III when cells are balanced)
summary(aov(y ~ drug * sex * age, data = bp))
# Type III with the car package: install.packages("car")
car::Anova(lm(y ~ drug * sex * age, data = bp), type = 3)
# Cell means table
aggregate(y ~ drug + sex + age, data = bp, FUN = mean)
# Simple effect of drug at each age, pooled error
emmeans::emmeans(lm(y ~ drug * sex * age, data = bp), pairwise ~ drug | age)📈 11. How to Run a Three-Way ANOVA in Python
Python fits a three-way ANOVA with statsmodels. The script builds the long-format DataFrame, fits the full factorial model with sum contrasts, prints the Type III table with partial eta squared, runs Shapiro-Wilk and Levene, prints a plain-English verdict, and saves one 300 dpi interaction plot.
Install once with pip install pandas numpy scipy statsmodels matplotlib.
11.1 The complete script
Python 3, saves three_way_anova_plot.png
# ============================================================
# Three-way ANOVA with Type III sums of squares and one figure
# StatsUnlock - https://statsunlock.com/three-way-anova-calculator/
# pip install pandas numpy scipy statsmodels matplotlib
# ============================================================
import numpy as np
import pandas as pd
import statsmodels.formula.api as smf
from statsmodels.stats.anova import anova_lm
from scipy import stats
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
np.random.seed(2026) # nothing here is random, but keep runs reproducible
pd.set_option("display.width", 160)
# ---- 1. Your data (long format: one row per patient) --------
# To use a file instead:
# df = pd.read_csv("my_data.csv")
# df = df.dropna(subset=["bp"]) # drop missing outcomes
cells = {
("Drug", "Male", "Young"): [120, 121, 119, 117],
("Drug", "Male", "Old"): [126, 125, 128, 133],
("Drug", "Female", "Young"): [115, 115, 119, 118],
("Drug", "Female", "Old"): [125, 122, 125, 127],
("Placebo", "Male", "Young"): [131, 134, 129, 131],
("Placebo", "Male", "Old"): [143, 148, 145, 150],
("Placebo", "Female", "Young"): [133, 131, 123, 130],
("Placebo", "Female", "Old"): [144, 144, 139, 142],
}
df = pd.DataFrame([(d, s, a, y) for (d, s, a), v in cells.items() for y in v],
columns=["drug", "sex", "age", "bp"])
df = df.dropna(subset=["bp"])
# ---- 2. Fit the model with sum-to-zero contrasts -------------
# Sum contrasts are REQUIRED for correct Type III tests.
model = smf.ols("bp ~ C(drug, Sum) * C(sex, Sum) * C(age, Sum)", data=df).fit()
table = anova_lm(model, typ=3).drop(index="Intercept")
sse = table.loc["Residual", "sum_sq"]
table["partial_eta_sq"] = table["sum_sq"] / (table["sum_sq"] + sse)
table.loc["Residual", "partial_eta_sq"] = np.nan
print(table.round(4))
# ---- 3. Assumption checks -----------------------------------
sw = stats.shapiro(model.resid)
groups = [g["bp"].values for _, g in df.groupby(["drug", "sex", "age"])]
lev = stats.levene(*groups, center="median")
print(f"\nShapiro-Wilk on residuals: W = {sw.statistic:.4f}, p = {sw.pvalue:.4f}")
print(f"Levene (median): F = {lev.statistic:.4f}, p = {lev.pvalue:.4f}")
# ---- 4. Plain-English verdict --------------------------------
alpha = 0.05
names = {"C(drug, Sum)": "Drug", "C(sex, Sum)": "Sex", "C(age, Sum)": "Age"}
dfe = int(table.loc["Residual", "df"])
print("\nVerdict:")
for term, row in table.drop(index="Residual").iterrows():
label = term
for k, v in names.items():
label = label.replace(k, v)
label = label.replace(":", " x ")
word = "SIGNIFICANT" if row["PR(>F)"] < alpha else "not significant"
print(f" {label:<18} F({int(row['df'])}, {dfe}) = {row['F']:.2f}, "
f"p = {row['PR(>F)']:.4f}, partial eta^2 = {row['partial_eta_sq']:.3f} -> {word}")
# ---- 5. One figure: interaction plot, one panel per sex -------
means = df.groupby(["sex", "age", "drug"])["bp"].agg(["mean", "sem"]).reset_index()
fig, axes = plt.subplots(1, 2, figsize=(9, 4.2), sharey=True)
colors = {"Young": "#2563eb", "Old": "#c2410c"}
for ax, sex in zip(axes, ["Male", "Female"]):
for age in ["Young", "Old"]:
m = means[(means.sex == sex) & (means.age == age)].set_index("drug").loc[["Drug", "Placebo"]]
ax.errorbar(m.index, m["mean"], yerr=m["sem"], marker="o", capsize=4,
lw=2, color=colors[age], label=age)
ax.set_title(sex)
ax.set_xlabel("Treatment")
ax.grid(alpha=0.3)
axes[0].set_ylabel("Systolic BP (mmHg), mean ± SE")
axes[1].legend(title="Age")
fig.suptitle("Drug x Sex x Age: three-way ANOVA")
fig.tight_layout()
fig.savefig("three_way_anova_plot.png", dpi=300)
print("\nSaved three_way_anova_plot.png at 300 dpi")
11.2 What each part does
| Line | What it does |
|---|---|
| pd.DataFrame([...]) | Turns the cell dictionary into one row per patient. |
| C(drug, Sum) | Sum-to-zero coding. Required for correct Type III tests. |
| a * b * c | Expands to all seven effects. |
| anova_lm(model, typ=3) | Type III table. Leave out typ and you get Type I. |
| sum_sq / (sum_sq + sse) | Partial eta squared for each term. |
| stats.shapiro(model.resid) | Normality test on the residuals. |
| stats.levene(..., center="median") | Brown-Forsythe Levene test across the 8 cells. |
| ax.errorbar(...) | Cell means with SE bars, one line per age group. |
| fig.savefig(..., dpi=300) | Saves a print-quality PNG. |
11.3 What the figure shows
The same two-panel interaction plot as the R version, with standard error bars on every mean. Steeper orange lines than blue lines in both panels show the Drug × Age interaction. Similar panels for male and female show the absent three-way interaction.
11.4 Common Python problems and fixes
| Symptom | Cause | Fix |
|---|---|---|
| Type III main effects do not match SPSS | Treatment coding, the default | Wrap every factor as C(x, Sum) |
| PatsyError: 'Series' object is not callable | A column is called C, which clashes with the C() function | Rename the column, for example to fc |
| Numbers treated as a continuous predictor | Levels coded 1, 2, 3 without C() | Always wrap factors in C() |
| LinAlgError: singular matrix | An empty factor combination | Check pd.crosstab([df.drug, df.sex], df.age) |
| Table shows ... instead of columns | pandas display width | pd.set_option("display.width", 160) |
11.5 One-liners
# Type I table (the default)
anova_lm(smf.ols("bp ~ drug * sex * age", data=df).fit())
# Cell means and SDs
df.groupby(["drug", "sex", "age"])["bp"].agg(["mean", "std", "count"])
# pingouin, if installed: pip install pingouin
import pingouin as pg
pg.anova(data=df, dv="bp", between=["drug", "sex", "age"], ss_type=3)📋 12. Reference Tables
12.1 Degrees of freedom for common designs
| Design | A, B, C | AB, AC, BC | ABC | Error (n per cell) |
|---|---|---|---|---|
| 2 × 2 × 2 | 1, 1, 1 | 1, 1, 1 | 1 | 8(n − 1) |
| 2 × 2 × 3 | 1, 1, 2 | 1, 2, 2 | 2 | 12(n − 1) |
| 2 × 3 × 3 | 1, 2, 2 | 2, 2, 4 | 4 | 18(n − 1) |
| 3 × 3 × 3 | 2, 2, 2 | 4, 4, 4 | 8 | 27(n − 1) |
| 2 × 3 × 4 | 1, 2, 3 | 2, 3, 6 | 6 | 24(n − 1) |
Conclusion: the three-way interaction uses the product of the three main-effect df, so larger designs spend a lot of df on it.
12.2 Critical F values at α = .05
| dferror | df1 = 1 | df1 = 2 | df1 = 3 | df1 = 4 | df1 = 8 |
|---|---|---|---|---|---|
| 16 | 4.49 | 3.63 | 3.24 | 3.01 | 2.59 |
| 24 | 4.26 | 3.40 | 3.01 | 2.78 | 2.36 |
| 36 | 4.11 | 3.26 | 2.87 | 2.63 | 2.21 |
| 48 | 4.04 | 3.19 | 2.80 | 2.57 | 2.14 |
| 60 | 4.00 | 3.15 | 2.76 | 2.53 | 2.10 |
| 120 | 3.92 | 3.07 | 2.68 | 2.45 | 2.02 |
Conclusion: if your F is above the value for your df, p is below .05. The worked example's Drug × Age F of 8.46 is well above 4.26.
12.3 Effect size benchmarks
| Size | Partial η² | Cohen's f | What it looks like |
|---|---|---|---|
| Small | .01 | .10 | Hard to see on a plot without error bars |
| Medium | .06 | .25 | Visible to a careful reader |
| Large | .14 | .40 | Obvious on the interaction plot |
Conclusion: compare with effects published in your own field before you call an effect small or large.
12.4 Type I, Type II and Type III sums of squares
| Type | Tests each effect after | Default in | Use when |
|---|---|---|---|
| Type I | The terms listed before it | R aov, statsmodels | You have a clear order of entry |
| Type II | All terms not containing it | R car::Anova | No interactions are present |
| Type III | All other terms | SPSS, SAS, jamovi | Unbalanced data with interactions, the usual case |
Conclusion: with equal cell sizes all three types give identical answers.
12.5 Approximate n per cell for 80% power, α = .05
| Effect df | Cohen's f = .10 | f = .25 | f = .40 |
|---|---|---|---|
| 1 (2×2×2, 8 cells) | 99 | 16 | 7 |
| 2 (2×2×3, 12 cells) | 81 | 14 | 6 |
| 4 (3×3×2, 18 cells) | 67 | 11 | 5 |
Conclusion: medium interactions need about 11 to 16 per cell. Small interactions need very large samples, so plan with G*Power or the pwr package.
12.6 Which test should you use?
| Your situation | Use |
|---|---|
| Three between-subjects factors, one continuous outcome | Three-way ANOVA (this page) |
| Same people measured under every condition | Three-way repeated measures ANOVA |
| Some factors within, some between subjects | Mixed ANOVA or a linear mixed model |
| Adding a continuous covariate | Three-way ANCOVA |
| Strong skew or outliers, ordinal outcome | Aligned rank transform ANOVA |
| Count or binary outcome | Poisson or logistic regression with interactions |
Conclusion: the three-way ANOVA assumes independent observations, so repeated measurements need a different model.
📈 13. Example Results
Eight worked three-way ANOVAs, each with its ANOVA summary, an interaction plot and a ready-to-use reporting sentence. Seven of them are built-in sample datasets, so you can load them in Section 2 and reproduce every number.
Alcohol, gender and time of night
The classic interaction that every statistics course teaches.
72 participants rated the attractiveness of a stranger after no alcohol, 2 pints or 4 pints, early or late in the evening. Sample dataset 8.
| Effect | F | p | Partial η² |
|---|---|---|---|
| Gender | F(1, 60) = 60.35 | p < .001 | .501 |
| Alcohol | F(2, 60) = 94.92 | p < .001 | .760 |
| Time | F(1, 60) = 9.23 | p = .004 | .133 |
| Gender × Alcohol | F(2, 60) = 47.05 | p < .001 | .611 |
| Gender × Time | F(1, 60) = 0.00 | p = 1.000 | .000 |
| Alcohol × Time | F(2, 60) = 0.76 | p = .473 | .025 |
| Gender × Alcohol × Time | F(2, 60) = 0.11 | p = .896 | .004 |
What it means: Alcohol has a big main effect, but the Gender × Alcohol interaction (F(2, 60) = 47.05, partial η² = .61) shows it is almost entirely driven by one group at 4 pints, where ratings fall from about 62 to 38. The other group barely changes. Time adds a small separate effect (later ratings are 3 points higher) that does not interact with anything.
How to write it: "A 2 × 3 × 2 ANOVA showed a significant Gender × Alcohol interaction, F(2, 60) = 47.05, p < .001, ηp² = .61, and a main effect of Time, F(1, 60) = 9.23, p = .004, ηp² = .13. The three-way interaction was not significant, F(2, 60) = 0.11, p = .896."
Drug trial on blood pressure
The worked example used in Sections 7, 9, 10 and 11.
32 patients, 4 per cell, in a Drug × Sex × Age design. Sample dataset 1.
| Effect | F | p | Partial η² |
|---|---|---|---|
| Drug | F(1, 24) = 234.26 | p < .001 | .907 |
| Sex | F(1, 24) = 9.22 | p = .006 | .277 |
| Age | F(1, 24) = 129.60 | p < .001 | .844 |
| Drug × Sex | F(1, 24) = 0.02 | p = .900 | .001 |
| Drug × Age | F(1, 24) = 8.46 | p = .008 | .261 |
| Sex × Age | F(1, 24) = 0.58 | p = .455 | .023 |
| Drug × Sex × Age | F(1, 24) = 0.14 | p = .708 | .006 |
What it means: All three main effects are significant, and so is Drug × Age (F(1, 24) = 8.46, p = .008, partial η² = .26). The drug lowers blood pressure by 12.25 mmHg in young patients and by 18.00 mmHg in old patients. The three-way interaction is tiny (partial η² = .006), so the Drug × Age pattern is the same for men and women.
How to write it: "A three-way ANOVA revealed a significant Drug × Age interaction, F(1, 24) = 8.46, p = .008, ηp² = .26; the drug reduced systolic pressure more in older (18.0 mmHg) than younger (12.3 mmHg) patients. The three-way interaction was not significant, F(1, 24) = 0.14, p = .708."
Income by education, sector and region
When p lands just above .05 with unequal cells.
46 survey respondents with cell sizes from 3 to 9. Sample dataset 9.
| Effect | F | p | Partial η² |
|---|---|---|---|
| Education | F(1, 38) = 71.51 | p < .001 | .653 |
| Sector | F(1, 38) = 15.91 | p < .001 | .295 |
| Region | F(1, 38) = 23.97 | p < .001 | .387 |
| Education × Sector | F(1, 38) = 3.98 | p = .053 | .095 |
| Education × Region | F(1, 38) = 1.59 | p = .214 | .040 |
| Sector × Region | F(1, 38) = 1.11 | p = .299 | .028 |
| Education × Sector × Region | F(1, 38) = 0.15 | p = .701 | .004 |
What it means: The three main effects are clear. The Education × Sector interaction sits at p = .053 with a partial η² of .09, a medium effect. With unequal cells and some cells of only 3 or 4, the study is underpowered for this interaction. The honest reading is that the degree premium may be larger in the private sector, but this sample cannot confirm it.
How to write it: "The Education × Sector interaction approached but did not reach significance, F(1, 38) = 3.98, p = .053, ηp² = .09; Type III sums of squares were used because cell sizes were unequal (n = 3 to 9)."
Customer satisfaction by channel, region and plan
What a clean null result looks like.
64 customers rated satisfaction from 1 to 10, 8 per cell. Sample dataset 5.
| Effect | F | p | Partial η² |
|---|---|---|---|
| Channel | F(1, 56) = 0.07 | p = .789 | .001 |
| Region | F(1, 56) = 0.00 | p = .982 | .000 |
| Plan | F(1, 56) = 2.61 | p = .112 | .044 |
| Channel × Region | F(1, 56) = 1.93 | p = .170 | .033 |
| Channel × Plan | F(1, 56) = 0.11 | p = .738 | .002 |
| Region × Plan | F(1, 56) = 0.02 | p = .893 | .000 |
| Channel × Region × Plan | F(1, 56) = 0.15 | p = .704 | .003 |
What it means: No effect reaches significance and every partial η² is below .05. The largest, Plan (partial η² = .04, p = .112), suggests premium customers may be slightly happier, but the difference is about 0.45 points on a 10-point scale. A null result is still a result: report every F with its effect size so readers can judge the sensitivity of the study.
How to write it: "None of the main effects or interactions were significant (all F ≤ 2.61, all p ≥ .112, all ηp² ≤ .04)."
VO2 max in athletes, 3 per cell
Large effects survive, small ones vanish.
24 athletes, only 3 per cell, with 16 error df. Sample dataset 6.
| Effect | F | p | Partial η² |
|---|---|---|---|
| Training | F(1, 16) = 6.29 | p = .023 | .282 |
| Sex | F(1, 16) = 31.33 | p < .001 | .662 |
| Altitude | F(1, 16) = 4.34 | p = .054 | .213 |
| Training × Sex | F(1, 16) = 0.77 | p = .394 | .046 |
| Training × Altitude | F(1, 16) = 0.00 | p = .991 | .000 |
| Sex × Altitude | F(1, 16) = 0.00 | p = .963 | .000 |
| Training × Sex × Altitude | F(1, 16) = 0.87 | p = .364 | .052 |
What it means: Sex (partial η² = .66) and Training (.28) are detected, but Altitude misses significance at p = .054 despite a partial η² of .21, which is a large effect by Cohen's bands. With 3 per cell the test has little power. Treat the Altitude result as inconclusive, not as evidence of no effect, and plan a larger study.
How to write it: "Sex, F(1, 16) = 31.33, p < .001, ηp² = .66, and Training, F(1, 16) = 6.29, p = .023, ηp² = .28, affected VO2 max; the effect of Altitude was not significant, F(1, 16) = 4.34, p = .054, ηp² = .21, though the small sample (n = 3 per cell) limited power."
Checkout time with 1,200 users
A tiny p-value attached to a tiny effect.
An A/B test of an app and a website with 150 users per cell (N = 1,200). Checkout time in seconds.
| Effect | F | p | Partial η² |
|---|---|---|---|
| Platform | F(1, 1192) = 21.96 | p < .001 | .018 |
| Customer | F(1, 1192) = 1.01 | p = .316 | .001 |
| Day | F(1, 1192) = 0.58 | p = .446 | .000 |
| Platform × Customer | F(1, 1192) = 0.58 | p = .448 | .000 |
| Platform × Day | F(1, 1192) = 0.48 | p = .488 | .000 |
| Customer × Day | F(1, 1192) = 0.53 | p = .468 | .000 |
| Platform × Customer × Day | F(1, 1192) = 1.30 | p = .254 | .001 |
What it means: Platform is highly significant (F(1, 1192) = 21.96, p < .001), but its partial η² is only .018 and the web version is about 1.6 seconds slower on a 50-second task. With 1,192 error df almost any real difference becomes significant. Decide on practical importance from the effect size and the raw difference, not from p.
How to write it: "Platform had a statistically significant but small effect on checkout time, F(1, 1192) = 21.96, p < .001, ηp² = .018; web checkout took about 1.6 s longer (51.4 vs 49.8 s)."
Skewed hospital length of stay
When the F tests should not be trusted as they stand.
64 patients, 8 per cell, with a long right tail and very unequal spreads. Sample dataset 10.
| Effect | F | p | Partial η² |
|---|---|---|---|
| Ward | F(1, 56) = 0.28 | p = .597 | .005 |
| AgeBand | F(1, 56) = 3.98 | p = .051 | .066 |
| Admission | F(1, 56) = 4.54 | p = .038 | .075 |
| Ward × AgeBand | F(1, 56) = 1.81 | p = .184 | .031 |
| Ward × Admission | F(1, 56) = 0.48 | p = .492 | .008 |
| AgeBand × Admission | F(1, 56) = 1.93 | p = .170 | .033 |
| Ward × AgeBand × Admission | F(1, 56) = 0.10 | p = .752 | .002 |
What it means: Shapiro-Wilk on the residuals fails badly (W = .75, p < .001) and Levene's test fails too (F(7, 56) = 2.68, p = .018). The emergency over-65 cells have SDs above 4.7 days while the planned under-65 cells are only 0.3 to 0.5, a ratio above 15. The Admission effect (p = .038) and the borderline AgeBand effect (p = .051) cannot be taken at face value. Log-transform the outcome and rerun, or use an aligned rank transform ANOVA.
How to write it: "Because residuals were non-normal (Shapiro-Wilk W = .75, p < .001) and variances unequal (Levene F(7, 56) = 2.68, p = .018), length of stay was log-transformed before analysis."
Caffeine, sleep loss and task difficulty
A genuine three-way interaction, and how to read it.
48 volunteers, 6 per cell, reaction time in milliseconds. Sample dataset 4.
| Effect | F | p | Partial η² |
|---|---|---|---|
| Caffeine | F(1, 40) = 42.87 | p < .001 | .517 |
| Sleep | F(1, 40) = 121.12 | p < .001 | .752 |
| Task | F(1, 40) = 295.90 | p < .001 | .881 |
| Caffeine × Sleep | F(1, 40) = 9.33 | p = .004 | .189 |
| Caffeine × Task | F(1, 40) = 20.67 | p < .001 | .341 |
| Sleep × Task | F(1, 40) = 1.07 | p = .307 | .026 |
| Caffeine × Sleep × Task | F(1, 40) = 13.07 | p < .001 | .246 |
What it means: The three-way interaction is significant (F(1, 40) = 13.07, p < .001, partial η² = .25). On the simple task, caffeine makes little difference whether people slept or not. On the complex task, caffeine cuts reaction time by 84 ms for sleep-deprived people but only 21 ms for rested people. The Caffeine × Sleep interaction exists only for the complex task, which is exactly what a three-way interaction means.
How to write it: "A significant Caffeine × Sleep × Task interaction, F(1, 40) = 13.07, p < .001, ηp² = .25, indicated that caffeine offset the effect of sleep deprivation on complex (84 ms benefit) but not simple tasks."
🧪 14. Data Collection Protocol
Study design: a three-way between-subjects ANOVA needs every unit measured once, in exactly one combination of three categorical factors, on one continuous outcome.
- Fix the three factors and their levels before you start. Write them down, for example Drug (Drug, Placebo), Sex (Male, Female), Age (Young, Old). Adding a level halfway through breaks the design.
- Check you can fill every cell. Multiply the levels: 2 × 3 × 2 = 12 cells. Each cell needs data, so rare combinations (for example, old patients in a paediatric ward) can make the design impossible.
- Plan n per cell with a power analysis. Use Table 12.5. Medium interactions need about 11 to 16 per cell for 80% power. Aim for equal n in every cell.
- Randomise manipulated factors. Assign units to Drug or Placebo at random within each Sex and Age group. Measured factors like sex cannot be randomised, so say so in the paper.
- Keep units independent. One value per person, plot or animal. Repeated readings of the same unit need a repeated measures or mixed model instead.
- Blind the measurement where you can. The person recording the outcome should not know the treatment.
- Use one instrument and one protocol across all cells, and record units and resolution.
- Record missing values as blanks, never as zero. A fake zero lowers a cell mean and inflates its variance.
- Write the outlier and transformation rules in advance, for example "values beyond 3 SD of the cell mean are checked against source records".
- Store the data in long format: one row per unit, one column per factor, one column for the outcome, exactly as the layout below shows.
| Drug | Sex | Age | SystolicBP |
|---|---|---|---|
| Drug | Male | Young | 120 |
| Placebo | Female | Old | 144 |
| Drug | Female | Old | 125 |
Minimum sample: at least 2 values in every cell so the error term exists, and in practice 10 or more per cell. Unequal cell sizes are allowed; choose Type III sums of squares for them.
Pre-registration: record the factors, levels, n per cell, alpha, SS type, the effect you expect (main effect or interaction) and how you will follow up an interaction, before you collect data.
Common collection mistakes:
- Leaving one factor combination empty, which makes the three-way interaction untestable.
- Collecting far more data in one cell than the others, which makes Type I and Type III answers diverge.
- Measuring the same person in several conditions and treating the rows as independent.
- Spelling a level two ways ("male" and "Male"), which creates a fourth level by accident.
- Recording the outcome in different units in different cells.
🎯 15. When to Use a Three-Way ANOVA
Use a three-way ANOVA when your research question is about how three factors work together, not just one at a time.
Use it when:
- ✓ You have exactly three categorical factors, each with 2 or more levels.
- ✓ The outcome is continuous: a measurement, a score or a time.
- ✓ Each unit appears in one cell only (between-subjects).
- ✓ You expect, or want to rule out, interactions between the factors.
- ✓ Every factor combination has data.
Do not use it when:
- ✗ The same units are measured more than once. Use repeated measures or mixed ANOVA.
- ✗ The outcome is a count or a yes or no. Use Poisson or logistic regression with interaction terms.
- ✗ The outcome is ordinal or heavily skewed and cannot be transformed. Use an aligned rank transform ANOVA.
- ✗ You have only two factors. Use the two-way ANOVA.
- ✗ You have several correlated outcomes. Use MANOVA.
- ✗ A factor is continuous, like age in years. Keep it continuous and use regression or ANCOVA instead of cutting it into groups.
Real-world examples:
- Clinical trial: does a drug's effect on blood pressure depend on sex and age group?
- Agronomy: fertilizer by irrigation by crop variety on yield per hectare.
- Psychology: caffeine by sleep deprivation by task difficulty on reaction time.
- Ecology: light by soil by watering regime on seedling height in a greenhouse.
Design guidance: keep the number of levels small. A 2×2×2 design needs 8 cells; a 3×3×3 needs 27. Every extra level multiplies the sample you need.
Decision tree: one factor, use a one-way ANOVA. Two factors, use a two-way ANOVA. Three factors, all between-subjects, continuous outcome, use this three-way ANOVA. Any factor within-subjects, use a mixed model. Non-normal outcome that transformation cannot fix, use the aligned rank transform.
🔧 16. Troubleshooting and Common Errors
"Each factor needs at least 2 levels"
Cause: a factor column holds one value only, or the columns are in the wrong order. Fix: check the header row. The outcome must be the fourth column. On the Upload tab, re-select the columns.
"Factor combinations have no data"
Cause: at least one cell is empty. Fix: collect data for it, merge two levels, or drop one level. A three-way ANOVA cannot estimate an interaction for a combination that was never observed.
A factor shows an extra level I did not expect
Cause: a spelling or spacing difference, such as "Male" and "male ". Fix: level names are case-sensitive and spaces at the ends are trimmed. Make the spelling identical in every row.
Some rows were skipped
Cause: the outcome cell was blank or had text such as "NA" or "120 mmHg". Fix: the validation line tells you how many rows were skipped. Put units in the header, not the cells.
My results differ from R's summary(aov())
Cause: your cells are unequal and R's aov() uses Type I SS. Fix: set Sums of squares to Type I here to match, or use car::Anova(type = 3) in R to match this page's default.
My results differ from Python statsmodels
Cause: statsmodels was run with treatment coding or without typ=3. Fix: use C(factor, Sum) for every factor and anova_lm(model, typ=3).
Levene's p differs slightly from SPSS
Cause: SPSS centres on the cell mean by default; this page centres on the median (Brown-Forsythe). Fix: nothing is wrong. Report which version you used.
Shapiro-Wilk fails but the plot looks fine
Cause: with large N, the test flags tiny departures that do not matter. Fix: trust the Q-Q plot (Chart 4) for N above about 100. If the points follow the line except at the ends, ANOVA is usually fine.
F is enormous and p is 0
Cause: the error SS is almost zero, often because values were copied down so each cell holds identical numbers. Fix: check the raw data. Real measurements always vary a little within a cell.
Partial eta squared values add up to more than 1
Cause: this is normal. Each partial value uses a different denominator. Fix: if you need values that add up, report eta squared (η²), shown in the same table.
Charts do not appear
Cause: the Chart.js library was blocked by a browser extension or network filter. Fix: allow cdnjs.cloudflare.com. The tables and text results work without it.
The Excel upload shows no columns
Cause: the first row is blank or merged. Fix: put plain column names in row 1 with no merged cells, then upload again.
⚖ 17. Assumptions and Limitations
Assumptions
- Independence of observations. Check the design: one value per unit, no clusters. If violated: the error term is too small, so p-values are too small and false positives rise. This is the most serious violation.
- Normal residuals within each cell. Check the Shapiro-Wilk test and the Q-Q plot. If violated: with equal n and 15 or more per cell, the effect is small. With small, skewed cells, p-values can be off in either direction.
- Equal variances across cells. Check Levene's test and the largest to smallest SD ratio. If violated with unequal n: when the larger cells have the larger variances, the test becomes too conservative; when the smaller cells have the larger variances, it becomes too liberal and false positives rise.
- A continuous outcome on an interval or ratio scale. Check what the numbers mean. If violated: ordinal scores give means that may not be meaningful.
- Fixed, categorical factors. Check that the levels are the ones you care about, not a random sample of levels. If violated: random factors need a mixed model; the F denominators change.
- No empty cells. Check the cell table. If violated: the full model cannot be estimated and the calculator stops.
Limitations
- It handles between-subjects factors only. For repeated measurements use a repeated measures ANOVA or a linear mixed model.
- It fits one outcome. For several correlated outcomes use MANOVA.
- It does not include covariates. Use ANCOVA or regression to adjust for a continuous variable.
- Post hoc tests use Bonferroni, which is simple and safe but conservative. Tukey HSD or Holm in R or Python give slightly more power.
- Simple effects for interactions are described but not computed automatically. Use
emmeansin R for full simple effects tables. - It does not offer Welch-type or robust ANOVA. For badly unequal variances use the
WRS2package in R or a log transform. - Power drops fast for higher-order interactions. A non-significant three-way interaction in a small study is weak evidence that none exists.
🏁 18. Conclusion
▶ Run the analysis above and this paragraph will summarise your own result: ___.
A three-way ANOVA calculator answers a question that simpler tests cannot: does the effect of one factor depend on the other two? It splits the variation in one outcome into seven parts, three main effects, three two-way interactions and one three-way interaction, and tests each against the same pooled error term.
The most useful habit is to read the table from the bottom up. A significant three-way interaction changes the meaning of every two-way interaction it contains, and a significant two-way interaction turns the related main effects into averages. In the worked example the drug lowers blood pressure in everyone, but by 12 mmHg in young patients and 18 mmHg in old ones. That difference is the finding, and a one-way analysis would have missed it.
The second habit is to check the design before the statistics. Every factor combination needs data, observations must be independent, and unequal cell sizes mean you must choose and report the sums of squares type. Type III is the usual choice and matches SPSS.
Load your data or one of the ten samples into this three-way ANOVA calculator, run it, look at the interaction plot, and copy the APA sentence from Section 5. Whenever you have three factors to test together, a 3 factor calculator like this one is the right place to start.
❓ 19. Frequently Asked Questions
Q1. What is a three-way ANOVA?
A three-way ANOVA is an analysis of variance with three categorical independent variables (factors) and one continuous outcome. It tests three main effects, three two-way interactions and one three-way interaction, giving seven F tests from one model.
Q2. How do I use this three-way ANOVA calculator?
Paste long-format data with four columns (Factor A, Factor B, Factor C, Outcome), upload a CSV or Excel file, or type rows into the manual table. Choose alpha and the sums of squares type, then press Run Three-Way ANOVA. Results, charts and APA sentences appear below.
Q3. Is this a factor calculator?
It is a 3 factor calculator in the statistics sense: it analyses three experimental factors at once. It does not find the factors of a whole number, such as the factors of 24 (1, 2, 3, 4, 6, 8, 12, 24). That is a different, arithmetic meaning of the word factor.
Q4. What is the difference between a two-way and a three-way ANOVA?
A two-way ANOVA has two factors and three F tests: two main effects and one interaction. A three-way ANOVA adds a third factor, which brings three two-way interactions and a three-way interaction, for seven F tests in total.
Q5. What does a three-way interaction mean?
It means the two-way interaction between two factors changes across the levels of the third factor. For example, the drug by age interaction might appear in men but not in women. You follow it up with separate two-way analyses at each level of the third factor.
Q6. How do I interpret a significant two-way interaction in a three-way ANOVA?
First check that the three-way interaction is not significant. Then look at the interaction plot and run simple effects: test one factor at each level of the other. Describe how the effect changes, for example the drug lowered blood pressure by 12 mmHg in young and 18 mmHg in old patients.
Q7. How many degrees of freedom does a three-way ANOVA have?
Each main effect has levels minus 1. Each two-way interaction multiplies two of those, and the three-way interaction multiplies all three. The error df is N minus the number of cells (a times b times c). A 2 x 2 x 2 design with N = 32 has 24 error df.
Q8. What sample size do I need for a three-way ANOVA?
At least 2 per cell so the error term exists, but for 80% power to detect a medium interaction (Cohen's f = .25) you need about 11 to 16 per cell. Small interactions need 70 to 100 per cell. Use G*Power with the numerator df of the effect you care about.
Q9. Should I use Type I, Type II or Type III sums of squares?
With equal cell sizes all three are identical. With unequal sizes and interactions in the model, Type III is the most common choice and matches SPSS and SAS. Type II has more power when interactions are truly absent. Type I depends on the order you enter the factors.
Q10. What are the assumptions of a three-way ANOVA?
Independent observations, normally distributed residuals within each cell, equal variances across cells, a continuous outcome, and data in every factor combination. This calculator checks normality with Shapiro-Wilk on the residuals and equal variances with the median-centred Levene test.
Q11. What if my data are not normal?
With equal cell sizes of 15 or more, ANOVA copes well with moderate non-normality. For strong skew, try a log or square-root transformation of the outcome. If that fails, use the aligned rank transform (ART) ANOVA, which keeps the factorial structure while using ranks.
Q12. What effect size should I report for a three-way ANOVA?
Report partial eta squared for every F test, because that is what most journals and SPSS use. Add partial omega squared for small samples, since it corrects the upward bias. Cohen's rough bands are .01 small, .06 medium and .14 large.
Q13. Why do my partial eta squared values add up to more than 1?
Each partial eta squared divides by its own effect SS plus the error SS, so the denominators differ and the values do not share a common total. If you need values that add up to at most 1, use eta squared, which divides every effect by the total SS.
Q14. Do I need post hoc tests after a three-way ANOVA?
Only for significant effects with three or more levels. A two-level factor has one comparison, so the F test already tells you which level is higher. This calculator runs Bonferroni-adjusted pairwise comparisons on the marginal means for factors with three or more levels.
Q15. How do I run a three-way ANOVA in Excel?
Excel's Analysis ToolPak stops at two factors, so you build the table with formulas. Use AVERAGEIFS for the marginal and cell means, DEVSQ for the total SS, SUMSQ of the residuals for the error SS, and F.DIST.RT for the p-values. Section 9 shows every step with spreadsheet screenshots.
Q16. How do I run a three-way ANOVA in R?
Set sum contrasts with options(contrasts = c("contr.sum", "contr.poly")), fit lm(y ~ A * B * C), then use car::Anova(model, type = 3). The base-R script in Section 10 builds the same Type III table without any packages and saves an interaction plot.
Q17. How do I run a three-way ANOVA in Python?
Use statsmodels: fit ols("y ~ C(A, Sum) * C(B, Sum) * C(C, Sum)", data=df) and pass it to anova_lm(model, typ=3). The Sum coding is essential for correct Type III results. Section 11 has a full script with assumption checks and a 300 dpi plot.
Q18. Can I use a three-way ANOVA with unequal group sizes?
Yes. Unequal cell sizes are fine as long as no cell is empty. The main change is that Type I and Type III sums of squares now give different answers, so choose Type III here and state it in your methods section.
Q19. What is the difference between a three-way ANOVA and a three-way repeated measures ANOVA?
In a standard three-way ANOVA each subject is measured once, in one cell. In a repeated measures version the same subjects appear in several cells, so their scores are correlated. That needs a different error structure, which this between-subjects calculator does not handle.
Q20. Can I cite this calculator in my thesis or paper?
Yes. Cite it as StatsUnlock (2026), Three-way ANOVA calculator, https://statsunlock.com/three-way-anova-calculator/. For formal submissions, reproduce the numbers with the R or Python scripts on this page and cite that software too.
📑 20. Cite This Tool
StatsUnlock. (2026). Three-way ANOVA calculator [Interactive statistical tool]. https://statsunlock.com/three-way-anova-calculator/@misc{statsunlock_threewayanova_2026, title={Three-Way ANOVA Calculator}, author={{StatsUnlock}}, year={2026}, note={Interactive statistical tool}, url={https://statsunlock.com/three-way-anova-calculator/}}Data were analysed with a three-way between-subjects ANOVA using the StatsUnlock Three-Way ANOVA Calculator (2026), with Type III sums of squares. Normality of residuals was checked with the Shapiro-Wilk test and homogeneity of variance with the median-centred Levene test. Effect sizes are reported as partial eta squared, and alpha was set at .05.🔗 21. Related Tools
Two factors and their interaction. The right follow-up inside each level when your three-way interaction is significant.
Open the tool →Compare three or more group means on a single factor.
Open the tool →When you have several related outcome variables instead of one.
Open the tool →Convert F and df into η², ω² and Cohen's f for a published result.
Open the tool →A rank-based alternative for one factor when normality fails.
Open the tool →When the same people or units are measured under every condition.
Open the tool →Some factors between subjects, some within. The fix when your three-way design has repeated measurements.
Open the tool →Add a continuous covariate, such as baseline score, to your group comparison.
Open the tool →Look up the critical F for any df and alpha level.
Open the tool →📖 22. Glossary of Terms
| Term | Plain-English meaning |
|---|---|
| Balanced design | Every cell has the same number of observations. |
| Bonferroni correction | Multiplying each p-value by the number of comparisons, to keep false positives under control. |
| Cell | One combination of levels of all three factors, such as Drug, Male, Young. |
| Degrees of freedom (df) | The number of values free to vary when a quantity is estimated. Each F test has two. |
| Effect coding | Coding factor levels as +1, 0 and −1 so that effects are measured from the grand mean. Needed for Type III tests. |
| Estimated marginal mean | The average of the cell means for one level, giving each cell equal weight. |
| Eta squared (η²) | The share of the total variation explained by one effect. |
| F ratio | An effect's mean square divided by the error mean square. Large values suggest a real effect. |
| Factor | A categorical variable that defines groups, such as treatment or sex. A 3 factor calculator handles three of them. |
| Interaction | When the effect of one factor depends on the level of another. |
| Level | One category of a factor, such as Drug or Placebo. |
| Levene's test | A test of whether the cells have similar variances. |
| Long format | A data layout with one row per observation and one column per variable. |
| Main effect | The effect of one factor averaged over the levels of the other factors. |
| Mean square (MS) | A sum of squares divided by its degrees of freedom. A variance estimate. |
| Omega squared (ω²) | An effect size like eta squared but corrected for small-sample bias. |
| Partial eta squared (ηp²) | An effect's SS divided by its SS plus the error SS. The usual ANOVA effect size. |
| p-value | How likely a result at least this large would be if the effect were truly zero. |
| Residual | An observation minus its cell mean. The part the model does not explain. |
| Shapiro-Wilk test | A test of whether the residuals follow a normal distribution. |
| Simple effect | The effect of one factor at a single level of another factor. |
| Sum of squares (SS) | The total of squared differences from a mean. The raw amount of variation. |
| Type III sums of squares | Each effect tested after all others. The standard for unbalanced designs with interactions. |
📚 23. References
These peer-reviewed sources support the methods used in this three-way ANOVA calculator: factorial designs, interactions, sums of squares for unbalanced data, effect sizes, assumption tests and multiple comparisons.
- Brown, M. B., & Forsythe, A. B. (1974). Robust tests for the equality of variances. Journal of the American Statistical Association, 69(346), 364–367. https://doi.org/10.1080/01621459.1974.10482955
- Shapiro, S. S., & Wilk, M. B. (1965). An analysis of variance test for normality (complete samples). Biometrika, 52(3–4), 591–611. https://doi.org/10.1093/biomet/52.3-4.591
- Royston, P. (1995). Remark AS R94: A remark on algorithm AS 181: The W-test for normality. Applied Statistics, 44(4), 547–551. https://doi.org/10.2307/2986146
- Shaw, R. G., & Mitchell-Olds, T. (1993). ANOVA for unbalanced data: An overview. Ecology, 74(6), 1638–1645. https://doi.org/10.2307/1939922
- Langsrud, Ø. (2003). ANOVA for unbalanced data: Use Type II instead of Type III sums of squares. Statistics and Computing, 13(2), 163–167. https://doi.org/10.1023/A:1023260610025
- Olejnik, S., & Algina, J. (2003). Generalized eta and omega squared statistics: Measures of effect size for some common research designs. Psychological Methods, 8(4), 434–447. https://doi.org/10.1037/1082-989X.8.4.434
- 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. https://doi.org/10.3389/fpsyg.2013.00863
- Levine, T. R., & Hullett, C. R. (2002). Eta squared, partial eta squared, and misreporting of effect size in communication research. Human Communication Research, 28(4), 612–625. https://doi.org/10.1111/j.1468-2958.2002.tb00828.x
- Richardson, J. T. E. (2011). Eta squared and partial eta squared as measures of effect size in educational research. Educational Research Review, 6(2), 135–147. https://doi.org/10.1016/j.edurev.2010.12.001
- Cohen, J. (1992). A power primer. Psychological Bulletin, 112(1), 155–159. https://doi.org/10.1037/0033-2909.112.1.155
- Faul, F., Erdfelder, E., Lang, A.-G., & Buchner, A. (2007). G*Power 3: A flexible statistical power analysis program for the social, behavioral, and biomedical sciences. Behavior Research Methods, 39(2), 175–191. https://doi.org/10.3758/BF03193146
- Rosnow, R. L., & Rosenthal, R. (1989). Definition and interpretation of interaction effects. Psychological Bulletin, 105(1), 143–146. https://doi.org/10.1037/0033-2909.105.1.143
- Gelman, A. (2005). Analysis of variance: Why it is more important than ever. The Annals of Statistics, 33(1), 1–53. https://doi.org/10.1214/009053604000001048
- Kim, H.-Y. (2014). Statistical notes for clinical researchers: Two-way analysis of variance (ANOVA), exploring possible interaction between factors. Restorative Dentistry & Endodontics, 39(2), 143–147. https://doi.org/10.5395/rde.2014.39.2.143
- Glass, G. V., Peckham, P. D., & Sanders, J. R. (1972). Consequences of failure to meet assumptions underlying the fixed effects analyses of variance and covariance. Review of Educational Research, 42(3), 237–288. https://doi.org/10.3102/00346543042003237
- Blanca, M. J., Alarcón, R., Arnau, J., Bono, R., & Bendayan, R. (2017). Non-normal data: Is ANOVA still a valid option? Psicothema, 29(4), 552–557. https://doi.org/10.7334/psicothema2016.383
- Wobbrock, J. O., Findlater, L., Gergle, D., & Higgins, J. J. (2011). The aligned rank transform for nonparametric factorial analyses using only ANOVA procedures. Proceedings of the SIGCHI Conference on Human Factors in Computing Systems, 143–146. https://doi.org/10.1145/1978942.1978963
- Bender, R., & Lange, S. (2001). Adjusting for multiple testing: When and how? Journal of Clinical Epidemiology, 54(4), 343–349. https://doi.org/10.1016/S0895-4356(00)00314-0
- Armstrong, R. A. (2014). When to use the Bonferroni correction. Ophthalmic and Physiological Optics, 34(5), 502–508. https://doi.org/10.1111/opo.12131
- Weissgerber, T. L., Milic, N. M., Winham, S. J., & Garovic, V. D. (2015). Beyond bar and line graphs: Time for a new data presentation paradigm. PLOS Biology, 13(4), e1002128. https://doi.org/10.1371/journal.pbio.1002128
