
数据分析这行干了七八年被问到最多的概率问题不是正态分布反而是二项分布。原因很朴素业务里绝大多数判断都是“是/否”型的——用户点没点、订单退没退、设备坏没坏、工单有没有一次解决。只要一件事的结果只有两种而你要在一批固定的样本里数“成功”的那一类出现了多少次二项分布就自动站到了台前。它回答的问题很具体已知单次事件发生的概率 p在 n 次条件相同、相互独立的试验里恰好发生 k 次的概率是多少。往后延伸一步累积概率、阈值判断、置信区间、样本量估算全都能从这一个公式里长出来。这篇东西写给三类人刚学完概率论但不知道能干嘛的学生、每天跟指标打交道却只记得点工具跑数的运营和数据分析师、以及想弄明白“为什么是这个公式”而不是照着函数名抄的工程师。我打算把公式拆到骨头缝里把代码给到能直接复制粘贴再把我自己在业务里踩过的坑一件件摆出来。1. 二项分布解决的到底是什么问题1.1 从抛硬币说起n 次独立重复试验抛一枚均匀硬币正面概率 0.5这是单次伯努利试验。伯努利试验的画像很干净结果只有两个一个叫成功通常记作 1一个叫失败记作 0两者概率加起来等于 1。现在把试验重复 10 次问“正好出现 3 次正面”的概率是多少——这就是二项分布要算的东西。它不关心第几次是正面只关心总共数出来几个。一个容易被忽略的细节是二项分布的名字来自二项式定理。把 (p q)^n 展开其中 q 1 - p第 k 项的系数正好是组合数 C(n, k)形如 C(n, k) · p^k · q^(n-k)。换句话说二项分布的概率质量函数就是二项式展开的通项。这个观察有实际价值当你想验证一段代码算得对不对时把 k 从 0 到 n 的所有概率加一遍结果必须精确等于 1误差只来自浮点舍入。我见过不少人写的实现偏了 0.02十有八九是组合数算错或者循环边界写成了range(n)而不是range(n1)。这个自检动作只要两行代码却能省掉后面所有的返工。还有一层理解上的分水岭二项分布描述的是“次数”不是“比例”。次数除以 n 才是频率而频率是一个新的随机变量它的期望是 p方差是 p(1-p)/n。很多业务争议其实出在这层转换上——有人拿一次抽样的频率当成了真实的 p然后在这个不稳定的地基上继续算概率后面的结论自然摇晃。1.2 三个成立条件少一个都不能用二项分布有三个前提缺任何一个算出来的数就只是“看起来像”没有解释力。第一试验次数 n 必须事先固定。这一条最容易被违反。打个比方你想统计“抛硬币直到出现第 3 次正面为止总共抛了多少次”那 n 本身就是随机的属于负二项分布的领地。再比如客服场景里“今天接到多少个电话”往往不是事先定死的而是自然到达的那更贴近泊松分布。判断方法很简单问自己一句“样本量是实验开始前就写在纸上的吗”答不上来就别硬套。第二每次试验只有两种互斥结果并且成功概率 p 在整个过程中保持不变。质检场景里如果你一边抽检一边把不合格品挑走剩下那批货的合格率就被你亲手改了p 不再是常数。第三各次试验之间相互独立。这是现实中最脆弱的假设。同一台机器连续生产的前后件可能共享模具磨损同一个用户连续收到三次推送第二次的点击率会被第一次影响同一批货的缺陷往往来自同一个批次问题天然聚簇。注意条件不满足时正确的做法不是“近似一下凑合用”而是换分布或者改抽样方式。用错分布比不算更危险因为它会给你一个看起来很专业的数字让你误以为决策有依据。1.3 它和超几何、泊松、几何分布怎么分工这几个分布经常在同一张表里出现混淆的人不少。我把它们的边界整理成一张表遇到问题时先对号入座。分布描述的场景关键前提典型用途二项分布n 次试验中成功次数n 固定、独立、p 恒定抽检、转化率、故障计数超几何分布从有限总体不放回抽取抽到多少个目标物总体有限、不放回、抽样改变比例小批量全检、抽签、库存盘点泊松分布单位时间或空间内事件发生次数事件独立、发生率稳定、n 不固定来电到达、页面报错、缺陷点分布几何分布首次成功所需的试验次数独立、p 恒定首次点击需要的曝光次数负二项分布第 r 次成功所需的试验次数独立、p 恒定累计转化所需曝光量有一条经验判据值得记住当总体容量 N 远大于样本量 n 时超几何分布会收敛到二项分布。工程上常用的门槛是 N ≥ 20n此时把抽检当成有放回处理误差通常在千分之几以内可以直接接受。反过来如果你从 50 件里抽 30 件出来检测总体被掏掉了一大半那就老老实实用超几何别偷懒。在真正需要把这些分布落到代码之前我会建议你把场景写成一句话格式是“在一次固定的 ___ 次观察里成功概率固定为 ___互相不影响的次数有多少”空格填不出来基本就说明场景不属于二项分布。2. 公式不是背的拆开组合数与指数项2.1 C(n, k) 到底在数什么P(X k) C(n, k) · p^k · (1-p)^(n-k) 这个式子里p^k 是 k 次成功各自发生的概率相乘(1-p)^(n-k) 是剩下 n-k 次失败相乘。这两块都好理解卡人的是 C(n, k)。C(n, k) 数的是“哪些位置是成功”的方案数。把 n 次试验排成一条队伍你需要挑出 k 个位置贴上“成功”标签。第一个位置有 n 种选法第二个剩 n-1 种一路乘到 n-k1得到 n·(n-1)···(n-k1)也就是排列数。但这里有个问题被选中的 k 个位置之间是有顺序的而你并不关心顺序——第 2 次和第 7 次成功跟第 7 次和第 2 次成功是同一件事。所以要把重复计数除掉除以 k!最终得到 C(n, k) n! / (k! · (n-k)!)。拿 n 5、k 2 走一遍5 × 4 20除以 2! 2得 10。手工枚举一下也确实只有 10 种组合方式。这个推导的关键在于“先按顺序数再除掉顺序带来的重复”也就是“排列除以阶乘”的套路。想清楚这一层公式里为什么偏偏出现一个组合数就不再是记忆负担了。主流的编程语言都自带组合数函数Python 3.8 以后有math.comb科学计算里用scipy.special.combJavaScript 需要自己写或者引第三方库。手写实现时用迭代乘法比先算阶乘再相除更稳因为 n 200 时阶乘已经溢出成无穷大了而迭代乘法在整除的意义下可以保证中间结果都是整数。2.2 期望 np 与方差 np(1-p) 的来路公式背下来没用推导过程才是能迁移的能力。这里有一个特别漂亮的做法引入示性变量。对第 i 次试验定义 Xᵢ成功取 1失败取 0。那么总成功次数 X X₁ X₂ … Xₙ。单个 Xᵢ 的期望是 1·p 0·(1-p) p方差是 E[Xᵢ²] - (E[Xᵢ])² p - p² p(1-p)。注意 Xᵢ² Xᵢ因为 0² 0、1² 1这一步是关键。期望可以线性相加不需要独立性E[X] np。方差则必须依赖独立性独立时 Var(X) Σ Var(Xᵢ) np(1-p)。这条推导的实用价值在于它告诉你方差的独立性有多重要——如果各次试验正相关真实方差会比 np(1-p) 大而你不会从公式里看出来。为什么方差一定是 p(1-p) 而不是别的形式看极端情况p 0 或 p 1 时方差为 0因为结果完全确定p 0.5 时方差最大为 0.25。这跟直觉一致越接近五五开结果的抖动越大。知道了标准差是 √(np(1-p))你就能快速判断一个观测值是否离谱。n 100、p 0.5 时标准差是 5那么观察到 70 次成功已经偏了 4 个标准差值得停下来查数据而不是急着庆祝。2.3 手算一个完整例子设 n 10p 0.3。我们算一下不同 k 的概率顺便验证之前说的“求和等于 1”。取 k 3C(10, 3) 120p³ 0.027(1-p)⁷ 0.7⁷ ≈ 0.0823543三者相乘得 120 × 0.027 × 0.0823543 ≈ 0.266828。把 k 0 到 10 全列出来kC(10,k)P(X k)累积 P(X ≤ k)010.02824750.02824751100.12106080.14930832450.23347440.382782731200.26682790.649610642100.20012100.849731652520.10291930.952650962100.03675690.989407871200.00900170.99840958450.00144670.99985629100.00013780.99999401010.00000590.9999999最后一行累加接近 1误差在小数点后第七位属于浮点舍入正常范围。期望是 10 × 0.3 3方差是 3 × 0.7 2.1标准差约 1.449。观察这张表还能发现一个有趣的规律概率峰值在 k 3和期望落在同一个位置附近。这不是巧合众数通常落在 ⌊(n1)p⌋ 附近n 10、p 0.3 时是 3.3 向下取整得 3对上了。顺带说一句这个例子和前半部分有一处对照累积到 k 3 的概率是 0.6496也就是约 65%。很多人在报告里写“大约七成”这种凑整会直接影响后面的决策阈值能算到小数点后两位就别偷懒。3. 代码实现从手写公式到调用现成库3.1 三条实现路径怎么选同一个需求至少有三种写法各自适合不同场合。第一条是纯手写用math.comb加幂运算。优点是零依赖、逻辑透明、便于在嵌入式或受限环境里落地缺点是组合数在 n 很大时会很慢n 超过几千建议别硬扛。第二条是用 SciPy 的stats.binom。它提供 pmf、cdf、sf、ppf、rvs、mean、var、std 一整套接口还能做向量化计算是数据分析的默认选择。所谓向量化就是传一个数组进去一次性返回所有 k 对应的概率比写循环快一个量级。第三条是走统计建模库比如 statsmodels 的离散模型接口或者直接用 GLM 的框架。适合你需要拟合参数、做假设检验、跟其他协变量一起分析的时候。日常算单个概率没必要上这么重的工具。我的建议是验证公式理解用第一条日常生产用第二条做因果或回归分析用第三条。3.2 最小可运行示例先从手写版开始这段代码的作用是建立一个可信的参照系。from math import comb def binom_pmf(n: int, k: int, p: float) - float: 二项分布概率质量函数n 次试验恰好 k 次成功 if k 0 or k n: return 0.0 return comb(n, k) * (p ** k) * ((1 - p) ** (n - k)) def binom_cdf(n: int, k: int, p: float) - float: 累积概率 P(X k) return sum(binom_pmf(n, i, p) for i in range(k 1)) print(binom_pmf(10, 3, 0.3)) # 0.2668279... print(binom_cdf(10, 3, 0.3)) # 0.6496107...用 SciPy 写同一个东西对比一下结果是否一致from scipy import stats n, p 50, 0.02 dist stats.binom(n, p) print(dist.pmf(0)) # 0.3641696... print(dist.pmf(1)) # 0.3716016... print(dist.pmf(2)) # 0.1858008... print(dist.cdf(2)) # 0.9215721... print(dist.sf(2)) # 0.0784279... 等价于 P(X 3) print(dist.mean(), dist.var(), dist.std()) # 1.0 0.98 0.9899495这里有个细节值得单独拎出来sf是生存函数等于 1 减去 cdf但它在数值上比1 - dist.cdf(k)更稳。原因是当概率极小的时候直接做减法会经历灾难性抵消把有效数字吃光。比如算 P(X ≥ 40) 且结果在 1e-15 量级用减法你可能得到 0用sf还能保住几位有效数字。凡是要算“至少”“超过”这类尾部概率一律优先用sf。再补一个查分位数的用法这个在业务里出场率极高# 95% 分位一批 200 件、不良率 3% 的货缺陷数上限是多少 q95 stats.binom.ppf(0.95, 200, 0.03) print(q95) # 10 # 反过来给定阈值 12 件超过它的概率有多大 print(stats.binom.sf(12, 200, 0.03)) # 0.0035...ppf是累积分布的反函数喂给它一个概率它返回对应的 k。业务里问“留多少余量才够用”本质上就是在求分位数。3.3 把分布画出来用眼睛验证数字看多了会麻木画图能发现很多算不出来的问题。下面这段代码把三个不同 p 值的分布叠在一起对比。import numpy as np import matplotlib.pyplot as plt from scipy import stats n 20 ks np.arange(0, n 1) fig, axes plt.subplots(1, 3, figsize(15, 4), shareyTrue) for ax, p in zip(axes, [0.1, 0.5, 0.9]): ax.bar(ks, stats.binom(n, p).pmf(ks), color#4c72b0) ax.set_title(fn{n}, p{p}) ax.set_xlabel(k) axes[0].set_ylabel(P(X k)) plt.tight_layout() plt.show()跑完你会看到三件事。p 0.5 时图形左右对称山峰在正中间p 0.1 时分布严重左偏绝大多数概率压在 k 0 到 3 之间尾巴长在右边p 0.9 时完全镜像过来。p 越偏离 0.5偏态越明显这时候用“均值加减标准差”来描述分布已经开始失真了得留意后面讲的近似条件。再做一个更实用的图固定 p把 n 从小到大排开观察形状怎么变化。fig, axes plt.subplots(1, 3, figsize(15, 4), shareyFalse) for ax, n in zip(axes, [5, 20, 100]): ks np.arange(0, n 1) ax.bar(ks, stats.binom(n, 0.3).pmf(ks), color#55a868) ax.set_title(fn{n}, p0.3) ax.set_xlabel(k) plt.tight_layout() plt.show()n 从 5 涨到 100图形从明显右偏逐渐变得接近钟形。这就是正态近似的直观来源后面 6.1 节会给出具体的门槛数字。4. 三个能直接抄的业务场景4.1 抽检与质量阈值不良率 2% 时该不该退货场景是这样的供应商声称某批货的不良率不超过 2%你从 1000 件里抽 50 件检测发现 3 件不合格。该不该拒收先把问题翻译成二项分布的语言。n 50假设供应商的说法为真p 0.02。我们要算的是 P(X ≥ 3)。from scipy import stats n, p 50, 0.02 d stats.binom(n, p) print(d.pmf(0), d.pmf(1), d.pmf(2)) # 0.3641696 0.3716016 0.1858008 print(d.cdf(2)) # 0.9215721 print(d.sf(2)) # 0.0784279 - P(X 3)结论如果真实不良率确实是 2%抽 50 件出现 3 件及以上不合格的概率是 7.84%。这个概率不算罕见——大约每 13 次抽检就会碰上 1 次。所以仅凭这一次结果拒收证据偏弱属于“有点可疑但没到铁证”。那要多少件不合格才够硬我们可以反算。从ppf(0.95)得到阈值 3意思是 50 件里发现 3 件及以上已经落进了尾部 5% 区域但要达到更严格的判据比如尾部 1%需要查ppf(0.99)结果是 4。也就是说至少 4 件不合格才有底气说“这个结果在 2% 假设下不太可能自然发生”。提示单次抽检的结论永远有犯错空间。真正的质量管控应该设计成序列抽样——先抽一批边界情况再追加样本而不是一次性拍板。二项分布在这里的作用是给出每一阶段的阈值而不是替你下结论。还有个容易犯的错抽完发现不合格品之后把这 3 件从批里剔掉再拿剩下的重新算。这么做就破坏了 p 恒定的前提因为剩余批次的构成已经变了。正确的补救方式是重新抽样或者改用超几何分布。4.2 A/B 实验两个转化率到底差得明显不明显这是所有做增长的人绕不开的场景。对照组曝光 1000 次、转化 40 次实验组曝光 1000 次、转化 52 次。转化率从 4.0% 涨到 5.2%看起来涨了 30%能上线吗别急着看相对涨幅先看这个差距是不是被随机波动解释得掉。import numpy as np from scipy import stats n1, x1 1000, 40 n2, x2 1000, 52 p_pool (x1 x2) / (n1 n2) # 合并比例 0.046 se np.sqrt(p_pool * (1 - p_pool) * (1/n1 1/n2)) z (x2/n2 - x1/n1) / se p_value 2 * (1 - stats.norm.cdf(abs(z))) print(f合并比例{p_pool:.4f}, 标准误{se:.6f}) print(fz{z:.4f}, 双尾 p{p_value:.4f}) # z1.2806, p0.2003p 值 0.20远远够不到 0.05 的门槛。翻译成人话如果两个版本的真实转化率完全一样出现“看起来差 1.2 个百分点”这种结果的概率高达 20%五次里有一次。这就是典型的“看着有提升其实是噪声”。那需要多大的样本才能判定这 1.2 个百分点是真的用二项分布的标准差做一个粗略估算单个版本的标准误是 √(p(1-p)/n)要让 1.2 个百分点的差异达到约 2 倍合并标准误对应双侧 5%代入 p ≈ 0.046 反解得每组大约需要 6000 到 7000 次曝光。粗略的工程化算法是def sample_size_two_prop(p1, p2, alpha0.05, power0.8): from scipy import stats z_a stats.norm.ppf(1 - alpha/2) z_b stats.norm.ppf(power) p_bar (p1 p2) / 2 n ((z_a * np.sqrt(2*p_bar*(1-p_bar)) z_b * np.sqrt(p1*(1-p1) p2*(1-p2))) ** 2) / (p2 - p1) ** 2 return int(np.ceil(n)) print(sample_size_two_prop(0.04, 0.052))算出来大约是每组 6200。这个数字在实验开始前就该算出来而不是跑完才回头追问“样本量是不是不够”。提前算样本量最大的好处是它逼你把“值得关注的提升幅度”想清楚而不是结果出来之后再给一个解释。需要提醒一点这里用的是正态近似版本的双比例检验本质上是二项分布在样本量较大时的近似。n 1000 时近似已经足够可靠。但如果你的转化事件只有十几二十次就得回到精确方法比如 Fisher 精确检验或者直接用二项分布算似然比。4.3 容量估算给排班和并发留多少余量另一类高频场景是留余量。假设某个服务每分钟的报错次数历史上均值是 4 次你想知道“留多少条告警阈值才不会天天误报”。这里更贴近泊松分布但如果你的统计粒度是“每 1000 次请求里有几次报错”二项分布就直接适用了。举个具体数字n 1000 次请求历史错误率 p 0.003也就是平均 3 次错误。from scipy import stats n, p 1000, 0.003 d stats.binom(n, p) for conf in [0.90, 0.95, 0.99, 0.999]: print(f{conf:.3f} 分位阈值 {d.ppf(conf)}) print(超过 10 次的概率 , d.sf(10))跑出来 95% 分位在 6 到 7 之间99% 分位在 8 左右而超过 10 次的概率只有万分之几。那么把告警阈值设在 9 次或 10 次日误报率就压到了可以接受的范围。这就是一个典型的“用分位数代替拍脑袋”的例子——不设阈值你会被噪声淹没设得太灵敏又会产生告警疲劳。同一套思路可以搬到排班如果每个班次时段内单个坐席接到咨询的概率是 p总共 n 个坐席或者反过来算某个时段需要安排多少人力都归到二项分布的框架里。前提依然是各次事件互不影响——如果一次系统故障会连锁引发一大批咨询独立性就破了这时候要往负二项或过离散模型那边靠。5. 踩过的坑与排查清单5.1 独立性被破坏的四个信号独立性是二项分布里最难验证也最容易被忽略的前提。以下四个信号出现任何一个我都会立刻停下手里的事去核对数据。第一个信号是数据天然聚簇。同一天、同一批次、同一个用户产生的样本往往不独立。比如统计“某功能一周内每天的失败次数”如果周一有版本发布那天的失败数会异常高这属于外部冲击不是随机波动。第二个信号是“不放回抽取”。哪怕抽样逻辑写的是“随机取”只要取出来不归还p 就在变。判断方法是看总体容量和样本量的比值超过 1:20 就要警觉。第三个信号是前一次结果影响后一次。用户行为里这是常态看过一次广告之后第二次的点击概率通常会变化。这种“学习效应”会让真实的方差比 np(1-p) 大得多。如果你发现观测数据的方差明显超过理论值方差膨胀基本可以确定是过离散。第四个信号是样本之间存在资源共享。多台设备连同一个电源、多个订单来自同一个账号、多个请求打在同一台缓存节点上都会引入相关性。处理方式有三条路一是重新设计抽样把独立性恢复回去二是改用能刻画相关性的模型比如贝塔二项分布或带随机效应的模型三是承认结果的不确定性把方差放宽别用理论标准差去构造置信区间。5.2 p 值漂移最容易忽略的假设p 恒定这条同样经常被打破而且打破的方式很隐蔽。商务大促期间转化率天然高于平日、某个渠道的用户质量在慢慢下滑、产品改版后老用户的行为习惯变化——这些都会让“同一个 p”变成“一组变化的 p”。有个诊断方法很好用把数据按时间切成若干段各自估计 p然后看这些 p 之间的差异是否超出抽样误差范围。如果各段的标准误都很小但点估计之间拉开了好几个标准误那就是 p 在漂移不是随机波动。还有一个更隐蔽的变体混合总体。假设你的用户里有两类人一类的转化概率是 0.10另一类是 0.01各占一半。整体数据看起来的“平均转化率”是 0.055但它的分布根本不是二项分布而是两个二项分布的混合。这种数据画出来往往比标准二项分布更“胖”尾部更厚。用单一 p 去拟合算出来的极端值概率会系统性偏低阈值定得太紧误报率飙升。注意遇到混合总体第一反应应该是分层而不是调参。把人群按来源、设备、新老用户切开各算一份往往一眼就能看出问题。5.3 常见问题速查表下面这张表是我这几年攒下来的遇到问题时按现象查原因能省不少时间。现象可能原因排查动作各 k 的概率加起来不等于 1循环边界漏了 k n或组合数溢出用math.comb重算检查range(n1)尾部概率算出 0 或负数用 1 - cdf 做减法遭遇数值抵消改用sf(k)观测方差远大于理论 np(1-p)样本不独立或 p 在漂移按时间/渠道分层重新估 p样本量小的时候结论反复横跳抽样误差主导尾部概率估计不可靠用精确方法或补样本量抽检结果和实际质量对不上不放回抽样导致 p 变化总容量小于 20 倍样本量时改用超几何分位数阈值一上线就频繁告警p 被高估或混入了异常批次剔除异常时段后重新拟合手写实现和库函数结果不一致浮点精度或组合数中途取整用对数伽马函数重写或直接调库字号这一列我还想补一句手写实现不一致的情况里八成是组合数在中间步骤被转成了浮点。对于 n 上千的场景正确的写法是全程用对数空间计算gammaln(n1) - gammaln(k1) - gammaln(n-k1)避免阶乘直接爆炸。6. 近似、区间与实际参数选择6.1 正态近似与泊松近似什么时候能用n 一大二项分布的计算量就上来了组合数动辄几十位数。这时候近似公式就派上用场了但用错条件反而会把结论带偏。正态近似的经验门槛是 np ≥ 5 且 n(1-p) ≥ 5有些教材要求 10 更稳。满足条件时X 近似服从均值 np、标准差 √(np(1-p)) 的正态分布。这里有个关键技巧叫连续性校正因为二项分布是离散的算 P(X ≥ 60) 时要减 0.5写成 P(X ≥ 59.5)。少了这一步尾部概率会有肉眼可见的偏差。举个例子n 100、p 0.5算 P(X ≥ 60)。不做校正时 z (60-50)/5 2.0单尾概率约 0.0228做校正后 z (59.5-50)/5 1.9概率约 0.0287。而精确值是多少用代码跑一下stats.binom.sf(59, 100, 0.5)得到约 0.0284。很明显校正后的 0.0287 更贴近真值不做校正的 0.0228 差了将近 25%。from scipy import stats print(stats.binom.sf(59, 100, 0.5)) # 精确值 0.02844 print(1 - stats.norm.cdf((60 - 50) / 5)) # 未校正 0.02275 print(1 - stats.norm.cdf((59.5 - 50) / 5)) # 已校正 0.02872泊松近似适用于 n 很大、p 很小、np 保持中等的情况。经验门槛是 n ≥ 20 且 p ≤ 0.05或者更直接一点n ≥ 100 且 np ≤ 10。这时候二项分布趋近于参数 λ np 的泊松分布。前面那个 n 50、p 0.02 的抽检例子就是典型λ 1用泊松算 P(X ≤ 2) e⁻¹(1 1 0.5) ≈ 0.9197而精确值是 0.9216差不到千分之二完全够用。选择逻辑可以归纳成两句话p 在 0.3 到 0.7 之间且 np 够大用正态近似p 很小、n 很大用泊松近似其余情况老老实实算精确值现代计算机算这点东西根本不费劲。真正的问题从来不是算力而是选错了模型。6.2 样本量与置信区间怎么定估计出频率之后下一个问题永远是“这个估计有多准”。教科书写的是 Wald 区间 p̂ ± z·√(p̂(1-p̂)/n)但这个方法在 p 接近 0 或 1、样本量不大的时候表现很差甚至会算出负数下界。我自己一律用 Wilson 得分区间稳定得多。import numpy as np from scipy import stats def wilson_ci(x, n, conf0.95): z stats.norm.ppf(1 - (1 - conf) / 2) phat x / n denom 1 z**2 / n center (phat z**2 / (2*n)) / denom half z * np.sqrt(phat*(1-phat)/n z**2/(4*n**2)) / denom return center - half, center half print(wilson_ci(52, 1000)) # (0.03986, 0.06755)对这组数据Wilson 区间是 (3.99%, 6.76%)。回头看 4.2 节的 A/B 案例对照组的点估计是 4.0%刚好落在实验组区间的下边缘。这就再次印证了前面的结论——两组差异不显著因为区间明显重叠。用区间而不是单点去汇报是让沟通少吵架的最有效手段。样本量的估算其实是从置信区间公式倒推出来的先确定你能容忍的误差范围 d再代入 n z²·p(1-p)/d²。假设你想把误差控制在 ±1 个百分点以内p 取最保守的 0.5那么 n 1.96² × 0.25 / 0.0001 ≈ 9604。这个数字为什么这么眼熟因为几乎所有民调都是“1000 个样本、误差 ±3%”背后就是这套算法。p 取 0.5 是最保守的假设因为它让 p(1-p) 达到最大值 0.25如果你有历史数据表明 p 大约在 0.05需要的样本量会小很多。6.3 小样本场景下的贝叶斯补位现实里经常遇到极端情况只有 30 个样本、0 次失败。这时候频率学派的方法是给不出答案的——点估计是 0置信区间也是 0 到 0但实际上你根本不敢说“永远不会失败”。贝叶斯方法在这里能补上位置而且实现简单得出奇。二项分布的共轭先验是 Beta 分布意思是先验取 Beta(a, b)观测到 x 次成功、n - x 次失败之后后验仍然是 Beta(a x, b n - x)。这个共轭性质让计算变成纯粹的加法。from scipy import stats # 先验均匀分布表示事前没有任何偏好 a0, b0 1, 1 x, n 52, 1000 post stats.beta(a0 x, b0 n - x) print(后验均值 , post.mean()) # 0.05289 print(95% 可信区间 , post.ppf([0.025, 0.975])) # [0.03904, 0.06671]后验均值是 0.0529跟频率学派的 0.052 几乎一致——样本量够大时先验的影响被数据淹没了。但换到小样本场景比如 n 10、x 0先验就变得关键均匀先验给出的后验均值是 1/12 ≈ 0.083可信区间上界大约 0.24。这个结论在业务上远比“0”更有指导意义因为它诚实地表达了“我见过 10 次没出问题但不确定性还很大”。先验怎么选是个需要克制的环节。我的习惯是用弱信息先验比如 Beta(1, 1)或者根据历史长期数据给一个 Beta(α, β)α β 控制在 10 以内避免先验压过数据。如果业务方坚持要“用历史数据做先验”那就把 α β 放大但要清楚地告诉他们这相当于把历史数据也当成了本次实验的证据样本量会被虚增可信区间会偏窄。回过头看二项分布这套东西最有意思的地方在于它的诚实。它不会给你一个漂亮的答案只会告诉你在假设成立的前提下某个数字出现的可能性有多大。所有的判断包括拒收一批货、上线一个版本、设置一个告警阈值最终都要你自己来下。我个人的经验是算完之后先不要急着看结论回头检查一遍那三个前提——n 固定、结果二分、独立且 p 恒定。我踩过的绝大多数坑都不是公式用错了而是这三个前提里悄悄碎掉了一个而我当时以为它还在。