原文:
towardsdatascience.com/my-easy-guide-to-pre-vs-post-treatment-tests-0206f56f83a4
https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/d611be4a1ec84393c029c8772a812efa.png
由Towfiqu barbhuiya在Unsplash上的照片
引言
我将以说明开始这篇帖子,那就是 A/B 测试对我来说从来都不是一项强项。“好吧,但如果你要写一篇关于它的帖子呢?”,你可能这么想。
事实是,我不得不学习和尽可能多地了解这个主题,以便在工作中快速而顺利地执行这些测试。好几个月来,我都在避免这些测试,因为我找不到任何直接与我理解产生共鸣的内容。
我阅读了许多关于 A/B 测试的概念性帖子,其中一些非常针对市场营销专业人士,因此涵盖了更多关于正确获取样本大小和测试期的任务。但是,当涉及到**前后测试(也称为前后测试,我们可以说是 A/B 测试的一种)时,知识就更加难以找到。
因此,这篇帖子可能是所有寻求一个简单教程来执行这种测试的 Python 用户的一个很好的前后测试(或前后测试)的介绍。
让我们深入探讨。
概念
前后测试就像 A/B 测试。它比较两组不同的数据,以确定它们是否不同,以及这种差异有多大。前后测试仅增加了时间因素。它还将比较测试组和控制组,以及它们在应用处理之前后的表现。
前后测试比较了干预前后的某种状态。
我认为,关于前后测试的文章不多,其中一个原因可能是因为设计这种测试和准备相关数据并不总是那么简单。
首先,我们需要将我们的数据分为控制样本和处理样本。控制样本将是不改变任何事物的观察结果。它们将保持在整个测试期间做它们的事情。另一方面,处理组是接受干预的那一组。
让我们用一个例子来说明:一家杂货连锁店观察到某些咖啡品牌的销量激增,并想测试如果他们将这些表现最好的新品牌的货架数量加倍,是否会增加销量。为了实现这一点,他们可以随机选择一些商店作为处理组并做出改变。
在干预一段时间后,企业将对两组的结果进行评估,以确定新的设计是否表现更好。
总结
对照组:咖啡区没有变化的商店
测试组:重新设计咖啡区的商店
干预前时期:干预前的时期。这个时期的大小必须考虑到业务的季节性以及任何可能影响结果的其他方面,如销售、促销、假日和周末。
干预后时期:干预后的时期。确定时期大小的情况相同。
数据集
在我们的案例中,我们将创建一个数据集来模拟干预前后的情况。
让我们导入一些模块。
importnumpyasnpimportpandasaspdimportscipy.statsasscsimportmatplotlib.pyplotaspltimportseabornassnsimportpingouinaspg接下来,我们创建以下图 1 所示的数据集。创建它的代码可以在这个 Git Hub 仓库中找到,包括整个代码。
变量
dt:日期(从 2024-01-01 到 2024-01-30)
store_id:1 到 1000
组别:对照组和治疗组
sales:每日每家商店的销售。
https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/c40c136b4a86d4f1dfceaebfe776e6fb.png
图 1:数据集已创建。图片由作者提供。
让我们检查每个组中有多少家商店。
# Check how many stores in each groupdf.groupby('group').store_id.nunique()---[OUT]group Control503Treatment497Name:store_id,dtype:int64很好。几乎 50/50 的分割。接下来,让我们看看这是否足够用于我们的测试。
样本大小
进行前后测试,就像 A/B 测试一样,需要我们检查结果是否可以推断到更大的群体,而不是仅仅出于偶然。因此,检查所需样本的大小以捕捉期望的效果是很重要的。
对于每种测试,都有一个可以针对样本大小进行的计算。这种计算基于测试的效力。现在,为了解释什么是效力,我需要稍微回顾一下,并提醒我们几个其他概念。
当我们在统计学中进行均值测试时,这实际上就是一个假设测试。所以每个假设测试都使用一个零假设Ho(一切保持不变)和一个备择假设Ha(当某事发生变化)。如果我不能证明备择假设是有效的,我会继续接受我所拥有的作为真理。当我接受一个错误的零假设时,这是一个第二类错误。如果我接受一个错误的备择假设,那是一个第一类错误。
回到我们的主题,对于前后测试,Ho表示干预没有效果,而Ha表示干预有效。由于我们正在测试治疗的有效性,我想确保我不会在没有一定程度的信心的情况下接受它有效。这正是效力测试给我们带来的。
力量计算为[1 – 第二类错误]。因此,我正在移除错误的概率。
将其翻译成文字,我正在从整个概率空间(100%)中移除我会接受一个虚假的治疗效果的几率。
下面的代码计算我们需要观察到的中等大小的效果所需的观测数。
fromstatsmodels.stats.powerimportTTestIndPower# Parameter for the power analysiseffect=0.2# effect size must be positivealpha=0.05power=0.8# Perform power analysispwr=TTestIndPower()result=pwr.solve_power(effect,power=power,nobs1=None,ratio=1,alpha=alpha)print(result)---[OUT]393.4056989990335由于我们每个组大约有 500 家商店,所以我们有足够的覆盖范围。
准备数据
让我们为测试准备数据输入。
首先,我们将 Pre 和 Post 时期之间的截止日期设置为2024-01-15。在这个例子中,这是任意的,只是为了举例。接下来,我们按store_id、group和前后分组数据,取每一天的平均值。然后我们转置值并添加一列来计算post - pre期间的差异。
# split between Pre and Post periodsdf['after']=np.where(df['dt']>'2024-01-15',1,0)# pre_post datadf_pre_post=(df# dataset.groupby(['store_id','group','after'])# groupings.sales.mean()# calculate sales means.reset_index().pivot(index=['store_id','group'],columns='after',values='sales')# pivot the data to put pre and post in columns.reset_index().rename(columns={0:'pre',1:'post'})# rename)# create col difference post-predf_pre_post=df_pre_post.assign(dif_pp=df_pre_post.post-df_pre_post.pre)这里是结果表格。
https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/5476497f4e284041228e1a5369f02cb4.png
图 2:准备测试输入的数据。图片由作者提供。
每次我们进行前后测试时,输入表应该大致看起来是这样的。
如果我们想开始获取一些见解,我们可以。
# Pre and Post periods meansdf_pre_post.groupby('group')[['pre','post']].mean()https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/5b671c62e7798af3663727bf9ce262cc.png
前后对比。图片由作者提供。
我们可以看到,治疗组在后期有更高的增加。如果我们想可视化这一点,我喜欢的两个图表如下。
# Visualizationplot=df.assign(after=np.where(df['dt']>'2024-01-15',1,0))sns.stripplot(x='group',y='sales',hue='after',dodge=True,data=plot);# BoxenPlotsns.boxenplot(x='group',y='sales',hue='after',data=plot);https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/42117f6ad00ee62b13525034e05069a5.png
治疗组显示后期的中位数更高且更紧凑。图片由作者提供。
再次强调,我们可以注意到控制组的分布几乎相同,但治疗组的均值更高,且分布更集中于其周围。此外,箱线图显示分布接近正态性。
因此,我们结束本节内容。让我们进入测试本身。
前后测试
Levene 和 T 测试
对于整个练习,我们的显著性水平为 5%。
我们从 Levene 测试开始,该测试检查样本的方差是否相等。这个测试对于确定下一步至关重要。因此,创建一个简单的函数并运行它。
# Checking for equal variances of the samples (Pre and Post)# This can be done with Levene's test.pg.homoscedasticity(df_pre_post,dv='dif_pp',group='group',method='levene',alpha=0.05)---[OUT]:W pval equal_var levene18.7010.000017False接下来,我们应该检查组间均值差异是否具有统计学意义。对于只有两组的 Pre-Post 测试,我们可以使用:
如果分布是正常的,则使用两个样本配对 T 测试
如果不正常,则使用 Wilcoxon 测试
让我们测试数据是否为正态分布。
# Let's test for normality of the distributions of Pre and Post periods.# Ho = Data is normally distributedprint(scs.shapiro(df_pre_post.query('group == "Control"')['pre']))[OUT]:ShapiroResult(statistic=0.9967775344848633,pvalue=0.41802579164505005)print(scs.shapiro(df_pre_post.query('group == "Control"')['post']))[OUT]:ShapiroResult(statistic=0.9979024529457092,pvalue=0.7954561114311218)print(scs.shapiro(df_pre_post.query('group == "Treatment"')['pre']))[OUT]:ShapiroResult(statistic=0.9956731796264648,pvalue=0.1869998276233673)print(scs.shapiro(df_pre_post.query('group == "Treatment"')['post']))[OUT]:ShapiroResult(statistic=0.9935430884361267,pvalue=0.032079797238111496)最后一个——治疗组的后期——p 值为 0.03。它足够接近 alpha 值,但不是正态分布。我们可以使用 Wilcoxon 测试来检查均值差异。
# Paired 2 sample test# This is a test for the null hypothesis that two related or repeated samples have identical average (expected) values.print(scs.ttest_rel(df_pre_post.query('group == "Control"')['pre'],df_pre_post.query('group == "Control"')['post']))print(scs.ttest_rel(df_pre_post.query('group == "Treatment"')['pre'],df_pre_post.query('group == "Treatment"')['post']))# Running Wilcoxon for the Not Normal sampleprint(scs.wilcoxon(df_pre_post.query('group == "Treatment"')['pre'],df_pre_post.query('group == "Treatment"')['post']))TtestResult(statistic=-0.9901946209840918,pvalue=0.3225560292205154,df=502)TtestResult(statistic=-14.576067836748276,pvalue=2.5969980216983843e-40,df=496)WilcoxonResult(statistic=21562.0,pvalue=2.544753810592371e-36)如您所见,我们也对非正态样本使用了 T 测试,但它非常接近正态性,因此结果非常相似。
结果:
控制组的均值差异不具有统计学意义(p 值 = 0.32)
治疗组的均值差异具有统计学意义(p 值 = 0.00000)
Games-Howell
既然我们知道存在差异且具有统计学意义,让我们量化它。
我们可以使用 Tukey 检验来检测方差是否相等,或者使用Games-Howell检验来处理方差不等的情况(我们的情况)。
- 双重差分法(DiD):由于测试组和对照组之间存在差异,以及前后之间存在差异,如果我们想隔离真实的变化效应,我们可以计算前后差异,然后比较这两个样本(对照组和治疗组)之间的差异。
# Performing Games-Howell Testpg.pairwise_gameshowell(data=df_pre_post,dv='dif_pp',between='group').round(2)https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/bb5509e9a6d0718689b98ca44cc19469.png
测试前后的结果。图片由作者提供。
就这样,我们得到了最终结果。由于 p 值低于我们的阈值 5%,因此结果是统计显著的。两组均值之间的差异是 3.65,效果强烈(hedges > 0.5)。
置信区间
https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/cbffd248ec76935f878f46311dec0134.png
图片由Dose Juice在Unsplash提供。
我们有两种方法来检查测试的置信区间。最简单的方法是使用scipy。我们从结果中获取diff和se,并使用 95%的置信度计算区间。在这里,我们观察到两组均值之间的差异可能在 2.80 美元到 4.50 美元之间。
# create 95% confidence interval for the Testscs.norm(loc=3.65,scale=0.43).interval(confidence=0.95)(2.8072154866477765,4.492784513352223)另一种方法是多次模拟这些数据,以确保我们的结果是稳健的。我们可以通过取样本、计算均值并将结果附加到数据框中来对数据进行 N 次自助法(bootstrap)。这将使我们能够真正看到均值之间的差异。
我使用 400 个样本,因为这是计算出的所需样本量(394)。
# Let's simulate 5000 times a Control versus a Test sample.# We can Bootstrapboot_means=[]# Simulation N timesN=5_000foriinrange(N):# take 400 stores from each groupcontrol=df_pre_post.query('group == "Control"').sample(n=400,replace=True)test=df_pre_post.query('group == "Treatment"').sample(n=400,replace=True)final_data=pd.concat([control,test])# shuffleboot_sample=(final_data.groupby('group')['dif_pp'].mean())#appendboot_means.append(boot_sample)# To Dataframeboot_means=pd.DataFrame(boot_means)# kde plotboot_means.plot(kind='kde')plt.title('Difference of Means Control vs Test');https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/647b65452e6fce48bcbbb28eec940c4d.png
测试结果。图片由作者提供。
治疗组的表现明显更好。我们还可以查看差异的分布。
# create a new column, diff, which is the difference between the two variants, scaled by the control groupboot_means['test_control_diff']=(boot_means['Treatment']-boot_means['Control'])# plot the bootstrap sample differenceax=boot_means['test_control_diff'].plot(kind='kde')ax.set_xlabel("$ diff in means")plt.title('Distribution of the Difference in Means');https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/5634392ac9f3b175a4167c0707cd3542.png
均值差异分布。图片由作者提供。
组间差异集中在约 3.6 美元,正如我们之前看到的。
最后,如果我们计算上图分布的置信区间,结果几乎与 Games-Howell 检验相同。
# Mean and Std.b_mu=boot_means.test_control_diff.mean()b_std=boot_means.test_control_diff.std()# create 95% confidence interval for the Testscs.norm(loc=b_mu,scale=b_std).interval(confidence=0.95)(2.7251897754505348,4.587888504398052)结果符合我们的预期。在 95%的置信水平下,两组均值之间的差异在 2.72 美元到 4.58 美元之间。
另一种方法——正态分布
还有另一种进行这种测试的方法,使用正态分布。正如我们在前几节的模拟中看到的,当我们多次重复 Bootstrap 过程时,均值分布将收敛到正态分布,这是根据中心极限定理。
因此,我们可以在进行这些模拟时利用这一点。如果我们收集样本的均值和标准差,然后使用这些参数生成正态分布,本质上我们就是在做同样的事情。
# Collecting Mean and Standard Deviation of the samplessample_means=(df_pre_post.groupby('group').agg({'dif_pp':['mean','std']}).reset_index())# Rename colssample_means.columns=['group','mean','std']# Get Mean and Stdmean_control=sample_means.query('group == "Control"')['mean'].values[0]std_control=sample_means.query('group == "Control"')['std'].values[0]mean_treatment=sample_means.query('group == "Treatment"')['mean'].values[0]std_treatment=sample_means.query('group == "Treatment"')['std'].values[0]N=10_000# Plot dataplot=pd.DataFrame({'group':np.repeat(['Control','Treatment'],N),'vals':np.concatenate([np.random.normal(loc=mean_control,scale=std_control,size=N),np.random.normal(loc=mean_treatment,scale=std_treatment,size=N)])})# Plotsns.kdeplot(data=plot,x='vals',hue='group');代码的结果将显示为以下图形。再次,我们看到治疗组表现更好。
https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/a053ccff8fcf78a75a09632febc94d69.png
组的正态分布。图片由作者提供。
接下来,我们将根据样本差异生成正态分布。我们收集:
样本大小
治疗均值与控制均值的差异
以及根据此公式计算的标准误差:
https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/53faa3c329d2d784e94bdc71c354b828.png
N=10_000# Plot parametersnA=df_pre_post.query('group == "Control"').shape[0]nB=df_pre_post.query('group == "Treatment"').shape[0]dif_mu=df_pre_post.groupby('group').dif_pp.mean()[1]-df_pre_post.groupby('group').dif_pp.mean()[0]# [mean diff Treatment] - [mean diff Control]dif_std=np.sqrt(((std_control**2)/nA)+((std_treatment**2)/nB))# Plotsns.kdeplot(np.random.normal(loc=dif_mu,scale=dif_std,size=N));print('95% Confidence Interval')scs.norm(loc=dif_mu,scale=dif_std).interval(confidence=0.95)https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/30db2890a241696174c13f0450316d32.png
前后测试的 95%置信区间。图片由作者提供。
在你离开之前
这篇帖子旨在作为前后测试的入门教程。我希望你能从中获得最大收益,并开始创建你自己的测试。
在我创建的这个数据集中,我确定差异大约是 3 美元,并且我能够在统计测试中确认这一差异,这表明我可能正在正确地学习如何进行一项坚实的测试。
这个工具应用广泛,不仅像人们可能认为的那样适用于在线业务,而且适用于任何业务,从杂货业到健康行业,安全,等等。
你所需要的只是正确分组,你将捕获数据的时间框架,样本大小以确定是否可以捕捉到效果,就是这样。
如果你喜欢这个内容,请关注我以获取更多。我还在领英上。让我们建立联系。
古斯塔沃·桑托斯 – Medium
这是包含所有代码的 GitHub 仓库链接。
GitHub – gurezende/Before-and-After-Testing: 如何执行前后统计测试。
参考文献
功率分析、统计显著性和效应量
使用 R 和 Python 进行营销分析的前后 A/B 测试统计
scipy.stats.ttest_rel – SciPy v1.13.1 手册
使用 R 和 Python 进行营销分析的前后 A/B 测试统计
Python 中 A/B 测试的实用指南
配对 t 检验
前置-后置测试指南