StatsCalculators.com

Two-Way ANOVA

Created:October 15, 2024
Last Updated:August 18, 2025

This calculator helps you examine how two different factors (independent variables) affect an outcome (dependent variable), both individually and in combination. Unlike simpler tests that only look at one factor at a time, Two-Way ANOVA reveals whether your factors work together in unexpected ways.

What You'll Get:

  • Main Effects: How each factor individually affects your outcome
  • Interaction Effects: Whether factors work together in surprising ways
  • Effect Sizes: Not just statistical significance, but practical importance
  • Visual Analysis: Interactive plots showing factor relationships
  • APA-Ready Report: Publication-quality results you can copy directly

Ready to explore your data? Start with our sample dataset to see how it works, or upload your own data to begin your analysis.

Calculator

1. Load Your Data

2. Select Columns & Options

Related Calculators

Learn More

Two-Way ANOVA

Definition

Two-Way ANOVA examines the influence of two categorical independent variables on one continuous dependent variable. It tests for main effects of each factor and their interaction effect. It helps determine if there are significant differences between group means in a dataset.
  • Factors: The independent categorical variables.
  • Levels: The groups or categories within each factor.
  • Interaction: Determines whether the effect of one factor depends on the level of the other factor.

Formulas

Total Sum of Squares Decomposition:

SSTotal=SSFactorA+SSFactorB+SSInteraction+SSErrorSS_{Total} = SS_{FactorA} + SS_{FactorB} + SS_{Interaction} + SS_{Error}

SSTotal=i=1aj=1b(XijXˉ)2SS_{Total} = \sum_{i=1}^{a} \sum_{j=1}^{b} (X_{ij} - \bar{X})^2where Xˉ\bar{X} is the grand mean

SSFactorA=bi=1a(Xˉi.Xˉ)2SS_{FactorA} = b \sum_{i=1}^{a} (\bar{X}_{i.} - \bar{X})^2whereXˉi.\bar{X}_{i.} is the mean of level ii of Factor A, and bb is the number of levels in Factor B.

SSFactorB=aj=1b(Xˉ.jXˉ)2SS_{FactorB} = a \sum_{j=1}^{b} (\bar{X}_{.j} - \bar{X})^2where Xˉ.j\bar{X}_{.j} is the mean of level $j$ of Factor B, and $a$ is the number of levels in Factor A.

SSInteraction=i=1aj=1b(XˉijXˉi.Xˉ.j+Xˉ)2SS_{Interaction} = \sum_{i=1}^{a} \sum_{j=1}^{b} (\bar{X}_{ij} - \bar{X}_{i.} - \bar{X}_{.j} + \bar{X})^2

Where:

  • SSFactorASS_{FactorA} = Sum of Squares for Factor A, df=a1df = a - 1 where aa is the number of levels in Factor A
  • SSFactorBSS_{FactorB} = Sum of Squares for Factor B, df=b1df = b - 1 where bb is the number of levels in Factor B
  • SSInteractionSS_{Interaction} = Sum of Squares for interaction with df=(a1)(b1)df = (a - 1)(b - 1)
  • SSErrorSS_{Error} = Residual Sum of Squares, df=Nabdf = N - a*b

Mean Square:

MS=SSdfMS = \frac{SS}{df}

F-Statistic for each factor:

fFactor=MSFactorMSErrorf_{Factor} = \frac{MS_{Factor}}{MS_{Error}}

F-statistics are calculated separately for each factor and interaction effect

Key Assumptions

Independence: Observations must be independent
Normality: Residuals should be normally distributed
Homoscedasticity: Equal variances across groups

Practical Example

Step 1: State the Data

Weight loss study examining effects of diet and exercise:

Raw Data:
DietExerciseWeight Loss (pounds)
Low-fatYes8, 10, 9
Low-fatNo6, 7, 8
High-fatYes5, 7, 6
High-fatNo3, 4, 5
Summary Statistics:
DietExerciseMeanN
Low-fatYes9.003
Low-fatNo7.003
High-fatYes6.003
High-fatNo4.003
Step 2: State Hypotheses

Main Effects:

  • Diet: H0:α1=α2=0H_0: \alpha_1 = \alpha_2 = 0
  • Exercise: H0:β1=β2=0H_0: \beta_1 = \beta_2 = 0

Interaction:

  • H0:(αβ)ij=0H_0: (\alpha\beta)_{ij} = 0 for all i,ji,j
Step 3: Calculate Test Statistics
SourcedfSSMSFp-value
Diet127.027.027.00.000826
Exercise112.012.012.00.008516
Diet:Exercise10.00.00.01.0000
Residuals881
Step 4: Draw Conclusions
  • Significant main effect of Diet (p = 0.000826)
  • Significant main effect of Exercise (p = 0.008516)
  • No significant interaction effect (p = 1.0000)
  • Diet and Exercise appear to have a significant effect on weight loss at α=0.05\alpha = 0.05
  • The interaction between Diet and Exercise are not statistically significant

Effect Size

Partial Eta-squared:

ηp2=SSFactorSSFactor+SSError\eta^2_p = \frac{SS_{Factor}}{SS_{Factor} + SS_{Error}}

For the example above,

  • Diet: ηp2=2727+8=0.77\eta^2_p = \frac{27}{27+8} = 0.77 (large effect)
  • Exercise: ηp2=1212+8=0.60\eta^2_p = \frac{12}{12+8} = 0.60 (large effect)
  • Interaction: ηp2=0.00\eta^2_p = 0.00 (no effect)

Code Examples

R
# Create the data
library(tidyverse)

data <- tibble(
  Diet = factor(rep(c("Low-fat", "High-fat"), each = 6)),
  Exercise = factor(rep(c("Yes", "No"), each = 3, times = 2)),
  WeightLoss = c(8, 10, 9, 6, 7, 8, 5, 7, 6, 3, 4, 5)
)

# Perform two-way ANOVA
model <- aov(WeightLoss ~ Diet * Exercise, data = data)

# Get summary
summary(model)


#------ Manual calculations ------#
# Compute the grand mean
grand_mean <- data |>
  summarize(grand_mean = mean(WeightLoss)) |>
  pull(grand_mean)

# Compute SS Total
ss_total <- data |>
  summarize(ss_total = sum((WeightLoss - grand_mean)^2)) |>
  pull(ss_total)

# Compute SS for Diet
ss_diet <- data |>
  group_by(Diet) |>
  summarize(group_mean = mean(WeightLoss), n = n()) |>
  ungroup() |>
  summarize(ss_diet = sum((group_mean - grand_mean)^2 * n)) |>
  pull(ss_diet)

# Compute SS for Exercise
ss_exercise <- data |>
  group_by(Exercise) |>
  summarize(group_mean = mean(WeightLoss), n = n()) |>
  ungroup() |>
  summarize(ss_exercise = sum((group_mean - grand_mean)^2 * n)) |>
  pull(ss_exercise)

# Compute SS Interaction
ss_interaction <- data |>
  group_by(Diet, Exercise) |>
  mutate(group_mean = mean(WeightLoss)) |>
  ungroup() |>
  group_by(Diet) |>
  mutate(diet_mean = mean(WeightLoss)) |>
  ungroup() |>
  group_by(Exercise) |>
  mutate(exercise_mean = mean(WeightLoss)) |>
  ungroup() |>
  mutate(interaction_term = (group_mean - diet_mean - exercise_mean + grand_mean)^2) |>
  summarize(ss_interaction = sum(interaction_term)) |>
  pull(ss_interaction)

ss_error <- ss_total - ss_diet - ss_exercise - ss_interaction

print(str_glue("SS total: {ss_total}"))
print(str_glue("SS diet: {ss_diet}"))
print(str_glue("SS exercise: {ss_exercise}"))
print(str_glue("SS interaction: {ss_interaction}"))
print(str_glue("SS error: {ss_error}"))

ms_diet = ss_diet / (2 - 1)
ms_exercise = ss_exercise / (2 - 1)
ms_error = ss_error / (2 * 2 * (3 - 1))

f_diet = ms_diet / ms_error
f_exercise = ms_exercise / ms_error
print(str_glue("F Diet: {f_diet}"))
print(str_glue("F Exercise: {f_exercise}"))
Python
import pandas as pd
import numpy as np
from scipy import stats

# Create the data
data = pd.DataFrame({
    'Diet': pd.Categorical(np.repeat(['Low-fat', 'High-fat'], 6)),
    'Exercise': pd.Categorical(np.tile(np.repeat(['Yes', 'No'], 3), 2)),
    'WeightLoss': [8, 10, 9, 6, 7, 8, 5, 7, 6, 3, 4, 5]
})

# Using statsmodels for ANOVA
import statsmodels.api as sm
from statsmodels.stats.anova import anova_lm

# Fit the model using statsmodels
model = sm.OLS.from_formula('WeightLoss ~ Diet + Exercise + Diet:Exercise', data=data)
fit = model.fit()
anova_table = anova_lm(fit, typ=2)
print("ANOVA results from statsmodels:")
print(anova_table)

#------ Manual calculations ------#
grand_mean = data['WeightLoss'].mean()
ss_total = np.sum((data['WeightLoss'] - grand_mean) ** 2)

# Compute SS for Diet
diet_means = data.groupby('Diet', observed=True)['WeightLoss'].agg(['mean', 'size']).reset_index()
ss_diet = np.sum((diet_means['mean'] - grand_mean) ** 2 * diet_means['size'])

# Compute SS for Exercise
exercise_means = data.groupby('Exercise', observed=True)['WeightLoss'].agg(['mean', 'size']).reset_index()
ss_exercise = np.sum((exercise_means['mean'] - grand_mean) ** 2 * exercise_means['size'])

# Compute SS Interaction
cell_means = data.groupby(['Diet', 'Exercise'], observed=True)['WeightLoss'].mean().reset_index()
cell_means = cell_means.merge(
    data.groupby('Diet', observed=True)['WeightLoss'].mean().reset_index().rename(columns={'WeightLoss': 'diet_mean'}),
    on='Diet'
)
cell_means = cell_means.merge(
    data.groupby('Exercise', observed=True)['WeightLoss'].mean().reset_index().rename(columns={'WeightLoss': 'exercise_mean'}),
    on='Exercise'
)

cell_means['interaction_term'] = (
    (cell_means['WeightLoss'] - cell_means['diet_mean'] - 
     cell_means['exercise_mean'] + grand_mean) ** 2
)
ss_interaction = cell_means['interaction_term'].sum()

ss_error = ss_total - ss_diet - ss_exercise - ss_interaction

# Calculate Mean Squares
df_diet = len(data['Diet'].unique()) - 1
df_exercise = len(data['Exercise'].unique()) - 1
df_interaction = df_diet * df_exercise
df_error = len(data) - (df_diet + 1) * (df_exercise + 1)

ms_diet = ss_diet / df_diet
ms_exercise = ss_exercise / df_exercise
ms_error = ss_error / df_error

# Calculate F statistics and p-values
f_diet = ms_diet / ms_error
f_exercise = ms_exercise / ms_error
p_diet = 1 - stats.f.cdf(f_diet, df_diet, df_error)
p_exercise = 1 - stats.f.cdf(f_exercise, df_exercise, df_error)

# Create ANOVA table
anova_manual = pd.DataFrame({
    'df': [df_diet, df_exercise, df_interaction, df_error],
    'sum_sq': [ss_diet, ss_exercise, ss_interaction, ss_error],
    'mean_sq': [ms_diet, ms_exercise, ss_interaction/df_interaction, ms_error],
    'F': [f_diet, f_exercise, (ss_interaction/df_interaction)/ms_error, np.nan],
    'PR(>F)': [p_diet, p_exercise, 
             1 - stats.f.cdf((ss_interaction/df_interaction)/ms_error, df_interaction, df_error), 
             np.nan]
}, index=['Diet', 'Exercise', 'Diet:Exercise', 'Residuals'])

print("ANOVA Table:")
print(anova_manual.round(4))

Verification