开始做AB实验那几年我被问得最多的一个问题就是“p值卡在0.07怎么办”很多人第一反应是再跑一周。实际你会发现再跑一周可能跌到0.03也可能涨到0.11完全看运气。这些年我最大的体会是显著性不是熬出来的更不是靠祈祷熬出来的。它背后有一套可以计算的逻辑你只要把逻辑链条理顺至少有一半“不显著”的问题根本不需要延长实验就能解决。这个系列的第一篇我会把显著性到底由什么决定、为什么有些实验怎么做都不显著、以及在不改动业务方案的前提下哪些操作能系统性地提升检出能力挨个拆开。这不是给统计教科书背书而是从我在业务里实际踩过的坑倒推出来的经验。适合两类人一类是刚接手AB实验的分析师和产品经理另一类是做了好几年但遇到“假阴性”“假阳性”时只能靠直觉判断的老手。今天先把方法论讲透下一篇我会专门用python把样本量计算、方差压缩和显著性检验完整跑一遍给出一套可以直接复制的代码。1. 先重新理解显著性它到底卡在哪里1.1 显著性不是一个“概率”很多非统计背景的同学会把p值理解成“方案有效的概率”这其实是个特别危险的误解。p值的准确定义是假设实验组和对照组真实没有差异你看到当前这么大差异甚至更大差异的概率。它回答的问题是“如果我没做改动结果会不会也长这样”而不是“我的改动到底有多大可能有效”。举个例子。p0.03的意思是如果方案没效我们有3%的概率看到眼下这个差异。这并不能反过来说“方案有效的概率是97%”。真实有效的概率还要结合事前预期、先验概率、实验设计质量来判断。但业务里没人做贝叶斯那一套通常就是用p值做闸门。所以理解p值的作用边界就很重要它只是一个“惊讶程度”的度量越惊讶越说明这不像是噪声。真正影响显著性的是下面四件事的组合样本量、方差、效应量、显著性阈值。这四件事里显著性阈值一般固定成0.05样本量、方差、效应量就是真正能下手的地方。我之前见过不少团队花了大量时间调模型和特征却不知道模型效果好坏和显著性提升其实是两回事。模型推断能力再强最终检验还是在p值这一个点上见真章。1.2 “不显著”通常逃不出三类原因一个AB实验跑完p值大于0.05原因基本可以收敛到三类样本量不足、方差太大、真实效应太小。这个归类看起来简单但绝大多数团队在排查时根本不会系统去分只会笼统说一句“没效果”或者“有问题”。样本量不足是最常见的。你计划跑两周结果第二周赶上大促很多用户被活动吸引实验组和对照组的注意力都被分流了两周累积下来的有效样本根本不足以支撑你设定的提升幅度。这时候p值会在0.05附近反复横跳今天0.06明天0.04后天又0.08。第二类是方差太大。这个在业务数据里太常见了。如果你看的是GMV、支付金额、订单量这种带有明显周期和大额波动的指标组内方差可能大到把真实差异完全淹没。我见过一个实验方案提升人均付费本来是明确的但因为客单价方差太大直接检验就是出不了显著性后来换了指标口径才显出来。第三类最扎心就是真实效应本身太小。产品改动确实有效但提升只有0.2%而当时实验设计是按“提升1%”来预估样本量的。这种情况下你怎么跑都很难显著除非把流量规模扩大十倍甚至几十倍。根因典型表现优先处理动作样本量不足p值在0.05附近波动或对照组和实验组的置信区间大范围重叠计算最小样本量延长时长或增加流量方差太大指标日环比剧烈波动置信区间宽得离谱换稳定指标、用协变量降噪、分层真实效应太小样本量已经很大p值始终稳定在0.1以上接受小收益或调整实验设计、重新定义目标人群1.3 一个常被忽略的前提做实验前先定义效应量你可能听过MDEMinimum Detectable Effect最小可检测效应这个概念但在真实业务里很多实验根本不知道自己的MDE是多少。产品提了个需求说“我们预期提升转化率”但具体是提升0.5个点还是0.1个点从来没有量化过。没有这个数值你就无法判断该分配多少流量、跑多少天。效应量定义不清的直接恶果是等到实验结果出来p值不显著了你根本不知道是样本不够还是方案无效还是方差问题。因为你没有一个明确的“及格线”。如果实验前就说好“转化率相对提升2%以上才有上线价值”那你可以直接反推所需样本量跑完只要没达到这个量级就没必要再用p值去硬扛。我个人的习惯是在实验需求文档里强制要求填三个数字当前基线值、预期绝对提升或相对提升、可接受的最短实验天数。如果没有这三个数字实验评审就不通过。这听起来很麻烦但实际能省掉后面至少一周的扯皮时间。2. 提升显著性的底层逻辑从统计公式反推可操作手段2.1 显著性公式里藏着三个杠杆两样本t检验的统计量长这样t (实验组均值 - 对照组均值) / sqrt(方差A / 样本量A 方差B / 样本量B)要让t值变大要么让分子变大要么让分母变小。分子就是观察到的组间差异分母是标准误。标准误再往下拆由两部分构成一组数据的波动幅度也就是标准差以及样本量。对应到业务语言就是三件事效果要大噪声要小人数要多。很多人把注意力全放在“人数要多”上因为这是最好理解的。增加样本量确实能降低标准误但它是最贵的杠杆。每天的流量就那么多产品改动有周期性不是你说加量就能加量。而且如果真实效应太小标准误要降到能检测出效应的水平需要的样本量可能会大到让你怀疑人生。噪声这个杠杆其实更划算。同样一批用户如果你能把他们的行为波动解释掉一部分剩下的纯粹随机噪声就会变小标准误随之下降显著性自然提升。这就像在菜市场里测一个商贩的平均营业额与其一天测到晚被砍价和客流高峰带偏不如把“工作日和周末”“是否节假日”这些因素先剥离掉信号会清晰得多。2.2 为什么“多跑一周”是最低效的解法当p值卡在0.06最自然的反应是多跑几天。这个选择本身不叫错但它是所有解法里性价比最低的。多跑一周本质上是在增大样本量可你要知道标准误是随着样本量开根号下降的。也就是说如果现在的样本量是1万你想把标准误降一半需要把样本量变成4万。很多实验根本不可能有这么大的流量余量。更麻烦的是越长周期的实验越容易被其他因素污染。大促来了流量结构变了算法迭代了用户行为变了竞品突然上了一波投放对照组和实验组同时受到影响。实验跑得越久你越难说是实验方案本身带来的差异还是外部环境造成的偏移。多跑一周有时候不仅不能解决问题还会让结论更不可信。从统计角度说如果你已经预估了最小样本量那么到了时间点没达到显著你再临时决定“要不要继续跑”这个决定本身就已经引入了主观性。你实际上是在挑选一个好看的停时点这在统计上叫窥视效应会让最后的p值失真。科学一点的做法是实验开始前就把样本量和运行时长写死到点看结果不给自己留“顺手加一周”的余地。2.3 提升效应量这件事能做的和不能做的效应量是三个杠杆里最直接的但也是最难人为控制的。一个产品改动如果真实提升只有0.5%你不可能靠统计方法把它“变”成1%的效应。效应量是干预本身带来的实验设计只能决定你能否干净地观测到它。但有一类事情确实可以在不改变产品思路的前提下把能检测到的效应量做大。最常见的是缩小实验人群定义。比如你的改动对全部用户平均提升0.3%但如果把范围限定到“近30天有活跃行为的老用户”提升可能变成1%。这并不算作弊因为你本来就没打算给沉默用户发这个改动实验是拿最终要上线的那个群体来做验证。只要定义在先不算选择偏差。还有一类是尽量降低对照组和实验组在基线水平上的差异。如果两组在实验开始前就不均衡比如实验组里新用户特别多对照组里老用户特别多那么观察到的差异里很大一部分是用户构成不同带来的压根不是方案效果。这种结构性偏差不会让你“提升效应”但能让你观察到的分子更接近真实的效应量。3. 最被低估的高杠杆操作降低方差3.1 先搞清楚方差从哪来降低方差之前得先知道方差从哪来。一个线上AB实验的观察指标方差一般来自四层用户本身的异质性、时间周期带来的波动、流量环境和活动影响、以及各种异常流量。你没法完全消掉这些但可以一层一层的拆解。用户异质性是最基础的。每个人的购买力、点击习惯、访问深度差别很大这就导致哪怕一组用户没有接受任何干预他们的平均转化率也有很大的随机变化。时间周期影响也很明显工作日和周末的行为有结构性差异月底和月初又不一样。所以我在设计实验时一般要求实验周期至少覆盖一个完整的自然周避免周一和周日单独比较。流量环境和异常流量是很多时候被忽视的。某个渠道突然导入了一批低质量流量或者某天爬虫大量访问这些都会把方差拉高。如果这些流量落在两组里的比例不完全一样还顺便制造出假信号。你在实验开始前把排除规则定好远比事后发现异常再删数据要可靠。3.2 指标口径选择别用“日均GMV”这种炸弹指标很多实验不显著问题从选指标开始就埋下了。像我前面说的支付总额、GMV、订单量这类指标天然带有大量高波动用户。几个大客户在某天集中下单对照组均值一下被拉得很高实验组的真实优势直接淹没在噪声里。不是说这些指标不重要而是它们不适合直接作为显著性检验的原始指标。更稳的做法是换一个人均指标或者比率指标。比如本来用GMV改成支付用户人均支付金额本来用订单量改成下单用户转化率。把总量摊到人头上等于先做了一层标准化削掉一部分因用户规模波动带来的方差。如果数据分布特别偏还可以考虑对指标做对数变换减少极端值的影响。我见过一个之前一直不显著的项目方案把结算页的入口改得更显眼最初看的是“提交订单总次数”怎么跑p值都降不下来。后来换成“提交订单用户数 / 访问结算页用户数”也就是一个有明确漏斗定义的转化率结果不到一周就显著了。变化不是因为方案变强了而是指标口径把无关的重复提交行为排除掉了方差大幅度下降。选指标时优先选分子分母都有明确业务边界、且受单个用户重复行为影响小的比率这是成本最低的降方差手段。3.3 用CUPED把历史数据变成你的杠杆CUPEDControlled-experiment Using Pre-Experiment Data是业界公认有效的方差压缩方法。它的核心思路很简单找到一个和当前指标高度相关、但不受实验干预影响的历史变量比如用户过去7天的活跃度或历史消费金额然后用这个历史变量把当前指标的噪声“解释”掉一部分。调整后的指标可以写成y_adjusted y - θ × (x - E[x])其中θ等于cov(x, y) / var(x)可以用回归来估计。直觉上很好理解如果历史变量x里包含了用户潜在水平的很多信息那么从当前指标y里减掉x贡献的那部分剩下的残差就是真正随机的噪声了。方差变小显著性自然提升。实际操作里我最常用的协变量是“实验前7天的目标指标均值”和“实验前30天的核心行为频次”。这两个变量的预测力强而且因为是完全发生在实验之前的数据肯定不受当前干预的影响。用AA数据去测试一下不同协变量组合能削掉10%到40%的方差。有一个要注意的坑协变量必须是实验开始之前的数据。如果你把实验期间的某个特征加进来当协变量而它其实受到了实验方案的影响那你就等于把一部分效果提前“控制掉了”实验结果会偏向不显著或者严重失真。3.4 分层与排除规则把结构性噪声挡在实验外方差压缩还有一类操作不依赖算法而是依赖实验设计。分层这个概念听起来很数学但落地其实不复杂。比如新老用户的行为差异巨大你完全随机分流实验组和对照组不一定恰好各占一半的新老用户。这两组的新老占比一旦有偏差所有后续比较都会被这个结构性差异干扰。解决方法有两个层级。第一层是流量分配时做分层随机也就是先按新老用户、平台、城市等级这些关键维度分层再在每一层内部做随机分组保证实验组和对照组在各维度上均衡。第二层是分析时加入分层变量用回归模型调整后再检验。两个方法是互补的分配时做到均衡分析时再吸收掉残余的层间差异。排除规则也属于结构性降噪。现在很多产品都有爬虫防护、内部账号、测试流量这些账号对实验指标的贡献往往是无效噪声甚至是有害异常点。比较好的做法是在实验计划阶段就把排除规则写清楚比如“排除近7天有异常点击频次的用户”“排除内部测试白名单账号”。关键点是不能等实验结果不理想之后才想起来去删那样删出来的显著结果没有说服力。4. 用python把“提升显著性”量化出来4.1 一个模拟看完方差的影响光说理论容易让人没感觉我用python跑一个例子给你看。假设对照组均值为100标准差分别是20和14实验组均值是101每组的样本量都是2000。这个场景可以理解为方案的真实提升是1个单位但一组数据的噪声大小不同。import numpy as np from scipy import stats rng np.random.default_rng(2024) n 2000 # 高方差场景标准差20 ctrl_high rng.normal(100, 20, n) exp_high rng.normal(101, 20, n) t_high, p_high stats.ttest_ind(exp_high, ctrl_high) # 低方差场景标准差14 ctrl_low rng.normal(100, 14, n) exp_low rng.normal(101, 14, n) t_low, p_low stats.ttest_ind(exp_low, ctrl_low) print(f高方差场景: t{t_high:.3f}, p{p_high:.3f}) print(f低方差场景: t{t_low:.3f}, p{p_low:.3f})我用固定随机种子跑出来的结果是高方差场景p值在0.11左右低方差场景p值在0.024左右。两组样本量、真实效应完全一样唯一的区别就是标准差从20降到了14。p值一个不显著一个显著。这个模拟直观地说明有时候你的方案并不是没效果而是统计噪声大到检测不出来。4.2 样本量计算你真正需要多少人在实验开始之前你应该先做样本量计算。流量允许的情况下我们一般会把统计功效设置成80%显著性水平设成5%然后根据预期效应量和历史标准差反推样本量。用statsmodels可以很直接算from statsmodels.stats.power import TTestIndPower analysis TTestIndPower() # 效应量 预期提升幅度 / 合并标准差 # 这里模拟的是提升1个单位高方差组标准差为20低方差组标准差为14 n_high analysis.solve_power(effect_size1/20, alpha0.05, power0.8, ratio1) n_low analysis.solve_power(effect_size1/14, alpha0.05, power0.8, ratio1) print(f高方差下每组约需样本量: {n_high:.0f}) print(f低方差下每组约需样本量: {n_low:.0f})算出来很直观高方差下每组需要六千多人低方差下每组只需要三千多人。同样是80%的功效和5%的显著性标准差从20降到14所需样本量差不多减了一半。换句话说在实验前花点时间降低方差比实验跑起来之后干等流量要高效得多。这里有个容易混淆的概念需要澄清效应量不是“你希望看到多少提升”而是“提升幅度除以标准差”。所以降低标准差本身就相当于放大了效应量。很多人不止一次问我“我预期提升10%为什么算出来样本量还是那么大”多半就是没有把当前指标的标准差考虑进去。4.3 在python里验证CUPED的效果CUPED的威力也可以用一个小模拟验证。我构造一组数据结果y受到一个历史变量pre的强烈影响真实实验效应是1噪声项的标准差是10。用普通t检验因为pre带来的方差没有被解释掉p值可能不显著但如果把pre作为协变量放进回归方差被吸收一部分显著性就很明显。import numpy as np from scipy import stats import statsmodels.api as sm rng np.random.default_rng(7) n_per 1000 treat np.concatenate([np.zeros(n_per), np.ones(n_per)]) pre rng.normal(0, 15, n_per * 2) noise rng.normal(0, 10, n_per * 2) # 模拟结果真实效应是1pre对结果的影响系数也是1 y 1.0 * treat pre noise ctrl_y y[treat 0] exp_y y[treat 1] t_raw, p_raw stats.ttest_ind(exp_y, ctrl_y) print(f未调整检验: t{t_raw:.3f}, p{p_raw:.3f}) X np.column_stack([treat, pre]) X sm.add_constant(X) model sm.OLS(y, X).fit() print(fCUPED调整后: 系数{model.params[1]:.3f}, 标准误{model.bse[1]:.3f}, p{model.pvalues[1]:.3f})我在固定种子下跑未调整的p值接近0.21调整后的p值降到0.024左右。coef基本稳定在1附近但标准误缩小了差不多一半。这就是CUPED在每个真实实验里给你的东西同样一批数据因为多了一个历史协变量检验灵敏度提升一大截。实际落地的时候不是非要用这个手写回归。很多实验平台内置了CUPED功能你只需要指定协变量。但自己跑一遍这个模拟你会更清楚为什么平台要让你上传实验前7天的指标数据也更容易判断平台给出的“方差缩减比例”到底是好还是坏。4.4 从模拟中得到的三个实用结论第一把标准差降下来比把样本量翻倍更现实。方差减半等价于在不增加任何流量的情况下让有效样本量翻倍。这是统计上经过严格证明的结论也是所有平台型实验工具都在推CUPED的原因。第二当你看到p值稳定在0.06、0.07这种尴尬区间时先不要急着加时间。你先看两个数字一是当前样本量够不够二是方差有没有明显压缩空间。如果样本量已经达到预估需求的90%以上方差又很粗糙优先做方差压缩。第三实验开始前用一个小时做样本量计算比实验结束后花一周去解释不显著要好得多。很多人觉得启动实验最重要其实实验开始前的十分钟才是决定这个实验能不能检出真实效果的黄金时间。5. 实操中的常见隐患避免科学变玄学5.1 中途偷看和“再等一周”会让p值失真我见过太多团队在实验期间每天打开报表看p值看到0.049就兴奋看到0.06就焦虑。这种持续监控的行为在统计上是有代价的。你每看一次p值就相当于多做了一次是否要停下来的判断这会让最终的假阳性率远超名义上的5%。更常见的是看到不显著就“再跑一周”看到显著就立刻停。这种做法本质上是把实验停止时间交给了随机波动。如果方案其实没有效果多跑几周总有可能撞上一个低于0.05的p值。这个结果上线之后十有八九在后续全量阶段现出原形。如果你确实需要随时监控不要用普通p值作为停止标准。至少应该用带边界控制的序贯检验方法或者先固定好最小样本量到时间再揭盲。在实验方案里把这两条写清楚你就躲掉了最不显眼但真实存在的统计陷阱。5.2 多重比较总有一个指标会小于0.05AB实验平台默认会展示几十个甚至上百个指标。你去看这几十个指标的p值就算实验方案完全没有真实效果平均每20个指标里也有一个会低于0.05。你挑其中最好看的一个写进汇报那基本上就是在自欺欺人。这个问题没有特别复杂的解法核心原则是“事前锁定”。在实验开始之前从业务目标出发选一个主指标最多再加两到三个次要指标。实验结束后主指标显著才谈得上验证通过。如果你实在想兼顾探索性分析可以选择Benjamini-Hochberg的FDR控制方法对p值做一下校正至少比直接裸看几十个p值要稳。真正常见的问题是产品经理喜欢“这也看看那也看看”看多了总能看到一个值得讲的故事。实验结果汇报时必须写清楚看过的指标总量和指标选择的时间点不然别人很难判断这个显著结果是预设的还是扒出来的。5.3 口径不一致再小的p值也是数字垃圾我复盘过一些线上实验平台上的历史数据发现有一个特别隐蔽的问题实验组和对照组的指标口径在中间被改过。比如实验开始的时候“转化率”的定义是“点击按钮的用户数/访问用户数”后来运营发现按钮点击有重复统计就把分子改成了“去重用户数”。前七天的数据和后七天的数据混在一起检验结果当然不可信。还有另一种情况是对照组用人数算人均实验组用事件数算人均。这种口径错位会导致组间差异虚高p值极度显著但实际没有任何业务含义。所以每次实验结果显著的时候不要只看p值先回到底层数据确认分子分母定义一致、统计周期一致、用户范围一致。如果你把指标口径变化的时间点和实验运行时间画在一张图上看很多时候会发现所谓的“显著”恰好出现在口径调整之后。这种情况提前预防最重要实验期间严禁改动统计口径任何指标定义变更都需要另开一个实验验证。5.4 正式实验前先做AA测试很多人把AA测试当成可有可无的形式主义其实它是检查实验系统健康度的最好工具。把两个同质分组都设为对照组不给任何干预跑完全程看结果是否显著。如果AA测试跑出来p值小于0.05说明分流系统、数据管道或者指标计算里存在系统性偏差在这种环境下你的AB实验结论是不可信的。我自己的习惯是在正式实验开始前随机抽一周历史流量跑AA确认两组在核心指标上没有显著差异。这个步骤能提前发现平台分流不均、数据延迟、埋点缺失等底层问题。你可能觉得多跑AA会拖慢节奏但比起上线一个被系统偏差污染的AB结果这个时间成本几乎可以忽略。如果AA测试不通过通常要先检查是否有未参与分层的关键维度比如渠道来源、客户端版本。还有一个容易被忽略的点就是实验平台是否把新用户和存量用户切成两套逻辑导致两组构成不一致。把这些问题解决完了再上正式实验显著性的意义才站得住。最后再说一个我自己的习惯。做完一次AB实验我一般不会只看主指标有没有显著还会顺手把方差缩减比例、实际样本量和预估样本量的比值记录下来。攒多了你会发现那些总是“不显著”的实验要么是方差缩减没做好要么是实验前压根没算过样本量。你把这些基础动作做扎实了再回头看那些曾经让你焦虑到失眠的0.07大部分根本不需要靠延长实验来赌运气。下一次你手里再卡一个不显著的结果先按这个框架拆一遍大概率会找到比“再跑一周”更好的答案。