上个月我帮一个团队看A/B测试结果,新版页面的平均点击时长比旧版多了0.8秒,样本量1200,t检验的p值停在0.055。业务同学说这差别够明显,运营同学说p值没过0.05就是失败,两边拉我当裁判。我花了几分钟用scipy把检验结果拆开摆清楚,最后结论是:显著性检验本身不难,难的是理解每个检验在问什么问题,以及p值之外还要看什么。这篇文章可以当一份scipy显著性检验的实战手册来用,我会把连续变量、分类变量的常用检验函数、参数选择和坑位一次说清,顺便聊聊那些文档里不会明说的统计直觉。
1. 显著性检验在做什么:先搞清楚p值到底在回答什么问题
1.1 假设检验的原点:原假设、备择假设与“先射箭再画靶”
很多人拿到数据就直接敲ttest_ind,但跑检验前最该做的事是明确原假设和备择假设。以新旧版本对比为例,如果你想验证新版本是否比旧版本效果好,那么原假设H0是“两组均值相等”,备择假设H1是“两组均值不等”或者“新版均值大于旧版”。在scipy里,这个方向体现在alternative参数上:默认是two-sided,对应“均值不等”;greater对应“第一组大于第二组”;less对应“第一组小于第二组”。
为什么要死磕这一步?因为p值是“在H0为真的前提下,当前数据出现的概率”。如果你连自己在拒绝什么都没想清楚,后面的数字都是空中楼阁。比如你设了alternative='greater',那scipy只会在正方向上累积极端概率;如果设成two-sided,p值会双倍分配。同样的数据,不同假设,结论可能完全不同,这不是函数的问题,是统计问题的定义问题。
1.2 p值的真实含义:一个容易误解的条件概率
最常见的错误解释,是把p值当成“原假设为真的概率”。我用抛硬币类比一下:一枚公平硬币,抛20次出现14次正面,那么“出现14次或更极端次数正面”的概率大约0.058。这是在“硬币公平”这个前提下算出来的条件概率,不是“硬币不公平的概率是5.8%”。
t检验也一样。p=0.03的意思是:如果两组数据其实没有差异,那么通过随机抽样得到“当前这么大的组间差异”的概率只有3%。它没有回答“两组有差异的概率是多少”。要回答后者需要贝叶斯框架,那不是scipy这套频率学派工具能直接给你的。所以业务同事问“那到底有多少把握说新版有效”,严谨的回答不是“97%把握”,而是“如果新版完全无效,我们几乎不太可能看到这么大的差异,因此更倾向于认为它不是巧合”。
1.3 统计显著、实际显著与样本量:p值为什么不能单独看
p值有一个非常反直觉的特性:样本量越大,越容易显著。同样是0.5秒的均值差,30个样本可能p=0.4,3000个样本可能p=0.001。这会导致一个典型困局:大样本下,微小到业务上毫无感知的差异也能跑出“高度显著”;小样本下,真正有业务价值的差异反而可能停留在p=0.06。
所以我现在给团队的铁律是:汇报p值时,必须附带效应量或原始均值差。scipy不会替你算效应量,但你可以自己算Cohen's d,或者至少把两组均值和标准差放在表格里。只看p值的结论,约等于抽奖。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据形态决定检验路线:SciPy工具全景与选用逻辑
2.1 先回答四个问题:变量类型、配对关系、样本数量、分布假设
初次接触scipy统计模块的人,很容易被函数数量吓到。其实选型只需要回答四个问题:
- 因变量是连续型数值,还是分类型计数?连续型走t检验、方差分析、秩检验;分类型走卡方检验或Fisher精确检验。
- 两组样本是否配对?同一个用户测了两次,是配对数据;两组互相独立的用户,是独立数据。配对数据用配对方法,独立数据用独立方法,用反了会直接影响p值。
- 需要比较几组?两组用t检验或Mann-Whitney U,三组以上用ANOVA或Kruskal-Wallis。不要三组数据两两t检验,理由后面讲。
- 数据是否近似正态、方差是否齐?满足就走参数检验,不满足就走非参数检验,或者直接用置换检验兜底。
这四个问题想清楚,95%的分析场景都能定位到正确函数。其余5%靠经验,比如数据严重偏态、有大量离群值、或者样本量极小,这些我都会在后面的章节展开。
2.2 正态性和方差齐性检验:shapiro、normaltest、levene
判断正态性,最常用的是Shapiro-Wilk检验,scipy里是shapiro:
python复制from scipy.stats import shapiro, normaltest, levene
# 单组数据正态性
stat, p = shapiro(data)
print(stat, p)
如果p<0.05,严格来说数据不符合正态分布假设。但这里有个大坑:shapiro对样本量极度敏感,n超过5000时,微小偏离就会触发显著。这时候我一般会结合Q-Q图和偏度、峰度一起判断,而不是只靠一个p值定生死。
多组比较还要看方差齐性,scipy里有levene和bartlett:
python复制# 多组数据方差齐性,p<0.05 视为方差不齐
stat, p = levene(group1, group2, group3)
两种检验的区别在于,levene对非正态数据更稳健,bartlett对正态性要求更高。我在实际项目里默认用levene,除非样本量很小且数据非常接近正态。
2.3 检验路线速查表:从假设条件到SciPy函数
| 变量类型 | 比较组数 | 配对/独立 | 满足正态+方差齐 | 对应SciPy函数 |
|---|---|---|---|---|
| 连续 | 2 | 独立 | 是 | ttest_ind |
| 连续 | 2 | 独立 | 否 | mannwhitneyu |
| 连续 | 2 | 配对 | 是 | ttest_rel |
| 连续 | 2 | 配对 | 否 | wilcoxon |
| 连续 | 3+ | 独立 | 是 | f_oneway |
| 连续 | 3+ | 独立 | 否 | kruskal |
| 分类 | 任意 | 独立 | - | chi2_contingency / fisher_exact |
提示:表格里的“满足正态”指近似正态即可。t检验对中等程度偏离并不敏感,真正需要警惕的是极端异常值和严重方差不齐。
这张表我直接贴在工位上了。带过的实习生每次来问统计分析,我都是先甩这张表过去,绝大部分问题都能解决。
3. 参数检验实战:t检验、方差分析与事后比较的完整流程
3.1 独立样本t检验:ttest_ind与Welch修正
两组独立连续变量比较,最基础的函数是ttest_ind:
python复制import numpy as np
from scipy.stats import ttest_ind
group_a = np.array([5.1, 4.8, 6.2, 5.7, 5.3])
group_b = np.array([5.9, 6.1, 6.5, 6.3, 5.8])
res = ttest_ind(group_a, group_b, equal_var=True)
print(res.statistic, res.pvalue)
默认equal_var=True对应Student's t检验,前提是两组方差近似相等。如果levene检验提示方差不齐,我会改成equal_var=False,也就是Welch's t检验。现在很多统计实践直接默认用Welch版本,因为它在方差不齐时更稳健,即便方差齐时功效损失也极小。用scipy跑Welch很简单,少传一个equal_var=False就行,代价几乎为零。
3.2 配对样本t检验:ttest_rel的正确打开方式
配对数据的经典场景:同一批用户分别体验旧版和新版,记录各自的点击时长。这时每个用户在前后两组各有一个值,数据天然配对,应该用ttest_rel:
python复制from scipy.stats import ttest_rel
before = np.array([5.1, 4.8, 6.2, 5.7, 5.3])
after = np.array([4.9, 4.5, 6.0, 5.5, 5.1])
res = ttest_rel(before, after)
print(res.statistic, res.pvalue)
配对t检验的本质是检验“差值的均值是否为0”。你可以先算diff = after - before,然后做单样本t检验ttest_1samp(diff, popmean=0),结果和ttest_rel完全一致。配对设计能抵消个体差异,检验功效通常更高,但前提是差值本身近似正态,而不是原始数据正态。还有一点:如果before和after里有缺失值,不能简单地在两组里各自删除,那样会破坏配对关系。正确做法是先对齐,再做行删除。
3.3 单因素方差分析与Tukey HSD:f_oneway之后怎么办
三组以上均值比较用f_oneway:
python复制from scipy.stats import f_oneway
group1 = np.array([5.1, 4.8, 6.2])
group2 = np.array([5.9, 6.1, 6.5])
group3 = np.array([6.2, 6.4, 6.0])
res = f_oneway(group1, group2, group3)
print(res.statistic, res.pvalue)
F检验显著只说明“至少有一组均值不同”,不告诉你哪几组不同。有些人拿到F显著结果,就把三组两两t检验一遍,这会让假阳性率飙升。正确做法是Tukey HSD事后比较。很遗憾,scipy没内置这个函数,我一般用statsmodels:
python复制from statsmodels.stats.multicomp import pairwise_tukeyhsd
data = np.concatenate([group1, group2, group3])
labels = ['g1']*len(group1) + ['g2']*len(group2) + ['g3']*len(group3)
result = pairwise_tukeyhsd(data, labels)
print(result)
输出会给出每一对组别的均值差、p值和置信区间,比两两t检验安全得多。
3.4 参数检验的三个隐蔽陷阱
第一个陷阱是NaN。ttest_ind遇到NaN默认返回NaN,必须显式传nan_policy='omit'。但配对数据不能用这一招,因为两组各自删掉NaN会错位,必须先按索引对齐再删除。
第二个陷阱是过度依赖中心极限定理。大样本下均值抽样分布确实趋近正态,所以t检验在很多轻度偏态数据上也能用。但如果数据严重右偏、有极端离群值,均值本身就会被拉走,t检验会失真。这种
