ANOVA (Analysis of Variance) › Worked Example: Comparing Fertilizers

Michael BrenndoerferJanuary 5, 20265 min read
Machine Learning from ScratchAll chapters
Part 2 of 9Hypothesis Testing
Chapter 17ANOVA (Analysis of Variance) › Worked Example: Comparing Fertilizers
Reading section 5 of 13 · Worked Example: Comparing Fertilizers · 5 minSection 5 of 13 · 5 min

Book contents

Section 5 of 13 · 5 min read

ANOVA (Analysis of Variance) › Worked Example: Comparing FertilizersOne section · Sections in this chapter5 / 13 ↓
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

A agricultural researcher tests three fertilizers to see if they produce different plant growth. She randomly assigns 10 plants to each fertilizer and measures growth (in cm) after 6 weeks.

In[4]:
Code
import numpy as np
from scipy import stats

# Plant growth data (cm) for three fertilizers
fertilizer_a = np.array([23, 25, 21, 24, 22, 26, 23, 24, 25, 22])
fertilizer_b = np.array([28, 31, 29, 30, 27, 32, 28, 29, 31, 30])
fertilizer_c = np.array([25, 27, 24, 26, 28, 25, 26, 27, 24, 26])

# Combine into groups
groups = [fertilizer_a, fertilizer_b, fertilizer_c]
group_names = ["Fertilizer A", "Fertilizer B", "Fertilizer C"]

# Step 1: Calculate basic statistics
k = len(groups)
ns = [len(g) for g in groups]
N = sum(ns)
means = [np.mean(g) for g in groups]
grand_mean = np.mean(np.concatenate(groups))

# Step 2: Calculate Sum of Squares Between (SSB)
ss_between = sum(n * (m - grand_mean) ** 2 for n, m in zip(ns, means))

# Step 3: Calculate Sum of Squares Within (SSW)
ss_within = sum(np.sum((g - m) ** 2) for g, m in zip(groups, means))

# Step 4: Calculate Total Sum of Squares (SST)
all_data = np.concatenate(groups)
ss_total = np.sum((all_data - grand_mean) ** 2)

# Verify decomposition: SST = SSB + SSW
assert np.isclose(ss_total, ss_between + ss_within)

# Step 5: Calculate degrees of freedom
df_between = k - 1
df_within = N - k
df_total = N - 1

# Step 6: Calculate Mean Squares
ms_between = ss_between / df_between
ms_within = ss_within / df_within

# Step 7: Calculate F-statistic
f_stat = ms_between / ms_within

# Step 8: Calculate p-value
p_value = stats.f.sf(f_stat, df_between, df_within)

# Step 9: Calculate effect size (eta-squared)
eta_squared = ss_between / ss_total

Let's trace through the calculation step by step:

Step 1: Basic Statistics

xˉA=23.5,xˉB=29.5,xˉC=25.8\bar{x}_A = 23.5, \quad \bar{x}_B = 29.5, \quad \bar{x}_C = 25.8 xˉ=23.5×10+29.5×10+25.8×1030=26.27\bar{x} = \frac{23.5 \times 10 + 29.5 \times 10 + 25.8 \times 10}{30} = 26.27

Step 2: Sum of Squares Between

SSBetween=∑j=13nj(xˉj−xˉ)2=10(23.5−26.27)2+10(29.5−26.27)2+10(25.8−26.27)2SS_{Between} = \sum_{j=1}^{3} n_j(\bar{x}_j - \bar{x})^2 = 10(23.5 - 26.27)^2 + 10(29.5 - 26.27)^2 + 10(25.8 - 26.27)^2

Step 3: Sum of Squares Within

SSWithin=∑j=13∑i=110(xij−xˉj)2SS_{Within} = \sum_{j=1}^{3}\sum_{i=1}^{10}(x_{ij} - \bar{x}_j)^2

Step 4: Mean Squares and F-Statistic

MSBetween=SSBetweenk−1=SSBetween2MS_{Between} = \frac{SS_{Between}}{k-1} = \frac{SS_{Between}}{2} MSWithin=SSWithinN−k=SSWithin27MS_{Within} = \frac{SS_{Within}}{N-k} = \frac{SS_{Within}}{27} F=MSBetweenMSWithinF = \frac{MS_{Between}}{MS_{Within}}
Out[5]:
Console
One-Way ANOVA: Effect of Fertilizer on Plant Growth
=================================================================

Group Statistics:
-----------------------------------------------------------------
  Fertilizer A: n = 10, mean = 23.50 cm
  Fertilizer B: n = 10, mean = 29.50 cm
  Fertilizer C: n = 10, mean = 25.80 cm
  Grand mean: 26.27 cm

ANOVA Table:
-----------------------------------------------------------------
Source                  SS     df         MS          F      p-value
-----------------------------------------------------------------
Between             183.27      2      91.63      40.83     6.87e-09
Within               60.60     27       2.24
Total               243.87     29
-----------------------------------------------------------------

Effect size (eta-squared): 0.752
  Interpretation: 75.2% of variance in growth is explained by fertilizer type

Decision (alpha = 0.05):
-----------------------------------------------------------------
F(2, 27) = 40.83, p < 0.001
Reject H_0: At least one fertilizer produces different growth
In[6]:
Code
# Verify our calculation with scipy
f_scipy, p_scipy = stats.f_oneway(fertilizer_a, fertilizer_b, fertilizer_c)
print("Verification with scipy.stats.f_oneway:")
print(f"  F = {f_scipy:.2f}, p = {p_scipy:.2e}")
Out[6]:
Console
Verification with scipy.stats.f_oneway:
  F = 40.83, p = 6.87e-09
Out[7]:
Visualization
Boxplots showing plant growth distributions for three fertilizers.
Boxplots showing the distribution of plant growth for each fertilizer, with group means marked. Fertilizer B clearly produces taller plants.
F-distribution plot with observed F-statistic in rejection region.
The F-distribution with the observed F-statistic far into the rejection region, confirming the significant difference.

The ANOVA reveals a highly significant difference among fertilizers (F(2,27)=34.62F(2, 27) = 34.62, p<0.001p < 0.001). The effect size η2=0.72\eta^2 = 0.72 indicates that 72% of the variation in plant growth is explained by fertilizer type, a very large effect.