AB测试是为Web或App界面或流程制作两个(A/B)或多个(A/B/n)版本,在同一时间维度,分别让组成成分相同(相似)的访客群组(目标人群)随机的访问这些版本,收集各群组的用户体验数据和业务数据,最后分析、评估出最好版本,正式采用。
方法通俗讲就是控制变量,然后通过统计学中的假设检验来分析来自两个或多个组样本均值的差异性,从而判断它们各自代表的总体的差异是否显著。
案例数据为对网页新、旧页面的AB测试结果,目标是判断两版页面的日转化率是否存在显著区别。
数据来源:点击链接
数据导入
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import statsmodels.api as sm
from scipy import stats
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 加载数据集
df = pd.read_csv('ab_data.csv')
df.head()
# 加载用户国家分布数据集
df1 = pd.read_csv('countries.csv')
df1.head()
# 去重
df.drop_duplicates( inplace=True)
合并
# 合并
data = df.merge(df1, on='user_id', how='inner')
data.head()
# 查看两组的页面成分
data.groupby(['landing_page','group']).converted.count()
# 删除实验组中的旧网页与对照组中的新网页
data = data.query("(group == 'control' & landing_page == 'old_page') | (group == 'treatment' & landing_page == 'new_page')")
检验
# 再次查看两组的页面成分---已全部删除
data.groupby(['landing_page','group']).converted.count()
# 查看user_id重复项
data.user_id.value_counts().loc[data.user_id.value_counts() >1]
# 删除重复值
data.drop_duplicates('user_id', keep = 'last', inplace = True)
时间格式转换
# 由于不清楚数据集的背景,所以将美国和加拿大的时间转换为美国中部时区时间,平衡下美国五个时区的时差,英国则无需转换
# 此转换操作是否合适待讨论
data['date'] = pd.to_datetime(data.timestamp.tolist(), utc=True).strftime("%Y-%m-%d")
data['date'].loc[data.query("country !='UK'").index] = pd.to_datetime(data.query("country !='UK'").timestamp.tolist(), utc=True).tz_convert("US/Central").strftime("%Y-%m-%d")
查看总体转化率
# 查看总体转化率
data.converted.mean()
查看实验组与对照组的转化率
# 查看实验组与对照组的转化率
data.groupby(['group']).converted.mean()
查看每日的转化率对比
# 查看每日的转化率对比
fig,ax = plt.subplots(figsize=(15, 6))
ax.plot(data.query("group =='control'").groupby('date').converted.mean(), label='control')
ax.plot(data.query("group =='treatment'").groupby('date').converted.mean(), label='treatment')
ax.tick_params(axis='x', which='major', labelrotation=60)
ax.set_title("日转化率对比")
ax.legend()
plt.show()
为避免辛普森悖论,将转化率细分到国家进行对比
# 分别查看每个地区的总体转化率对比
data.groupby([ 'country','group']).converted.agg({
'count', 'mean'})
避免新奇效应,细分到每天进行观察
# 分别查看每个地区每日的转化率对比
fig,ax = plt.subplots(ncols=1, nrows=3, figsize=(12, 15))
for i,j in enumerate(data.country.unique()):
ax[i].plot(data.query("group =='control' & country == @j").groupby('date').converted.mean(), label='control')
ax[i].plot(data.query("group =='treatment' & country == @j").groupby('date').converted.mean(), label = 'treatment')
ax[i].tick_params(axis='x', which='major', labelrotation=60)
ax[i].legend(loc='upper right')
ax[i].set_title(j)
plt.subplots_adjust(hspace =0.4)
plt.show()
查看样本量,避免数量不均衡带来的影响
data.groupby(['country', 'group','date']).converted.count().unstack().T
问题:
零假设与备择假设:
检验类型:
显著性水平选取:
# 剔除最后一天的数据
data =data.query("date != '2017-01-24'")
# 对照组的日转化率
control_DCVR = data.query("group == 'control'").groupby('date').converted.mean()
# 实验组的日转化率
treatment_DCVR = data.query("group == 'treatment'").groupby('date').converted.mean()
查看核密度估计图和QQ图
# 查看核密度估计图
fig, ax = plt.subplots(nrows=2, ncols=2, figsize=(16, 12))
title_list = ['control', 'treatment']
for i,j in enumerate([control_DCVR, treatment_DCVR]):
sns.distplot(control_DCVR, fit=stats.norm, ax=ax[0][i])
(mu, sigma) = stats.norm.fit(j)
ax[0][i].legend(['Normal dist. ($\mu=$ {:.2f} and $\sigma=$ {:.2f} )'.format(mu, sigma)],loc='best')
ax[0][i].set_title(title_list[i])
stats.probplot(j, plot=ax[1][i])
Shapiro-Wilk检验正态性
print('control Shapiro-Wilk test p-value:', stats.shapiro(control_DCVR).pvalue)
print('treatment Shapiro-Wilk test p-value:', stats.shapiro(treatment_DCVR).pvalue)
结果:
control Shapiro-Wilk test p-value: 0.4410777688026428
treatment Shapiro-Wilk test p-value: 0.37171587347984314
Shapiro-Wilk检验 原假设为指定数据服从正态分布
p值均大于0.05,不能拒绝原假设,说明对照组与实验组数据均符合正态分布
# levene test
print('levene test p-value:', stats.levene(control_DCVR, treatment_DCVR, center='mean').pvalue)
结果:
levene test p-value: 0.5175103523245008
levene检验 原假设为两独立样本来自的总体方差相等
p值大于0.05,不能拒绝原假设,认为对照组与实验组数据均具有齐次性
满足t检验的前提条件,于是接下来进行t检验
import statsmodels.stats.weightstats as sw
t_value, p_value, df = sw.ttest_ind(control_DCVR, treatment_DCVR)
print('t值:{}\np值:{}\n自由度:{}'.format(t_value, p_value, df))
结果:
结论:
问题:
检验类型:
查看每个国家日转化率的相关统计量
# 描述统计
(data.groupby(['group', 'country', 'date'])
.converted
.mean()
.reset_index()
.groupby(['group', 'country'])
.converted
.agg({
'mean', 'std', 'count'})
.round(4)
)
正态性检验
# 正态性检验
data_temp = data.groupby(['group', 'country', 'date']).converted.mean().unstack([0,1])
for i in data_temp.columns:
print('{} {} Shapiro-Wilk test p-value:'.format(i[0], i[1]), stats.shapiro(data_temp[i]).pvalue)
查看加拿大对照组数据的峰度与偏度
print('control CA kurtosis: ', abs(data_temp[('control', 'CA')].kurtosis()))
print('control CA skew: ', abs(data_temp[('control', 'CA')].skew()))
方差齐性检验
# levene test---测试对照组各国间的方差齐性
print('levene test between control group p-value:',stats.levene(*data_temp.T.values[:3], center='mean').pvalue)
# 测试实验组各国间的方差齐性
print('levene test between treatment group p-value:',stats.levene(*data_temp.T.values[3:], center='mean').pvalue)
零假设与备择假设:
显著性水平选取:
Kruskal-Wallis检验
stats.kruskal(*data_temp.T.values)
结论:
经过检验,新旧两版本网页的日转化率没有显著性差异,各国两版本网页的日转化率也没有显著性差异。