



严谨的产品迭代过程(策略,算法, 界面调整, 功能调整), 一定要先经过AB测试, 在少部分流量上进行测试, 没问题了再逐渐放量
一般采用A组B组人数相等的方法进行流量分配
常用的分流方法
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;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;
为什么要计算参与AB测试的最少参与人数🐣
什么是假设检验?
假设检验(hypothesis testing),又称统计假设检验,是用来判断样本与样本、样本与总体的差异是由抽样误差引起还是本质差别造成的统计推断方法。显著性检验是假设检验中最常用的一种方法,也是一种最基本的统计推断形式,其基本原理是先对总体的特征做出某种假设,然后通过抽样研究的统计推理,对此假设应该被拒绝还是接受做出推断。
假设检验的基本思想
是否正确,首先假定该假设
正确,然后根据样本对假设
做出接受或拒绝的决策
,否则应接受假设
。
就越有说服力,常记这个概率值为α(0<α<1),称为检验的显著性水平
假设检验举例——鉴别可口可乐和百事可乐
可口可乐和百事可乐作为市场上最常见的两种可乐饮料大家一定都喝过,我们知道这两种可乐的味道差不多很难分辨, 如果现在有一名同学说自己能通过味道区分出两种可乐,那么如何设计实验来验证他是否具备这项能力呢?
我们可以假设这名同学不具备这个能力,如果通过实验能证明我们的假设是错误的,那么就说明这名同学具备品尝出两种可乐的能力来
不能区分两种不同的可乐
可以区分两种不同的可乐
接下来我们给该同学用相同的杯子倒若干杯不同的可乐,让他品尝说出杯子里的可乐究竟是可口可乐还是百事可乐,如果答对了可能有两种情况:
我们都知道只喝一杯他如果答对了说明不了什么问题,那么究竟喝几杯能下结论说这个同学不是瞎蒙的呢?我们可以计算一下概率
AB测试中的假设检验
通过上面的例子我们对假设检验有了基本的了解, 接下来我们看一下如何在AB测试中应用假设检验
老的方案老的设计
新的方案新的设计
在上面的例子中,实验的次数越多我们得到的结论就越准确,那么在AB测试中,实验的次数实际上就是参与实验的人数,我们可以通过设置显著性水平(Significance level) α来倒推参与实验的最少人数,具体计算可以通过上面给出的工具网站实现
显著性水平α : 依据实验结果做出推翻原假设(否定原方案) 选择备择假设(采用新方案) 的决定,犯错的概率, 一般设置为5%
统计功效(1−β): 依据实验结果做出保留原假设(保留原方案) 不选择备择假设(不采用新方案) 的决定,犯错的概率, 一般β设置为20% 那么1−β 为80%
辛普森悖论为英国统计学家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测试中如何避免辛普森悖论
方案A(现有方案)转化率 | 方案B(新方案)转化率 | |
|---|---|---|
Android 用户 | 70/800 = 8.75% | 80/800 = 10% |
iphone 用户 | 10/200 = 5% | 12/200 = 6% |
总用户 | 80/1000 = 8% | 92/1000 = 9.2% |

提出假设
原假设 老的设计比较好, 新版设计没有用
备选假设 新的设计比较好
选择变量
虽然我们已经知道了旧的设计的转化率(13%左右), 但是我们依然要需要设计两组, 原因是为了避免其他因素带来的误差, 比如季节因素, 促销因素。这两组人在其它条件都相同的只是页面设计不同的情况下进行实验, 这样能保证两组间的差异是由于设计导致的
确定实验人数
import numpy as np
import pandas as pd
import scipy.stats as stats
import statsmodels.stats.api as sms
import matplotlib as mpl
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
在企业场景下,我们需要与工程团队配合,收集满足要求的数据, 这里我们使用准备好的数据集, 对数据进行处理
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+ MBuser_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_users3894
users = session_counts[session_counts < 2].index
df = df[df['user_id'].isin(users)]数据采样
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_testuser_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
首先我们来计算一下两组的转化率和标准差
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%
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]
AB测试的应用场景
AB测试还是ABC测试
AB测试需要注意如下几点