A/B测试常用于对某个改进产生效果的评估,比如用于测试网页修改效果。A/B测试其实是一场实验,实验中分为对照组实验组。实验组是进行了某项操作的实验结果,而对照组除了没有进行该项操作外,其余的条件都和实验组相同。通过这样的设置就可以控制其他因素不变,而只关注想要验证的因素。
A/B测试的原理,来源于假设检验,大家可以看看我前面《一文详解假设检验、两类错误和p值》这篇文章了解假设检验的概念,另外《利用python进行假设假设检验》这篇文章介绍了怎么用python进行假设检验。这篇文章主要是介绍如何利用python进行A/B测试。具体的原理和python实现的细节可以参考我前面的两篇文章,接下来我会直接展示过程和结果。所使用的数据是电子商务公司设计了新网站页面,想提高用户转化率,得到的实验数据。我们的分析目标是决定是否应该使用新页面,也就是新页面是否带来了更高的用户转化。

1.数据介绍和处理

首先,我们导入相应的包和数据:

import pandas as pd
import numpy as np
import random
import matplotlib.pyplot as plt
%matplotlib inline
random.seed(42)

导入数据:

df=pd.read_csv('ab_data.csv')
df.head()

F检验 python 代码 python jb检验_F检验 python 代码


数据中包含用户的身份id、所属组别和是否成功转化等信息。由于不能100%保证实验用户是否真正接受到了新页面或者旧页面。我们可以通过查看group和landing_page是否一致来辨别。代码如下:

df[((df['group'] == 'treatment') == (df['landing_page'] == 'new_page')) == False].shape[0]

F检验 python 代码 python jb检验_python_02


说明这种情况是存在的。所以接下来要帅选数据,只选取group和landing_page相一致的数据,代码如下:

df1=df.query('group=="control"').query('landing_page=="old_page"') 
df2=df1.append(df.query('group=="treatment"').query('landing_page=="new_page"'))

我们验证一下效果:

df2[((df2['group'] == 'treatment') == (df2['landing_page'] == 'new_page')) == False].shape[0]

F检验 python 代码 python jb检验_F检验 python 代码_03


结果为0,说明新的数据中已经不存在不一致的情况。接下来,我们分别计算一下,总体的转化率、控制组的转化率和实验组的转化率。

float(df2.query('converted==1')['user_id'].count())/float(df2['user_id'].count())

总体转化率的结果为:
0.11959708724499628

float(df2.query('group=="control"').query('converted==1')['user_id'].count())/float(df2.query('group=="control"')['user_id'].count())

控制组的转化率为:0.1203863045004612

float(df2.query('group=="treatment"').query('converted==1')['user_id'].count())/float(df2.query('group=="treatment"')['user_id'].count())

实验组的转化率为:
0.11880806551510564
从着三组的转化率来看,差别不是很大。

2.A/B测试

A/B测试的原理来源于假设检验,在本例中验证的是实验组和控制组的转化率是否相等。那么我们可以设置假设为:


H0: p_old =p_new H1: p_old < p_new

当H0为真的情况下,实验组和控制组的转化率相等,等于总体的转化率。我们可以计算:

float(df2.query('converted==1').count()[0])/float(df2.count()[0])

值为::
0.11959708724499628
统计数据中控制组和实验组的样本量n_new和n_old:

:
df2.query('landing_page=="new_page"').count()[0]
df2.query('landing_page=="old_page"').count()[0]

n_new=145310, n_old=145274
接着我们模拟在零假设成立下,即p_new=p_old=0.1196,两样本转化率差的分布:

p_diffs=[]
for _ in range(10000):
    new_page_converted=np.random.choice([0,1],size=145310,p=[0.8804,0.1196]).mean()
    old_page_converted=np.random.choice([0,1],size=145274,p=[0.8804,0.1196]).mean()
    p_diffs.append(new_page_converted-old_page_converted)

绘制p_diffs的直方图如下:

plt.hist(p_diffs)

F检验 python 代码 python jb检验_python_04


下面我们计算零假设成立下的P值,也就p_diffs的数值中,有多大比例大于 样本中观察到的实际差值。关于p值为什么是这样计算的,可以参考《利用python进行假设假设检验》。

real_new_page_converted=float(df2.query('landing_page=="new_page"').query('converted==1').count()[0])/float(df2.query('landing_page=="new_page"').count()[0])  #计算样本中实验组的实际转化率
real_old_page_converted=float(df2.query('landing_page=="old_page"').query('converted==1').count()[0])/float(df2.query('landing_page=="old_page"').count()[0])   #计算样本中控制组的实际转化率
real_converted=real_new_page_converted-real_old_page_converted    #计算样本中实验组和控制组的实际转化率差值
p_diffs=np.array(p_diffs)     #转化成ndarray
(p_diffs>real_converted).mean()  #计算p值,也就是p_diffs中大于实际转化率的差值的比例

F检验 python 代码 python jb检验_python_05


这个p值足够大,所以我们无法拒绝原假设。我们倾向于认为新的页面没有带来转化率的提升。另外我们也可以使用python的内置包来计算p值,代码如下:

import statsmodels.api as sm
convert_old = df2.query('landing_page=="old_page"').query('converted==1').count()[0]
convert_new = df2.query('landing_page=="new_page"').query('converted==1').count()[0]
n_old = df2.query('landing_page=="old_page"').count()[0]
n_new = df2.query('landing_page=="new_page"').count()[0
z_score, p_value = sm.stats.proportions_ztest([convert_old, convert_new], [n_old, n_new],alternative='smaller')
from scipy.stats import norm
norm.cdf(z_score)
z_score,norm.ppf(1-0.05),p_value

F检验 python 代码 python jb检验_ab测试_06


可以看到,z-score为1.31092,小于标准正太分布下的值1.6449,所以我们无法接受零假设,我们倾向于认为新页面没有带来转化率的提升。结果与上部分得出的结论一样,另外这里计算的p值和上一步计算的p值很相近。