1. 先把 t 分布这件事想明白
"t分布表精确完整图"这个关键词被反复搜出来,多半是有人正在赶一份报告、写一份课程作业,或者刚跑完一组对照实验要做显著性判断。翻遍手边的教材附录,发现那张t分布表只有寥寥几行,单尾双尾还要分两张,自由度到 30 就戛然而止,往上直接一个"∞"。我这些年帮不少做数据分析的朋友看过这类问题,几乎每个人都卡在同一个环节:t检验的公式能默写,t分布表却读不明白,t值算出来之后不知道拿它跟哪个数比,最后 p 值到底是 0.03 还是 0.06 全靠猜。
这篇要解决的就是这一整条链路:t分布为什么长这样、t分布表怎么读、自由度怎么数、三类t检验分别在什么场景下用、每一步的计算怎么落地,以及怎么用代码把查表和画图这两件最枯燥的事全自动干掉。刚入门、被作业逼着查表的人可以照着抄流程;工作里要用 A/B 测试结果说话的产品、运营、研发,可以重点看选型逻辑和坑位清单。看完至少要能做到:拿到一组数据,知道自己该做哪种检验,知道自由度从哪里来,知道 t 值该往哪张表里对,也知道什么时候该干脆放弃 t 检验换别的路子。
1.1 为什么小样本不能直接套正态分布
很多人第一次接触 t 分布会觉得它是个"多余的发明"——正态分布那么好用,为什么还要再搞一个形状几乎一样的东西出来?关键差别在标准差是不是已知。
正态分布做区间估计时,公式里用的是总体标准差 σ,这是个理论上的确定值。可现实中 σ 几乎永远不知道,你手里只有样本标准差 s。s 本身是个估计量,样本量越小,它抖得越厉害。样本量 5 的时候,s 可能只有真值的七成,也可能高到真值的一倍半。这时候如果把 s 当 σ 用,直接套正态分布的 1.96,置信区间会系统性地偏窄,假阳性率飙得比 5% 高出一大截。
t 分布要处理的正是这个"分母本身也在抖"的问题。它的定义很直白:一个标准正态变量除以一个独立的卡方变量除以其自由度之后的平方根。写法上就是 Z 除以 √(χ²/ν)。因为分母是随机变量,整条曲线的尾巴必然比正态更厚——这就是所谓的厚尾。样本量越小,尾部越厚;自由度趋近无穷时,t 分布收敛到标准正态。
生活里的类比很好找。正态分布像是"你已经知道尺子的刻度误差是固定的",t 分布像是"你手里的尺子每次量都会轻微变形,量得次数越少,你越不敢对读数打包票"。所以样本量 30 以上时,t 分布和正态几乎是重合的,1.96 和 2.04 的差距在实际决策里无所谓;但样本量 10 的时候,双尾 5% 的临界值是 2.228,你拿 1.96 去比,结论可能直接反过来。
注意:t 分布假定数据来自正态总体。样本量很大时靠中心极限定理还能扛,样本量小且分布严重偏态时,t 检验的 p 值不可信,这时候该考虑秩检验之类的替代方案。
1.2 t 分布表到底在记录什么信息
一张标准的 t 分布表,本质是分位数对照表。行是自由度,列是显著性水平,格子里的数字是临界值 t*,含义是:当总体真的没有差异时,统计量 |t| 超过这个数的概率恰好等于你指定的 α。
这里有个容易混淆的点。表里印的"0.05"到底是单尾还是双尾,不同教材的处理不一样。有的表头写"双尾 α",有的写"单尾 α",还有的干脆两栏并列。同一个自由度下,双尾 0.05 对应的临界值,等于单尾 0.025 对应的临界值。你如果拿着双尾表的数字去做单尾判断,等价于把标准放松了一倍,假阳性率直接翻番。
t 分布表的另一个特点是只给常用几个 α,通常是 0.10、0.05、0.02、0.01,精算场景偶尔有 0.001。算出 t 值落在两个临界值之间怎么办?老派做法是写"0.01 < p < 0.05",报个区间。现在有了计算工具,直接给精确 p 值就行,审稿和评审也更认精确值。所以我在实际工作里的原则是:表用来建立直觉和做交叉验证,正式结论一律用软件算精确 p 值。
至于标题里说的"精确完整图",指的是三样东西的合体:t 分布的密度曲线图、临界值在曲线上的位置标注、以及对应的分位数表。图负责让你一眼看出尾部和 α 的关系,表负责给出可以直接抄的数字,两者配合才能既看懂又会用。
1.3 三类 t 检验的选型逻辑
选错检验类型是比算错数更常见的错误,而且错了之后往往还看不出来,因为软件照样吐 p 值。
| 场景特征 | 该用的检验 | 自由度 | 典型例子 |
|---|---|---|---|
| 一组数据,跟一个已知标准比 | 单样本 t 检验 | n − 1 | 某批次产品尺寸是否符合设计值 50mm |
| 两组独立对象,各测一次 | 独立样本 t 检验 | n₁ + n₂ − 2(合并方差)或 Welch 近似 | 实验组与对照组的转化率对比 |
| 同一批对象,前后各测一次 | 配对样本 t 检验 | 对数 − 1 | 同一批用户使用新功能前后的日均时长 |
判断的核心只有一个问题:两组数据之间有没有配对关系。同一个对象测两次、同一份样品做两种处理、双胞胎分组,这些都属于配对,必须用配对检验。配对检验把"个体差异"从误差项里消掉了,功效通常远高于把它当独立样本硬做。
我见过最典型的翻车案例,是某团队做用户行为实验,同一批用户先看旧版再看新版,收集了两列时长,然后直接跑独立样本 t 检验。结果 p 值 0.21,结论是"改版无效"。后来改成配对检验,p 值 0.003。原因很朴素:用户之间的时长差异极大,有人天生用 5 分钟,有人天生用 50 分钟,这个巨大的个体间方差把真实的前后差异彻底淹没了。配对之后,比较的是每个人的"变化量",个体差异自然抵消。
独立样本这一支还有个分支要处理:方差齐不齐。传统教材给的是合并方差公式,前提是两组总体方差相等。这个前提在真实数据里经常不成立,尤其是两组样本量差很多的时候。所以现在的默认做法是直接用 Welch 校正版本,不依赖方差齐性假设,自由度用近似公式算出来往往不是整数。两种方法在方差接近、样本量接近时结果几乎一样,在方差悬殊时差别可能很大,而 Welch 更稳。
2. t 分布表的读法与自由度计算
2.1 自由度怎么数才不数错
自由度这个概念,通俗讲就是"你手里有多少条独立的信息可以用来估计误差"。用 n 个数据估一个均值,会消耗掉一条信息,剩 n − 1。
| 检验类型 | 自由度公式 | 说明 |
|---|---|---|
| 单样本 t 检验 | n − 1 | n 为样本量 |
| 配对样本 t 检验 | 对数 − 1 | 先把每对做差,再对差值序列算 |
| 独立样本(合并方差) | n₁ + n₂ − 2 | 两组各估一个均值,共消耗 2 |
| 独立样本(Welch) | 近似公式,常为小数 | 软件输出的 df 可能是 17.34 这种 |
| 方差分析后的两两比较 | 组内自由度 | n − k,k 为组数 |
Welch 的自由度公式比较长,写出来是:
df = (s₁²/n₁ + s₂²/n₂)² / [ (s₁²/n₁)²/(n₁−1) + (s₂²/n₂)²/(n₂−1) ]手工算这个基本是自虐,所以实际工作中一律交给软件。但有个细节要知道:Welch 的 df 通常小于 n₁ + n₂ − 2,也就是"惩罚"更重,临界值更大,结论更保守。这正是它更稳的原因——它在方差悬殊且样本量不均衡时,能有效控制假阳性。
提示:查表时如果算出 Welch 的 df 是 17.34,保守做法是往下取整到 17 去查表,临界值会略微偏大,结论偏保守。软件里不用管这个,它直接按连续自由度算精确 p 值。
2.2 单尾还是双尾,这一步最容易翻车
单双尾的选择不取决于你想不想让结果显著,而取决于你的研究问题有没有方向性。
举两个具体例子。你要检验"新工艺生产的零件直径是否偏离 50mm",偏离可能是变大也可能变小,两个方向都算问题,这是双尾。你要检验"新配方的强度是否高于旧配方",只有"高于"才有价值,"低于"不算成功,逻辑上是单尾。
但实际操作里,绝大多数场景默认双尾,理由有三条。第一,双尾更保守,评审和审稿人更容易接受;第二,事前的方向性假设很容易被数据反向打脸,如果真实效应是反方向的,单尾检验会直接给你一个接近 1 的 p 值,白白浪费一次实验;第三,很多人是看到数据方向之后才决定用单尾,这属于事后挑方向,会严重夸大显著性。
行业内的通用做法是:除非在收集数据之前就已经在方案里写死方向性假设,否则一律双尾。我自己所在的团队就是把这条写进了分析规范,避免事后扯皮。
换算关系记牢:双尾 α 的临界值 = 单尾 α/2 的临界值。df = 10 时,双尾 0.05 的临界值是 2.228,单尾 0.05 的临界值是 1.812。差了 20% 还多,足以让一个边缘结果翻转。
2.3 一份够用的 t 分布临界值表
下面这张表是双尾视角,也就是表头的 α 表示"两侧尾部面积之和"。这个格式跟大多数统计软件的输出习惯一致,也是我最推荐记住的版本。
| df | α=0.10 | α=0.05 | α=0.02 | α=0.01 |
|---|---|---|---|---|
| 1 | 6.314 | 12.706 | 31.821 | 63.657 |
| 2 | 2.920 | 4.303 | 6.965 | 9.925 |
| 3 | 2.353 | 3.182 | 4.541 | 5.841 |
| 4 | 2.132 | 2.776 | 3.747 | 4.604 |
| 5 | 2.015 | 2.571 | 3.365 | 4.032 |
| 6 | 1.943 | 2.447 | 3.143 | 3.707 |
| 7 | 1.895 | 2.365 | 2.998 | 3.499 |
| 8 | 1.860 | 2.306 | 2.896 | 3.355 |
| 9 | 1.833 | 2.262 | 2.821 | 3.250 |
| 10 | 1.812 | 2.228 | 2.764 | 3.169 |
| 12 | 1.782 | 2.179 | 2.681 | 3.055 |
| 15 | 1.753 | 2.131 | 2.602 | 2.947 |
| 20 | 1.725 | 2.086 | 2.528 | 2.845 |
| 25 | 1.708 | 2.060 | 2.485 | 2.787 |
| 30 | 1.697 | 2.042 | 2.457 | 2.750 |
| 40 | 1.684 | 2.021 | 2.423 | 2.704 |
| 60 | 1.671 | 2.000 | 2.390 | 2.660 |
| 120 | 1.658 | 1.980 | 2.358 | 2.617 |
| ∞ | 1.645 | 1.960 | 2.326 | 2.576 |
从这张表能读出三件事。第一,自由度小于 10 的时候,临界值下降得非常陡,df 从 1 到 5,双尾 0.05 的临界值从 12.706 掉到 2.571,差了五倍。这就是小样本必须有更大"证据"才能下结论的来源。第二,df 超过 30 之后,数值变化很慢,30 到 ∞ 之间只差 0.08 左右,这也是"30 是大样本分界线"这种经验说法的由来。第三,最后一行的 ∞ 就是正态分布的临界值,1.96 那个熟悉的数字。
手工查表的时候,如果 df 不在表里,比如 df = 18,就找相邻的那一行取保守值,或者用 df = 20 那一行,临界值略小一点、结论略激进一点;更保守的做法是用 df = 15 那一行。这种差异在实际决策中通常不影响结论,除非你的 t 值恰好落在临界值附近 0.01 的范围内,那时就必须用精确计算。
2.4 t 值、临界值、p 值三者的换算
这三个数其实是同一件事的三种表达方式,搞清它们的转换关系,查表这件事就再也不会卡住。
t 值是从数据算出来的统计量,衡量"观测到的差异相当于几个标准误"。临界值是在给定 α 和 df 下,t 分布的分位点。p 值是"如果总体真的没差异,出现当前这么大或更大 |t| 的概率"。
判断规则很干脆:|t| ≥ 临界值,则 p ≤ α,拒绝原假设;|t| < 临界值,则 p > α,不能拒绝。注意措辞是"不能拒绝"而不是"接受",因为不显著只说明证据不足,不等于证明了没差异。
反向换算也常用。已知 α = 0.05、df = 12,查表得临界值 2.179。如果你的 t = 2.5,那 p 就小于 0.05;想更精确,就查 df = 12、α = 0.02 对应的 2.681,2.5 < 2.681,说明 p 大于 0.02。所以 0.02 < p < 0.05,报区间。用软件算,这个例子的精确 p 值大约是 0.0277。
置信区间是第四个表达方式,而且信息量最大。双侧 1 − α 置信区间的公式是:
置信区间 = 点估计 ± t*(α/2, df) × 标准误比如均值差是 3.2,标准误是 1.4,df = 12,双尾 0.05 的 t* = 2.179,那么区间就是 3.2 ± 3.05,即 [0.15, 6.25]。这个区间的下限贴近 0,说明效应可能很小,虽然 p 值刚过线但实际意义有限——这种信息是单看 p 值看不出来的。我一直建议报告里同时给 p 值和置信区间,评审的人能自己判断效应的实际大小。
3. 三类 t 检验的完整计算过程
3.1 单样本 t 检验:从公式到落地
单样本的场景其实比想象中多。除了最直观的"这批零件符不符合标准",还有一类隐藏用法:把两组数据的差值当成一组新数据,做单样本检验,等价于配对检验。所以把单样本吃透,配对也就通了。
计算分四步。第一步,算样本均值 x̄。第二步,算样本标准差 s,分母是 n − 1 而不是 n。第三步,算标准误 SE = s / √n。第四步,t = (x̄ − μ₀) / SE。
拿个具体数字走一遍。某产线的目标厚度是 12.00mm,抽 10 件测得的均值是 11.94mm,样本标准差 0.09mm。
- SE = 0.09 / √10 = 0.09 / 3.1623 = 0.02846
- t = (11.94 − 12.00) / 0.02846 = −0.06 / 0.02846 ≈ −2.108
- df = 10 − 1 = 9
- 查表,df = 9、双尾 0.05 的临界值是 2.262
|t| = 2.108 < 2.262,p 略大于 0.05。精确算下来 p ≈ 0.0638。结论是:在 5% 水平下不能认为产线偏离目标值,但已经相当接近临界,属于"需要关注、建议扩大样本再确认"的状态。
这个例子里最值得说的细节是标准差的单位和小数位。0.09 和 0.090 在计算上一样,但如果你在中间步骤把 SE 四舍五入成 0.028,t 就变成 2.143,虽然还是同一个结论,但如果数据再往边缘靠一点,这种舍入就可能让结论翻转。我的习惯是所有中间量保留至少 6 位有效数字,只在最终报告时舍入。
注意:这里的 μ₀ 是事先确定的对照值,绝不能是"拿样本均值反过来当对照值",那是自欺欺人。同样,μ₀ 的来源要写清楚——是设计规格、历史均值,还是行业标准。
3.2 独立样本 t 检验:方差齐性是绕不开的分叉口
独立样本要处理两组互不相关的数据,公式里多了一个"合并"的步骤。
合并方差的计算是:
sp² = [ (n₁ − 1)s₁² + (n₂ − 1)s₂² ] / (n₁ + n₂ − 2)本质是按自由度加权平均两个方差,样本量大的那一组权重更高,这很合理。然后:
t = (x̄₁ − x̄₂) / [ sp × √(1/n₁ + 1/n₂) ]举个实际例子。A 组 12 个样本,均值 86.5,标准差 9.2;B 组 12 个样本,均值 79.1,标准差 8.6。
- 合并方差 = [11 × 84.64 + 11 × 73.96] / 22 = [931.04 + 813.56] / 22 = 1744.6 / 22 ≈ 79.30
- sp = √79.30 ≈ 8.905
- 标准误 = 8.905 × √(1/12 + 1/12) = 8.905 × √0.16667 = 8.905 × 0.40825 ≈ 3.635
- t = (86.5 − 79.1) / 3.635 = 7.4 / 3.635 ≈ 2.036
- df = 12 + 12 − 2 = 22
- 查表,df = 22、双尾 0.05 的临界值约 2.074
|t| = 2.036 < 2.074,p 略大于 0.05,精确值约 0.0538。又是一个卡在边缘的案例。
现在换 Welch 版本。两组标准差 9.2 和 8.6,方差 84.64 和 73.96,其实比较接近,Welch 的结果会和上面非常接近:
- 标准误 = √(84.64/12 + 73.96/12) = √(7.053 + 6.163) = √13.216 ≈ 3.635
- t 同样约 2.036
- 自由度用近似公式算出来约 21.87
因为方差接近、样本量相等,Welch 的 df 逼近 22,结论完全一样。但如果把 B 组的标准差改成 3.0、样本量改成 30,情况就完全不同了:合并方差会被 A 组的 84.64 主导,算出来的 t 值偏小,而 Welch 会给出更合理的判断。这就是为什么我建议默认用 Welch——在方差齐的时候它几乎不损失什么,在不齐的时候它能救你一命。
方差齐性本身要不要检验?学术上争议很大。常用的 Levene 检验或 F 检验,在样本量小的时候功效低、容易漏掉真实的不齐;样本量大的时候又过于敏感,一点点无关紧要的差异都会被判成"不齐"。比较务实的做法是:不预检,直接用 Welch。这样既省一步,也避免"先检验再选方法"带来的双重判断问题。
3.3 配对样本 t 检验:把个体差异消掉
配对的思路非常干净:先对每一对求差 dᵢ = xᵢ − yᵢ,然后把这列差值当成单样本,检验"差值的总体均值是否为 0"。
t = d̄ / ( sd / √n ),df = n − 1继续举例。8 位参与者在两种界面下各完成一次任务,用时(秒)如下。
| 参与者 | 界面 A | 界面 B | 差值 d |
|---|---|---|---|
| 1 | 42 | 38 | 4 |
| 2 | 55 | 47 | 8 |
| 3 | 38 | 41 | −3 |
| 4 | 61 | 52 | 9 |
| 5 | 47 | 44 | 3 |
| 6 | 50 | 39 | 11 |
| 7 | 44 | 46 | −2 |
| 8 | 58 | 49 | 9 |
差值均值 d̄ = (4+8−3+9+3+11−2+9)/8 = 39/8 = 4.875。差值标准差:先算离均差平方和。各差值减 4.875 得 −0.875、3.125、−7.875、4.125、−1.875、6.125、−6.875、4.125,平方后约 0.766、9.766、62.016、17.016、3.516、37.516、47.266、17.016,合计约 194.88。除以 7 得方差 27.84,开方得 sd ≈ 5.277。
- SE = 5.277 / √8 = 5.277 / 2.8284 ≈ 1.866
- t = 4.875 / 1.866 ≈ 2.613
- df = 7
- 查表,df = 7、双尾 0.05 的临界值是 2.365
2.613 > 2.365,p < 0.05,精确值约 0.0346,可以认为两种界面存在显著差异。
关键在于对比:如果把这 8 对数据错误地当成独立样本处理,两组的合并标准差会因为个体间差异巨大而变得很大(A 组标准差约 8.0,B 组约 5.1),t 值会掉到 1 附近,p 值超过 0.3,结论完全反转。这就是配对设计的价值——它把"人跟人不一样"这个巨大的噪声源直接消掉了。
配对检验有个前提:差值的分布要大致正态,而不是原始数据正态。这一点很多人搞错。原始数据可以偏态,只要差值近似对称就没问题。差值里如果出现明显的极端值,比如某一对的差值特别大,就要检查是不是记录错误或者个体异常反应。
3.4 效应量:把"显著"翻译成"有用"
p 值小只说明"不太可能是巧合",不说明"效果好"。样本量足够大的时候,0.01 的差异也能做出 p < 0.001。所以我做完 t 检验一定补一个效应量。
最常用的是 Cohen's d:
d = (x̄₁ − x̄₂) / sp配对场景用差值均值和差值标准差:
d = d̄ / sd按惯例,d 约 0.2 算小效应,0.5 中等,0.8 大。这个分档是经验性的,不同领域差别很大:心理学里 0.5 已经值得说,某些工程场景 0.2 就足够影响决策。所以更靠谱的做法是结合业务成本来判断,而不是死抠分档。
上面前面那个独立样本例子,t = 2.036,d = 7.4 / 8.905 ≈ 0.83,属于大效应。虽然 p 值刚过临界,但效应量说明差异本身很可观,只是样本量不够支撑确定性。这种情况正确的行动是扩大样本,而不是宣告失败。反过来,如果 d 只有 0.1 而 p = 0.03,那大概率是样本量堆出来的"显著但无用",要不要改方案就得看改动成本了。
提示:报告结论时给一个"三件套"——均值差 + 置信区间 + 效应量。这三样加起来,读的人就能自己判断这个差异在业务上值不值得追。
4. 用代码把整条链路跑通
4.1 三个检验的代码骨架
手工查表适合建立直觉,实际工作一律交给代码。Python 生态里scipy.stats是标准选择,几个函数覆盖全部场景。
import numpy as np from scipy import stats # 单样本:检验样本均值是否等于 12.00 sample = np.array([11.95, 12.02, 11.88, 11.99, 11.91, 12.01, 11.87, 11.96, 11.93, 11.98]) t1, p1 = stats.ttest_1samp(sample, popmean=12.00) print(f"单样本: t={t1:.4f}, p={p1:.4f}, df={len(sample)-1}") # 独立样本:默认就是 Welch,不假设方差齐性 group_a = np.array([92, 86, 79, 95, 88, 91, 84, 90, 87, 93, 89, 85]) group_b = np.array([78, 82, 75, 80, 84, 77, 81, 79, 76, 83, 80, 74]) t2, p2 = stats.ttest_ind(group_a, group_b, equal_var=False) print(f"独立样本(Welch): t={t2:.4f}, p={p2:.4f}") # 如果想用传统合并方差版本,显式指定 t2b, p2b = stats.ttest_ind(group_a, group_b, equal_var=True) print(f"独立样本(合并): t={t2b:.4f}, p={p2b:.4f}") # 配对样本 before = np.array([42, 55, 38, 61, 47, 50, 44, 58]) after = np.array([38, 47, 41, 52, 44, 39, 46, 49]) t3, p3 = stats.ttest_rel(before, after) print(f"配对样本: t={t3:.4f}, p={p3:.4f}")几个实测下来容易忽略的点。ttest_ind的默认参数在新版里是equal_var=True,也就是传统合并方差,这跟很多人的直觉相反,必须显式写equal_var=False才是 Welch。ttest_rel返回的统计量方向取决于你传参的顺序,ttest_rel(before, after)和反过来传,t 值符号会变,但 p 值不变,写报告时要注意方向别搞反。
4.2 手算与代码对账
把第 3 节的手算结果和代码输出对一遍,是检验自己有没有理解到位的最好方式。上面单样本那段数据,手算 t ≈ −2.108,代码跑出来应该也是这个数量级;配对那段手算 t ≈ 2.613,代码输出应当基本吻合(差异只来自我手算时的舍入)。
这种对账习惯我保持了挺久,好处是当软件结果和你的预期差很远时,你能快速判断是数据有问题还是自己公式记错了。曾经有一次,代码跑出一个 t 值 0.02、p 值 0.98,看起来"完全没差异",我手算一遍发现是分组标签映射错了,两组数据其实一模一样。软件不会告诉你这种错误,它只负责算。
再补一个手工算 p 值的思路,用于验证软件输出:
from scipy import stats t_value = 2.613 df = 7 # 双尾 p 值:两侧尾部面积之和 p_two_sided = 2 * (1 - stats.t.cdf(abs(t_value), df)) print(f"双尾 p = {p_two_sided:.4f}") # 单尾 p 值 p_one_sided = 1 - stats.t.cdf(t_value, df) print(f"单尾 p = {p_one_sided:.4f}") # 反查临界值:给定 α 和 df,求分位点 crit = stats.t.ppf(1 - 0.025, df) print(f"df={df} 双尾0.05 临界值 = {crit:.4f}")stats.t.ppf就是"用代码查表",第一个参数传的是单尾累积概率。双尾 0.05 对应传 0.975,也就是 1 − 0.025。这个转换第一次用很容易写错,写成ppf(0.95, df)就变成单尾 0.05 的临界值了,数值会小一截。
4.3 把 t 分布画成一张能用的图
标题里提到的"精确完整图",落到实操就是三件事:画出密度曲线、标出临界值位置、把拒绝域填色。这张图放进报告里,比一堆数字直观得多。
import numpy as np import matplotlib.pyplot as plt from scipy import stats df = 9 alpha = 0.05 x = np.linspace(-4.5, 4.5, 1000) y = stats.t.pdf(x, df) y_norm = stats.norm.pdf(x) crit = stats.t.ppf(1 - alpha/2, df) fig, ax = plt.subplots(figsize=(9, 4.5)) ax.plot(x, y, label=f"t 分布 (df={df})", color="#2b6cb0", lw=2) ax.plot(x, y_norm, label="标准正态", color="#a0aec0", lw=1.5, linestyle="--") # 左右拒绝域填色 left = x <= -crit right = x >= crit ax.fill_between(x[left], y[left], color="#e53e3e", alpha=0.45) ax.fill_between(x[right], y[right], color="#e53e3e", alpha=0.45) # 标注临界值 for c in (-crit, crit): ax.axvline(c, color="#e53e3e", linestyle=":", lw=1.2) ax.annotate(f"{c:.3f}", xy=(c, 0.02), xytext=(c + 0.15, 0.06), fontsize=9, color="#c53030") ax.set_title(f"t 分布双尾检验示意 (df={df}, α={alpha})") ax.set_xlabel("t 值") ax.set_ylabel("概率密度") ax.legend(loc="upper right") ax.grid(alpha=0.25) plt.tight_layout() plt.savefig("t_distribution.png", dpi=150)这段代码有几个值得调的地方。np.linspace的范围我一般取 ±4.5,因为 df 很小时 t 分布的尾部拖得很长,取 ±3 会截断得很难看;但如果 df 只有 2,建议扩到 ±10,否则曲线的厚尾特性完全看不出来。fill_between的alpha别设太高,0.4 左右比较好看,太深会盖住曲线。标临界值的注释位置要手动微调,左右两侧用不同的偏移量,否则文字会跟竖线打架。
画图的时候顺手把正态曲线叠上去,能非常直观地展示"自由度小的时候 t 分布更胖"。df = 3 时两条线的差异肉眼可见,df = 30 时基本重合,这比看表格里的数字有说服力得多。我通常会在报告里放两张图,一张 df 小的、一张 df 大的,读者一眼就懂为什么样本量重要。
5. 实际踩过的坑与排查手册
5.1 正态性检验被用反了
这是我认为最常见也最隐蔽的错误。很多人做 t 检验前会跑一个 Shapiro-Wilk 检验,看到 p < 0.05 就慌了,觉得"数据不正态,不能用 t 检验"。
问题在于:正态性检验的原假设是"数据服从正态分布"。p 小于 0.05 才是拒绝正态。这个方向跟"我们想要显著"的思维惯性正好相反,一不留神就理解反了。更要命的是,样本量很大的时候,Shapiro-Wilk 对任何微小偏离都极其敏感,几乎必然给出 p < 0.001,数据明明看着挺正常也会被判"不正态";样本量很小的时候,它又没有足够功效发现真正的问题。
我的处理方法是:看图优先,检验为辅。画一个 Q-Q 图或者直方图,用眼睛判断有没有严重的偏态或者双峰。轻度偏离正态,t 检验在样本量 15 以上就非常稳健,根本不用管。真正需要警惕的是极端偏态、明显双峰、或者尾部有极端离群值这三种情况。
顺带说一句,t 检验对正态性的敏感程度被严重高估了。它真正敏感的是离群值,因为均值和标准差都会被一两个极端值带跑。一组 20 个数据里混进一个数量级异常的值,t 值可能从 3 掉到 1.2。所以数据清洗时,异常值检查比正态性检验重要得多。
5.2 常见问题速查表
| 现象 | 可能原因 | 排查与处理 |
|---|---|---|
| p 值刚好在 0.05 附近跳来跳去 | 样本量不足,效应处于边缘 | 报置信区间和效应量,扩大样本后复测 |
| 显著但效应量极小 | 样本量过大 | 用效应量和业务成本判断实际价值 |
| 方差齐性检验 p 很小 | 两组离散程度确实不同 | 改用 Welch 版本,或考虑对数变换 |
| 配对检验结果远好于独立检验 | 个体间差异大,配对信息被浪费 | 确认配对关系成立后优先用配对 |
| 同一批数据做多次比较,总有显著的 | 多重比较导致假阳性膨胀 | 用 Bonferroni 或 FDR 校正 |
| t 值为正但业务方向相反 | 组别标签或相减顺序搞反 | 检查数据传入顺序和分组映射 |
| 数据里有 0 或极大值 | 单位错误、录入错误、真实极端 | 逐个核查原始记录,别盲目删除 |
表里最后一条我想展开说。异常值处理最大的忌讳是"看着不顺眼就删"。我的原则是:先定位来源,能查到是录入错误的直接修正,查不到的做敏感性分析——把异常值删掉跑一遍,保留跑一遍,如果两种结果结论一致,就当它不存在;如果结论翻转,那必须在报告里明确说明,而不是悄悄选一个好看的结果。
5.3 多重比较和样本量这两件事
只要你在同一份数据上做了不止一次检验,p 值就不可信了。做 10 次独立的双尾检验,α = 0.05,至少出现一次假阳性的概率是 1 − 0.95¹⁰ ≈ 40%。所以"我试了几个分组,终于找到一个显著的"这种做法,本质是在制造假阳性。
常见的校正方法有两种。Bonferroni 最简单,把 α 除以比较次数,10 次比较就按 0.005 判。缺点是太保守,真正的效应也可能被压掉。FDR(错误发现率)控制更温和,适合探索性分析,按 p 值排序后用 (i/m) × α 的阈值逐个比。实际工作里,确认性分析用 Bonferroni,探索性分析用 FDR,这个分工比较常见。
样本量的事更值得提前算。单样本场景下,粗略的估算公式是:
n ≈ (z_{1−α/2} + z_{1−β})² × σ² / Δ²双尾 α = 0.05、功效 80% 时,前面括号里大约是 (1.96 + 0.84)² = 7.84。所以 n ≈ 7.84 × σ² / Δ²,Δ 是你希望检测出的最小差异。举例,σ = 10,想检测出 Δ = 5 的差异,n ≈ 7.84 × 100 / 25 ≈ 31.4,取 32。这个估算偏乐观(没考虑 t 分布的小样本惩罚),实际建议再加 10% 到 15%。
我见过太多"做完实验才发现样本量不够"的情况。事后算一下功效,发现只有 0.3,那这个不显著的结论其实什么也说明不了,等于实验白做。所有 t 检验在设计阶段就该把这个数算出来,这是我自己交过学费的一条。
6. 几条从踩坑里攒下来的经验
t 分布表真正值得记住的只有三个数:df 无限大时的 1.96、df = 10 附近的双尾 0.05 临界值 2.228、以及"df 越小临界值越大"这个趋势。剩下所有数值,现场查表或者让代码算,都比硬背靠谱。我见过有人把 df = 5 的 2.571 记成 2.015,刚好把两个结论反过来。
看到 p 值先问三件事:这个 p 是单尾还是双尾算的,自由度是多少,样本量多大。这三条不清楚,p 值本身就是个孤零零的数字,没法解读。我在评审别人的分析报告时,如果只看到一个 p 值,其余都没写,基本会直接退回去重做。
配对和独立的选择,比公式算得准不准重要一个数量级。数据之间有配对关系却当独立处理,损失的统计功效可能是几倍。判断标准就一句话:这两列数据的第 i 行,是不是来自同一个对象。
"显著"和"值得行动"是两个独立的问题。p 值回答前者,效应量和置信区间回答后者。这两件事混在一起谈,决策一定会出问题——要么因为一个 p = 0.04 去推一个实际毫无感觉的改动,要么因为一个 p = 0.08 放弃了一个真正有价值的方案。
报告里把方法交代清楚,比结果好看更重要。用了单样本还是配对、有没有做方差齐性假设、α 定在多少、有没有做多重比较校正,这些写明白,别人才能判断你的结论站不站得住。这也是我自己写分析报告时最先写的一段,虽然枯燥,但省了后面无数轮来回解释。
最后再补一个我觉得很实用的小技巧:把常用的ttest_1samp、ttest_ind、ttest_rel三个函数封装成一个统一的入口,输入是两组数据和风险标志,输出固定包含 t 值、p 值、df、置信区间、效应量五项。用了一段时间之后你会发现,真正容易出错的地方从来不是公式,而是忘了给某一项,或者给错了顺序。固定输出格式这件事,能把这些低级错误一次性堵死。