非参数检验
本章问题: 数据严重偏态 (如收入、点击量), 或者只有"有序"分类 (如满意度), 还能用 t 检验吗? 用 非参数检验, 不依赖正态假设, 适用范围更广。
1. 什么时候用非参数?
| 数据情况 | 推荐 |
|---|---|
| 连续 + 正态 | t 检验 (效率高) |
| 连续 + 偏态 / 小样本 | 非参数 (Wilcoxon, Mann-Whitney) |
| 有序分类 (1-5 星) | 非参数 (基于秩) |
| 样本量 < 20 | 非参数 (难判断正态) |
| 极端异常值 | 非参数 (对异常值稳健) |
⚠️ 非参数的代价: 在数据真的正态时, 非参数 power 较低 (效率 ≈ 95%)。所以"数据明明正态, 用非参数"是浪费。
2. 核心思想: 用"秩"代替"数值"
秩 (rank) = 把数据从小到大排序, 给每个值的"名次"。
import numpy as np
from scipy.stats import rankdata
data = [3.2, 1.5, 4.8, 2.1, 5.0]
ranks = rankdata(data) # [3, 1, 4, 2, 5]
print(f"数据: {data}")
print(f"秩: {ranks}")
# 秩的好处: 摆脱具体数值, 只看相对大小
# 不受异常值影响
3. 符号检验 (Sign Test): 最简单的非参数
3.1 单样本符号检验
例: 5 个病人用药后血压变化 (+3, -2, +5, -1, +4), 检验"改善是否有显著"?
H₀: 中位数 = 0 (改善 = 0) H₁: 中位数 ≠ 0
把差值 d 的符号 (+/-) 看作"二项分布", 统计 + 的个数:
from scipy.stats import binom
import numpy as np
diffs = np.array([3, -2, 5, -1, 4])
n_pos = (diffs > 0).sum() # 3
n_neg = (diffs < 0).sum() # 2
# 零假设下, + 和 - 各占一半
n = n_pos + n_neg
p = 2 * binom.cdf(min(n_pos, n_neg), n, 0.5)
print(f"符号检验: + = {n_pos}, - = {n_neg}, p = {p:.4f}")
# p = 1.0 (3 vs 2, 完全无法拒绝 H₀)
简单但浪费信息 (只用了符号, 没用大小)。
4. Wilcoxon 符号秩检验: 单样本/配对
既看符号, 又看"秩", 信息利用率更高。
from scipy.stats import wilcoxon
import numpy as np
# 配对: 同一批 20 个病人, 用药前后血压
before = np.array([180, 165, 170, 175, 190, 185, 178, 192, 188, 170,
182, 168, 175, 195, 178, 188, 172, 165, 180, 175])
after = np.array([165, 158, 162, 168, 180, 175, 170, 184, 178, 162,
170, 160, 168, 188, 172, 180, 165, 158, 172, 168])
w_stat, p_val = wilcoxon(before - after, alternative="greater")
# "greater" 因为我们预期 before > after (血压降)
print(f"Wilcoxon 符号秩: W = {w_stat}, p = {p_val:.4f}")
4.1 大样本时
n > 25 时, Wilcoxon 的 W 近似正态:
# 小样本: 直接用精确分布
# 大样本: 上面 z 公式 (Python 自动)
5. Wilcoxon 秩和检验 (Mann-Whitney U): 两独立样本
最常用的两样本非参数检验, 替代两样本 t 检验。
from scipy.stats import mannwhitneyu
import numpy as np
# A 组 (老方法), B 组 (新方法), 各 20 个
np.random.seed(42)
group_a = np.random.exponential(2, 20) + 5
group_b = np.random.exponential(2.5, 20) + 5 # B 组期望更大
u_stat, p_val = mannwhitneyu(group_a, group_b, alternative="less")
print(f"Mann-Whitney U: U = {u_stat}, p = {p_val:.4f}")
# p 小 → A 组显著小于 B 组
5.1 U 统计量含义
- U = A 组里"赢" B 组的对数 + B 组里"赢" A 组的对数
- H₀ 下 U 期望 = n₁n₂ / 2
- 偏离越大, p 越小
6. Kruskal-Wallis H 检验: 多组 (3+)
非参数的 ANOVA, 比较 3+ 独立组的分布。
from scipy.stats import kruskal
import numpy as np
# 3 种教学方法的效果
np.random.seed(42)
method_1 = np.random.normal(75, 5, 30)
method_2 = np.random.normal(78, 5, 30)
method_3 = np.random.normal(82, 5, 30)
h_stat, p_val = kruskal(method_1, method_2, method_3)
print(f"Kruskal-Wallis: H = {h_stat:.3f}, p = {p_val:.4f}")
# 显著 → 至少两组有差异
7. 游程检验 (Runs Test): 检验"随机性"
数据是否随机排列? 例如: 抛硬币 HTHTTHHHT, 看是不是真随机。
from scipy.stats import runs
# 例: 抛硬币 20 次
sequence = "HTHHHTHTTHTHHHTHHTHH"
n_h = sequence.count("H")
n_t = sequence.count("T")
r_stat, p_val = runs(np.array([c == "H" for c in sequence]))
print(f"游程数: {r_stat}, p = {p_val:.4f}")
# p 小 → 不随机 (有趋势)
7.1 游程检验的 ML 应用
- 特征选择: 检验特征取值是否随机
- 时间序列: 检验残差是否独立 (白噪声)
- A/B 测试: 检验用户分配是否真的随机 (SRM check)
8. 检验方法选择速查
数据多少组? ─┬─ 1 组 (跟已知值比)
│ ├─ 正态 → 单样本 t
│ └─ 偏态 → Wilcoxon 符号秩
│
├─ 2 组 (独立)
│ ├─ 正态 → 独立 t (Welch)
│ └─ 偏态 → Mann-Whitney U
│
├─ 2 组 (配对)
│ ├─ 正态 → 配对 t
│ └─ 偏态 → Wilcoxon 符号秩
│
└─ 3+ 组
├─ 正态 → ANOVA (下章)
└─ 偏态 → Kruskal-Wallis
9. 实战: ML 模型对比 (非参数)
import numpy as np
from scipy import stats
# 模拟 5 个随机种子下, 模型 A 和 B 的 F1
np.random.seed(42)
f1_a = np.random.normal(0.85, 0.03, 5) # A
f1_b = np.random.normal(0.88, 0.02, 5) # B 略高
# 1. 配对 t 检验 (正态, 但 n=5 太依赖正态假设)
t_stat, p_t = stats.ttest_rel(f1_b, f1_a)
print(f"配对 t 检验: t = {t_stat:.3f}, p = {p_t:.4f}")
# 2. Wilcoxon 符号秩 (不依赖正态, n=5 更稳)
w_stat, p_w = stats.wilcoxon(f1_b - f1_a)
print(f"Wilcoxon 符号秩: W = {w_stat}, p = {p_w:.4f}")
# 3. 配对 Bootstrap (更稳)
n_boot = 10000
diffs = f1_b - f1_a
boot_means = [np.mean(np.random.choice(diffs, size=len(diffs), replace=True))
for _ in range(n_boot)]
ci_low, ci_high = np.percentile(boot_means, [2.5, 97.5])
print(f"Bootstrap 95% CI: [{ci_low:.4f}, {ci_high:.4f}]")
# CI 不包含 0 → 显著
💡 学术报告: 论文里 ML 模型对比, Wilcoxon 符号秩 + Bootstrap CI 是当前最佳实践 (Demšar, 2006)。
10. Python 一行检验大全
import scipy.stats as stats
def auto_test(x, y=None, paired=False, alternative="two-sided"):
"""自动选择参数/非参数检验"""
if y is None:
# 单样本
if len(x) >= 30 and stats.shapiro(x).pvalue > 0.05:
return stats.ttest_1samp(x, 0, alternative=alternative)
else:
return stats.wilcoxon(x - (x.mean() if y is None else 0), alternative=alternative)
# 两样本
if paired:
# 配对
if len(x) >= 30 and stats.shapiro(x - y).pvalue > 0.05:
return stats.ttest_rel(x, y, alternative=alternative)
else:
return stats.wilcoxon(x - y, alternative=alternative)
else:
# 独立
if len(x) >= 30 and stats.shapiro(x).pvalue > 0.05 and stats.shapiro(y).pvalue > 0.05:
return stats.ttest_ind(x, y, equal_var=False, alternative=alternative)
else:
return stats.mannwhitneyu(x, y, alternative=alternative)
11. 小结
| 你学到了 | 关键点 |
|---|---|
| 非参数 vs 参数 | 数据不满足正态, 用非参数 |
| 秩 | 用"名次"代替"数值", 摆脱分布假设 |
| 符号检验 | 最简单, 只看符号 |
| Wilcoxon 符号秩 | 单/配对, 看符号 + 秩 |
| Mann-Whitney U | 两独立样本, 替代 t 检验 |
| Kruskal-Wallis | 多组 (3+), 替代 ANOVA |
| 游程检验 | 检验随机性 |
| ML 实战 | 模型对比用 Wilcoxon + Bootstrap CI |
12. 习题
-
两组数据, 各 15 个, 偏态:
- Mann-Whitney U 检验 p 值?
- 如果用独立 t 检验, 会有什么后果?
-
5 个 ML 模型在同一数据集上的 F1: [0.82, 0.85, 0.84, 0.83, 0.86]
- 跟 0.80 基准比, 单样本 Wilcoxon 符号秩 p 值?
- Bootstrap 95% CI?
👉 查看参考答案
-
Mann-Whitney U 更稳, 因为不假设正态。强行用 t 检验, 在偏态数据下 p 值会显著错 (假阳性或假阴性都可能)。
-
计算:
- 差值 d = [0.02, 0.05, 0.04, 0.03, 0.06], 全部 > 0
- Wilcoxon: W = 0 (秩和), p ≈ 0.0625 (双侧) 或 0.031 (单侧)
- 单侧显著 (5 个都超过 0.80, 但样本量小)
- Bootstrap 10000 次差值均值, 95% CI ≈ [0.026, 0.054] (不含 0)
13. 下一章
- 相关与线性回归: 连续变量预测的统计学基础
- 机器学习 → 假设检验与 A/B 测试: 完整 ML 实战
📚 本章来源: 改编自 Triola《基础统计学》第 14 版 第 13 章, 加入 ML 模型对比实战。