在 A/B 实验场景下,对于比率型指标的检验,以访购率为例,今天的访购率相比于昨天的访购率是有显著提升还是随机波动?这涉及到两样本二项总体的比例检验。
1 显著性检验
对每个访问过平台的商品的人,都有两个可能结果,购买商品和没有购买商品,购买与否可以看作是一个随机事件,顾客购买商品的可能性可以用概率来刻画。对平台而言,努力提高顾客的购买率是核心商业价值。
| 日期 | 第 1 天(今天) | 第2天(昨天) | 第3天(前天) | ··· |
|---|---|---|---|---|
| 购买 UV | \(a_1\) | \(a_2\) | \(a_3\) | ··· |
| 访问 UV | \(n_1\) | \(n_2\) | \(n_3\) | ··· |
| 访购率 | \(a_1/n_1\) | \(a_2/n_2\) | \(a_3/n_3\) | ··· |
设一组样本(今天),顾客购买与否可以用 0-1 分布(伯努利分布)来刻画,对每个顾客 \(x_1,x_2,\ldots,x_{n_{1}}\) (取值为 1 表示购买或者 0 表示没有购买)假定服从伯努利分布 \(\mathrm{Bernoulli}(p_1)\) ,且每个顾客购买与否相互独立,且购买意愿强弱由平台决定,是一样的,则该天所有人的购买行为累加 \(\sum_{i=1}^{n_1}x_i\) 就服从二项分布 \(\mathrm{Binom}(n_1,p_1)\) 。
当然,实际上,对同一件商品,不同的人看到后购买意愿是不同的,受顾客本身的因素影响。而平台的威力在于,它已经知道顾客的很多信息,比如年龄、性别、收入水平、城市线、历史购买习惯等,使得平台可以个性化地、针对性地推送,这个个性化服务包含了用户因素,就是说平台的推送包含了用户因素,那么平台的服务质量可以看作是影响访购率的主要因素。
基于以上考量,同理,设另一组样本(昨天),每个顾客的购买行为为 \(y_1,y_2,\ldots,y_{n_2}\) (取值为 1 或者 0),累加之后,\(\sum_{i=1}^{n_2}y_i\) 服从二项分布 \(\mathrm{Binom}(n_2,p_2)\) 。
结合前面表格的记号,则有
\[ \begin{aligned} a_1 &= \sum_{i=1}^{n_1}x_i \sim \mathrm{Binom}(n_1,p_1) \\ a_2 &= \sum_{i=1}^{n_2}y_i \sim \mathrm{Binom}(n_2,p_2) \end{aligned} \]
今天的访购率相比于昨天的访购率是有显著提升还是随机波动?转化为统计检验问题如下:
\[ H_0: p_1 = p_2 \quad vs. \quad H_1: p_1 \neq p_2 \]
对于这个检验问题,我们构造检验统计量 \(T\) ,在大样本情形下,根据中心极限定理,可得
\[ T = \frac{(\hat{p_1}-\hat{p_2}) - (p_1 - p_2)}{\sqrt{\frac{p_1(1-p_1)}{n_1}+\frac{p_2(1-p_2)}{n_2}}} \sim N(0,1) \]
其中,\(\hat{p_1},\hat{p_2}\) 是根据样本观察值计算的样本均值,根据替换法(Slutsky 定理),以样本均值替换总体均值。
\[ T \approx \frac{(\hat{p_1}-\hat{p_2}) - (p_1 - p_2)}{\sqrt{\frac{\hat{p_1}(1-\hat{p_1})}{n_1}+\frac{\hat{p_2}(1-\hat{p_2})}{n_2}}} \sim N(0,1) \]
在原假设成立的情况下,\(p_1 - p_2 = 0\) ,检验统计量 \(T\) 简化为
\[ \hat{T} = \frac{(\hat{p_1}-\hat{p_2})}{\sqrt{\frac{\hat{p_1}(1-\hat{p_1})}{n_1}+\frac{\hat{p_2}(1-\hat{p_2})}{n_2}}} \]
其中,\(\hat{p_1} = a_1/n_1,\hat{p_2}=a_2/n_2\) 。
1.1 手搓版本
在显著性水平 \(\alpha = 0.05\) 的情况下,下面计算检验统计量和 P 值。
n1 <- 86
n2 <- 93
a1 <- 30
a2 <- 50
p1 <- a1 / n1
p2 <- a2 / n2
T_hat <- (p1 - p2) / sqrt(p1 * (1 - p1) / n1 + p2 * (1 - p2) / n2)
# 检验统计量的值
T_hat
## [1] -2.59
# P 值
2 * pnorm(q = T_hat) # 这是一个双侧检验,正态分布是对称的
## [1] 0.009602
检验结果显示,P 值小于 0.05,拒绝 H0 。根据前面检验统计量的极限分布,检验统计量 \(\hat{T}\sim N(0,1)\) ,那么 \(\hat{T}^2 \sim \chi^2(1)\) 。换算成卡方分布,那么检验统计量和 P 值如下:
# 检验统计量
T_hat^2
## [1] 6.707
# P 值
1 - pchisq(q = T_hat^2, df = 1)
## [1] 0.009602
两种计算方法得到的 P 值是一样的。
1.2 比例检验 prop.test()
在 R 语言软件中,函数 prop.test() (两/多样本)、chisq.test() (两/多样本)和 binom.test() (单样本)都可以做比例检验问题。
# x 表示购买的用户 n 表示访问的用户
prop.test(
x = c(30, 50), n = c(86, 93),
conf.level = 0.95,
alternative = "two.sided"
)
##
## 2-sample test for equality of proportions with continuity correction
##
## data: c(30, 50) out of c(86, 93)
## X-squared = 5.7, df = 1, p-value = 0.02
## alternative hypothesis: two.sided
## 95 percent confidence interval:
## -0.34287 -0.03473
## sample estimates:
## prop 1 prop 2
## 0.3488 0.5376
函数 prop.test() 和前面计算的卡方检验统计量、P值存在差异是因为它内部调用卡方检验,且存在 Yates 矫正。手搓的版本实际上是 \(Z\) 检验(检验统计量的极限分布是标准正态分布),这二者是不同的检验方法。
1.3 卡方检验 chisq.test()
为了验证这一点,调用卡方检验函数 chisq.test() 来计算。在做卡方检验前,先将数据整理成列联表的形式。这个两样本的比例检验问题等价于 \(2\times 2\) 的列联表检验。
| 第一天 | 第二天 | 总计(行和) | |
|---|---|---|---|
| 购买 | \(a_1\) (30) | \(a_2\) (50) | 80 |
| 未购买 | \(n_1-a_1\) (56) | \(n_2-a_2\) (43) | 99 |
| 总计(列和) | \(n_1\) (86) | \(n_2\) (93) | 179 |
此种情形下,根据中心极限定理,检验统计量 \(T\) 极限分布为标准正态分布 \(N(0,1)\) ,其形式与前面的两样本比例相等的检验的公式非常接近,只是在列联表的框架下重新表示罢了。
\[ \begin{aligned} \widetilde{p} &= \frac{a_1 + a_2}{n_1+n_2} \\ T &= \frac{(\frac{a_1}{n_1} - \frac{a_2}{n_2})}{ \sqrt{(\frac{1}{n_1} + \frac{1}{n_2})*\widetilde{p}*(1-\widetilde{p}) }} \end{aligned} \]
此检验统计量 \(T^2\) 渐近服从自由度为 1 的卡方分布 \(\chi^2(1)\)。
p_wide <- (a1 + a2) / (n1 + n2)
chi <- (p1 - p2)^2 / ((1 / n1 + 1 / n2) * p_wide * (1 - p_wide))
# 卡方统计量
chi
## [1] 6.443
# P 值
1 - pchisq(q = chi, df = 1)
## [1] 0.01114
函数 chisq.test() 需要一个矩阵(二维列联表在 R 语言内的表示是一个矩阵)。
# 直接使用卡方检验 chisq.test()
chisq.test(
x = rbind(
c(30, 50), # 购买
c(56, 43) # 未购买
), correct = F # 不矫正
)
##
## Pearson's Chi-squared test
##
## data: rbind(c(30, 50), c(56, 43))
## X-squared = 6.4, df = 1, p-value = 0.01
不矫正的结果和前面手动计算的结果完全一样了。
1.3.1 Yates 矫正
在 \(2\times2\) 的列联表情形下,默认使用 Yates 连续性矫正,矫正后的检验统计量如下。
\[ \chi^2 = \sum_{i=1}^{2}\sum_{j=1}^{2}\frac{(|O_{ij}-E_{ij}| -0.5)^2}{E_{ij}} \]
其中,\(O_{ij}\) 表示观测的频数,\(E_{ij}\) 表示期望的频数(需要计算,见后方表格)。这个矫正使得卡方统计量的值变得更小,在小样本(某个格子的样本量小于 5)场景下,检验结果更加保守一些。矫正后的检验结果如下:
chisq.test(
x = rbind(
c(30, 50), # 购买
c(56, 43) # 未购买
), correct = TRUE
)
##
## Pearson's Chi-squared test with Yates' continuity correction
##
## data: rbind(c(30, 50), c(56, 43))
## X-squared = 5.7, df = 1, p-value = 0.02
卡方统计量和 P 值计算的结果与比例检验函数 prop.test() 完全一致。下面手动计算矫正过程,先计算各个 \(\hat{p_{ij}}\) 的值。
| 第一天 | 第二天 | \(\hat{p_{i\cdot}}\) | |
|---|---|---|---|
| 购买 | \((a_1+a_2)/(n_1+n_2)*n_1/(n_1+n_2)\) | 略 | \((a_1+a_2)/(n_1+n_2)\) |
| 未购买 | 略 | 略 | \((n_1-a_1+n_2-a_2)/(n_1+n_2)\) |
| \(\hat{p_{\cdot j}}\) | \(n_1/(n_1+n_2)\) | \(n_2/(n_1+n_2)\) |
上表中,各 \(\hat{p_{ij}}\) 与总样本量 \(n_1+n_2\) 相乘的结果就是期望频数 \(E_{ij}\) 。
p11 <- (a1 + a2) / (n1 + n2) * n1 / (n1 + n2)
a11 <- (abs(a1 - (n1 + n2) * p11) - 0.5)^2 / ((n1 + n2) * p11)
a11
## [1] 1.638
p12 <- (a1 + a2) / (n1 + n2) * n2 / (n1 + n2)
a12 <- (abs(a2 - (n1 + n2) * p12) - 0.5)^2 / ((n1 + n2) * p12)
a12
## [1] 1.515
p21 <- n1 / (n1 + n2) * (n1 - a1 + n2 - a2) / (n1 + n2)
a21 <- (abs(n1 - a1 - (n1 + n2) * p21) - 0.5)^2 / ((n1 + n2) * p21)
a21
## [1] 1.324
p22 <- n2 / (n1 + n2) * (n1 - a1 + n2 - a2) / (n1 + n2)
a22 <- (abs(n2 - a2 - (n1 + n2) * p22) - 0.5)^2 / ((n1 + n2) * p22)
a22
## [1] 1.224
# 卡方检验统计量的值
a11 + a12 + a21 + a22
## [1] 5.702
# P 值
1 - pchisq(q = a11 + a12 + a21 + a22, df = 1)
## [1] 0.01695
这就和 Yates 矫正后的卡方检验结果一样了。
1.4 Fisher 精确检验 fisher.test()
适用于小样本的情形,不依赖近似,精确计算。当 \(2\times2\) 的列联表中某个格子的值小于 5 时。
先引入一个优势(odds)的概念。对于二项分布的参数 \(p\) ,也叫成功概率,即事件发生的概率,在本文这个示例里,代表购买的概率。优势的定义如下:
\[ \mathrm{优势} = \frac{p}{1-p} \]
购买的概率相对于不购买的倍数,如果优势大于 1,表示购买的概率比不购买的概率大。在此基础上再定义优势比(odds ratio),两样本的二项分布的成功概率的比较。优势比的定义如下:
\[ \mathrm{优势比} = \frac{p_1/(1-p_1)}{p_2/(1-p_2)} \]
两样本的二项分布的比例检验等价于检验优势比为 1 的 Fisher 精确检验。
Fisher 精确检验:原假设 \(H_0\) 优势比为 1,备择假设 \(H_1\) 优势比不为 1。
a1 / n1 # 也就是 p1
## [1] 0.3488
a2 / n2 # 也就是 p2
## [1] 0.5376
# 优势比 odds ratio
p1 / (1 - p1) / (p2 / (1 - p2))
## [1] 0.4607
fisher.test(
x = rbind(
c(30, 50), # 购买
c(56, 43) # 未购买
),
or = 1, # 原假设:优势比 odds ratio = 1
conf.level = 0.95,
alternative = "two.sided"
)
##
## Fisher's Exact Test for Count Data
##
## data: rbind(c(30, 50), c(56, 43))
## p-value = 0.02
## alternative hypothesis: true odds ratio is not equal to 1
## 95 percent confidence interval:
## 0.2410 0.8778
## sample estimates:
## odds ratio
## 0.4628
根据这组数据来看,P 值小于 0.05,拒绝原假设。
1.5 二项检验 binom.test()
# x 表示购买的用户 n 表示访问的用户
# 检验访购率是否为 30/86=34.88%(假设这是昨天的访购率)
binom.test(x = 50, n = 93, p = 30 / 86, conf.level = 0.95, alternative = "two.sided")
##
## Exact binomial test
##
## data: 50 and 93
## number of successes = 50, number of trials = 93, p-value = 3e-04
## alternative hypothesis: true probability of success is not equal to 0.3488
## 95 percent confidence interval:
## 0.4312 0.6416
## sample estimates:
## probability of success
## 0.5376
其中,置信水平 \(1-\alpha\) 为 0.95 ,则假设检验的显著性水平 \(\alpha=0.05\) 。在给出检验结论的同时也给出了两比例之差的置信区间。
2 样本量预估与功效计算
在平台做 A/B 实验的过程中,假定访购率是实验目标之一,希望新的策略可以提升访购率。经过几天的实验,累积了一批数据,对照组数据如前表所示,实验组数据如下:
| 日期 | 第 1 天 | 第2天 | 第3天 | ··· |
|---|---|---|---|---|
| 购买 UV | \(b_1\) | \(b_2\) | \(b_3\) | ··· |
| 访问 UV | \(m_1\) | \(m_2\) | \(m_3\) | ··· |
| 访购率 | \(b_1/m_1\) | \(b_2/m_2\) | \(b_3/m_3\) | ··· |
理想态下,实验组每天的访购率都比对照组高,新策略的效果很好。实际上,在很多时候,可能某一天实验组比对照组访购率高且达到统计显著性,而有的时候,没有达到统计显著性,甚至访购率比对照组还要低。
根据前面的记号,实验组、对照组购买 UV 服从二项分布,具体如下,
\[ \begin{aligned} b_1 &= \sum_{i=1}^{m_1}x^t_i \sim \mathrm{Binom}(m_1,p^t_1) \\ a_1 &= \sum_{i=1}^{n_1}x^c_i \sim \mathrm{Binom}(n_1,p^c_1) \end{aligned} \]
其中,\(p^t_1\) 和 \(p^c_1\) 分别为实验组 Treatment 和对照组 Control 第一天的访购率。
2.1 实验前预估样本量
另有一个函数 power.prop.test() 可以计算比例检验的功效,预估达到统计显著性需要的样本量。再根据业务上放量的情况,可以预估实验天数。
# 样本量预估
power.prop.test(
p1 = 0.45, # 当前业务的访购率(对照组)
p2 = 0.46, # 新策略实施后业务的访购率,绝对提升 1%(实验组)
sig.level = 0.05, # 显著性水平
power = 0.85, # 功效水平
alternative = "two.sided" # 双边检验
)
##
## Two-sample comparison of proportions power calculation
##
## n = 44527
## p1 = 0.45
## p2 = 0.46
## sig.level = 0.05
## power = 0.85
## alternative = two.sided
##
## NOTE: n is number in *each* group
# 样本量预估
library(pwr) # 或者使用 pwr 包
pwr.2p.test( # 实验组和对照组相同的样本量
h = ES.h(p1 = 0.45, p2 = 0.46), # Effect size Cohen's d
sig.level = 0.05, # 显著性水平
power = 0.85, # 功效水平
alternative = "two.sided" # 双边检验
)
##
## Difference of proportion power calculation for binomial distribution (arcsine transformation)
##
## h = 0.02008
## n = 44527
## sig.level = 0.05
## power = 0.85
## alternative = two.sided
##
## NOTE: same sample sizes
在这样的情况下,对照组和实验组各需要累积 44527 个样本才能有把握说,新策略能带来 1% 的提升。在新策略刚上线的时候,往往放量比较小,一天可能也就大几千,随着2-3天的实验结果显示效果不错,再增加放量,7 天下来,累积的样本量也能满足实验所需,若效果还能保持稳定良好,那么,实验结束,全量运行。在现如今的成熟的互联网平台上做实验,业务上 1% 的提升很可能就是一个季度的目标,带来的价值是很大的!
2.2 实验中计算功效
实验进行几天后,实验组和对照组积累的样本量也不小了,要看一看是否可以全量了。我们将目前的效果提升情况和实验组、对照组的样本量代入,获取功效。这个功效的意思是有多少把握可以确定带来提升。如果还没有达到给结论所需要的功效标准(比如 0.85),那就继续放量,继续观察。
# 实验中计算功效
pwr.2p2n.test(
h = ES.h(p1 = 0.45, p2 = 0.46), # Effect size Cohen's d
n1 = 19500, # 实验组样本量
n2 = 245000, # 对照组样本量
sig.level = 0.05, # 显著性水平
alternative = "two.sided" # 双边检验
)
##
## difference of proportion power calculation for binomial distribution (arcsine transformation)
##
## h = 0.02008
## n1 = 19500
## n2 = 245000
## sig.level = 0.05
## power = 0.77
## alternative = two.sided
##
## NOTE: different sample sizes
试验新的策略,并不确定效果的情况下,实验所收集的样本量有限,或者做实验的成本本就不低,或者实验场景所限,能够收集的样本量有限。更有甚者,样本之间不独立。这就需要 bootstrap 方法来获取检验统计量的分布及其方差。当然,实验样本量太少,而不足以代表总体,或者说样本失去对总体的代表性,那么什么统计方法也是不足用的。