Statistical Hypothesis Testing
Overview
Hypothesis testing provides a framework for making data-driven decisions by testing whether observed differences are statistically significant or due to chance.
Testing Framework
- Null Hypothesis (H0): No effect or difference exists
- Alternative Hypothesis (H1): Effect or difference exists
- Significance Level (α): Threshold for rejecting H0 (typically 0.05)
- P-value: Probability of observing data if H0 is true
Common Tests
- T-test: Compare means between two groups
- ANOVA: Compare means across multiple groups
- Chi-square: Test independence of categorical variables
- Mann-Whitney U: Non-parametric alternative to t-test
- Kruskal-Wallis: Non-parametric alternative to ANOVA
Implementation with Python
import pandas as pd
import numpy as np
from scipy import stats
import matplotlib.pyplot as plt
# Sample data
group_a = np.random.normal(100, 15, 50) # Mean=100, SD=15
group_b = np.random.normal(105, 15, 50) # Mean=105, SD=15
# Test 1: Independent samples t-test
t_stat, p_value = stats.ttest_ind(group_a, group_b)
print(f"T-test: t={t_stat:.4f}, p-value={p_value:.4f}")
if p_value < 0.05:
print("Reject null hypothesis: Groups are significantly different")
else:
print("Fail to reject null hypothesis: No significant difference")
# Test 2: Paired t-test (same subjects, two conditions)
before = np.array([85, 90, 88, 92, 87, 89, 91, 86, 88, 90])
after = np.array([92, 95, 91, 98, 94, 96, 99, 93, 95, 97])
t_stat, p_value = stats.ttest_rel(before, after)
print(f"\nPaired t-test: t={t_stat:.4f}, p-value={p_value:.4f}")
# Test 3: One-way ANOVA (multiple groups)
group1 = np.random.normal(100, 10, 30)
group2 = np.random.normal(105, 10, 30)
group3 = np.random.normal(102, 10, 30)
f_stat, p_value = stats.f_oneway(group1, group2, group3)
print(f"\nANOVA: F={f_stat:.4f}, p-value={p_value:.4f}")
# Test 4: Chi-square test (categorical variables)
# Create contingency table
contingency = np.array([
[50, 30], # Control: success, failure
[45, 35] # Treatment: success, failure
])
chi2, p_value, dof, expected = stats.chi2_contingency(contingency)
print(f"\nChi-square: χ²={chi2:.4f}, p-value={p_value:.4f}")
# Test 5: Mann-Whitney U test (non-parametric)
u_stat, p_value = stats.mannwhitneyu(group_a, group_b)
print(f"\nMann-Whitney U: U={u_stat:.4f}, p-value={p_value:.4f}")
# Visualization
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
# Distribution comparison
axes[0, 0].hist(group_a, alpha=0.5, label='Group A', bins=20)
axes[0, 0].hist(group_b, alpha=0.5, label='Group B', bins=20)
axes[0, 0].set_title('Group Distributions')
axes[0, 0].legend()
# Q-Q plot for normality
stats.probplot(group_a, dist="norm", plot=axes[0, 1])
axes[0, 1].set_title('Q-Q Plot (Group A)')
# Before/After comparison
axes[1, 0].plot(before, 'o-', label='Before', alpha=0.7)
axes[1, 0].plot(after, 's-', label='After', alpha=0.7)
axes[1, 0].set_title('Paired Comparison')
axes[1, 0].legend()
# Effect size (Cohen's d)
cohens_d = (np.mean(group_a) - np.mean(group_b)) / np.sqrt(
((len(group_a)-1)*np.var(group_a, ddof=1) +
(len(group_b)-1)*np.var(group_b, ddof=1)) /
(len(group_a) + len(group_b) - 2)
)
axes[1, 1].text(0.5, 0.5, f"Cohen's d = {cohens_d:.4f}",
ha='center', va='center', fontsize=14)
axes[1, 1].axis('off')
plt.tight_layout()
plt.show()
# Normality test (Shapiro-Wilk)
stat, p = stats.shapiro(group_a)
print(f"\nShapiro-Wilk normality test: W={stat:.4f}, p-value={p:.4f}")
# Effect size calculation
def calculate_effect_size(group1, group2):
n1, n2 = len(group1), len(group2)
var1, var2 = np.var(group1, ddof=1), np.var(group2, ddof=1)
pooled_std = np.sqrt(((n1-1)*var1 + (n2-1)*var2) / (n1+n2-2))
cohens_d = (np.mean(group1) - np.mean(group2)) / pooled_std
return cohens_d
effect_size = calculate_effect_size(group_a, group_b)
print(f"Effect size (Cohen's d): {effect_size:.4f}")
# Confidence intervals
from scipy.stats import t as t_dist
def calculate_ci(data, confidence=0.95):
n = len(data)
mean = np.mean(data)
se = np.std(data, ddof=1) / np.sqrt(n)
margin = t_dist.ppf((1 + confidence) / 2, n - 1) * se
return mean - margin, mean + margin
ci = calculate_ci(group_a)
print(f"95% CI for Group A: ({ci[0]:.2f}, {ci[1]:.2f})")
# Additional tests and visualizations
# Test 6: Levene's test for equal variances
stat_levene, p_levene = stats.levene(group_a, group_b)
print(f"\nLevene's Test for Equal Variance:")
print(f"Statistic: {stat_levene:.4f}, P-value: {p_levene:.4f}")
# Test 7: Welch's t-test (doesn't assume equal variance)
t_stat_welch, p_welch = stats.ttest_ind(group_a, group_b, equal_var=False)
print(f"\nWelch's t-test (unequal variance):")
print(f"t-stat: {t_stat_welch:.4f}, p-value: {p_welch:.4f}")
# Power analysis
from scipy.stats import nct
def calculate_power(effect_size, sample_size, alpha=0.05):
t_critical = stats.t.ppf(1 - alpha/2, 2*sample_size - 2)
ncp = effect_size * np.sqrt(sample_size / 2)
power = 1 - stats.nct.cdf(t_critical, 2*sample_size - 2, ncp)
return power
power = calculate_power(abs(effect_size), len(group_a))
print(f"\nStatistical Power: {power:.2%}")
# Bootstrap confidence intervals
def bootstrap_ci(data, n_bootstrap=10000, ci=95):
bootstrap_means = []
for _ in range(n_bootstrap):
sample = np.random.choice(data, size=len(data), replace=True)
bootstrap_means.append(np.mean(sample))
lower = np.percentile(bootstrap_means, (100-ci)/2)
upper = np.percentile(bootstrap_means, ci + (100-ci)/2)
return lower, upper
boot_ci = bootstrap_ci(group_a)
print(f"\nBootstrap 95% CI for Group A: ({boot_ci[0]:.2f}, {boot_ci[1]:.2f})")
# Multiple testing correction (Bonferroni)
num_tests = 4
bonferroni_alpha = 0.05 / num_tests
print(f"\nBonferroni Corrected Alpha: {bonferroni_alpha:.4f}")
print(f"Use this threshold for {num_tests} tests")
# Test 8: Kruskal-Wallis test (non-parametric ANOVA)
h_stat, p_kw = stats.kruskal(group1, group2, group3)
print(f"\nKruskal-Wallis Test (non-parametric ANOVA):")
print(f"H-statistic: {h_stat:.4f}, p-value: {p_kw:.4f}")
# Effect size for ANOVA
f_stat, p_anova = stats.f_oneway(group1, group2, group3)
# Calculate eta-squared
grand_mean = np.mean([group1, group2, group3])
ss_between = sum(len(g) * (np.mean(g) - grand_mean)**2 for g in [group1, group2, group3])
ss_total = sum((x - grand_mean)**2 for g in [group1, group2, group3] for x in g)
eta_squared = ss_between / ss_total
print(f"\nEffect Size (Eta-squared): {eta_squared:.4f}")
Interpretation Guidelines
- p < 0.05: Statistically significant (reject H0)
- p ≥ 0.05: Not statistically significant (fail to reject H0)
- Effect size: Magnitude of the difference (small/medium/large)
- Confidence intervals: Range of plausible parameter values
Assumptions Checklist
- Independence of observations
- Normality of distributions (parametric tests)
- Homogeneity of variance
- Appropriate sample size
- Random sampling
Common Pitfalls
- Misinterpreting p-values
- Multiple testing without correction
- Ignoring effect sizes
- Violating test assumptions
- Confusing correlation with causation
Deliverables
- Test results with p-values and test statistics
- Effect size calculations
- Visualization of distributions
- Confidence intervals
- Interpretation and business implications
1---2name: statistical-hypothesis-testing3description: Conduct statistical tests including t-tests, chi-square, ANOVA, and p-value analysis for statistical significance, hypothesis validation, and A/B testing4---5
6# Statistical Hypothesis Testing
7
8## Overview
9
10Hypothesis testing provides a framework for making data-driven decisions by testing whether observed differences are statistically significant or due to chance.
11
12## Testing Framework
13
14- **Null Hypothesis (H0)**: No effect or difference exists
15- **Alternative Hypothesis (H1)**: Effect or difference exists
16- **Significance Level (α)**: Threshold for rejecting H0 (typically 0.05)
17- **P-value**: Probability of observing data if H0 is true
18
19## Common Tests
20
21- **T-test**: Compare means between two groups
22- **ANOVA**: Compare means across multiple groups
23- **Chi-square**: Test independence of categorical variables
24- **Mann-Whitney U**: Non-parametric alternative to t-test
25- **Kruskal-Wallis**: Non-parametric alternative to ANOVA
26
27## Implementation with Python
28
29```python
30import pandas as pd
31import numpy as np
32from scipy import stats
33import matplotlib.pyplot as plt
34
35# Sample data
36group_a = np.random.normal(100, 15, 50) # Mean=100, SD=15
37group_b = np.random.normal(105, 15, 50) # Mean=105, SD=15
38
39# Test 1: Independent samples t-test
40t_stat, p_value = stats.ttest_ind(group_a, group_b)
41print(f"T-test: t={t_stat:.4f}, p-value={p_value:.4f}")
42if p_value < 0.05:
43 print("Reject null hypothesis: Groups are significantly different")
44else:
45 print("Fail to reject null hypothesis: No significant difference")
46
47# Test 2: Paired t-test (same subjects, two conditions)
48before = np.array([85, 90, 88, 92, 87, 89, 91, 86, 88, 90])
49after = np.array([92, 95, 91, 98, 94, 96, 99, 93, 95, 97])
50
51t_stat, p_value = stats.ttest_rel(before, after)
52print(f"\nPaired t-test: t={t_stat:.4f}, p-value={p_value:.4f}")
53
54# Test 3: One-way ANOVA (multiple groups)
55group1 = np.random.normal(100, 10, 30)
56group2 = np.random.normal(105, 10, 30)
57group3 = np.random.normal(102, 10, 30)
58
59f_stat, p_value = stats.f_oneway(group1, group2, group3)
60print(f"\nANOVA: F={f_stat:.4f}, p-value={p_value:.4f}")
61
62# Test 4: Chi-square test (categorical variables)
63# Create contingency table
64contingency = np.array([
65 [50, 30], # Control: success, failure
66 [45, 35] # Treatment: success, failure
67])
68
69chi2, p_value, dof, expected = stats.chi2_contingency(contingency)
70print(f"\nChi-square: χ²={chi2:.4f}, p-value={p_value:.4f}")
71
72# Test 5: Mann-Whitney U test (non-parametric)
73u_stat, p_value = stats.mannwhitneyu(group_a, group_b)
74print(f"\nMann-Whitney U: U={u_stat:.4f}, p-value={p_value:.4f}")
75
76# Visualization
77fig, axes = plt.subplots(2, 2, figsize=(12, 10))
78
79# Distribution comparison
80axes[0, 0].hist(group_a, alpha=0.5, label='Group A', bins=20)
81axes[0, 0].hist(group_b, alpha=0.5, label='Group B', bins=20)
82axes[0, 0].set_title('Group Distributions')
83axes[0, 0].legend()
84
85# Q-Q plot for normality
86stats.probplot(group_a, dist="norm", plot=axes[0, 1])
87axes[0, 1].set_title('Q-Q Plot (Group A)')
88
89# Before/After comparison
90axes[1, 0].plot(before, 'o-', label='Before', alpha=0.7)
91axes[1, 0].plot(after, 's-', label='After', alpha=0.7)
92axes[1, 0].set_title('Paired Comparison')
93axes[1, 0].legend()
94
95# Effect size (Cohen's d)
96cohens_d = (np.mean(group_a) - np.mean(group_b)) / np.sqrt(
97 ((len(group_a)-1)*np.var(group_a, ddof=1) +
98 (len(group_b)-1)*np.var(group_b, ddof=1)) /
99 (len(group_a) + len(group_b) - 2)
100)
101axes[1, 1].text(0.5, 0.5, f"Cohen's d = {cohens_d:.4f}",
102 ha='center', va='center', fontsize=14)
103axes[1, 1].axis('off')
104
105plt.tight_layout()
106plt.show()
107
108# Normality test (Shapiro-Wilk)
109stat, p = stats.shapiro(group_a)
110print(f"\nShapiro-Wilk normality test: W={stat:.4f}, p-value={p:.4f}")
111
112# Effect size calculation
113def calculate_effect_size(group1, group2):
114 n1, n2 = len(group1), len(group2)
115 var1, var2 = np.var(group1, ddof=1), np.var(group2, ddof=1)
116 pooled_std = np.sqrt(((n1-1)*var1 + (n2-1)*var2) / (n1+n2-2))
117 cohens_d = (np.mean(group1) - np.mean(group2)) / pooled_std
118 return cohens_d
119
120effect_size = calculate_effect_size(group_a, group_b)
121print(f"Effect size (Cohen's d): {effect_size:.4f}")
122
123# Confidence intervals
124from scipy.stats import t as t_dist
125
126def calculate_ci(data, confidence=0.95):
127 n = len(data)
128 mean = np.mean(data)
129 se = np.std(data, ddof=1) / np.sqrt(n)
130 margin = t_dist.ppf((1 + confidence) / 2, n - 1) * se
131 return mean - margin, mean + margin
132
133ci = calculate_ci(group_a)
134print(f"95% CI for Group A: ({ci[0]:.2f}, {ci[1]:.2f})")
135
136# Additional tests and visualizations
137
138# Test 6: Levene's test for equal variances
139stat_levene, p_levene = stats.levene(group_a, group_b)
140print(f"\nLevene's Test for Equal Variance:")
141print(f"Statistic: {stat_levene:.4f}, P-value: {p_levene:.4f}")
142
143# Test 7: Welch's t-test (doesn't assume equal variance)
144t_stat_welch, p_welch = stats.ttest_ind(group_a, group_b, equal_var=False)
145print(f"\nWelch's t-test (unequal variance):")
146print(f"t-stat: {t_stat_welch:.4f}, p-value: {p_welch:.4f}")
147
148# Power analysis
149from scipy.stats import nct
150def calculate_power(effect_size, sample_size, alpha=0.05):
151 t_critical = stats.t.ppf(1 - alpha/2, 2*sample_size - 2)
152 ncp = effect_size * np.sqrt(sample_size / 2)
153 power = 1 - stats.nct.cdf(t_critical, 2*sample_size - 2, ncp)
154 return power
155
156power = calculate_power(abs(effect_size), len(group_a))
157print(f"\nStatistical Power: {power:.2%}")
158
159# Bootstrap confidence intervals
160def bootstrap_ci(data, n_bootstrap=10000, ci=95):
161 bootstrap_means = []
162 for _ in range(n_bootstrap):
163 sample = np.random.choice(data, size=len(data), replace=True)
164 bootstrap_means.append(np.mean(sample))
165 lower = np.percentile(bootstrap_means, (100-ci)/2)
166 upper = np.percentile(bootstrap_means, ci + (100-ci)/2)
167 return lower, upper
168
169boot_ci = bootstrap_ci(group_a)
170print(f"\nBootstrap 95% CI for Group A: ({boot_ci[0]:.2f}, {boot_ci[1]:.2f})")
171
172# Multiple testing correction (Bonferroni)
173num_tests = 4
174bonferroni_alpha = 0.05 / num_tests
175print(f"\nBonferroni Corrected Alpha: {bonferroni_alpha:.4f}")
176print(f"Use this threshold for {num_tests} tests")
177
178# Test 8: Kruskal-Wallis test (non-parametric ANOVA)
179h_stat, p_kw = stats.kruskal(group1, group2, group3)
180print(f"\nKruskal-Wallis Test (non-parametric ANOVA):")
181print(f"H-statistic: {h_stat:.4f}, p-value: {p_kw:.4f}")
182
183# Effect size for ANOVA
184f_stat, p_anova = stats.f_oneway(group1, group2, group3)
185# Calculate eta-squared
186grand_mean = np.mean([group1, group2, group3])
187ss_between = sum(len(g) * (np.mean(g) - grand_mean)**2 for g in [group1, group2, group3])
188ss_total = sum((x - grand_mean)**2 for g in [group1, group2, group3] for x in g)
189eta_squared = ss_between / ss_total
190print(f"\nEffect Size (Eta-squared): {eta_squared:.4f}")
191```
192
193## Interpretation Guidelines
194
195- **p < 0.05**: Statistically significant (reject H0)
196- **p ≥ 0.05**: Not statistically significant (fail to reject H0)
197- **Effect size**: Magnitude of the difference (small/medium/large)
198- **Confidence intervals**: Range of plausible parameter values
199
200## Assumptions Checklist
201
202- Independence of observations
203- Normality of distributions (parametric tests)
204- Homogeneity of variance
205- Appropriate sample size
206- Random sampling
207
208## Common Pitfalls
209
210- Misinterpreting p-values
211- Multiple testing without correction
212- Ignoring effect sizes
213- Violating test assumptions
214- Confusing correlation with causation
215
216## Deliverables
217
218- Test results with p-values and test statistics
219- Effect size calculations
220- Visualization of distributions
221- Confidence intervals
222- Interpretation and business implications