Two-Way ANOVA
Calculator
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:
where is the grand mean
where is the mean of level of Factor A, and is the number of levels in Factor B.
where is the mean of level $j$ of Factor B, and $a$ is the number of levels in Factor A.
Where:
- = Sum of Squares for Factor A, where is the number of levels in Factor A
- = Sum of Squares for Factor B, where is the number of levels in Factor B
- = Sum of Squares for interaction with
- = Residual Sum of Squares,
Mean Square:
F-Statistic for each factor:
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:
Diet | Exercise | Weight Loss (pounds) |
---|---|---|
Low-fat | Yes | 8, 10, 9 |
Low-fat | No | 6, 7, 8 |
High-fat | Yes | 5, 7, 6 |
High-fat | No | 3, 4, 5 |
Summary Statistics:
Diet | Exercise | Mean | N |
---|---|---|---|
Low-fat | Yes | 9.00 | 3 |
Low-fat | No | 7.00 | 3 |
High-fat | Yes | 6.00 | 3 |
High-fat | No | 4.00 | 3 |
Step 2: State Hypotheses
Main Effects:
- Diet:
- Exercise:
Interaction:
- for all
Step 3: Calculate Test Statistics
Source | df | SS | MS | F | p-value |
---|---|---|---|---|---|
Diet | 1 | 27.0 | 27.0 | 27.0 | 0.000826 |
Exercise | 1 | 12.0 | 12.0 | 12.0 | 0.008516 |
Diet:Exercise | 1 | 0.0 | 0.0 | 0.0 | 1.0000 |
Residuals | 8 | 8 | 1 |
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
- The interaction between Diet and Exercise are not statistically significant
Effect Size
Partial Eta-squared:
For the example above,
- Diet: (large effect)
- Exercise: (large effect)
- Interaction: (no effect)
Code Examples
R
1# Create the data
2library(tidyverse)
3
4data <- tibble(
5 Diet = factor(rep(c("Low-fat", "High-fat"), each = 6)),
6 Exercise = factor(rep(c("Yes", "No"), each = 3, times = 2)),
7 WeightLoss = c(8, 10, 9, 6, 7, 8, 5, 7, 6, 3, 4, 5)
8)
9
10# Perform two-way ANOVA
11model <- aov(WeightLoss ~ Diet * Exercise, data = data)
12
13# Get summary
14summary(model)
15
16
17#------ Manual calculations ------#
18# Compute the grand mean
19grand_mean <- data |>
20 summarize(grand_mean = mean(WeightLoss)) |>
21 pull(grand_mean)
22
23# Compute SS Total
24ss_total <- data |>
25 summarize(ss_total = sum((WeightLoss - grand_mean)^2)) |>
26 pull(ss_total)
27
28# Compute SS for Diet
29ss_diet <- data |>
30 group_by(Diet) |>
31 summarize(group_mean = mean(WeightLoss), n = n()) |>
32 ungroup() |>
33 summarize(ss_diet = sum((group_mean - grand_mean)^2 * n)) |>
34 pull(ss_diet)
35
36# Compute SS for Exercise
37ss_exercise <- data |>
38 group_by(Exercise) |>
39 summarize(group_mean = mean(WeightLoss), n = n()) |>
40 ungroup() |>
41 summarize(ss_exercise = sum((group_mean - grand_mean)^2 * n)) |>
42 pull(ss_exercise)
43
44# Compute SS Interaction
45ss_interaction <- data |>
46 group_by(Diet, Exercise) |>
47 mutate(group_mean = mean(WeightLoss)) |>
48 ungroup() |>
49 group_by(Diet) |>
50 mutate(diet_mean = mean(WeightLoss)) |>
51 ungroup() |>
52 group_by(Exercise) |>
53 mutate(exercise_mean = mean(WeightLoss)) |>
54 ungroup() |>
55 mutate(interaction_term = (group_mean - diet_mean - exercise_mean + grand_mean)^2) |>
56 summarize(ss_interaction = sum(interaction_term)) |>
57 pull(ss_interaction)
58
59ss_error <- ss_total - ss_diet - ss_exercise - ss_interaction
60
61print(str_glue("SS total: {ss_total}"))
62print(str_glue("SS diet: {ss_diet}"))
63print(str_glue("SS exercise: {ss_exercise}"))
64print(str_glue("SS interaction: {ss_interaction}"))
65print(str_glue("SS error: {ss_error}"))
66
67ms_diet = ss_diet / (2 - 1)
68ms_exercise = ss_exercise / (2 - 1)
69ms_error = ss_error / (2 * 2 * (3 - 1))
70
71f_diet = ms_diet / ms_error
72f_exercise = ms_exercise / ms_error
73print(str_glue("F Diet: {f_diet}"))
74print(str_glue("F Exercise: {f_exercise}"))
Python
1import pandas as pd
2import numpy as np
3from scipy import stats
4
5# Create the data
6data = pd.DataFrame({
7 'Diet': pd.Categorical(np.repeat(['Low-fat', 'High-fat'], 6)),
8 'Exercise': pd.Categorical(np.tile(np.repeat(['Yes', 'No'], 3), 2)),
9 'WeightLoss': [8, 10, 9, 6, 7, 8, 5, 7, 6, 3, 4, 5]
10})
11
12# Using statsmodels for ANOVA
13import statsmodels.api as sm
14from statsmodels.stats.anova import anova_lm
15
16# Fit the model using statsmodels
17model = sm.OLS.from_formula('WeightLoss ~ Diet + Exercise + Diet:Exercise', data=data)
18fit = model.fit()
19anova_table = anova_lm(fit, typ=2)
20print("ANOVA results from statsmodels:")
21print(anova_table)
22
23#------ Manual calculations ------#
24grand_mean = data['WeightLoss'].mean()
25ss_total = np.sum((data['WeightLoss'] - grand_mean) ** 2)
26
27# Compute SS for Diet
28diet_means = data.groupby('Diet', observed=True)['WeightLoss'].agg(['mean', 'size']).reset_index()
29ss_diet = np.sum((diet_means['mean'] - grand_mean) ** 2 * diet_means['size'])
30
31# Compute SS for Exercise
32exercise_means = data.groupby('Exercise', observed=True)['WeightLoss'].agg(['mean', 'size']).reset_index()
33ss_exercise = np.sum((exercise_means['mean'] - grand_mean) ** 2 * exercise_means['size'])
34
35# Compute SS Interaction
36cell_means = data.groupby(['Diet', 'Exercise'], observed=True)['WeightLoss'].mean().reset_index()
37cell_means = cell_means.merge(
38 data.groupby('Diet', observed=True)['WeightLoss'].mean().reset_index().rename(columns={'WeightLoss': 'diet_mean'}),
39 on='Diet'
40)
41cell_means = cell_means.merge(
42 data.groupby('Exercise', observed=True)['WeightLoss'].mean().reset_index().rename(columns={'WeightLoss': 'exercise_mean'}),
43 on='Exercise'
44)
45
46cell_means['interaction_term'] = (
47 (cell_means['WeightLoss'] - cell_means['diet_mean'] -
48 cell_means['exercise_mean'] + grand_mean) ** 2
49)
50ss_interaction = cell_means['interaction_term'].sum()
51
52ss_error = ss_total - ss_diet - ss_exercise - ss_interaction
53
54# Calculate Mean Squares
55df_diet = len(data['Diet'].unique()) - 1
56df_exercise = len(data['Exercise'].unique()) - 1
57df_interaction = df_diet * df_exercise
58df_error = len(data) - (df_diet + 1) * (df_exercise + 1)
59
60ms_diet = ss_diet / df_diet
61ms_exercise = ss_exercise / df_exercise
62ms_error = ss_error / df_error
63
64# Calculate F statistics and p-values
65f_diet = ms_diet / ms_error
66f_exercise = ms_exercise / ms_error
67p_diet = 1 - stats.f.cdf(f_diet, df_diet, df_error)
68p_exercise = 1 - stats.f.cdf(f_exercise, df_exercise, df_error)
69
70# Create ANOVA table
71anova_manual = pd.DataFrame({
72 'df': [df_diet, df_exercise, df_interaction, df_error],
73 'sum_sq': [ss_diet, ss_exercise, ss_interaction, ss_error],
74 'mean_sq': [ms_diet, ms_exercise, ss_interaction/df_interaction, ms_error],
75 'F': [f_diet, f_exercise, (ss_interaction/df_interaction)/ms_error, np.nan],
76 'PR(>F)': [p_diet, p_exercise,
77 1 - stats.f.cdf((ss_interaction/df_interaction)/ms_error, df_interaction, df_error),
78 np.nan]
79}, index=['Diet', 'Exercise', 'Diet:Exercise', 'Residuals'])
80
81print("ANOVA Table:")
82print(anova_manual.round(4))
Alternative Tests
When assumptions are violated:
- Aligned Rank Transform ANOVA: For non-normal data
- Scheirer-Ray-Hare Test: Non-parametric alternative
Related Calculators
One-Way ANOVA Calculator
Independent T Test Calculator
Repeated Measures ANOVA Calculator
ANCOVA Calculator
Help us improve
Found an error or have a suggestion? Let us know!