

大家好,我是正在实战各种AI项目的程序员晚枫。
今天学习假设检验,这是用数据验证猜想的科学方法。
A/B测试显示新界面转化率更高,是真的有效还是偶然?假设检验帮你做出判断。
什么是假设检验?
核心思想
通过样本数据,判断某个假设是否成立。
基本步骤
- 提出假设:原假设H₀(通常是无效果)vs 备择假设H₁
- 选择检验方法:根据数据类型选择t检验、卡方检验等
- 计算p值:在原假设成立时,观察到当前结果的概率
- 做出决策:p < 0.05则拒绝原假设,认为有显著差异
关键概念
- p值:小于0.05通常认为"统计显著"
- 置信度:1 - p,如95%置信度
- 第一类错误:假阳性(实际没效果但认为有)
- 第二类错误:假阴性(实际有效果但没发现)
t检验:比较两组均值
独立样本t检验
比较两个独立组的均值是否有差异。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| from scipy import stats import numpy as np
old_version = np.random.normal(0.12, 0.02, 1000) new_version = np.random.normal(0.15, 0.02, 1000)
t_stat, p_value = stats.ttest_ind(new_version, old_version)
print(f"t统计量: {t_stat:.4f}") print(f"p值: {p_value:.6f}")
if p_value < 0.05: print("结论:新版本转化率显著高于旧版本(p < 0.05)") else: print("结论:差异不显著,可能是随机波动")
|
配对样本t检验
同一组对象前后对比。
1 2 3 4 5 6 7 8 9 10 11
| before = np.array([65, 70, 72, 68, 75, 80, 78, 82, 70, 74]) after = np.array([72, 78, 75, 74, 82, 85, 83, 88, 76, 80])
t_stat, p_value = stats.ttest_rel(after, before)
print(f"平均提升: {(after - before).mean():.2f}分") print(f"p值: {p_value:.6f}")
if p_value < 0.05: print("培训效果显著!")
|
单样本t检验
与已知值比较。
1 2 3 4 5 6 7
| this_year = np.array([78, 82, 75, 80, 77, 85, 79, 81, 76, 84])
t_stat, p_value = stats.ttest_1samp(this_year, 75)
print(f"今年平均分: {this_year.mean():.2f}") print(f"p值: {p_value:.6f}")
|
卡方检验:分类变量关联性
卡方独立性检验
检验两个分类变量是否独立。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| from scipy.stats import chi2_contingency import pandas as pd
observed = pd.DataFrame({ '购买': [120, 150], '未购买': [80, 50] }, index=['男性', '女性'])
chi2, p_value, dof, expected = chi2_contingency(observed)
print("观察值:") print(observed) print("\n期望值(如果独立):") print(pd.DataFrame(expected, index=observed.index, columns=observed.columns)) print(f"\n卡方值: {chi2:.4f}") print(f"p值: {p_value:.6f}")
if p_value < 0.05: print("结论:性别和购买行为有关联") else: print("结论:性别和购买行为无关")
|
卡方拟合优度检验
检验观测分布是否符合期望分布。
1 2 3 4 5 6 7 8 9 10 11 12 13
| from scipy.stats import chisquare
observed = [45, 55, 48, 52, 38, 62] expected = [50, 50, 50, 50, 50, 50]
chi2, p_value = chisquare(observed, expected)
print(f"卡方值: {chi2:.4f}") print(f"p值: {p_value:.6f}")
if p_value < 0.05: print("骰子可能不公平!")
|
实战:完整的A/B测试分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| import numpy as np from scipy import stats import matplotlib.pyplot as plt
np.random.seed(42) group_a = np.random.binomial(1, 0.12, 5000) group_b = np.random.binomial(1, 0.135, 5000)
conv_a = group_a.mean() conv_b = group_b.mean()
print("=== A/B测试结果 ===") print(f"A组转化率: {conv_a:.2%}") print(f"B组转化率: {conv_b:.2%}") print(f"相对提升: {(conv_b - conv_a) / conv_a:.2%}")
t_stat, p_value = stats.ttest_ind(group_b, group_a)
print(f"\nt统计量: {t_stat:.4f}") print(f"p值: {p_value:.6f}")
pooled_std = np.sqrt(((len(group_a)-1)*group_a.var() + (len(group_b)-1)*group_b.var()) / (len(group_a) + len(group_b) - 2)) cohens_d = (conv_b - conv_a) / pooled_std
print(f"效应量(Cohen's d): {cohens_d:.4f}") print(" 小效应: 0.2, 中效应: 0.5, 大效应: 0.8")
se = np.sqrt(conv_a*(1-conv_a)/len(group_a) + conv_b*(1-conv_b)/len(group_b)) ci_lower = (conv_b - conv_a) - 1.96 * se ci_upper = (conv_b - conv_a) + 1.96 * se
print(f"\n95%置信区间: [{ci_lower:.4f}, {ci_upper:.4f}]")
print("\n=== 结论 ===") if p_value < 0.05: print(f"✓ B组显著优于A组 (p={p_value:.4f} < 0.05)") if cohens_d >= 0.2: print(f"✓ 效应量为{cohens_d:.2f},实际意义明显") print("建议:全面推广B方案") else: print("效应量较小,需权衡成本收益") else: print(f"✗ 差异不显著 (p={p_value:.4f})") print("建议:继续观察或增加样本量")
fig, ax = plt.subplots(figsize=(10, 6))
groups = ['A组', 'B组'] conversions = [conv_a, conv_b] colors = ['#3498db', '#e74c3c']
bars = ax.bar(groups, conversions, color=colors, alpha=0.7, edgecolor='black')
for bar, conv in zip(bars, conversions): height = bar.get_height() ax.text(bar.get_x() + bar.get_width()/2., height, f'{conv:.2%}', ha='center', va='bottom', fontsize=14, fontweight='bold')
ax.set_ylabel('转化率', fontsize=12) ax.set_title(f'A/B测试结果 (p={p_value:.4f})', fontsize=14, fontweight='bold') ax.set_ylim(0, max(conversions) * 1.2)
if p_value < 0.05: ax.annotate('*', xy=(1, conv_b), xytext=(1, conv_b + 0.01), fontsize=30, ha='center', color='red')
plt.tight_layout() plt.savefig('ab_test_result.png', dpi=300) plt.show()
|
性能对比:scipy vs 手动计算
1 2 3 4 5 6 7 8 9 10 11
| from scipy import stats import numpy as np
group_a = np.random.randn(1000) + 5 group_b = np.random.randn(1000) + 5.5
%timeit stats.ttest_ind(group_a, group_b)
%timeit ...
|
进阶用法
常用检验速查表
| 场景 | 检验方法 | scipy函数 |
|---|
| 两组均值比较 | 独立样本t检验 | ttest_ind |
| 配对样本比较 | 配对t检验 | ttest_rel |
| 多组均值比较 | 方差分析(ANOVA) | f_oneway |
| 分类变量关联 | 卡方检验 | chi2_contingency |
| 正态性检验 | Shapiro-Wilk | shapiro |
| 非参数两组比较 | Mann-Whitney U | mannwhitneyu |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| t_stat, p_value = stats.ttest_ind(group_a, group_b)
t_stat, p_value = stats.ttest_rel(before, after)
t_stat, p_value = stats.ttest_1samp(sample, popmean=100)
f_stat, p_value = stats.f_oneway(group_a, group_b, group_c)
from scipy.stats import chi2_contingency table = pd.crosstab(df['gender'], df['purchase']) chi2, p_value, dof, expected = chi2_contingency(table)
|
避坑指南
❌ 坑1:p值误解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
def cohens_d(group1, group2): n1, n2 = len(group1), len(group2) d = (group1.mean() - group2.mean()) / np.sqrt( ((n1-1)*group1.std()**2 + (n2-1)*group2.std()**2) / (n1+n2-2) ) return d
effect = cohens_d(group_a, group_b) print(f"效应量: {effect:.3f}")
|
❌ 坑2:多重比较问题
1 2 3 4 5 6 7
|
from statsmodels.stats.multitest import multipletests
p_values = [0.01, 0.03, 0.04, 0.06, 0.12] reject, corrected_p, _, _ = multipletests(p_values, method='bonferroni', alpha=0.05) print(f"校正后显著的: {reject}")
|
实战案例:A/B测试分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| import pandas as pd import numpy as np from scipy import stats
np.random.seed(42) n = 5000
ab_test = pd.DataFrame({ 'user_id': range(n), 'group': np.random.choice(['control', 'treatment'], n), 'converted': np.where( np.random.rand(n) < 0.35, 1, 0 ) })
ab_test.loc[ab_test['group'] == 'treatment', 'converted'] = np.where( np.random.rand(n//2) < 0.40, 1, 0 )
conv_rate = ab_test.groupby('group')['converted'].mean() print("=== A/B测试结果 ===") print(conv_rate) print(f"提升: {(conv_rate['treatment'] - conv_rate['control']) / conv_rate['control'] * 100:.1f}%")
table = pd.crosstab(ab_test['group'], ab_test['converted']) chi2, p_value, dof, expected = stats.chi2_contingency(table) print(f"\n卡方检验: chi2={chi2:.2f}, p={p_value:.4f}") print(f"结论: {'显著' if p_value < 0.05 else '不显著'}")
from statsmodels.stats.proportion import proportion_confint for group in ['control', 'treatment']: n_group = (ab_test['group'] == group).sum() n_conv = ab_test[ab_test['group'] == group]['converted'].sum() ci_low, ci_high = proportion_confint(n_conv, n_group, alpha=0.05) print(f"{group}: {n_conv/n_group:.3f} [{ci_low:.3f}, {ci_high:.3f}]")
|
假设检验完整流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
|
def hypothesis_test(sample1, sample2, alpha=0.05): '''完整的假设检验流程''' from scipy import stats _, p1 = stats.shapiro(sample1[:5000]) _, p2 = stats.shapiro(sample2[:5000]) is_normal = (p1 > alpha) and (p2 > alpha) if is_normal: _, p_var = stats.levene(sample1, sample2) equal_var = p_var > alpha t_stat, p_value = stats.ttest_ind(sample1, sample2, equal_var=equal_var) test_name = 't检验' else: u_stat, p_value = stats.mannwhitneyu(sample1, sample2) test_name = 'Mann-Whitney U检验' significant = p_value < alpha effect_size = (sample1.mean() - sample2.mean()) / np.sqrt( ((len(sample1)-1)*sample1.std()**2 + (len(sample2)-1)*sample2.std()**2) / (len(sample1)+len(sample2)-2) ) print(f"检验方法: {test_name}") print(f"统计量: {t_stat if is_normal else u_stat:.4f}") print(f"p值: {p_value:.4f}") print(f"结论: {'拒绝原假设(有显著差异)' if significant else '不能拒绝原假设'}") print(f"效应量(Cohen's d): {effect_size:.3f}") return p_value, significant
hypothesis_test(group_a, group_b)
|
下节预告
下一课我们将学习数据分布分析,了解正态分布和QQ图。
👉 继续阅读:数据分布分析-正态分布与QQ图
💬 加入学习交流群
扫码加入Python学习交流群,和数千名同学一起进步:
👉 点击加入交流群
群里不定期分享:
- 数据分析实战案例
- Python学习资料
- 求职面试经验
- 行业最新动态
推荐:AI Python数据分析实战营
🎁 限时福利:送《利用Python进行数据分析》实体书
👉 点击了解详情
课程导航
上一篇: 描述性统计-用数字概括数据特征
下一篇: 数据分布分析-正态分布与QQ图
PS:假设检验是数据驱动决策的基础。记住:p < 0.05只是门槛,还要结合实际业务意义。
📚 推荐教材
主教材:《Excel+Python 飞速搞定数据分析与处理(图灵出品)》
💬 联系我
主营业务:AI 编程培训、企业内训、技术咨询
🎓 AI 编程实战课程
想系统学习 AI 编程?程序员晚枫的 AI 编程实战课 帮你从零上手!