AB测试实战
学习目标
- 掌握如何设计AB测试
- AB测试效果如何解读
- 掌握如何进行实验设计与效果计算
1、AB测试介绍
-
很多网站/APP的首页都会挂一张头图(Banner),用来展示重要信息,头图是否吸引人会对公司的营收带来重大影响,一家寿险公司Humana设计了如下三张头图,现在需要决定使用哪一张放到首页,咋办?开会讨论?举手表决?领导拍板?
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OZpmgsin-1632058811314)(img\abtest5.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-acwWpnYG-1632058811315)(img\abtest6.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z3BnuIAe-1632058811315)(img\abtest7.png)]
-
AB测试:以“对照”知“优劣”
-
AB测试主要应用于互联网行业,它通过对相同特征的互联网用户(流量)在同一时间内实施两种不同的策略。来观察每组流量在关键目标指标上的表现。从而找出表现更优的策略。并凭借实验结果对策略和人群展开交叉分析,得到洞察
-
无论是添加功能,头图替换,还是添加新的按钮,使用了新的推荐算法,所有可能会对业务造成影响的主动调整最好都要经过AB测试
-
在上面的Humana公司的例子中,头图的迭代就是使用了AB测试
- 第一张图是最初的版本,第二张设计图在第一张的基础上,减少了文字描述,换了更大的按钮,
- 在第二张图片的基础上又做了新的迭代,设计了第三张图片,主要的变化调整了按钮的;颜色和文字内容,添加了Shop这样带有明显销售倾向的词语
- 两次迭代的决定都是基于AB测试的结果,经过AB测试验证后上线都得到了很好的效果
-
1.1 AB测试的关键点:目标KPI和策略
- 目标KPI:指评判AB测试效果优劣的最终指标
- 在Humana公司首页头图调整的例子中,目标KPI是该头图的点击率
- 策略:AB组分别采取的策略的差异点
- 如:注册按钮采用的颜色,位置,注册页面采用的海报,标语等
- 有多少个差异点,就可以设计多少次AB测试,直到穷尽策略差异点后找出最有效的那一组策略。
- 只要分流量的时候做到随机,那么AB组人群的特征就是类似的,因此最终结果的差异只能是策略不同造成的。
1.2 AB测试的作用
- 实现目标KPI的最大化:通过在小样本用户中进行实验(避免错误决策影响用户体验),找到对KPI最优的策略
- Humana公司的头图调整例子中,第二张图片对比第一张带来了433%的点击率提升
- 第三张图片对比第二张有带来了192%的点击率提升
- 后续分析,沉淀诀窍:
- 每组流量都是随机分配的,因此每组人群中都包含了各种不同特征的子人群
- 通过研究不同子人群对于不同策略的响应程度,可以获得每组人群在策略上的偏好,帮助未来更好的个性化创新和设计
- 从上面的例子中,我们可以得出一些有意的结论,
- 如头图文字不易过多,要主题突出, 文字简洁
- 头图使用的按钮颜色, 红色更容易吸引人点击等
1.3 AB测试实施的一般步骤
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-avLnFoCV-1632058811316)(img\ab_flow.png)]
严谨的产品迭代过程(策略,算法, 界面调整, 功能调整), 一定要先经过AB测试, 在少部分流量上进行测试, 没问题了再逐渐放量
- 明确KPI 提升转化率, 提升点击率, 提升分享率
- 圈人, 通过假设检验确定足够的实验人数
- 通过目标KPI提升多少, 确定实验人数
- AA 测试 : 确定圈人没圈跑偏(量组流量的组成类似)
- 实验组和对照组在AA测试后, 观察目标KPI 没有太大的区别
- AB测试: 对照组看老产品, 给测试组看新的产品
- 回收结果,分析
- 如果B组确实比A组好, 并且达到了圈人时设定的目标KPI 说明新设计有效的
- 如果B组确实比A组好, 但是提升幅度有限, 没有达到圈人时设定的目标KPI 的提升度, 说明这个提升没有说服力, 不能证明新的设计有效
- A组比B组好, 新的设计不行
2、AB测试常见问题和应对方案
2.1 如何分配流量
一般采用A组B组人数相等的方法进行流量分配
常用的分流方法
- 利用随机数排序,例如My SQL中的 rand()函数
select a.*,case when rand()<0.1 then 'ctrl'
when rand() between 0.1 and 0.55 then 'test1' else 'test2' end as ab_group_tag
from (select distinct customerID from user_table) a
order by ab_group_tag;
- 利用某些随机ID的尾数,例如case when ID like ‘%1’then ‘Ctrl’else ‘Test’end as AB_group
select a.*,case when customerID like '1%' then 'ctrl'
when customerID like '2%' or customerID like '3%' or customerID like '4%' or customerID like '5%' then 'test1'
else 'test2' end as ab_group_tag
from(select distinct customerID from user_table) a
order by ab_group_tag;
- 不管用哪种方法都要在人群身上留好标签以便事后分析
2.2 确定试验有效的最小参与人数
-
AB测试的效果会一定程度上受到随机波动的影响
- 完全随机的两组人不发优惠券,观察同一段时间的人均交易数,人均交易额这些KPI,都不会完全一样
- 为了使测试结果显著有效,我们首先要确保测试组里人数最少的一组达到验证效果有效性的最小样本数量
-
帮助计算样本量的网站:https://www.evanmiller.org/ab-testing/sample-size.html
- 首先介绍比例类的KPI,例如领券率,点击率,核销率的样本量计算方式:我们以基准率10%,需要探知最低效果2%为例:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lqgiw2Af-1632058811316)(img\ABtest1.png)]
- 上图中 Baseline conversion rate: 这里填写基准比率 10%
- Minimum Detectable Effect:这里填写最低的可探知效果2%
- Significance level α:显著性水平 一般选择5%
- Statistical power 1−β:统计功效(statistical power )一般选择80%
-
经过计算得知,我们想通过AB测试确定新的策略能够将KPI提升两个点参数实验的每组人数最少为3623人
2.3 AB测试中的假设检验
为什么要计算参与AB测试的最少参与人数?
- 我们做AB测试的目的是在尽量不影响用户体验的前提下,让少部分用户来验证新旧方案的优劣。这里就需要对用户进行抽样, 也就是抽取部分用户来代表全体用户来参与实验,并得出结论
- 如果抽取的用户数量过少,不能代表所有用户的观点,结果没有意义
- 如果抽取的用户过多,一旦我们的新方案与预期效果偏差较大则会对用户体验带来较大影响
- 我们可以使用假设检验的理论帮助我们计算参与实验的最少人数
什么是假设检验?
假设检验(hypothesis testing),又称统计假设检验,是用来判断样本与样本、样本与总体的差异是由抽样误差引起还是本质差别造成的统计推断方法。显著性检验是假设检验中最常用的一种方法,也是一种最基本的统计推断形式,其基本原理是先对总体的特征做出某种假设,然后通过抽样研究的统计推理,对此假设应该被拒绝还是接受做出推断。
假设检验的基本思想
- 假设检验的基本思想是“小概率事件”原理,其统计推断方法是带有某种概率性质的反证法。
- 小概率思想是指小概率事件在一次试验中基本上不会发生。反证法思想是先提出检验假设,再用适当的统计方法,利用小概率原理,确定假设是否成立
- 即为了检验一个假设 H 0 H_0 H0是否正确,首先假定该假设 H 0 H_0 H0正确,然后根据样本对假设 H 0 H_0 H0做出接受或拒绝的决策
- 如果样本观察值导致了“小概率事件”发生,就应拒绝假设 H 0 H_0 H0,否则应接受假设 H 0 H_0 H0。
- 假设检验中所谓“小概率事件”,并非逻辑中的绝对矛盾,而是基于人们在实践中广泛采用的原则,即小概率事件在一次试验中是几乎不发生的
- 概率小到什么程度才能算“小概率事件”,显然,“小概率事件”的概率越小,否定原假设 H 0 H_0 H0就越有说服力,常记这个概率值为α(0<α<1),称为检验的显著性水平
- 对于不同的问题,检验的显著性水平α不一定相同,一般认为,事件发生的概率小于0.1、0.05或0.01等,即“小概率事件” 。
假设检验举例——鉴别可口可乐和百事可乐
可口可乐和百事可乐作为市场上最常见的两种可乐饮料大家一定都喝过,我们知道这两种可乐的味道差不多很难分辨, 如果现在有一名同学说自己能通过味道区分出两种可乐,那么如何设计实验来验证他是否具备这项能力呢?
我们可以假设这名同学不具备这个能力,如果通过实验能证明我们的假设是错误的,那么就说明这名同学具备品尝出两种可乐的能力来
- 原假设 H 0 H_0 H0 不能区分两种不同的可乐
- 备择假设 H 1 H_1 H1 可以区分两种不同的可乐
接下来我们给该同学用相同的杯子倒若干杯不同的可乐,让他品尝说出杯子里的可乐究竟是可口可乐还是百事可乐,如果答对了可能有两种情况:
- 瞎蒙的
- 真的能尝出来
我们都知道只喝一杯他如果答对了说明不了什么问题,那么究竟喝几杯能下结论说这个同学不是瞎蒙的呢?我们可以计算一下概率
- 如果是瞎猜,猜对的概率是0.5, 那么猜对一杯的概率是0.5, 连着猜对两杯的概率是0.5*0.5 = 0.25 连着猜对三杯的概率是0.5 * 0.5 * 0.5=0.125
- 如果该同学连喝5杯都做出了正确的判断, 那么我们可以计算得知 0.5^5 = 0.03125 也就是说有 3.125%的可能他是猜对的, 那么此时我们就可以推翻原假设: 该同学是蒙的,因为连着猜对5杯的概率实在太低了
- 那么尝对了5杯之后我们是否有100%的把握说该同学一定具备通过味道区分可乐的能力呢? 答案也是否定的, 因为仍然有3.125%的可能他是瞎蒙的, 这个3.125%就是显著性水平(Significance level) 一般用α表示, 一般我们把显著性水平α的阈值设置为5%, 也就是α≤5%的时候我们认为这个结果是具有统计学意义的
- 当然我们还可以接着做更多次实验, 比如让该同学再多喝几杯, 如果都能做出正确判断, 那么对应的显著性水平α 就越小, 我们得出该同学能够通过味道区分出两种可能的把握就越大
AB测试中的假设检验
通过上面的例子我们对假设检验有了基本的了解, 接下来我们看一下如何在AB测试中应用假设检验
- 原假设 H 0 H_0 H0 老的方案老的设计
- 备择假设 H 1 H_1 H1 新的方案新的设计
在上面的例子中,实验的次数越多我们得到的结论就越准确,那么在AB测试中,实验的次数实际上就是参与实验的人数,我们可以通过设置显著性水平(Significance level) α来倒推参与实验的最少人数,具体计算可以通过上面给出的工具网站实现
显著性水平α : 依据实验结果做出推翻原假设(否定原方案) 选择备择假设(采用新方案) 的决定,犯错的概率, 一般设置为5%
统计功效(1−β): 依据实验结果做出保留原假设(保留原方案) 不选择备择假设(不采用新方案) 的决定,犯错的概率, 一般β设置为20% 那么1−β 为80%
2.4 AB测试与辛普森悖论
辛普森悖论为英国统计学家E.H.辛普森于1951年提出,即在某个条件下的两组数据,分别讨论时都会满足某种性质,可是一旦合并考虑,却可能导致相反的结论。
方案A(现有方案)转化率 | 方案B(新方案)转化率 | |
---|---|---|
Android 用户 | 70/800 = 8.75% | 20/200 = 10% |
iphone 用户 | 10/200 = 5% | 50/800 = 6.25% |
总用户 | 80/1000 = 8% | 70/1000 = 7% |
AB测试中产生辛普森悖论的原因:流量分割不均匀导致的实验组与对照组的用户特征不一致
AB测试中如何避免辛普森悖论
- 要得到科学可信的 AB 测试试验结果,就必须合理的进行正确的流量分割,保证试验组和对照组里的用户特征是一致的,并且都具有代表性,可以代表总体用户特征。
方案A(现有方案)转化率 | 方案B(新方案)转化率 | |
---|---|---|
Android 用户 | 70/800 = 8.75% | 80/800 = 10% |
iphone 用户 | 10/200 = 5% | 12/200 = 6% |
总用户 | 80/1000 = 8% | 92/1000 = 9.2% |
- 在实验设计上,如果我们觉得某两个变量对试验结果都有影响,那我们就应该把这两个变量放在同一层进行互斥试验,不要让一个变量的试验动态影响另一个变量的检验。如果我们觉得一个试验可能会对新老客户产生完全不同的影响,那么就应该对新客户和老客户分别展开定向试验,观察结论
- 在试验实施上,对试验结果我们要积极的进行多维度的细分分析,除了总体对比,也看一看对细分受众群体的试验结果,不要以偏盖全,也不要以全盖偏。一个试验版本提升了总体活跃度,但是可能降低了年轻用户的活跃度,那么这个试验版本是不是更好呢?一个试验版本提升总营收0.1%,似乎不起眼,但是可能上海地区的年轻女性 iPhone 用户的购买率提升了20%,这个试验经验就很有价值了。
3、AB测试代码实战
3.1 项目背景
-
假设您在一家中型电商公司工作。 UI设计师设计了最新的产品页面想通过页面更新提高转化率
- 目前的转化率全年平均在13%左右
- 目标转化率达到 15%。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bpxNGsNF-1632058811317)(img/ab_7.jpeg)]
-
在新的页面上线之前,我们要通过一小部分用户上测试页面的效果, 所以需要进行A/B测试
3.2 设计实验
-
提出假设
-
首先,我们要确保在项目开始时就制定了一个假设
-
鉴于我们不知道新设计的性能是否会比我们当前的设计更好或更差(或相同?),我们将选择双尾测试:
H 0 H_0 H0 原假设 老的设计比较好, 新版设计没有用
H 1 H_1 H1 备选假设 新的设计比较好
-
-
选择变量
- 对照组: 看到旧的设计
- 实验组: 看到新的设计
虽然我们已经知道了旧的设计的转化率(13%左右), 但是我们依然要需要设计两组, 原因是为了避免其他因素带来的误差, 比如季节因素, 促销因素。这两组人在其它条件都相同的只是页面设计不同的情况下进行实验, 这样能保证两组间的差异是由于设计导致的
- 我们的设计的目标KPI是转化率, 所以,我们会添加一个字段来记录用户的购买情况
- 购买了产品的用户 值为1
- 未购买产品的用户值为0
-
确定实验人数
- AB测试只会选择一小部分用户来参与实验, 用小部分的实验结果来估计整体的结果,每组的人数越多,我们得到的结果就越精准,但同时我们付出的成本就越大,通过功效分析我们可以计算出满足实验条件的最小人群
- 检验功效 (1 — β) :一般设置为0.8
- α :在设计实验的时候我们设置为0.05
- 效果大小:旧的页面转化率为13%,新页面我们希望转化率能提升2%,所以我们可以用13%和15%来计算预期效果大小
- 确定实验人数的计算过程可以通过Python代码实现
import numpy as np import pandas as pd import scipy.stats as stats import statsmodels.stats.api as sms import matplotlib.pyplot as plt import seaborn as sns from math import ceil %matplotlib inline # 计算effect_size 0.13为当前的转换率 0.15为目标转化率 也就是说我们希望通过新的设计带来2%的提升 effect_size = sms.proportion_effectsize(0.13, 0.15) required_n = sms.NormalIndPower().solve_power( effect_size, # 传入上面计算的 effect_size power=0.8, # 设置 1-β = 80% alpha=0.05, # 设置 α 为5% ratio=1 # 对照组和测试组人一样, 这里的ratio 比例就是1 ) #对结果向上取整 required_n = ceil(required_n) print(required_n)
4720
- 计算结果说明每组至少需要4720人
- 在实践中将功率参数设置为 0.8 意味着如果我们的设计之间存在转化率的实际差异,假设差异是我们估计的差异(13% 对 15%),我们有大约 80% 的机会检测到它 与我们计算的样本量在我们的测试中具有统计显着性。
3.3 收集准备数据
-
在企业场景下,我们需要与工程团队配合,收集满足要求的数据, 这里我们使用准备好的数据集, 对数据进行处理
- 加载数据到DataFrame
- 检查和清理数据
- 从DataFrame中采样数据每组4720行
df = pd.read_csv('data/ab_data.csv') df.head()
user_id timestamp group landing_page converted 0 851104 2017-01-21 22:11:48.556739 control old_page 0 1 804228 2017-01-12 08:01:45.159739 control old_page 0 2 661590 2017-01-11 16:55:06.154213 treatment new_page 0 3 853541 2017-01-08 18:28:03.143765 treatment new_page 0 4 864975 2017-01-21 01:52:26.210827 control old_page 1 - 查看数据基本情况
df.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 294478 entries, 0 to 294477 Data columns (total 5 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 user_id 294478 non-null int64 1 timestamp 294478 non-null object 2 group 294478 non-null object 3 landing_page 294478 non-null object 4 converted 294478 non-null int64 dtypes: int64(2), object(3) memory usage: 11.2+ MB
- 数据一共294478行,每一条数据代表一次用户访问,共五列:
user_id
- 访问的用户IDtimestamp
- 访问的时间group
- 该用户被放到那一组 {control
对照,treatment
实验}landing_page
-该用户看到的是哪一种落地页 {old_page
老页面,new_page
新页面}converted
- 改次访问是否有转化 (binary,0
=无转化,1
=转化)
- 在后续的分析中,我们实际上主要用到的是
group
和converted
这两个字段 - 创建透视表, 查询是否对照组看到的都是老页面
df.pivot_table(index = 'group',columns='landing_page',values = 'user_id',aggfunc='count')
landing_page new_page old_page group control 1928 145274 treatment 145311 1965 - 在我们进行后续处理之前, 还要查看是否有用户进行了多次操作
session_counts = df['user_id'].value_counts(ascending=False) multi_users = session_counts[session_counts>1].count() multi_users
3894
- 说明一共有3894个用户访问了不止一次, 整体数据有20多万条, 所以我们直接把这部分数据删除
users = session_counts[session_counts < 2].index df = df[df['user_id'].isin(users)]
-
数据采样
- 接下来我们从处理后的数据中,每组采样4720条数据, 我们这里使用DataFrame的sample()方法进行简单随机采样
control_sample = df[df['group'] == 'control'].sample(n=required_n, random_state=22) treatment_sample = df[df['group'] == 'treatment'].sample(n=required_n, random_state=22) # 这里random_state 为随机数种子, 如果也传入22, 那么后续结果会与讲义中一样 ab_test = pd.concat([control_sample, treatment_sample], axis=0) ab_test.reset_index(drop=True, inplace=True) ab_test
user_id timestamp group landing_page converted 0 763854 2017-01-21 03:43:17.188315 control old_page 0 1 690555 2017-01-18 06:38:13.079449 control old_page 0 2 861520 2017-01-06 21:13:40.044766 control old_page 0 3 630778 2017-01-05 16:42:36.995204 control old_page 0 4 656634 2017-01-04 15:31:21.676130 control old_page 0 … … … … … … 9435 908512 2017-01-14 22:02:29.922674 treatment new_page 0 9436 873211 2017-01-05 00:57:16.167151 treatment new_page 0 9437 631276 2017-01-20 18:56:58.167809 treatment new_page 0 9438 662301 2017-01-03 08:10:57.768806 treatment new_page 0 9439 944623 2017-01-19 10:56:01.648653 treatment new_page 1 - 查看两组数据情况
ab_test.groupby('group')['landing_page'].value_counts() # landing_page 落地页类型 old_page老页面 new_page 新页面 # control 控制组 treatment 对照组
group landing_page
control old_page 4720
treatment new_page 4720
Name: landing_page, dtype: int64
3.4 分析实验结果
-
首先我们来计算一下两组的转化率和标准差
conversion_rates = ab_test.groupby('group')['converted'].mean().to_frame() conversion_rates conversion_rates.style.format('{:.3f}')
group conversion_rate control 0.123 treatment 0.126 -
从上面的统计数据来看,旧的和新的落地页表现非常相似,新设计表现略好, 12.3% 与 12.6%
- 从对照组的数据看, 转换率为12.3% 比之前我们的整体表现要差一些, 可能与采样人群的差异有关
- 测试组的数据比对照组要好一些, 但是这个结果是否具有统计意义?
3.5 假设检验
- 最后一步是假设检验, 我们的样本量比较大,可以应用Z检验来计算P值, 如果P值<0.05, 说明
- Z检验一般用于大样本(即样本容量大于30)平均值差异性检验的方法。它是用标准正态分布的理论来推断差异发生的概率,从而比较两个平均数的差异是否显著。
- 我们可以使用statsmodels.stats.proportion 模块来计算P值和置信区间
from statsmodels.stats.proportion import proportions_ztest, proportion_confint
control_results = ab_test[ab_test['group'] == 'control']['converted'] #获取对照组是否转化的数据
treatment_results = ab_test[ab_test['group'] == 'treatment']['converted'] #获取实验组是否转化的数据
n_con = control_results.count() # 获取对照组人数
n_treat = treatment_results.count() # 获取实验组人数
successes = [control_results.sum(), treatment_results.sum()] # 获取实验组和对照组成功转化的人数
nobs = [n_con, n_treat]
z_stat, pval = proportions_ztest(successes, nobs=nobs) #计算P值
(lower_con, lower_treat), (upper_con, upper_treat) = proportion_confint(successes, nobs=nobs, alpha=0.05) #计算置信区间
print(f'z statistic: {z_stat:.2f}')
print(f'p-value: {pval:.3f}')
print(f'ci 95% for control group: [{lower_con:.3f}, {upper_con:.3f}]')
print(f'ci 95% for treatment group: [{lower_treat:.3f}, {upper_treat:.3f}]')
z statistic: -0.34
p-value: 0.732
ci 95% for control group: [0.114, 0.133]
ci 95% for treatment group: [0.116, 0.135]
3.6 最终结论
- 最终计算出的p-value = 0.732 远大于我们实验设置的α = 0.05, 我们不能拒绝原假设 旧的主页表现更好, 这就意味着,我们的新页面并不比旧的页面表现好
- 查看实验组的置信区间([0.116, 0.135] 或 11.6-13.5%),注意到:
- 13% 的基准转化率在置信区间内
- 15% 的目标值(我们的目标是 2% 的提升)并不在置信区间内
- 这意味着新设计的真实转化率可能与之前的转化率接近, 证明我们的新改进并没有效果
4、小结
AB测试的应用场景
- 互联网行业应用广泛:页面结构调整, 换新图标,添加新功能……
- 实体行业应用相对复杂一些:不同优惠券效果测试
AB测试还是ABC… 测试
- AB测试:一次测试一个方案
- ABC…… 测试: 一次测试多个方案,但需要流量足够大,否则难以满足实验要求的最少人数
AB测试需要注意如下几点
- 流量分配
- 确定有效的最小参与人数
- 确定基准指标和提升目标
- 设置显著性水平α (一般是5%)和 统计功效 1-β (一般是80%)
- 出结果之后计算P值 如果P<5% 则可以拒绝原假设
- 我们可以通过AB测试工具网站帮助确定人数,也可以使用statsmodels 模块来通过代码实现
- import statsmodels.stats.api 计算需要人数
- statsmodels.stats.proportion 计算P值和置信区间