1. 背景
某电商公司希望通过改进设计来提高转化率。以往该公司全年转化率平均在13%左右,现在希望新页面的转化率能有2%的提升,达到15%。在推出新页面之前,该公司通过AB测试在小范围的用户中进行测试,以确保新页面的效果能够达到预期目标。
2. 设计A/B test实验
在A/B测试实验设计这一步,通常需要完成以下6个步骤:
- 1、提出假设
- 2、确定实验分组
- 3、计算实验样本量及试验周期
- 4、上线AB测试并收集数据
- 5、数据分析及假设检验
- 6、得出结论及建议
Step1:提出假设
设计AB test实验的第一步通常是提出假设。假设是对于某个特定变化我们所期望的结果,也是后续实验的基础,我们需要在后续实验中通过数据验证这个假设是否成立。如果验证成立,我们可以将这个变化推广到全部用户。如果验证不成立,则需要继续优化这个假设或者放弃这个修改方案,以寻找更好的变化。
在这个实验中,我们希望新页面可以提升2%的转化率,原则上我们应选择单尾检验,准确的说,应该选择右侧单尾检验,因为我们的假设是新页面的转化率要大于旧页面的转化率。但是,在本案例中,我们并不能确定新页面的性能一定比当前的页面更好。所以,这里选择双尾检验。
Step2:确定实验分组
在此次AB测试中,我们分为实验组和对照组两组:
对照组(control组):这一组用户将看到旧版落地页
实验组(treatment组):这一组用户将看到新版落地页
为了后续计算每一组的转化效率,需要记录每一位参与实验的用户的购买行为,也就是说无论用户看到的是新版落地页还是旧版落地页,都需要记录这位用户最终是否购买了产品。这可以通过在网站上添加相应的追踪代码来实现:
- 0 0 0:代表用户在测试期间没有购买产品
- 1 1 1:代表用户在测试期间购买了产品
这样,后续就可以很容易地计算出每个组的均值,从而得到新旧两版落地页的转化率。
==================================================================
Step3:计算实验样本量、试验周期
- 实验样本量的确定
每一个实验组所需样本量计算公式如下:
N = σ 2 δ 2 ( Z 1 − α 2 + Z 1 − β ) 2 N = \frac{\sigma^2}{\delta^2}(Z_{1-\frac{\alpha}{2}}+Z_{1-\beta})^2 N=δ2σ2(Z1−2α+Z1−β)2
在这个公式当中,
α
\alpha
α为犯第一类错误的概率,
β
\beta
β为犯第二类错误的概率,σ代表的是样本数据的标准差,δ代表的是预期实验组和对照组两组数据的差值。一般情况下,我们会设置:
- 显著性水平: α = 0.05 \alpha=0.05 α=0.05,即在拒绝原假设之前,我们有95%的把握新版落地页的转化率比旧版落地页要高
- 统计功效(
1
−
β
1-\beta
1−β):
β
=
0.2
\beta=0.2
β=0.2,即表示测试检测特定效果的能力,如果该特定效果存在的话。在此案例中就是,如果新版落地页真的比旧版转换率要高,该测试有80%的概率能检测出这个状况。
当衡量指标为比率类指标时,标准差计算公式为:
σ 2 = P A ( 1 − P A ) + P B ( 1 − P B ) \sigma^2 = P_A(1-P_A)+P_B(1-P_B) σ2=PA(1−PA)+PB(1−PB)
其中,𝑃𝐴和𝑃𝐵分别是对照组和实验组的观测值。在此案例中𝑃𝐴=13%,𝑃𝐵=15%
每个组所需的最小样本量为:
N = σ 2 δ 2 ( Z 1 − α 2 + Z 1 − β ) 2 = ( 0.13 ∗ ( 1 − 0.13 ) + 0.15 ∗ ( 1 − 0.15 ) ) / ( 0.15 − 0.13 ) 2 ∗ ( 1.96 + 0.84 ) 2 = 4716 N = \frac{\sigma^2}{\delta^2}(Z_{1-\frac{\alpha}{2}}+Z_{1-\beta})^2 \\ = (0.13*(1-0.13)+0.15*(1-0.15))/(0.15-0.13)^2*(1.96+0.84)^2 \\ = 4716 N=δ2σ2(Z1−2α+Z1−β)2=(0.13∗(1−0.13)+0.15∗(1−0.15))/(0.15−0.13)2∗(1.96+0.84)2=4716
#计算每一组的样本量
(0.13*(1-0.13)+0.15*(1-0.15))/(0.15-0.13)**2*(1.96+0.84)**2
# 引入需要的包
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
# 计算效果量
effect_size = sms.proportion_effectsize(0.13, 0.15)
effect_size
# 计算所需的样本量
required_n = sms.NormalIndPower().solve_power(
effect_size,
power=0.8,
alpha=0.05,
ratio=1 ,
)
required_n
#向上取整
np.ceil(required_n)
- 实验周期的确定
根据上面最小样本量的计算,我们知道此次AB测试至少需要9440个用户参与测试,假如该落地页以往每天的平均浏览量为1000,则实验周期至少需要的天数为:
试验周期 = 9440 / 1000 = 9.4 ≈ 10 (天) 试验周期 = 9440/1000 = 9.4 ≈10(天) 试验周期=9440/1000=9.4≈10(天)
===================================================================
Step4:上线AB测试并收集数据
上线AB测试,同时收集数据。(我们这里采用的是kaggle上的数据)
===================================================================
Step5:数据分析及假设检验
这里使用的是Kaggle上的A/B测试数据集来模拟练习。
- 导入数据集
# 导入数据集
df = pd.read_csv("ab_data.csv")
df.head()
df.info()
df["group"].value_counts()
df["landing_page"].value_counts()
- 数据清洗
# 检查缺失值并处理
df.isnull().sum()
# 检查重复值并处理
df.duplicated().sum() #对于整体数据集没有重复值
# 检查用户是否有重复值
df["user_id"].duplicated().sum()
#查看重复值对应的用户Id
df[df["user_id"].duplicated()]["user_id"]
# 储存所有的重复用户ID
del_id = df[df["user_id"].duplicated()]["user_id"].values
len(del_id)
del_id
#查看重复用户的所有数据
df["user_id"].isin(del_id).sum()
#非重复用户对应的布尔值结果
~df["user_id"].isin(del_id)
#非重复用户对应的所有数据
df_new = df[~df["user_id"].isin(del_id)]
df_new
#提取时间年月日形式
pd.to_datetime(df_new['timestamp'],format='%Y-%m-%d')
# 处理时间,并检查时间天数
date_days = pd.to_datetime(df_new['timestamp'],format='%Y-%m-%d').dt.strftime('%Y-%m-%d')
date_days
#查看数据共有多少天
len(date_days.unique())
# 确保数据一一匹配
df_new.info()
# control组对应old_page,treatment组对应new_page
(((df_new['group']=='treatment')&(df_new['landing_page']=='new_page'))|((df_new['group']=='control')&(df_new['landing_page']=='old_page'))).sum()
# 确保contorl组每个用户看到的是旧页面,treatment组看到的是新页面
pd.crosstab(df_new['group'], df_new['landing_page'])
至此,所有的数据清洗工作已完成。
- 抽样
由于提供的数据量太大,所以每组4720个样本,这里我们选择每组抽样5000个
#随机提取5000个样本
df_new[df_new['group'] == 'control'].sample(n=required_n, random_state=22)
#每组各取5000个数据
required_n = 5000
control_sample = df_new[df_new['group'] == 'control'].sample(n=required_n, random_state=22)
treatment_sample = df_new[df_new['group'] == 'treatment'].sample(n=required_n, random_state=22)
ab_test = pd.concat([control_sample, treatment_sample], axis=0)
ab_test.reset_index(drop=True, inplace=True)
ab_test
#查看数据是不是一一对应,有无交叉重复
pd.crosstab(ab_test['group'], ab_test['landing_page'])
- 可视化结果【选做】
#查看均值和标准差
conversion_rates = ab_test.groupby('group')['converted'].agg([np.mean,np.std])
conversion_rates
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']
#查看数据
control_results
treatment_results
#计算控制组的转换数
control_results.sum()
#计算Z_test参数
n_con = control_results.count()
n_treat = treatment_results.count()
#控制组和实验组的转换下单数量
successes = [control_results.sum(), treatment_results.sum()]
#控制组和实验组的样本量
nobs = [n_con, n_treat]
#z-test
z_stat, pval = proportions_ztest(successes, nobs=nobs)
#z统计量计算结果
z_stat
#p值
pval
#各组置信区间
(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}]')
==================================================================
Step6:分析结果及建议
由于我们计算出来的P值=0.607远高于显著水平
α
=
0.05
\alpha=0.05
α=0.05,所以我们不能拒绝原假设H0.这意味着新版落地页与旧版落地页没有明显不同(更不用说更好了……)
此外,我们继续看置信区间,treatment组的置信区间为
[
0.117
,
0.136
]
[0.117,0.136]
[0.117,0.136],可以看出:
- 它包括我们的转化率基准线13%
- 它不包括我们的转化率目标值15%
也可以说明,新版落地页的真实转化率更有可能与我们的基线相似,而没有办法达到我们期望的15%。进一步证明了,新版设计并不是一个很好的改进。