分析A/B测试结果
- 简介
- I - 概率
- II - A/B 测试
- III - 回归
简介
对于这个项目,你将要了解的是电子商务网站运行的 A/B 测试的结果。你的目标是通过这个 notebook 来帮助公司弄清楚他们是否应该使用新的页面,保留旧的页面,或者应该将测试时间延长,之后再做出决定。
I - 概率
让我们先导入库,然后开始你的任务吧。
import pandas as pd
import numpy as np
import random
import matplotlib.pyplot as plt
%matplotlib inline
#We are setting the seed to assure you get the same answers on quizzes as we set up
random.seed(42)
1.
现在,导入 ab_data.csv
数据,并将其存储在 df
中。 使用你的 dataframe 来回答课堂测试 1 中的问题。
a. 导入数据集,并在这里查看前几行:
df = pd.read_csv("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 |
b. 使用下面的单元格来查找数据集中的行数。
df.shape
(294478, 5)
c. 数据集中独立用户的数量。
df.user_id.nunique()
290584
d. 用户转化的比例。
#计算独立用户的转化比例
df.query('converted==1').user_id.nunique()/df.user_id.nunique()
0.12104245244060237
e. new_page
与 treatment
不一致的次数。
df[((df['group'] == 'treatment') != (df['landing_page'] == 'new_page'))]['user_id'].count()
3893
f. 是否有任何行存在缺失值?
df.isnull().sum()
user_id 0
timestamp 0
group 0
landing_page 0
converted 0
dtype: int64
2.
对于 treatment 不与 new_page 一致的行或 control 不与 old_page 一致的行,我们不能确定该行是否真正接收到了新的或旧的页面。我们应该如何处理这些行?在课堂中的 测试 2 中,给出你的答案。
a. 现在,使用测试题的答案创建一个符合测试规格要求的新数据集。将新 dataframe 存储在 df2 中。
df2 = df.drop(df[((df['group'] == 'treatment') != (df['landing_page'] == 'new_page'))].index)
df2.shape
(290585, 5)
# Double Check all of the correct rows were removed - this should be 0
df2[((df2['group'] == 'treatment') == (df2['landing_page'] == 'new_page')) == False].shape[0]
0
3.
使用 df2 与下面的单元格来回答课堂中的 测试3 。
a. df2 中有多少唯一的 user_id?
df2.user_id.nunique()
290584
b. df2 中有一个重复的 user_id 。它是什么?
df2[df2.user_id.duplicated()==True]['user_id']
2893 773192
Name: user_id, dtype: int64
c. 这个重复的 user_id 的行信息是什么?
df2.query('user_id=="773192"')
user_id | timestamp | group | landing_page | converted | |
---|---|---|---|---|---|
1899 | 773192 | 2017-01-09 05:37:58.781806 | treatment | new_page | 0 |
2893 | 773192 | 2017-01-14 02:55:59.590927 | treatment | new_page | 0 |
d. 删除 一个 含有重复的 user_id 的行, 但需要确保你的 dataframe 为 df2。
#这里删除哪一行都可以
df2 = df2.drop(2893)
df2.shape
(290584, 5)
4.
在下面的单元格中,使用 df2 来回答与课堂中的 测试 4 相关的测试题目。
a. 不管它们收到什么页面,单个用户的转化率是多少?
df2.converted.mean()
0.11959708724499628
b. 假定一个用户处于 control
组中,他的转化率是多少?
#将控制组和试验组分别存储到df_ctrl和df_tr中
df_ctrl = df2.query('group == "control"')
df_tr = df2.query('group == "treatment"')
#控制组的转化率
df_ctrl.converted.mean()
0.1203863045004612
c. 假定一个用户处于 treatment
组中,他的转化率是多少?
#试验组的转化率
df_tr.converted.mean()
0.11880806551510564
d. 一个用户收到新页面的概率是多少?
df_tr.shape[0]/df2.shape[0]
0.5000619442226688
e. 使用这个问题的前两部分的结果,给出你的建议:你是否认为有证据表明一个页面可以带来更多的转化?在下面写出你的答案。
通过上面的计算,控制组的转化率为12.04%,试验组的转化率为11.88%,略低于控制组约0.16%。到目前为止我们没有充分的依据来判断新页面是否能带来更多的转化。我们需要通过假设检验来进一步观察样本转化率的差异值是否具有统计显著性。
II - A/B 测试
请注意,由于与每个事件相关的时间戳,你可以在进行每次观察时连续运行假设检验。
然而,问题的难点在于,一个页面被认为比另一页页面的效果好得多的时候你就要停止检验吗?还是需要在一定时间内持续发生?你需要将检验运行多长时间来决定哪个页面比另一个页面更好?
一般情况下,这些问题是A / B测试中最难的部分。如果你对下面提到的一些知识点比较生疏,请先回顾课程中的“描述统计学”部分的内容。
1.
现在,你要考虑的是,你需要根据提供的所有数据做出决定。如果你想假定旧的页面效果更好,除非新的页面在类型I错误率为5%的情况下才能证明效果更好,那么,你的零假设和备择假设是什么? 你可以根据单词或旧页面与新页面的转化率 p o l d p_{old} pold 与 p n e w p_{new} pnew 来陈述你的假设。
在这里给出你的答案。
H 0 H_{0} H0: p n e w p_{new} pnew - p o l d p_{old} pold ≤ 0
H 1 H_{1} H1: p n e w p_{new} pnew - p o l d p_{old} pold > 0
2.
假定在零假设中,不管是新页面还是旧页面, p n e w p_{new} pnew and p o l d p_{old} pold 都具有等于 转化 成功率的“真”成功率,也就是说, p n e w p_{new} pnew 与 p o l d p_{old} pold 是相等的。此外,假设它们都等于ab_data.csv 中的 转化 率,新旧页面都是如此。
每个页面的样本大小要与 ab_data.csv 中的页面大小相同。
执行两次页面之间 转化 差异的抽样分布,计算零假设中10000次迭代计算的估计值。
使用下面的单元格提供这个模拟的必要内容。如果现在还没有完整的意义,不要担心,你将通过下面的问题来解决这个问题。你可以通过做课堂中的 测试 5 来确认你掌握了这部分内容。
a. 在零假设中, p n e w p_{new} pnew 的 convert rate(转化率) 是多少?
p_new = df2.converted.mean()
p_new
0.11959708724499628
b. 在零假设中, p o l d p_{old} pold 的 convert rate(转化率) 是多少?
p_old = df2.converted.mean()
p_old
0.11959708724499628
c. n n e w n_{new} nnew 是多少?
n_new = df_tr.shape[0]
n_new
145310
d. n o l d n_{old} nold?是多少?
n_old = df_ctrl.shape[0]
n_old
145274
e. 在零假设中,使用 p n e w p_{new} pnew 转化率模拟 n n e w n_{new} nnew 交易,并将这些 n n e w n_{new} nnew 1’s 与 0’s 存储在 new_page_converted 中。(提示:可以使用 numpy.random.choice。)
new_page_converted = np.random.choice(2, size=n_new, p=[1-p_new,p_new])
new_page_converted
array([0, 0, 0, ..., 0, 1, 0])
f. 在零假设中,使用 p o l d p_{old} pold 转化率模拟 n o l d n_{old} nold 交易,并将这些 n o l d n_{old} nold 1’s 与 0’s 存储在 old_page_converted 中。
old_page_converted = np.random.choice(2, size=n_old, p=[1-p_old,p_old])
old_page_converted
array([0, 0, 0, ..., 0, 0, 0])
g. 在 (e) 与 (f)中找到 p n e w p_{new} pnew - p o l d p_{old} pold 模拟值。
#计算新旧页面转化率的差值
new_page_converted.mean() - old_page_converted.mean()
-0.0011172529901225942
h. 使用**a. 到 g. ** 中的计算方法来模拟 10,000个 p n e w p_{new} pnew - p o l d p_{old} pold 值,并将这 10,000 个值存储在 p_diffs 中。
p_diffs = []
for _ in range(10000):sample_new = np.random.choice(2,n_new,p=[1-p_new,p_new])sample_old = np.random.choice(2,n_old,p=[1-p_old,p_old])p_diffs.append(sample_new.mean()-sample_old.mean())
i. 绘制一个 p_diffs 直方图。这个直方图看起来像你所期望的吗?通过回答课堂上的匹配问题,确保你完全理解这里计算出的内容。
plt.hist(p_diffs);
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K3CFgnh3-1572261723847)(output_57_0.png)]
j. 在p_diffs列表的数值中,有多大比例大于 ab_data.csv 中观察到的实际差值?
#实际观测差异值
obs_diff = df_tr.converted.mean() - df_ctrl.converted.mean()
obs_diff
-0.0015782389853555567
p_diffs = np.array(p_diffs)
(p_diffs > obs_diff).mean()
0.9018
k. 用文字解释一下你刚才在 **j.**中计算出来的结果。在科学研究中,这个值是什么? 根据这个数值,新旧页面的转化率是否有区别呢?
上面得到的结果即为假设检验的p值。 p值约为0.90,接近于1,说明新旧页面转化率的差异值不具有统计显著性。在显著性水平0.05下,我们没有理由拒绝零假设,即新页面并没有比旧页面带来更高的转化率。
l. 我们也可以使用一个内置程序 (built-in)来实现类似的结果。尽管使用内置程序可能更易于编写代码,但上面的内容是对正确思考统计显著性至关重要的思想的一个预排。填写下面的内容来计算每个页面的转化次数,以及每个页面的访问人数。使用 n_old
与 n_new
分别引证与旧页面和新页面关联的行数。
import statsmodels.api as sm#计算新旧页面的转化次数
convert_old = df_ctrl.query("converted == 1").shape[0]
convert_new = df_tr.query("converted == 1").shape[0]#计算新旧页面的访问人数
n_old = df_ctrl.shape[0]
n_new = df_tr.shape[0]n_old, convert_old, n_new, convert_new
(145274, 17489, 145310, 17264)
m. 现在使用 stats.proportions_ztest
来计算你的检验统计量与 p-值。这里 是使用内置程序的一个有用链接。
sm.stats.proportions_ztest([convert_new,convert_old],[n_new, n_old],alternative='larger')
(-1.3109241984234394, 0.9050583127590245)
n. 根据上题算出的 z-score 和 p-value,我们认为新旧页面的转化率是否有区别?它们与 j. 与 k. 中的结果一致吗?
利用z-test测试得到的p值与之前的结果基本一致。p值大于0.05,所以在显著性水平0.05下,我们无法拒绝零假设。和之前的计算得出的结论相同。
III - 回归分析法之一
1.
在最后一部分中,你会看到,你在之前的A / B测试中获得的结果也可以通过执行回归来获取。
a. 既然每行的值是转化或不转化,那么在这种情况下,我们应该执行哪种类型的回归?
对于结果为二分类的问题,我们可以使用逻辑回归。
b. 目标是使用 statsmodels 来拟合你在 a. 中指定的回归模型,以查看用户收到的不同页面是否存在显著的转化差异。但是,首先,你需要为这个截距创建一个列( 原文:column) ,并为每个用户收到的页面创建一个虚拟变量列。添加一个 截距 列,一个 ab_page 列,当用户接收 treatment 时为1, control 时为0。
df2['intercept'] = 1
df2[['control','treatment']] = pd.get_dummies(df['group'])
df2.head()
user_id | timestamp | group | landing_page | converted | intercept | control | treatment | |
---|---|---|---|---|---|---|---|---|
0 | 851104 | 2017-01-21 22:11:48.556739 | control | old_page | 0 | 1 | 1 | 0 |
1 | 804228 | 2017-01-12 08:01:45.159739 | control | old_page | 0 | 1 | 1 | 0 |
2 | 661590 | 2017-01-11 16:55:06.154213 | treatment | new_page | 0 | 1 | 0 | 1 |
3 | 853541 | 2017-01-08 18:28:03.143765 | treatment | new_page | 0 | 1 | 0 | 1 |
4 | 864975 | 2017-01-21 01:52:26.210827 | control | old_page | 1 | 1 | 1 | 0 |
#去掉‘control'列,并按照提示要求重新命名’treatment‘列
df2 = df2.drop('control',axis=1)
df2.rename(columns={'treatment' : 'ab_page'}, inplace=True)
df2.head()
user_id | timestamp | group | landing_page | converted | intercept | ab_page | |
---|---|---|---|---|---|---|---|
0 | 851104 | 2017-01-21 22:11:48.556739 | control | old_page | 0 | 1 | 0 |
1 | 804228 | 2017-01-12 08:01:45.159739 | control | old_page | 0 | 1 | 0 |
2 | 661590 | 2017-01-11 16:55:06.154213 | treatment | new_page | 0 | 1 | 1 |
3 | 853541 | 2017-01-08 18:28:03.143765 | treatment | new_page | 0 | 1 | 1 |
4 | 864975 | 2017-01-21 01:52:26.210827 | control | old_page | 1 | 1 | 0 |
c. 使用 statsmodels 导入你的回归模型。 实例化该模型,并使用你在 b. 中创建的2个列来拟合该模型,用来预测一个用户是否会发生转化。
lm = sm.Logit(df2['converted'],df2[['intercept','ab_page']])
results = lm.fit()
Optimization terminated successfully.Current function value: 0.366118Iterations 6
d. 请在下方提供你的模型摘要,并根据需要使用它来回答下面的问题。
results.summary()
Dep. Variable: | converted | No. Observations: | 290584 |
---|---|---|---|
Model: | Logit | Df Residuals: | 290582 |
Method: | MLE | Df Model: | 1 |
Date: | Thu, 22 Aug 2019 | Pseudo R-squ.: | 8.077e-06 |
Time: | 17:47:58 | Log-Likelihood: | -1.0639e+05 |
converged: | True | LL-Null: | -1.0639e+05 |
LLR p-value: | 0.1899 |
coef | std err | z | P>|z| | [0.025 | 0.975] | |
---|---|---|---|---|---|---|
intercept | -1.9888 | 0.008 | -246.669 | 0.000 | -2.005 | -1.973 |
ab_page | -0.0150 | 0.011 | -1.311 | 0.190 | -0.037 | 0.007 |
e. 与 ab_page 关联的 p-值是多少? 为什么它与你在 II 中发现的结果不同?
提示: 与你的回归模型相关的零假设与备择假设分别是什么?它们如何与 Part II 中的零假设和备择假设做比较?
与ab_page关联的p值为0.190,而在第二部分假设检验中计算得到的p值是0.90。
逻辑回归中变量的p值与第二部分假设检验的检验方向不同。回归中的零假设是ab_page的系数等于0,备择假设是系数不等于0,是一个双尾检验。p值的大小可以指示ab_page变量是否有利于预测转化率。这里得到的p值:0.190 > 0.05,所以我们认为ab_page这个变量,即“用户访问新页面”这个因素,与用户转化率没有太大关系,不适合用于预测转化率。
f. 现在,你一定在考虑其他可能影响用户是否发生转化的因素。讨论为什么考虑将其他因素添加到回归模型中是一个不错的主意。在回归模型中添加附加项有什么弊端吗?
在实际情况中,还有很多因素可能会影响转化率,比如用户浏览页面的时长,不同类型的用户,用户访问的不同时间段等等;考虑进更多的因素有助于我们挖掘出真正影响用户转化率的关键因素。
但是,添加更多的附加项会使分析的情况变得更复杂,回归模型可能不再适用;或者,如果不同的变量之间有相关性,会出现多重共线性现象,也可能会导致我们得到错误的推论。
g. 现在,除了测试不同页面的转化率是否会发生变化之外,还要根据用户居住的国家或地区添加一个 effect 项。你需要导入 countries.csv 数据集,并将数据集合并在适当的行上。 这里 是链接表格的文档。
这个国家项对转化有影响吗?不要忘记为这些国家的列创建虚拟变量—— 提示: 你将需要为这三个虚拟变量增加两列。 提供统计输出,并书面回答这个问题。
countries_df = pd.read_csv('./countries.csv')
df_new = countries_df.set_index('user_id').join(df2.set_index('user_id'), how='inner')
df_new.country.value_counts()
US 203619
UK 72466
CA 14499
Name: country, dtype: int64
#构建虚拟变量
df_new[['CA','UK','US']] = pd.get_dummies(df_new['country'])
df_new.head(1)
country | timestamp | group | landing_page | converted | intercept | ab_page | CA | UK | US | |
---|---|---|---|---|---|---|---|---|---|---|
user_id | ||||||||||
834778 | UK | 2017-01-14 23:08:43.304998 | control | old_page | 0 | 1 | 0 | 0 | 1 | 0 |
#逻辑回归模型
lm = sm.Logit(df_new['converted'],df_new[['intercept','UK','CA']])
results = lm.fit()
results.summary()
Optimization terminated successfully.Current function value: 0.366116Iterations 6
Dep. Variable: | converted | No. Observations: | 290584 |
---|---|---|---|
Model: | Logit | Df Residuals: | 290581 |
Method: | MLE | Df Model: | 2 |
Date: | Thu, 22 Aug 2019 | Pseudo R-squ.: | 1.521e-05 |
Time: | 18:00:27 | Log-Likelihood: | -1.0639e+05 |
converged: | True | LL-Null: | -1.0639e+05 |
LLR p-value: | 0.1984 |
coef | std err | z | P>|z| | [0.025 | 0.975] | |
---|---|---|---|---|---|---|
intercept | -1.9967 | 0.007 | -292.314 | 0.000 | -2.010 | -1.983 |
UK | 0.0099 | 0.013 | 0.746 | 0.456 | -0.016 | 0.036 |
CA | -0.0408 | 0.027 | -1.518 | 0.129 | -0.093 | 0.012 |
结论: 从结果来看,不同国家的p值远大于0.05,不具有统计显著性,对用户转化率没有太大影响。
h. 虽然你现在已经查看了国家与页面在转化率上的个体性因素,但现在我们要查看页面与国家/地区之间的相互作用,测试其是否会对转化产生重大影响。创建必要的附加列,并拟合一个新的模型。
提供你的摘要结果,以及根据结果得出的结论。
提示:页面与国家/地区的相互作用
df3['new_CA'] = df3['new_page'] * df3['CA']
df3['new_UK'] = df3['new_page'] * df3['UK']
#创建交叉项
df_new['CA_page'] = df_new['CA']*df_new['ab_page']
df_new['UK_page'] = df_new['UK']*df_new['ab_page']lm = sm.Logit(df_new['converted'], df_new[['intercept','ab_page','CA','UK','CA_page','UK_page']])
results = lm.fit()
results.summary()
Optimization terminated successfully.Current function value: 0.366109Iterations 6
Dep. Variable: | converted | No. Observations: | 290584 |
---|---|---|---|
Model: | Logit | Df Residuals: | 290578 |
Method: | MLE | Df Model: | 5 |
Date: | Thu, 22 Aug 2019 | Pseudo R-squ.: | 3.482e-05 |
Time: | 18:02:23 | Log-Likelihood: | -1.0639e+05 |
converged: | True | LL-Null: | -1.0639e+05 |
LLR p-value: | 0.1920 |
coef | std err | z | P>|z| | [0.025 | 0.975] | |
---|---|---|---|---|---|---|
intercept | -1.9865 | 0.010 | -206.344 | 0.000 | -2.005 | -1.968 |
ab_page | -0.0206 | 0.014 | -1.505 | 0.132 | -0.047 | 0.006 |
CA | -0.0175 | 0.038 | -0.465 | 0.642 | -0.091 | 0.056 |
UK | -0.0057 | 0.019 | -0.306 | 0.760 | -0.043 | 0.031 |
CA_page | -0.0469 | 0.054 | -0.872 | 0.383 | -0.152 | 0.059 |
UK_page | 0.0314 | 0.027 | 1.181 | 0.238 | -0.021 | 0.084 |
结论:由结果可见,国家和页面的交叉项对应的p值远大于0.05,依然不具有统计显著性,对用户转化率没有太大影响。
查看一下这个A/B测试运行的时间
df2['timestamp'] = pd.to_datetime(df2['timestamp'])
df2.timestamp.max() - df2.timestamp.min()
Timedelta('21 days 23:59:49.081927')
最终结论: 根据目前收集的数据得到的结果,页面的替换对用户转化率不会造成明显的影响。这个试验进行了21天,可以考虑适当延长试验的时间来做决策。也可以收集更多方面的数据来探索影响用户转化率的关键因素,从而帮助提高转化率。