【Python数据分析案例】(三)—— 链接预览对链接点击率影响分析(A/B test)
网盘截屏
▶全部源码和数据,请点击“支付下载”获取!支付后无网盘链接,请联系客服QQ:3345172409或1919588043(微信同号)☺
导读
所谓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检验的结果一致,第一组和第二组的年龄和跟随总和两个特征变量显著不同。
这个结果令人惊讶,因为用户的年龄和用户在实验前一天关注的人的个数不应该受到处理的影响,这应该是由于没有保证样本抽样的随机性造成的。
结论:
相比之下,真正应该受到处理影响的分享、点击、评论和点赞的数量并没有显着差异,因此我认为这三个群体在用户行为方面的处理没有显着差异。也就是说,为基于应用程序的共享链接提供预览不会显著影响链接的点击率。