跳转至

7.8   配对检验

暑假训练营结束,小率拿到一张体育老师发的记录表:同一批同学训练前后各跑一次 100 米。老师问:训练到底有没有让大家变快?

图 7.8.0 配对检验的训练前后场景

同学 训练前(秒) 训练后(秒) 前 - 后(秒)
A 15.2 14.7 0.5
B 16.1 15.8 0.3
C 14.8 14.1 0.7
D 15.7 15.2 0.5
... ... ... ...
能不能把训练前当一组,训练后当另一组,直接做两独立样本 t 检验?
不能。训练前后来自同一个人,这两个数天然配对。

7.8.1   配对数据先看「自己和自己比」

配对数据(Paired Data)指两列观测之间有一一对应关系。常见例子包括:

  • 同一个人训练前后;
  • 同一位患者用药前后;
  • 同一个用户看旧页面和新页面;
  • 同一台机器调整前后。

配对检验的关键动作是先求差值:

\[ d_i = x_{i,\text{前}} - x_{i,\text{后}} \]

然后把问题变成:这些差值的总体均值是否等于 0?

这样每个人天生快慢的差异就被减掉了。
对。配对的威力,就是把个体基线先消掉。

图 7.8.1 配对检验先把同一个人的前后值相减

图 7.8.1   配对检验不是比较两堆数,而是比较每个人自己的变化量。

7.8.2   配对 t 检验就是差值上的单样本 t

若差值近似来自正态总体,使用配对 t 检验(Paired t-Test):

\[ H_0: \mu_d = 0,\qquad H_1: \mu_d > 0 \]

统计量是:

\[ t = \frac{\bar d - 0}{s_d/\sqrt n},\qquad df=n-1 \]

这里 \(n\) 是配对数量,不是观测值总数。12 名同学训练前后各一次,配对数仍然是 12,自由度是 11。

所以 `ttest_rel` 和“先算差值再单样本 t”应该完全一样?
完全一样。配对 t 的本体就是差值检验。

需要注意

配对检验要求「配对关系」真实存在。不能把两个班的成绩硬凑成第 1 个对第 1 个、第 2 个对第 2 个。那不是配对,是人为捏配对。

7.8.3   二分类配对用 McNemar

如果配对结果不是数值,而是「是/否」,例如同一批学生辅导前后是否通过测验,就不能做配对 t。此时看 McNemar 检验。

后:通过 后:未通过
前:通过 一直通过 退步
前:未通过 进步 一直未通过

McNemar 的核心是只看发生改变的人:进步人数和退步人数是否明显不对称。

一直通过和一直未通过的人,不提供“变化方向”的信息。
正是如此。配对二分类看的是改变朝哪边更多。

7.8.4   Python 复现训练数据

完整脚本见:docs/assets/scripts/ch07_hypothesis_testing/08_paired_test/main.py

import numpy as np
from scipy import stats

before = np.array([15.2, 16.1, 14.8, 15.7, 16.4, 15.5, 14.9, 16.0])
after = np.array([14.7, 15.8, 14.1, 15.2, 15.9, 15.0, 14.6, 15.5])

diff = before - after
t_rel, p_rel = stats.ttest_rel(before, after, alternative="greater")
t_one, p_one = stats.ttest_1samp(diff, popmean=0, alternative="greater")

print(f"平均进步 = {diff.mean():.2f} 秒")
print(f"配对 t: t = {t_rel:.2f}, p = {p_rel:.4f}")
print(f"差值单样本 t: t = {t_one:.2f}, p = {p_one:.4f}")

小率的笔记本

判断配对先问一句:两个数是不是来自同一个对象?如果是,就先算差值,再对差值做检验。配对 t 的自由度是配对数减 1。若是配对二分类,改用 McNemar。