Skip to content

推断统计

从样本推断总体——假设检验、置信区间、p 值


🎯 核心问题

你在电商网站做了改版,1000 个用户中改版后的转化率从 10% 升到了 11%。

问题是:这是真正的提升,还是随机波动?

推断统计回答的就是这个问题——量化为"随机波动"的概率


📐 假设检验框架

五步法

1. 提出假设  →  H₀: 没有效果   H₁: 有效果
2. 选择检验  →  t检验 / z检验 / χ²检验 / ...
3. 计算统计量 → 从数据中算出检验统计量
4. 计算 p 值  → 如果 H₀ 为真,看到当前结果的概率
5. 做出决策  →  p < α:拒绝 H₀;p ≥ α:不拒绝

第一类 vs 第二类错误

H₀ 为真 H₀ 为假
拒绝 H₀ 第一类错误(假阳性)α ✅ 正确决策
不拒绝 H₀ ✅ 正确决策 第二类错误(假阴性)β

决策类比

  • 第一类错误 = 把好人关进监狱(冤枉)
  • 第二类错误 = 把坏人放走了(漏网)

α 通常设为 0.05,意味着接受 5% 的冤枉率


🧪 常用检验方法

Z 检验 — 比例检验

适用场景:比较两个比例(如 A/B 测试转化率)

import numpy as np
from scipy import stats

# A:1000 人,100 人转化 (10%)
# B:1000 人,110 人转化 (11%)
n_a, c_a = 1000, 100
n_b, c_b = 1000, 110

p_a, p_b = c_a/n_a, c_b/n_b
p_pool = (c_a + c_b) / (n_a + n_b)

# Z 统计量
se = np.sqrt(p_pool * (1-p_pool) * (1/n_a + 1/n_b))
z_stat = (p_b - p_a) / se

# 双侧 p 值
p_value = 2 * (1 - stats.norm.cdf(abs(z_stat)))

print(f"Z = {z_stat:.3f}, p = {p_value:.4f}")
# p ≈ 0.46 → 不显著!1% 的差异很可能是随机波动

t 检验 — 均值比较

适用场景:比较两组的均值(如实验组 vs 对照组用户停留时长)

# 对照组:n=500, mean=120s, std=40s
# 实验组:n=500, mean=128s, std=42s
np.random.seed(42)
control = np.random.normal(120, 40, 500)
treatment = np.random.normal(128, 42, 500)

# 独立样本 t 检验
t_stat, p_value = stats.ttest_ind(treatment, control)

# Cohen's d(效应量)
cohens_d = (treatment.mean() - control.mean()) / np.sqrt(
    (treatment.var() + control.var()) / 2
)

print(f"t = {t_stat:.3f}, p = {p_value:.4f}, d = {cohens_d:.3f}")

效应量解读: - d = 0.2 → 小效应(可能需要大样本才能检测) - d = 0.5 → 中效应(肉眼可见的差异) - d = 0.8 → 大效应(明显差异)

χ²(卡方)检验 — 分类变量独立性

适用场景:用户群体 × 购买行为是否相关?

#           购买   未购买
# 男性      120    380
# 女性       90    410
observed = np.array([[120, 380], [90, 410]])

chi2, p_value, dof, expected = stats.chi2_contingency(observed)

print(f"χ² = {chi2:.3f}, p = {p_value:.4f}")
# p < 0.05 → 性别与购买行为有显著关联

ANOVA — 多组比较

适用场景:A/B/C/D 四个版本,哪个最好?

# 4 个版本的转化率数据
a = np.random.binomial(1, 0.10, 500)
b = np.random.binomial(1, 0.11, 500)
c = np.random.binomial(1, 0.13, 500)
d = np.random.binomial(1, 0.12, 500)

f_stat, p_value = stats.f_oneway(a, b, c, d)
print(f"F = {f_stat:.3f}, p = {p_value:.4f}")
# p < 0.05 → 至少有一个版本与众不同

非参数检验 — 数据不满足正态假设时

# Mann-Whitney U 检验(t 检验的非参数替代)
u_stat, p_value = stats.mannwhitneyu(treatment, control, alternative='two-sided')
print(f"Mann-Whitney U = {u_stat:.0f}, p = {p_value:.4f}")

📏 置信区间

与其说「转化率是 11%」,不如说「我有 95% 的把握,转化率在 9.2% 到 12.8% 之间」。

def proportion_ci(successes, n, confidence=0.95):
    """比例的 Wald 置信区间"""
    p = successes / n
    z = stats.norm.ppf(1 - (1 - confidence) / 2)
    se = np.sqrt(p * (1 - p) / n)
    return p - z * se, p + z * se

ci_low, ci_high = proportion_ci(c_b, n_b)
print(f"转化率: {p_b:.3f}, 95% CI: [{ci_low:.3f}, {ci_high:.3f}]")

置信区间 vs p 值

指标 回答的问题 局限
p 值 "效果为零的概率是多少?" 不告诉你效果有多大
置信区间 "真实效果在哪个范围?" 不直接做决策

最佳实践:同时报告 p 值 + 置信区间 + 效应量。


⚠️ p 值陷阱

陷阱 1:p > 0.05 ≠ "没有效果"

可能只是样本量不够。做功效分析

from statsmodels.stats.power import TTestIndPower

analysis = TTestIndPower()
n_needed = analysis.solve_power(
    effect_size=0.2,  # 小效应
    power=0.80,
    alpha=0.05
)
print(f"检测小效应需要每组 {n_needed:.0f} 个样本")

陷阱 2:p < 0.05 ≠ "效果很大"

100 万用户 × 0.01% 的提升 → p < 0.001,但业务价值为零。

陷阱 3:p-hacking

p-hacking 的危险之处在于,你不需要恶意——只需好奇心。 — Andrew Gelman

常见 p-hacking 手法: - 数据达到显著性就停 - 换指标重新检验 - 删除"异常值" - 分亚组分析直到找到显著结果

# 演示:20 次独立检验中,随机出现"显著结果"的概率
n_tests = 20
alpha = 0.05
prob_at_least_one = 1 - (1 - alpha)**n_tests
print(f"做 {n_tests} 次检验,至少一次假阳性的概率: {prob_at_least_one:.1%}")
# → 64%!你有一大半概率找到"显著"的随机噪声

防御: 1. 预注册分析计划(pre-registration) 2. Bonferroni 校正:α / 检验次数 3. 在独立 hold-out 集上验证


🛠 检验选择速查

要比较什么? 数据类型 推荐检验 Python 函数
一个比例 vs 基准 二元 Z 检验 stats.binom_test()
两个比例 二元 Z 检验 / χ² stats.chi2_contingency()
两组均值 连续 t 检验 stats.ttest_ind()
多组均值 连续 ANOVA stats.f_oneway()
配对前后 连续 配对 t 检验 stats.ttest_rel()
分布差异 连续/序数 KS 检验 stats.ks_2samp()
相关性 连续 Pearson r stats.pearsonr()
秩相关性 序数 Spearman ρ stats.spearmanr()

🔗 相关资源