One-Way ANOVA
ANOVA (Analysis of Variance) is a statistical method used to compare the means of three or more groups to determine whether at least one group mean is significantly different from the others.
If the difference in means is only due to random variation, we accept the null hypothesis (H₀).
If the differences are larger than what would be expected by chance, we reject H₀ and conclude that at least one group mean is different.
Why not use multiple t-tests?
Running multiple t-tests between groups increases the risk of Type I error (false positives). ANOVA solves this by testing all groups at once.
Types of ANOVA
There are two common types:
One-way ANOVA
Two-way ANOVA
One-way ANOVA is used when we want to test the effect of a single independent variable (factor) on a continuous dependent variable.
Example: Testing the effect of different diets (Diet A, Diet B, Diet C) on weight loss. Here, diet is the factor.
Two-way ANOVA is used when we want to examine the effect of two independent variables (factors) on a continuous dependent variable. It also tests whether there is an interaction between the two factors.
Example: Testing the effect of diets (Diet A, Diet B, Diet C) and exercise levels (Low, Medium, High) on weight loss.
Independent Variable 1: Diet
Independent Variable 2: Exercise
Let’s import numpy as np.
we use NumPy for working with arrays and numerical operations.
we also import scipy.stats as stats, which contains statistical functions like ANOVA, t-tests, normality test, e.t.c.
import numpy as np
import scipy.stats as stats
Here, we set the significance level at 5%. i.e 0.05
alpha = 0.05
Example 1 - Manual
These lists hold the batting averages of three baseball teams (Team A, Team B, Team C).
Each number is a player’s batting average.
We’ll use these data sets as the groups to compare in the ANOVA test.
# Baseball team batting averages
team_A = [0.285, 0.270, 0.290, 0.300, 0.275, 0.295, 0.280, 0.265, 0.285]
team_B = [0.260, 0.250, 0.245, 0.255, 0.270, 0.265, 0.250, 0.240, 0.255]
team_C = [0.305, 0.295, 0.310, 0.320, 0.300, 0.315, 0.290, 0.305, 0.315]
This runs the Shapiro-Wilk test on each team’s data to check if the data is normally distributed.
Returns a
p-value
for each team:If
p-value > alpha (0.05)
: data is likely normal.If
p-value < alpha
: data is not normal (assumption violated).
# Step 1 - Shapiro-Wilk test for normality
_, p_value_shapiro_A = stats.shapiro(team_A)
_, p_value_shapiro_B = stats.shapiro(team_B)
_, p_value_shapiro_C = stats.shapiro(team_C)
print(p_value_shapiro_A)

print(p_value_shapiro_B)

# Step 2 Levene's test for equal variances
_, p_value_levene_test = stats.levene(team_A, team_B, team_C)
print(p_value_levene_test)

np.mean(team_X)
: Calculates the mean batting average for each team (A, B, C).overall_mean
: Calculates the grand mean (mean of all players across all teams).These means help measure how much each group differs from the overall average in ANOVA.
# Step 3: Calculate group means and overall mean
mean_A = np.mean(team_A)
mean_B = np.mean(team_B)
mean_C = np.mean(team_C)
overall_mean = np.mean(team_A + team_B + team_C)
print(mean_A)

print(mean_B)

print(mean_C)

print(overall_mean)

SSB (Sum of Squares Between): Measures how much the group means differ from the overall mean.
For each team:
Take the difference between the team mean and overall mean, square it, and multiply by the team size.
Add these up for all teams to get SSB.
Larger SSB means bigger differences between groups.
# Step 4: Calculate SSB (Between-group sum of squares)
SSB = len(team_A) * (mean_A - overall_mean)**2 + len(team_B) * (mean_B - overall_mean)**2 + len(team_C) * (mean_C - overall_mean)**2
print(SSB)

SSW (Sum of Squares Within): Measures variability inside each group (how much individual scores differ from their group mean).
Calculate the squared differences between each player’s average and their team mean, then sum them for each team.
Add all teams’ sums to get total SSW.
Smaller SSW means less variation within groups.
# Step 5: Calculate SSW (Within-group sum of squares)
SSW_A = sum((x - mean_A)**2 for x in team_A)
SSW_B = sum((x - mean_B)**2 for x in team_B)
SSW_C = sum((x - mean_C)**2 for x in team_C)
SSW = SSW_A + SSW_B + SSW_C
print(SSW_A)

print(SSW_B)

print(SSW)

Degrees of Freedom Between (df_between): Number of groups minus 1 (3 – 1 = 2).
Degrees of Freedom Within (df_within): Total number of observations minus number of groups.
These are needed to calculate mean squares and the F-statistic in ANOVA
# Step 6: Degrees of freedom
df_between = 3 - 1 # 3 groups
df_within = len(team_A + team_B + team_C) - 3 # Total samples - number of groups
MSB (Mean Square Between): Average variance between groups (SSB divided by df_between).
MSW (Mean Square Within): Average variance within groups (SSW divided by df_within).
These are used to calculate the F-statistic.
# Step 7: Calculate MSB and MSW
MSB = SSB / df_between
MSW = SSW / df_within
print(MSB)

print(MSW)

# Step 8: Calculate F-statistic
F_statistic_manual = MSB / MSW
print(F_statistic_manual)

Here, we calculate the p-value manually using the F-distribution’s survival function
# Step 9: Calculate the p-value manually using the F-distribution's survival function
p_value_manual = stats.f.sf(F_statistic_manual, df_between, df_within)
print(p_value_manual)

Example 2
pegasus = [220, 215, 225, 230, 235, 240] # Marathon times in minutes for Pegasus shoes
vaporfly = [210, 205, 215, 200, 195, 220] # Marathon times in minutes for Vaporfly shoes
speedgoats = [250, 245, 255, 260, 240, 270] # Marathon times in minutes for Speedgoats shoes
The data in each group should be roughly normally distributed, which is particularly important when sample sizes are small. However, for larger samples, ANOVA remains reliable even if normality is slightly violated because of the Central Limit Theorem.
stat, shapiro_pvalue_pegasus = stats.shapiro(pegasus)
print(shapiro_pvalue_pegasus)

if shapiro_pvalue_pegasus > alpha:
print("The data is likely normally distributed (fail to reject H0).")
else:
print("The data is NOT normally distributed (reject H0).")

stat, shapiro_pvalue_vaporfly = stats.shapiro(vaporfly)
print(shapiro_pvalue_vaporfly)

if shapiro_pvalue_vaporfly > alpha:
print("The data is likely normally distributed (fail to reject H0).")
else:
print("The data is NOT normally distributed (reject H0).")

stat, shapiro_pvalue_speedgoats = stats.shapiro(speedgoats)
print(shapiro_pvalue_speedgoats)

if shapiro_pvalue_speedgoats > alpha:
print("The data is likely normally distributed (fail to reject H0).")
else:
print("The data is NOT normally distributed (reject H0).")

# Perform Levene's test for homogeneity of variances
stat, pvalue_levene = stats.levene(pegasus, vaporfly, speedgoats)
print(pvalue_levene)

if pvalue_levene < alpha:
print("Reject the null hypothesis, different variance")
else:
print("Fail to reject the null hypothesis, same variance")

# Perform one-way ANOVA
F_statistic, pvalue_anova = stats.f_oneway(pegasus, vaporfly, speedgoats)
print(pvalue_anova)

if pvalue_anova < alpha:
print("Reject the null hypothesis, different means")
else:
print("Fail to reject the null hypothesis, same mean")

Ryan is a Data Scientist at a fintech company, where he focuses on fraud prevention in underwriting and risk. Before that, he worked as a Data Analyst at a tax software company. He holds a degree in Electrical Engineering from UCF.