4. 样本量计算与功效分析:如何确定最小样本量、功效函数、效应量计算、Python实现

在A/B测试中,样本量不足会导致检验功效过低,无法检测出真实存在的差异;样本量过大则浪费流量与时间。本章系统讲解样本量计算的理论基础、功效函数、效应量概念,并给出可直接复用的Python实现。

4.1 核心概念速览

概念 符号 定义 典型取值/说明
显著性水平 α 犯第一类错误(假阳性)的概率 0.05(双尾)
检验功效 1-β 正确拒绝原假设的概率 0.80 或 0.90
效应量 d / δ 标准化后的组间差异大小 小=0.2,中=0.5,大=0.8
最小样本量 n 每组所需的最小观测数 由α、1-β、d共同决定
经验法则:在α=0.05、功效=0.80的条件下,检测小效应量(d=0.2)需要每组约393个样本;检测中等效应量(d=0.5)需要每组约64个样本。

4.2 样本量计算公式推导

对于两独立样本均值检验(双尾),最小样本量公式为:

n = 2 * ((Z_α/2 + Z_β) / d)²

其中:
  Z_α/2 = 标准正态分布在 α/2 处的临界值(α=0.05 时 ≈ 1.96)
  Z_β   = 标准正态分布在 β 处的临界值(功效=0.80 时 β=0.20,Z_β ≈ 0.84)
  d     = Cohen's d = (μ₁ - μ₂) / σ_pooled

对于比例检验(如转化率),使用基于正态近似的公式:

n = ( (Z_α/2 + Z_β)² * (p₁(1-p₁) + p₂(1-p₂)) ) / (p₁ - p₂)²

其中:
  p₁ = 对照组预期转化率
  p₂ = 实验组预期转化率

4.3 功效函数与效应量计算

功效函数描述了在给定样本量和效应量下,检验能够正确拒绝原假设的概率。功效随样本量增加而增加,随效应量减小而减小。

效应量计算(Cohen's d):

d = (mean_A - mean_B) / pooled_std

pooled_std = sqrt( ((n_A-1)*var_A + (n_B-1)*var_B) / (n_A + n_B - 2) )
实践要点:效应量是样本量计算的输入参数,通常基于历史数据、行业基准或最小可检测差异(MDE)来设定。不要使用实验后的观测效应量来反推样本量——这会导致循环论证。

4.4 Python实现:样本量计算与功效分析

以下代码使用 statsmodels 和 scipy 实现完整的样本量计算与功效分析流程。

import numpy as np
from scipy import stats
import statsmodels.stats.power as smp

# ========== 1. 均值检验样本量计算 ==========
def sample_size_means(d, alpha=0.05, power=0.80, alternative='two-sided'):
    """
    计算两独立样本均值检验所需的最小样本量(每组)
    
    参数:
        d: Cohen's d 效应量
        alpha: 显著性水平
        power: 检验功效
        alternative: 'two-sided' 或 'one-sided'
    
    返回:
        n: 每组所需样本量
    """
    power_analysis = smp.TTestIndPower()
    n = power_analysis.solve_power(
        effect_size=d,
        alpha=alpha,
        power=power,
        alternative=alternative
    )
    return int(np.ceil(n))

# 示例:检测中等效应量 d=0.5
n_means = sample_size_means(d=0.5)
print(f"均值检验所需样本量(每组): {n_means}")

# ========== 2. 比例检验样本量计算 ==========
def sample_size_proportions(p1, p2, alpha=0.05, power=0.80):
    """
    计算两样本比例检验所需的最小样本量(每组)
    
    参数:
        p1: 对照组比例
        p2: 实验组比例
        alpha: 显著性水平
        power: 检验功效
    
    返回:
        n: 每组所需样本量
    """
    z_alpha = stats.norm.ppf(1 - alpha/2)
    z_beta = stats.norm.ppf(power)
    
    p_bar = (p1 + p2) / 2
    numerator = (z_alpha * np.sqrt(2 * p_bar * (1 - p_bar)) + 
                 z_beta * np.sqrt(p1*(1-p1) + p2*(1-p2)))**2
    denominator = (p1 - p2)**2
    
    n = numerator / denominator
    return int(np.ceil(n))

# 示例:转化率从 10% 提升到 12%
n_prop = sample_size_proportions(p1=0.10, p2=0.12)
print(f"比例检验所需样本量(每组): {n_prop}")

# ========== 3. 功效曲线绘制 ==========
def power_curve(effect_sizes, n_range, alpha=0.05):
    """
    生成不同效应量下的功效曲线数据
    
    参数:
        effect_sizes: 效应量列表
        n_range: 样本量范围
        alpha: 显著性水平
    
    返回:
        dict: 每个效应量对应的功效值列表
    """
    power_analysis = smp.TTestIndPower()
    results = {}
    for d in effect_sizes:
        powers = [power_analysis.solve_power(
            effect_size=d, nobs1=n, alpha=alpha, ratio=1.0
        ) for n in n_range]
        results[f"d={d}"] = powers
    return results

# 生成数据
n_values = np.arange(20, 500, 10)
effect_sizes = [0.2, 0.5, 0.8]
power_data = power_curve(effect_sizes, n_values)

# 输出部分结果供检查
for d in effect_sizes:
    idx = np.where(n_values == 100)[0][0]
    print(f"d={d}, n=100 时功效: {power_data[f'd={d}'][idx]:.3f}")
常见陷阱:
  • 忽略多重比较校正:同时测试多个指标时,需对α进行Bonferroni或其他方法校正。
  • 使用单尾检验时未确认方向:单尾检验样本量更小,但必须事先明确效应方向。
  • 未考虑样本损耗:实际投放中用户流失、数据清洗会导致有效样本减少,建议在计算结果上增加10%-20%的冗余。

4.5 样本量、效应量与功效的关系

样本量 · 效应量 · 功效三角关系 样本量 (n) 效应量 (d) 功效 (1-β) 固定功效时 n↑ 可检测 d↓ 固定效应量时 n↑ 功效↑ 固定样本量时,d↑ 功效↑ 关键推论 • 已知任意两个参数,可求解第三个 • 样本量不足时,即使存在真实效应也可能无法检出(低功效) • 效应量越小,所需样本量呈平方级增长

4.6 实际案例:电商转化率A/B测试样本量规划

场景:某电商平台计划测试新版结算页面,预期转化率从当前的 8% 提升到 9%。设定 α=0.05(双尾),功效=0.80。

# 电商转化率测试样本量计算
p_control = 0.08   # 对照组转化率
p_treatment = 0.09 # 实验组预期转化率

n_ecommerce = sample_size_proportions(p1=p_control, p2=p_treatment)
print(f"电商A/B测试所需样本量(每组): {n_ecommerce}")

# 考虑10%的样本损耗
n_adjusted = int(np.ceil(n_ecommerce / 0.9))
print(f"考虑10%损耗后的样本量(每组): {n_adjusted}")

# 计算可检测的最小效应量(给定样本量)
def min_detectable_effect(n, alpha=0.05, power=0.80):
    """给定样本量,计算可检测的最小效应量"""
    power_analysis = smp.TTestIndPower()
    d = power_analysis.solve_power(
        nobs1=n, alpha=alpha, power=power, ratio=1.0
    )
    return d

# 假设只能获取每组5000个样本
n_available = 5000
mde = min_detectable_effect(n_available)
print(f"n={n_available} 时可检测的最小效应量: {mde:.3f}")

# 转化为可检测的转化率差异
# 假设 pooled_std ≈ sqrt(p*(1-p)) ≈ sqrt(0.085*0.915) ≈ 0.279
pooled_std = np.sqrt(0.085 * 0.915)
detectable_diff = mde * pooled_std
print(f"可检测的最小转化率绝对差异: {detectable_diff:.4f} ({detectable_diff*100:.2f}%)")
输出解读:
  • 每组需要约 5,029 个样本(未调整损耗)
  • 调整10%损耗后,每组需要约 5,588 个样本
  • 若只能获取每组5,000个样本,可检测的最小效应量 d≈0.056,对应转化率绝对差异约 1.56 个百分点——这意味着从8%提升到9.56%才能被可靠检测

4.7 样本量计算决策流程

样本量计算决策流程 步骤1:确定指标类型 步骤2:均值检验 / 比例检验 步骤3:设定 α、功效、效应量 步骤4:计算最小样本量 n 步骤5:评估流量可行性 步骤6:调整参数 / 启动测试 流量不足时返回调整效应量或α

4.8 常见问题与避坑指南

问题 错误做法 正确做法
效应量来源 使用实验后的观测效应量反推样本量 基于历史数据、文献或最小可检测差异(MDE)预先设定
多重比较 对每个指标单独计算样本量,未做校正 使用Bonferroni校正:α_adj = α / k(k为指标数)
样本损耗 按理论样本量直接启动实验 增加10%-20%的冗余样本量
组间比例 默认1:1分流 若使用非均衡设计(如2:1),需调整公式中的ratio参数
连续监测 达到样本量后立即停止并检验 使用序贯检验或提前停止边界,避免功效膨胀
推荐工具:除了Python的statsmodels.stats.power模块,还可以使用R语言的pwr包、G*Power桌面软件进行交叉验证。对于复杂设计(如聚类随机试验),建议使用模拟法(Monte Carlo simulation)估算样本量。