一.基础
1.来源
生物医学 - 双盲测试
双盲测试中病人被随机分成两组,在不知情的情况下分别给予安慰剂和测试用药,经过一段时间的实验后再来比较这两组病人的表现是否具有显著的差异,从而决定测试用药是否有效
- 病人分成2组
- 安慰剂 & 测试用药
- 是否有显著差异
- 测试用药是否有效
互联网 - 界面 / 流程
- web或app设计多个方案
- 不同访客群体
- 收集数据
- 评估最好版本并采用
2.概念
针对产品或者流程策略的各个版本,在同一时间维度,分别让组成成分相同的用户随即应用,收集各组用户体验数据,分析和评估版本中的最优解,从而应用
本质就是控制变量法
简单来说,就是在产品正式全面迭代之前,为同一个目标制定不少于两个的方案,将用户随机分流至对应方案内,在保证每组用户特征相同的前提下,根据用户的数据反馈,帮助产品决策
3.好处
消除不同意见,提高团队效率,根据实际效果确定最佳方案
快速试错,无法确定新功能上线后的效果,不确定是否全量该版本
通过对比实验,验证问题真正原因,提高产品设计和运营水平
建立数据驱动文化,持续不断优化的闭环过程
- 建立一套科学的运营,优化体系
- 降低人为风险因素
- 避免过度依赖牛人/老板
- 有效知识沉淀
降低新产品或新特性的发布风险,为产品创新护航
4.适用场景
前台
- 用户体验
- 文案
- 业务流程
后端
- 算法:推荐算法,广告算法,搜索排序等
- 后端架构升级
广告效果 / 落地页 / 营销邮件 / 计算算法 /智能硬件 / 网站迭代的AB测试
客户端的界面调整实验:使用不同的策略对客户端的UI布局进行调整
算法策略的调整实验:最常见的是推荐搜索或者广告的算法策略调整实验
运营策略:比如运营做了两套针对双十一的方案,但不知道哪个方案效果更好,可以将两种实验同时上线,配置AB测试查看曝光点击转化
5.限制
不适合原始的创新解决方案
不适合战略级的策略/决策验证
用户量不大的业务场景
6.弊端
设计,开发工作量增加
代码维护成本增高
用户规模要求高
只有现象,没有原因
7.逻辑
设计:核心为了减少误差(需求)
抽样:样本选择及样本量
检验:判断样本及总体差异(工具实现检验)
8.实现方法
服务端控制:同一个版本,服务端分别对n批同样的人群进行控制
客户端控制:发布两个同样的版本,只是版本号不一样,用于作区分,进而灰度n批次同样的人
二.原理
AB测试的本质:假设检验
假设检验的指导思想:反证法(假设无效,反证有效)
我们现在有两个随机均匀的样本组A,B,对其中一个组A做出某种改动,实验结束后分析两组用户行为数据,通过显著性检验,判断这个改动对于我们所关注的核心指标是否有显著的影响
在这个实验中,我们的假设检验如下:
原假设H0:这项改动不会对核心指标有显著的影响
备择假设H1:这项改动会对核心指标有显著的影响
如果我们在做完实验之后,通过显著性检验发现P值足够小,我们则推翻原假设,证明这项改动会对我们所关注的核心指标产生显著影响,否则接受原假设,认为该改动未产生显著影响
一句话概括:AB测试就是随机均匀样本组的对照实验
实验后,收集到每个样本的指标值,如何衡量这个指标水平呢?
最简单的,是这些样本在该指标上的均值和方差
中心极限定理,独立同分布,样本均值分布为正态分布
我们将AB实验中样本均值无差异的假设与变量的联合分布对应起来就是
小概率事件:拟定一个阈值α,即显著性水平,我们称某个事件为小概率事件,当这个事件发生的概率比置信水平还小,一般地,α=0.05
万事俱备,只需要计算上述统计量的实际值,并计算出比这个实际值更极端的情况发生的概率,我们一般称之为P值,如果P值小于α,我们就有理由拒绝原假设
中心极限定理给出了样本均值的分布,下面我们再介绍一下样本方差的分布形式
首先给出样本方差的计算公式
当总体是正态分布时,样本方差的分布形式为:
通过样本方差,消掉总体方差
三.应用
1.基本步骤
2.系统架构
3.注意事项
样本数量
- 不要盲目追求过大样本量
- 科学预估最小样本量:显著性水平(α),统计功效(1-β),当前转化率,最小提升比例
样本质量
- 检验样本的有效性
- AA测试
测试时间
- = 最小测试样本量 / 每日流量
- 用户行为周期
- 用户适应周期
多试验影响
- 原则上保证实验的纯粹性
- 纵向切分保证不同测试流量独享
- 横向切分保证不同应用在不同层次
四.Python实现
数据读入
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
course = pd.read_csv('course_page_actions.csv')
点击率分析
#控制组注册率
control_enroll_num = course[(course['group']=='control')&(course['action']=='enroll')]['id'].nunique()
control_view_num = course[(course['group']=='control')&(course['action']=='view')['id'].nunique()
control_ctr = round((control_enroll_num*100 / control_view_num),4)
control = {'注册数':control_enroll_num,
'浏览数':control_view_num,
'注册率':str(control_ctr) + '%'}
拆分数据 & 设置画布布局
#用于拟合正态分布曲线
from scipy.stats import norm
#设置布局
fig = plt.figure(figsize=(16,4))
ax1 = fig.add_subplot(121)
ax2 = fig.add_subplot(122)
exp_duration = course.query('group == "experiment"')['duration']
con_duration = course.query('group == "control"')['duration']
同等条件下的正态分布曲线图,显示数据方差与均值
sns.displot(exp_duration,fit=norm,color='#F77B72',kde_kws={"color":'#F77B72',"lw":3},ax=ax1)
#求同等条件下正态分布的mu和sigma
(mu,sigma) = norm.fit(exp_duration)
#添加图例:使用格式化输入,loc='best',表示自动将图例放到最合适的位置
ax1.legend(['Normal dist.($\mu=${:.2f} and $\sigma=${:.2f})'.format(mu,sigma)],loc='best')
sns.distplot(con_duration,fit=norm,color='#4CB5AB',kde_kws={"color":'#4CB5AB',"lw":3},ax=ax2)
#求同等条件下正态分布的mu和sigma
(mu,sigma) = norm.fit(con_duration)
#添加图例:使用格式化输入,loc='best',表示自动将图例放到最合适的位置
ax2.legend(['Normal dist.($\mu=${:.2f} and $\sigma=${:.2f})'.format(mu,sigma),loc='best')
添加子图标题
ax1.set_title('实验组浏览时长分布')
ax2.set_title('控制组浏览时长分布')
假设检验
我们将从控制组和实验组中各抽取一定数量的样本来进行假设检验
样本量:a-level
≤100:10%
100<n≤500:5%
500<n≤1000:1%
n>2000:0.1%
样本量过大,a-level就没有什么意义了
为了使假设检验的数据样本更加合理,我们可以使用分层抽样,python没有现成的库或函数
from mysampling import get_sample
'''
df:输入的数据框
pandas.dataframe:对象
sampling:抽样方法,可选值有["simple_random","stratified","systematic"],按顺序分别为简单随机抽样,分层抽样,系统抽样
stratified_col:需要分层的列名的列表list,只有在分层抽样时才生效
k:抽样个数或抽样比例(int或float)
int:必须大于0
float:必须在区间(0,1)中
0<k<1:k表示抽样对于总体的比例
k≥1:k表示抽样的个数,当为分层抽样时,代表每层的样本量'''
data = get_sample(df=course,sampling='stratified',stratified_col=['group'],k=300)
因为总体未知,所以我们使用两独立样本T检验,其实双样本Z检验也能达到类似的效果
#总体未知,可采用两独立样本T检验
from scipy import stats
exp_duration = data.query('group == "experiment"')['duration']
con_duration = data.query('group == "control"')['duration']
stats.test_ind(a=exp_duration,b=con_duration)
import statsmodels.api as sm
sm.stats.ztest(x1=exp_duration,x2=con_duration)