我们在日常数据相关的工作中,常常需要去推断结果Y是否由原因X造成。“相关性并不意味着因果关系”,相信做数据分析的同学都明白这个道理。有一个喜闻乐见的例子:夏天海岸,鲨鱼袭击事件较其他季节多20%,同时冰淇淋销量比其他季节多100%,冰淇淋销量和鲨鱼袭击事件成正相关关系,得出结论销售冰淇凌会导致鲨鱼袭击。这实际上是违背常识的。
要探究上述问题,最好的方法可能是使用随机流量的AB实验,但是AB实验也存在一些局限性,在之前写过的一篇文章《AB实验踩坑之路》中提到,有些情况下可能没办法控制想要测试的干扰变量。
所以,在不方便进行AB实验的时候,使用手边已有的历史数据进行推断和决策就变得很重要,这个时候可以用因果推断或者观察性研究来解决。当然在可以实验的情况下还是推荐AB实验的。
首先看我们拥有的数据条件是观测数据还是实验数据。观测数据和实验数据的差别在于我们研究的干扰变量(treatment)和目标变量(outcome)是否独立。常见的破坏他们独立性的因素包括混淆变量(confounder)和选择偏差(selection bias)。举例在上面冰淇凌和鲨鱼的例子中,存在季节因素同时影响了treatment和outcome,那这里季节因素就是混淆变量。
比如我要进行分析留存率的影响因素,比如看连败是否影响留存进而确定要不要加温暖局,treatment是干扰变量即连败,outcome是DAU、次留等。这里是否存在混淆变量?是的,比如玩家活跃程度、玩家水平等因素都可能是混淆变量。因为我们现有的只有历史观测数据,不满足随机分组,所以不能直接按照treatment来区分实验组和对照组,不能直接比较他们的差值。
常见的因果推断方法有PSM、Uplift Model等,首先我们的数据非实验数据,不满足Uplift Model需要的假设。其次,PSM也是常用的方法,但是PSM有很强的假设:没有遗漏的混淆因子,这个assumption很容易被挑战,在我们的留存率分析中,有太多的因素影响到outcome和我们要分析的treatment了。考虑到我们的数据本身是时序面板数据,可以考虑使用双重差分(Differences In Difference,DiD)。 DiD的大概原理如下图,按照时间维度分为intervention之前和之后,蓝线是实验组,红线是对照组。DiD有个很重要的假设是趋势平行,在施加treatment之前,两组人的Outcome的变化趋势是一样的。如果没有treatment,这个趋势也会继续保持下去,正如图中虚线所示。那施加了treatment之后,下图中多出来的这一部分C就认为是treatment effect。
理想情况下DiD
假设1和假设2通常会满足,假设3需要手动验证,比如我们构造的实验组和对照组,在某个时间节点可以观察treatment前7天,实验组和对照组的趋势是否平行,如果平行则认为满足平行趋势假设,即实验组和对照组是同质的。如果不平行,需要重新构造实验组和对照组,或者使用PSM方法来构造同质用户。
3. 目标变量outcome:次留
首先要看treatment日期前两组用户的留存曲线是否平行,即实验组和对照组是同质的,才可以观察treatment日期之后两组留存曲线的相对关系是否发生了改变。
历史上取了4个日期的数据,实验组和对照组因为是用的11月17日的用户筛选出来,在其他日期部分用户没有登陆,所以留存率是小于1的。可以看到在11月15日和11月16日两组用户曲线的Gap基本相同,在11月17号实验组用户经历了连败后,曲线之间的Gap大大缩小。
treatment日期前两天和后一天入组用户活跃比例
我们首先得到第一层差分,即各组内11月16日和11月18日之间的活跃情况的差异。然后我们计算第二层差分,即两个差异的差异,此计算结果就是我们的处理效用。
第一层差分表示组内两天活跃情况的差异,公式如下:
第二层差分表示两组两天活跃情况的差异,公式如下:
我们怎么用公式去拟合数据并求解处理效用呢?我们设置了两个哑变量(dummy variable)treated_group和after_treated,分别代表是否实验组、是否已经treated即11月18日。公式如下:
数据样例如下:
roleid | is_act | thedate | treated_group | after_treated |
---|---|---|---|---|
0 | 1 | 20211116 | 1 | 0 |
1 | 1 | 20211116 | 1 | 0 |
2 | 1 | 20211116 | 1 | 0 |
3 | 1 | 20211118 | 1 | 1 |
4 | 1 | 20211118 | 1 | 1 |
... | ... | ... | ... | ... |
拟合使用python statsmodels库进行拟合:
fit_res = smf.ols('is_act ~ treated_group*after_treated', data=combined_df).fit()
拟合结果是:
coef | std err | t | P>|t| | [0.025 | 0.975] | |
---|---|---|---|---|---|---|
Intercept | 0.9371 | 0.001 | 790.154 | 0.000 | 0.935 | 0.939 |
treated_group | 0.0295 | 0.002 | 14.668 | 0.000 | 0.026 | 0.033 |
after_treated | -0.0162 | 0.002 | -9.636 | 0.000 | -0.019 | -0.013 |
treated_group:after_treated | -0.0195 | 0.003 | -6.859 | 0.000 | -0.025 | -0.014 |
这里的treated_group:after_treated就代表处理效用,系数是负的,说明treatment给目标变量outcome的影响是负的。P值远小于0.05说明结果是显著的。所以我们可以得出结论,连败会对玩家的活跃留存造成负面影响。
在11月15日和11月16日,实验组和对照组曲线从图上看是接近平行的,但毕竟“目测”的方法没有说服力,有没有什么方法验证曲线趋势是平行的呢?我们也可以用OLS的方法来验证是否平行。
我们把第二个哑变量设置成是否是11月16日,然后对11月15日和11月16日的数据进行过回归,可以看到交互项的系数P值远大于0.05,说明没有呈现出显著性,满足平行趋势假设。
coef | std err | t | P>|t| | [0.025 | 0.975] | |
---|---|---|---|---|---|---|
Intercept | 0.9169 | 0.001 | 778.473 | 0.000 | 0.915 | 0.919 |
treated_group | 0.0267 | 0.002 | 13.370 | 0.000 | 0.023 | 0.031 |
next_day | 0.0202 | 0.002 | 12.132 | 0.000 | 0.017 | 0.023 |
treated_group:next_day | 0.0028 | 0.003 | 0.989 | 0.322 | -0.003 | 0.008 |
看更长的时间维度,可以看到11月10日到11月16日实验组和对照组基本都是平行。或者使用安慰剂检验的方法,看实验组和对照组在其他一个时间段中,DiD交互项系数的结果应该是不显著的。
更长时间范围用户活跃比例
至此,我们完成一个简单的双重差分DiD分析。方法也比较基础,希望能够起到抛砖引玉的作用,如果有错误的地方,还麻烦指点,一起讨论。
总结来说,观测数据也有很多的利用价值,若可以通过数据科学的方法挖掘出可用的信息,也可以在实验前检测一些初步的想法,使实验更加高效。