Hypothesis Testing
A new checkout page has a higher average order value than the old one. Is the new page better, or did it get lucky? Hypothesis testing gives a disciplined way to answer.
The logic
- State the null hypothesis: there is no real difference.
- State the alternative hypothesis: there is a difference.
- Choose a significance level, written alpha, before looking at the data. 0.05 is a common choice.
- Calculate a p-value: the probability of seeing a difference at least as large as yours if the null hypothesis were true.
- If the p-value is below alpha, reject the null hypothesis.
A p-value is not the probability that the null hypothesis is true. It describes how surprising the data would be if there were no effect.
Comparing two means: the t-test
import numpy as np
from scipy import stats
rng = np.random.default_rng(5)
old_page = rng.normal(loc=50, scale=15, size=200)
new_page = rng.normal(loc=54, scale=15, size=200)
t_stat, p_value = stats.ttest_ind(new_page, old_page, equal_var=False)
print("old mean:", round(old_page.mean(), 2))
print("new mean:", round(new_page.mean(), 2))
print("t statistic:", round(t_stat, 2))
print("p-value:", round(p_value, 4))
print("significant at 0.05:", p_value < 0.05)
equal_var=False runs Welch’s t-test, which does not assume that the two groups have the same variance. It is the safer default.
Use stats.ttest_rel when the two sets of measurements come from the same subjects, such as before and after.
Comparing proportions: the chi-square test
For counts in categories, such as how many visitors bought on each page, use a chi-square test on the table of counts.
from scipy import stats
# bought did not buy
table = [[ 120, 880], # old page
[ 150, 850]] # new page
chi2, p_value, dof, expected = stats.chi2_contingency(table)
print("conversion old:", 120 / 1000, " new:", 150 / 1000)
print("p-value:", round(p_value, 4))
The new page converted 15% of visitors against 12%, yet the p-value is just above 0.05. With 1,000 visitors per page, a difference of this size could still plausibly be chance, so you would keep the test running before deciding.
Two kinds of error
| The null hypothesis is true | The null hypothesis is false | |
|---|---|---|
| You reject it | Type I error (false positive) | Correct |
| You do not reject it | Correct | Type II error (false negative) |
Alpha is the rate of Type I errors you accept. Power is the probability of detecting a real effect, and it rises with sample size and with the size of the effect.
Significant is not the same as important
With enough data, a tiny difference becomes statistically significant. Always report the size of the effect and a confidence interval, not only the p-value.
import numpy as np
from scipy import stats
rng = np.random.default_rng(8)
a = rng.normal(50.0, 15, 200_000)
b = rng.normal(50.3, 15, 200_000)
print("difference in means:", round(b.mean() - a.mean(), 2))
print("p-value:", stats.ttest_ind(b, a, equal_var=False).pvalue)
The difference is tiny, yet the p-value is far below 0.05.
Common mistakes
- Testing many things and reporting the one that worked. With 20 tests at alpha 0.05, one false positive is expected by chance alone.
- Stopping the test as soon as the result looks significant. Decide the sample size in advance.
- Treating “not significant” as “no effect”. You may simply not have enough data.
Practice
Change the new page’s mean from 54 to 51 and run the t-test again. Is the result still significant with 200 visitors per page? What about with 2,000?