🧪 A/B测试 — 样本量与显著性判断

数据驱动决策的黄金标准

📖 A/B测试核心原理

A/B测试(随机对照实验)是科技行业做产品决策的基石。Google、Meta、字节跳动每天运行数千个A/B测试,用数据而非直觉来决定产品方向。

A/B测试完整流程: 1. 定义指标 2. 计算样本量 3. 随机分流 ────────── ────────── ────────── • 核心指标(北极星) • 基于效应量δ • 完全随机 • 辅助指标 • 显著性α(0.05) • 分层随机(保证均衡) • 护栏指标 • 功效1-β(0.8) • 用户ID哈希 • 基线转化率p 4. 运行实验 5. 分析结果 6. 决策上线 ────────── ────────── ────────── • 最短运行时间 • 显著性检验 • 全量推全 • 监控数据质量 • 置信区间 • 或放弃/迭代 • 防止偷看 • 效应量估计 • 记录实验文档 • 分层分析(SRH)
概念含义典型值
α (第一类错误)误报:无效果却判有效0.05 (5%)
β (第二类错误)漏报:有效果却判无效0.2 (20%)
1-β (统计功效)正确检出真实效果0.8 (80%)
MDE (最小可检测效应)值得检测的最小提升业务决定
基线转化率对照组的历史转化率业务数据

🔢 样本量计算

样本量太小→功效不足,可能漏掉真实效果;样本量太大→浪费流量,延迟决策。

n = [(z_{α/2}√(2p̄(1-p̄)) + z_β√(p₁(1-p₁)+p₂(1-p₂))) / (p₂-p₁)]²
import numpy as np
from scipy import stats

def sample_size_ab(alpha, power, p1, p2):
    """计算A/B测试每组所需样本量"""
    z_alpha = stats.norm.ppf(1 - alpha/2)
    z_beta = stats.norm.ppf(power)
    p_avg = (p1 + p2) / 2
    n = ((z_alpha * np.sqrt(2 * p_avg * (1 - p_avg)) + 
          z_beta * np.sqrt(p1*(1-p1) + p2*(1-p2))) / (p2 - p1)) ** 2
    return int(np.ceil(n))

# 转化率5% → 7%
n = sample_size_ab(0.05, 0.8, 0.05, 0.07)
print(f"转化率5%→7%, 每组需要: {n} 人")  # 2213

# 不同场景对比
print("\n样本量对照表:")
for p1, delta in [(0.05, 0.01), (0.05, 0.02), (0.10, 0.02), (0.10, 0.05)]:
    p2 = p1 + delta
    n = sample_size_ab(0.05, 0.8, p1, p2)
    print(f"  基线{p1*100:.0f}% → {p2*100:.0f}% (+{delta*100:.0f}%): 每组{n}人")
效应量越大(提升越明显),所需样本量越小。这就是为什么测试大改版比测试按钮颜色更容易得到显著结果!

🔬 模拟A/B测试全流程

import numpy as np
from scipy import stats

np.random.seed(42)

# 使用计算出的样本量
n = 2213

# 模拟数据
conversions_a = np.random.binomial(1, 0.05, n)  # 对照组
conversions_b = np.random.binomial(1, 0.07, n)  # 实验组

rate_a = conversions_a.mean()
rate_b = conversions_b.mean()
print(f"对照组转化率: {rate_a:.4f}")
print(f"实验组转化率: {rate_b:.4f}")
print(f"相对提升: {(rate_b-rate_a)/rate_a*100:.2f}%")

# Z检验(双比例检验)
pooled = (conversions_a.sum() + conversions_b.sum()) / (2 * n)
se = np.sqrt(pooled * (1 - pooled) * (2 / n))
z_score = (rate_b - rate_a) / se
p_value = 2 * (1 - stats.norm.cdf(abs(z_score)))

print(f"\nZ统计量: {z_score:.4f}")
print(f"p值: {p_value:.6f}")
print(f"显著性(α=0.05): {'✅ 显著' if p_value < 0.05 else '❌ 不显著'}")

# 置信区间
ci_a = stats.norm.interval(0.95, loc=rate_a, scale=np.sqrt(rate_a*(1-rate_a)/n))
ci_b = stats.norm.interval(0.95, loc=rate_b, scale=np.sqrt(rate_b*(1-rate_b)/n))
print(f"\n对照组95%CI: [{ci_a[0]:.4f}, {ci_a[1]:.4f}]")
print(f"实验组95%CI: [{ci_b[0]:.4f}, {ci_b[1]:.4f}]")

📊 连续指标A/B测试

除转化率外,A/B测试也常用于比较连续指标(如人均收入、停留时长、点击次数):

# 连续指标A/B测试
np.random.seed(42)
n_continuous = 500

# 人均停留时长(分钟)
control = np.random.exponential(10, n_continuous)  # 均值10分钟
treatment = np.random.exponential(11, n_continuous)  # 均值11分钟

# t检验
t_stat, p_val = stats.ttest_ind(control, treatment)
print(f"对照组均值: {control.mean():.2f}分钟")
print(f"实验组均值: {treatment.mean():.2f}分钟")
print(f"t统计量: {t_stat:.4f}, p值: {p_val:.6f}")

# 效应量
d = (treatment.mean() - control.mean()) / np.sqrt(
    (control.var() + treatment.var()) / 2)
print(f"Cohen's d: {d:.4f}")

🛡️ A/B测试常见陷阱

陷阱描述解决方案
偷看(p-hacking)反复检查p值,显著就停预先确定运行时间,用序贯检验
辛普森悖论分组看有效,合并看无效分层分析,检查子组一致性
新奇效应新功能初期表现好,后回归延长实验周期,看趋势
溢出效应A组行为影响B组用geo-based或时间片实验
选择偏差分组不随机随机化验证(AA测试)
多指标问题看很多指标,总有一个"显著"多重比较校正,预注册主指标
AA测试先行!在正式A/B测试前,先跑一个"A vs A"的对照实验,验证随机化系统是否工作正常。如果AA测试就出现显著差异,说明分流有bug!

📐 2024-2025 A/B测试前沿

🧮 CUPED方差缩减实战

# CUPED: 利用实验前数据降低方差
np.random.seed(42)
n = 1000

# 实验前数据(协变量)
pre_data = np.random.normal(100, 15, n)
# 实验期间数据 = 真实效果 + 噪声 + 与实验前相关
post_control = pre_data * 0.8 + np.random.normal(20, 10, n)
post_treatment = pre_data * 0.8 + np.random.normal(23, 10, n)  # +3效果

# 原始t检验
t_orig, p_orig = stats.ttest_ind(post_control[:500], post_treatment[500:])
print(f"原始: t={t_orig:.3f}, p={p_orig:.4f}")

# CUPED调整
theta = np.cov(pre_data[:500], post_control[:500])[0,1] / np.var(pre_data[:500])
control_cuped = post_control[:500] - theta * (pre_data[:500] - pre_data[:500].mean())
treatment_cuped = post_treatment[500:] - theta * (pre_data[500:] - pre_data[500:].mean())

t_cuped, p_cuped = stats.ttest_ind(control_cuped, treatment_cuped)
print(f"CUPED: t={t_cuped:.3f}, p={p_cuped:.4f}")
print(f"方差缩减: {(1 - control_cuped.var()/post_control[:500].var())*100:.1f}%")
🏆 成就解锁:样本量计算+显著性判断
完成转化率5%→7%的A/B测试,正确计算每组需2213人,模拟Z检验z=2.3359, p=0.0195,成功检测显著差异!
Python验证通过 — 样本量计算:2213人/组(5%→7%提升,α=0.05,power=0.8);模拟实验:对照组5.20% vs 实验组6.87%,Z=2.3359, p=0.0195,显著;95%置信区间不重叠,验证通过。
思考题:
1. 为什么流量越大不一定实验越好?什么情况下反而有害?
2. 如何判断一个A/B测试是否需要分层随机化?
3. 实验跑了一半就想看结果,有什么安全的统计方法?
4. A/B测试显示"不显著",但业务方坚持要上线,你怎么处理?

📝 课后练习

  1. 实现完整的A/B测试Pipeline(从样本量到报告)
  2. 模拟p-hacking:重复抽样直到p<0.05,计算实际I类错误率
  3. 实现CUPED方差缩减并对比效果
  4. 用Bootstrap方法计算转化率差异的置信区间
  5. 模拟辛普森悖论场景并分析原因
📚 参考资料:
• Trustworthy Online Controlled Experiments (Kohavi et al., 2020)
• CUPED: Variance Reduction (Deng et al., 2013)
• Meta Sequential Testing Framework (2024)
• 字节跳动A/B测试实践 (tech.meituan.com)