按规则判定四个实验
目标
编写用标准库计算 SRM、z 检验、置信区间、样本量和 Holm 校正的函数,并按规定的规则判定四个实验的结果。
为什么重要
小团队的 A/B 测试样本少,每天都要看,没有结果就拆开来看。三者都是把偶然变成发现的习惯。必须先定规则,再按规则计算,实验才能守护产品决策。
材料
/opt/fixtures/founder/ab/exp1.csv—user_id,variant,day,segment,converted(A 为对照,B 为变更,14 天,细分群体 10 个)/opt/fixtures/founder/ab/exp2.csv—user_id,variant,converted(50:50 设计)/opt/fixtures/founder/ab/exp3_daily.csv—day,a_users,a_conv,b_users,b_conv(A/A 实验按日期的增量)/opt/fixtures/founder/ab/exp4.csv—variant,users,conversions(control 与 v1 至 v5)
定义
- SRM:针对期望 50:50 的卡方拟合优度,自由度 1。
chi2 = Σ(관측−기대)²/기대,p = math.erfc(math.sqrt(chi2/2))。p < 0.001 即为 SRM。 - z 检验:
diff = pb − pa。z 用合并比率p = (ca+cb)/(na+nb)的标准误√(p(1−p)(1/na+1/nb))来计算,双侧p = 2(1−Φ(|z|))。 - 95% 置信区间:
diff ± Φ⁻¹(0.975)·√(pa(1−pa)/na + pb(1−pb)/nb)(非合并)。Φ 是statistics.NormalDist()。 - 判定:p < 0.05 且 ci_low > 0 →
"ship_b",p < 0.05 且 ci_high < 0 →"keep_a",其余为"inconclusive"。 - 样本量(每一边):阅读材料中的公式,α = 0.05 双侧,统计功效 0.8,
math.ceil。 - Holm:把 p 按升序排列,在第 k=0,1,… 个上,如果
p·(m−k) ≤ 0.05就拒绝,第一次不满足就停止。 - 所有 p、比率、差值,都在小数点后第六位四舍五入。
步骤
- 在
/root/founder/ab/ab.py中创建srm(n_a, n_b)→{"chi2": x, "p": y},并在/root/founder/ab/srm.json中,为 exp1、exp2 各写入{"a": n, "b": n, "chi2": x, "p": y, "srm": true/false}。 - 在
ab.py中创建ztest(ca, na, cb, nb)→{"diff", "z", "p", "ci_low", "ci_high"}(ca、cb 是转化数,na、nb 是用户数)。 - 在
/root/founder/ab/result.json中写入 exp1 的a_users,a_conv,b_users,b_conv,diff,p,ci_low,ci_high,decision。 - 在
ab.py中创建sample_size(p_base, mde),并在/root/founder/ab/power.json中写入baseline(exp1 A 的转化率,小数点后第四位)、mde(0.01)、n_per_arm(= sample_size(baseline, 0.01))、exp1_min_arm(exp1 两边用户数中较小的一个)、powered(exp1_min_arm ≥ n_per_arm)。 - 在
/root/founder/ab/peeking.json中写入 exp3 的daily_p(从第 1 天起用累计合计计算出的 21 个 p)、first_significant_day(累计 p < 0.05 的第一天,没有则为 null)、final_p、final_decision(按最后一天累计值做出的判定)。 - 在
ab.py中创建holm(pvalues)(名称→p 的字典 → 被拒绝的名称的排序后的列表),并在/root/founder/ab/multi.json中写入 exp4 的p(v1 至 v5 各自相对于 control)、naive(p < 0.05 的名称,排序)、holm。 - 在
/root/founder/ab/segments.json中,为 exp1 的 10 个细分群体,分别写入p、naive(p < 0.05,排序)、holm。 - 在
/root/founder/ab/decision.json中写入exp1(判定)、exp2_trustworthy(没有 SRM 则为 true)、exp3_decision(最后一天的判定)、exp4_ship(Holm 拒绝的清单)、segment_claims(Holm 拒绝的细分群体清单)。
参考
from statistics import NormalDist; N = NormalDist(); N.cdf(z); N.inv_cdf(0.975)- 常见错误:在置信区间中使用合并标准误,使用单侧 p,不累计增量而是按日期分别计算 p,不做校正就报告细分群体,对存在 SRM 的实验照常判定。
先确认样本比例
在 /root/founder/ab/ab.py 中创建 srm(n_a, n_b),并在 /root/founder/ab/srm.json 中写入 exp1、exp2 的 a、b、chi2、p、srm。
期望值是 (n_a+n_b)/2。自由度为 1 的卡方尾部概率,可以用 math.erfc(math.sqrt(chi2/2)) 求得。SRM 的标准是 p < 0.001。
两个比率的 z 检验与 95% 置信区间
在 ab.py 中创建 ztest(ca, na, cb, nb)。diff、z、p(双侧)、ci_low、ci_high 保留到小数点后第六位。
检验统计量用合并比率的标准误,置信区间用各自比率的标准误(非合并)。Φ 是 statistics.NormalDist().cdf,临界值是 inv_cdf(0.975)。
判定 exp1
在 /root/founder/ab/result.json 中写入 exp1 的 a_users、a_conv、b_users、b_conv、diff、p、ci_low、ci_high、decision。
判定规则:p < 0.05 且下限 > 0 则为 ship_b,p < 0.05 且上限 < 0 则为 keep_a,其余为 inconclusive。
这个实验是否大到足以捕捉 1 个百分点
在 ab.py 中创建 sample_size(p_base, mde),并在 /root/founder/ab/power.json 中写入 baseline、mde、n_per_arm、exp1_min_arm、powered。
p2 = p1 + mde,p̄ = (p1+p2)/2,z 值是 inv_cdf(0.975) 和 inv_cdf(0.8)。把括号内的整体除以 mde 后平方,再向上取整。
如果每天都看 A/A 实验
在 /root/founder/ab/peeking.json 中写入 exp3 的 daily_p(累计 21 个)、first_significant_day、final_p、final_decision。
exp3_daily.csv 是当天的增量。从第 1 天起逐日累加,用截至当天的累计合计来调用 ztest。日期从 1 开始计数。
五个变体——Holm 校正
在 ab.py 中创建 holm(pvalues),并在 /root/founder/ab/multi.json 中写入 exp4 的 p(v1 至 v5)、naive、holm。
把每个变体与 control 做 ztest(control 在 a 一侧)。Holm 在按升序排列的第 k 个(从 0 开始)上判断 p·(m−k) ≤ 0.05,第一次失败就停止。
拆成十个细分群体来看
在 /root/founder/ab/segments.json 中写入 exp1 的 10 个细分群体的 p、naive、holm。
按细分群体分别统计 A 和 B,调用同样的 ztest,并对 10 个 p 应用同样的 holm。
每个实验的上线决策
在 /root/founder/ab/decision.json 中写入 exp1、exp2_trustworthy、exp3_decision、exp4_ship、segment_claims。
从前面步骤的文件中汇集。存在 SRM 的实验,就是不可信的实验。偷看反例的结论,不是第一个显著的日期,而是最后一天的判定。