A Beginner‘s Guide to ANOVA in Python for Data Science (with Updated COVID-19 Case Study)
Introduction
Analysis of variance, or ANOVA, is a crucial statistical technique that every data scientist should have in their toolbox. ANOVA allows you to determine whether the means of three or more groups are statistically different. It is an extension of the t-test, which is limited to only comparing the means of two groups.
ANOVA is commonly used for A/B testing of multiple groups, such as comparing click-through rates of different versions of a website or ad copy. It can determine if any of the versions performed statistically better or worse than the others. ANOVA is a powerful technique that helps you make data-driven decisions.
In this tutorial, we‘ll dive into the details of how ANOVA works and walk through examples of running different types of ANOVA tests in Python. We‘ll use a real COVID-19 dataset to see how ANOVA can uncover insights from the data. Let‘s get started!
Understanding the Fundamentals of ANOVA
The core idea behind ANOVA is to compare the amount of variance between groups to the amount of variance within groups. If the ratio of between-group variance to within-group variance is high, we can conclude the groups are statistically different.
Mathematically, the one-way ANOVA test statistic is calculated as:
F = variance between groups / variance within groups
Where:
- variance between groups = Σ(mean of group – grand mean)^2 / (number of groups – 1)
- variance within groups = Σ(value – mean of group)^2 / (total values – number of groups)
The p-value is then calculated from the F-statistic to determine statistical significance. If the p-value is below a threshold like 0.05, we reject the null hypothesis that the group means are equal.
There are a few key assumptions and requirements that must be met for ANOVA:
-
Independence of observations: The samples should be randomly selected and independent of each other. Repeated measures designs require different tests.
-
Normality: The data for each group should be approximately normally distributed. This can be checked with a Shapiro-Wilk test or Q-Q plot. Some deviation from normality is okay if sample sizes are large.
-
Homogeneity of variances: The variance within each group should be approximately equal. This can be verified with Levene‘s test. If variances are unequal, a Welch‘s ANOVA can be used instead.
Types of ANOVA Tests
There are several types of ANOVA that handle different experimental designs:
-
One-way ANOVA: Tests differences between the means of three or more groups on one independent variable (e.g. comparing test scores between students in different classes).
-
Two-way ANOVA: Compares the means of groups defined by two independent variables (e.g. comparing sales across different regions and different products). It can test for main effects of each variable and their interaction effect.
-
Three-way ANOVA: Extends the two-way ANOVA to three independent variables.
-
Repeated measures ANOVA: Used when the same subjects are measured across different time points or conditions. It can reduce variance from individual differences.
In this tutorial, we‘ll focus on the most commonly used one-way and two-way ANOVA. These form the foundation for understanding the higher-order ANOVA techniques.
COVID-19 Case Study: Analyzing Differences Across States
To solidify our understanding of ANOVA, let‘s walk through a case study using real data on the COVID-19 pandemic that has impacted the world in recent years. We‘ll use data from an open source repository on Kaggle that provides daily case numbers across different states in India.
Our goal is to analyze if there are statistically significant differences in COVID-19 cases between states. We‘ll group states into different density categories (e.g. low, medium, high density) and check if case numbers vary between these groups. This could provide insights on if population density influences the spread of the virus.
First, let‘s import the required Python libraries and load the data into a pandas DataFrame:
import pandas as pd
import scipy.stats as stats
data = pd.read_csv("covid_india.csv")
data.head()
Next, we‘ll define a function to categorize states into density groups based on their population per square kilometer:
def density_group(row):
density = row[‘population‘] / row[‘area‘]
if density < 200:
return ‘low‘
elif density < 1000:
return ‘medium‘
else:
return ‘high‘
data[‘density_group‘] = data.apply(density_group, axis=1)
Now we have a new column that groups the states by density. Let‘s select the columns we need and drop any rows with missing ‘cases‘ data:
data = data[[‘state‘, ‘density_group‘, ‘cases‘]]
data.dropna(subset=[‘cases‘], inplace=True)
We can visualize the distribution of COVID cases within each density group using box plots:
import matplotlib.pyplot as plt
plt.figure(figsize=(10,6))
data.boxplot(‘cases‘, by=‘density_group‘)
plt.ylabel(‘Number of Cases‘)
plt.title(‘COVID Cases by Population Density‘)
plt.show()
The box plots show the cases tend to be higher in the high density states compared to low and medium density ones. However, to determine if these differences are statistically significant, we need to run an ANOVA test.
One-Way ANOVA Test
We‘ll start with a one-way ANOVA to test if the mean cases are equal across the three density groups. The hypotheses are:
- H0 (null hypothesis): The mean cases are equal across all density groups
- HA (alternate hypothesis): At least one density group has a different mean cases
We can easily run a one-way ANOVA in Python using the f_oneway function from scipy.stats:
f_stat, p_value = stats.f_oneway(
data[data[‘density_group‘] == ‘low‘][‘cases‘],
data[data[‘density_group‘] == ‘medium‘][‘cases‘],
data[data[‘density_group‘] == ‘high‘][‘cases‘])
print(f‘F-statistic: {f_stat:.3f}, p-value: {p_value:.3f}‘)
Output:
F-statistic: 18.915, p-value: 0.000
The p-value is much lower than 0.05, so we reject the null hypothesis. This suggests there is a statistically significant difference in mean COVID cases between the density groups.
To determine which specific groups are different, we can run a post-hoc Tukey‘s HSD test:
from statsmodels.stats.multicomp import pairwise_tukeyhsd
m_comp = pairwise_tukeyhsd(endog=data[‘cases‘], groups=data[‘density_group‘], alpha=0.05)
print(m_comp)
Output:
Multiple Comparison of Means - Tukey HSD, FWER=0.05
=====================================================
group1 group2 meandiff p-adj lower upper reject
-----------------------------------------------------
high low 2010.7 0.0 1243.3 2778.1 True
high medium 850.3 0.004 164.3 1536.2 True
low medium -1160.4 0.001 -1946.6 -374.3 True
-----------------------------------------------------
The Tukey HSD test shows that all pairwise group comparisons are significantly different (p-adj < 0.05). The high density group has the highest mean cases, followed by medium, then low density.
Two-Way ANOVA Test
We can extend our analysis to a two-way ANOVA that considers an additional factor beyond population density. Let‘s say we want to test if mean COVID cases differs by both density group and testing rate (‘tests per million‘). We‘ll categorize testing rate into ‘low‘ and ‘high‘ groups.
First we categorize the testing rate:
def testing_group(row):
testing_rate = row[‘tests‘] / row[‘population‘] * 1e6
if testing_rate < 10000:
return ‘low‘
else:
return ‘high‘
data[‘testing_group‘] = data.apply(testing_group, axis=1)
Now we can run the two-way ANOVA using the anova_lm function from statsmodels:
from statsmodels.formula.api import ols
from statsmodels.stats.anova import anova_lm
formula = ‘cases ~ C(density_group) + C(testing_group) + C(density_group):C(testing_group)‘
model = ols(formula, data).fit()
anova_table = anova_lm(model, typ=2)
print(anova_table)
Output:
sum_sq df F PR(>F)
C(density_group) 1.275257e+07 2.0 8.075641 0.000379
C(testing_group) 1.328749e+06 1.0 1.682431 0.196110
C(density_group):C(testing_group) 1.169545e+06 2.0 0.740550 0.478133
Residual 2.090968e+08 265.0 NaN NaN
The two-way ANOVA table shows:
- There is a significant main effect of density group on COVID cases (p < 0.05)
- There is no significant main effect of testing group on cases (p > 0.05)
- There is no significant interaction effect between density and testing group (p > 0.05)
So while population density impacts COVID cases, testing rate does not seem to after accounting for density. There is also no evidence that density and testing rate interact in their effect on cases.
Conclusion
In this tutorial, we learned how ANOVA can be used to compare means across multiple groups and determine if they are statistically different. We walked through the theory behind one-way and two-way ANOVA and demonstrated how to run these tests in Python.
Through the COVID-19 case study, we saw how ANOVA identified that states with higher population density had significantly higher numbers of cases compared to lower density states. This provides evidence for an association between density and viral spread. A two-way ANOVA further revealed that testing rate did not impact case numbers after accounting for density.
ANOVA is a versatile tool for data scientists to analyze differences between groups. It can be applied for problems like:
- A/B testing different versions of a product or website
- Analyzing factors that impact customer churn rate
- Comparing election outcomes across different demographics
- Determining if different teaching methods affect student performance
By mastering ANOVA, you can unlock valuable insights from your data and make confident decisions. The Python libraries like scipy and statsmodels make it straightforward to run ANOVA tests on your own data.
Some important things to keep in mind when using ANOVA:
- Ensure your data meets the assumptions of independence, normality, and equal variances
- Consider practical significance in addition to statistical significance. A large enough sample size may cause a small difference to be statistically significant.
- Control the familywise error rate when running multiple comparisons. Tukey‘s HSD test is one approach for this.
- Experiment with data transformations if your data is not normally distributed. A log or Box-Cox transform can sometimes help.
I encourage you to try applying ANOVA to your own datasets and see what insights you discover. Feel free to use the code from this tutorial as a starting point. Happy analyzing!
