Python数据分析案例21——链接预览对链接点击率影响分析(A/B test)

所谓A/B test,其实类似于初中生物说的对照试验。对用户分组,每个组使用一个方案(方案应遵从单变量前提),在相同的时间维度上去观察用户的反应(体现在业务数据和用户体验数据上)。需要注意的是各个用户群组的组成成分应当尽量相似,譬如新老用户很有可能表现出较大的偏好差异。最后根据假设检验的结果,判断哪些版本较之原版有统计意义上的差异,并根据效应量选出其中表现最好的版本。

说白了就是一个对照组实验,一组用户不处理,一组用户特殊处理一下,然后对比一下两组用户的行为特征是不是有显著性变化。若行为特征变好了说明这样处理是有效的。

核心方法就是传统统计学里面的两个分类变量的t检验罢了.......“AB测试” 看着很神秘,其实就是统计学最基础的t检验。


案例背景:

一家即时消息公司希望对提供应用内共享链接预览的更改如何影响链接的点击率进行 A/B 测试。用户被随机分为三个变体:对照组和两个处理组。

需要这代码演示数据的同学可以参考:数据​​​​​​​

变体描述

实验中有三个不同的组,其中两个是处理组,另一个是对照组。每个组有 10000 个用户。每个变体的描述如下:

  • 处理 A:提供内容的预览以及共享链接。
  • 处理 B:提供内容的预览和缩略图以及共享链接。
  • 对照组:仅以纯文本形式打印共享链接本身。

比较用户特征的均值

将测试以下 16 个用户特征在两个处理组中的任何一个之间是否不同。例如,可以将比较对照组和处理方案 A、对照组和处理方案 B 以及处理方案 A 和处理方案 B 之间的用户年龄。它们的特征变量定义如下表所示。


对照分析 

导入包,读取数据

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt 
import seaborn as sns       
plt.rcParams ['axes.unicode_minus']=False               
pd.set_option('precision', 4)
#pd.set_option('display.max_columns', 40)
data=pd.read_csv('data.csv',parse_dates=['regDate'])
data.head()

第一列变量variant就是处理的组别,有0,1,2三个取值,对应三个组。

查看数据基础信息:

data.info(0)

 

 时间变量处理一下,表示用户的账后注册时长:

data['regDate']=(data['regDate']-pd.to_datetime('2014-01-01')).map(lambda x:x.days) #Processing time variables

画不同组别的所有特征的箱线图对比:

dis_cols = 4 ;     dis_rows = len(data.columns)
plt.figure(figsize=(3 * dis_cols, 3 * dis_rows),dpi=256)
for i in range(len(data.columns)-1):
    plt.subplot(dis_rows,dis_cols,i+1)
    sns.boxplot(x='variant',y=data.columns[i+1],width=0.8,orient="v",data=data)
    plt.xlabel(('different groups'),fontsize=8)
    plt.ylabel(data.columns[i+1], fontsize=12)
plt.tight_layout()
plt.show()

 计算不同组别的均值

data.groupby('variant').mean()

  计算不同组别的标准差

data.groupby('variant').std()

 通过上面的描述性统计分析,我们发现每组的标准差数据值差异不是很大,均值也都差不多。因此,选择使用等方差的假设。


t 检验和置信区间

导入包,准备存储结果的数据框:

import statsmodels.stats.api as sms
df_result=pd.DataFrame(columns=['Compare','Feature','t_statistic','p_value','df','CI_low','CI_up'])

计算每一个特征,每两个组的t检验结果:

for c in data.columns[1:]:
    print(c)
    x0 = data[data['variant'] == 0][c]
    x1 = data[data['variant'] == 1][c]
    x2 = data[data['variant'] == 2][c]
    cm01 = sms.CompareMeans(sms.DescrStatsW(x0), sms.DescrStatsW(x1))
    cm02 = sms.CompareMeans(sms.DescrStatsW(x0), sms.DescrStatsW(x2))
    cm12 = sms.CompareMeans(sms.DescrStatsW(x1), sms.DescrStatsW(x2))
    result01=list(cm01.ttest_ind(alternative='two-sided', usevar='pooled'))
    result02=list(cm02.ttest_ind(alternative='two-sided', usevar='pooled'))
    result12=list(cm12.ttest_ind(alternative='two-sided', usevar='pooled'))
    
    ci01=cm01.zconfint_diff(alpha=0.05, alternative='two-sided', usevar='pooled')
    ci02=cm02.zconfint_diff(alpha=0.05, alternative='two-sided', usevar='pooled')
    ci12=cm12.zconfint_diff(alpha=0.05, alternative='two-sided', usevar='pooled')
    
    df_result=df_result.append({'Compare':'Group 0vs.1','Feature':c,'t_statistic':result01[0],'p_value':result01[1],'df':result01[2],'CI_low':ci01[0],'CI_up':ci01[1]},ignore_index=True)
    df_result=df_result.append({'Compare':'Group 0vs.2','Feature':c,'t_statistic':result02[0],'p_value':result02[1],'df':result02[2],'CI_low':ci02[0],'CI_up':ci02[1]},ignore_index=True)
    df_result=df_result.append({'Compare':'Group 1vs.2','Feature':c,'t_statistic':result12[0],'p_value':result12[1],'df':result12[2],'CI_low':ci12[0],'CI_up':ci12[1]},ignore_index=True) 

查看结果表:

df_result.groupby(['Feature','Compare']).sum().unstack().loc[data.columns[1:].to_list(),:]    #.style.highlight_between(left=0, right=0.05, subset=['p_value'])

 可以看到上表,将每对组别的对比,所有的特征的t统计量,P值,自由度df,CI上下界都打印出来了。

我们对于t检验进行查看,p值小于0.05时候,说明两组的该变量具有显著性差异。我们来找一下哪些p值小于0.05:

df=df_result.groupby(['Feature','Compare']).sum().unstack().loc[data.columns[1:].to_list(),:]['p_value']
df.where(df<0.05)

 

 可以看到,只有两个变量的p值是小于0.05的。如上表所示,在比较组 1 和组 2 时,仅针对两个变量 age 和 followSum 拒绝原始假设。这意味着组 1 和组 2 中的年龄和 followSum 这两个特征之间在显著性为 0.05 水平下存在显著差异。

但是,age表示年龄,followSum表示用户在实验前一天关注的人的个数,这两个变量不应该和处理方式有关系。

画出CI图对比:

df_CI=df_result.groupby(['Feature','Compare']).sum().unstack().loc[data.columns[1:].to_list(),:][['CI_low','CI_up']].stack().reset_index()
dis_cols = 4 ;     dis_rows = len(data.columns)
plt.figure(figsize=(3 * dis_cols, 3 * dis_rows),dpi=256)
colors=['orange','blue','green']
for f,feature in enumerate(df_CI['Feature'].unique()):
    df_CI_f=df_CI[df_CI['Feature']==feature].set_index('Compare').drop(columns='Feature')
    #print(df_CI_f)
    ax=plt.subplot(dis_rows,dis_cols,f+1)
    for i,c in enumerate(df_CI_f.index):
        plt.plot(df_CI_f.loc[c,:].to_numpy(),(i,i),'o-',color=colors[i],label=c)
    plt.xlabel((f'CI of {feature}'),fontsize=10)
    plt.yticks([])
    plt.legend(loc="upper right")
plt.tight_layout()
plt.show()

与上述t检验的结果一致,第一组和第二组的年龄和跟随总和两个特征变量显著不同。

这个结果令人惊讶,因为用户的年龄和用户在实验前一天关注的人的个数不应该受到处理的影响,这应该是由于没有保证样本抽样的随机性造成的。


结论:

相比之下,真正应该受到处理影响的分享、点击、评论和点赞的数量并没有显着差异,因此我认为这三个群体在用户行为方面的处理没有显着差异。也就是说,为基于应用程序的共享链接提供预览不会显著影响链接的点击率。


创作不易,看官觉得写得还不错的话点个关注和赞吧,本人会持续更新python数据分析领域的代码文章~(需要定制代码可私信)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阡之尘埃

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值