【因果推断python】57_The Difference-in-Differences 3

目录

3) Enlightenment: A Flexible Functional Form

Key Concepts


3) Enlightenment: A Flexible Functional Form

有好消息也有坏消息。首先是好消息:我们已经发现问题与函数形式有关,因此我们可以通过修正函数形式来解决这个问题。也就是说,我们已经反复说过,TWFE 中的这种特定偏差来自时间异质效应。出现这种情况的原因有很多,其中之一是效应需要一段时间才能成熟(例如:营销活动可能需要 10 天才能充分发挥潜力)。换句话说,传统 TWFE 的函数形式不够灵活,无法捕捉这种异质性,从而导致我们讨论过的偏差。与大多数情况下一样,了解问题已经为找到解决方案迈出了一大步。

在上一节的最后,我们看到了仅仅考虑到每个时间段相对于治疗的不同效果(事件研究设计)是不够的。尽管这并不奏效,但背后的意图是好的。它确实使模型更加灵活,但并没有解决问题。我们需要想出另一种方法,让模型比这更灵活。

为此,让我们回到最初的例子,当时我们正试图模拟推出新功能(处理)所带来的增量安装次数。我们看到,简单的 TWFE 模型在这里行不通:Installs_{it}=\tau D_{it}+\gamma_i+\theta_t+e_{it}

不仅如此,我们还知道它行不通,因为它限制性太强。它迫使影响必须是相同的\tau_{it}=\tau \forall i,t,也就是说,它迫使时间同质。如果这就是问题所在,那么一个简单的解决方法就是允许每个时间和单位都有不同的影响。Installs_{it}=\sum_{i=0}^N\sum_{t=0}^T\tau_{it}D_{it}+\gamma_i+\theta_t+e_{it}

这相当于运行下面的公式:

installs ~ treat:C(unit):C(date) + C(unit) + C(date)

不幸的是,我们无法拟合这个公式。它的参数会比我们的数据点多。由于日期和单位是相互影响的,因此每个单位在每个时间段 T*N 都会有一个治疗效果参数。但这正是我们所拥有的样本数!OLS 在这里根本无法运行。

好了,现在我们需要减少模型中治疗效果参数的数量。要做到这一点,我们可以考虑以某种方式对单位进行分组。如果我们稍加思考,就会发现一种非常自然的分组方法:按队列分组!我们知道,随着时间的推移,整个组群的效应遵循相同的模式。因此,对上面那个不切实际的模型的一个自然改进就是允许效应按组群而不是单位变化:

Installs_{it}=\sum_{g=0}^G\sum_{t=0}^T\tau_{gt}D_{it}+\gamma_i+\theta_t+e_{it}

其中,G 表示队列总数,g 表示每个队列。由于 G 通常远小于 N,因此该模型的治疗效果参数(T*G)数量更为合理。

formula = f"""installs ~ treat:C(cohort):C(date) + C(unit) + C(date)"""

# for nicer plots latter on
df_heter_str = df_heter.astype({"cohort": str, "date":str})

twfe_model = smf.ols(formula, data=df_heter_str).fit()

要想知道这个模型是否有效,我们可以通过强制每个人的 treat 为零来对 Y0 进行反事实预测。然后,我们可以用观察到的治疗结果 Y1 减去 \hat{Y}_0 来估计效果。让我们看看这是否与真实的 ATT 匹配。

df_pred = (df_heter_str
           .assign(**{"installs_hat_0": twfe_model.predict(df_heter_str.assign(**{"treat":0}))})
           .assign(**{"effect_hat": lambda d: d["installs"] - d["installs_hat_0"]}))

print("Number of param.:", len(twfe_model.params))
print("True Effect: ", df_pred.query("treat==1")["tau"].mean())
print("Pred. Effect: ", df_pred.query("treat==1")["effect_hat"].mean())
Number of param.: 467
True Effect:  0.8544117647058823
Pred. Effect:  0.8544117647058977

确实如此!我们终于建立了一个足够灵活的模型来捕捉时间异质性,从而估算出了正确的治疗效果!我们可以做的另一件很酷的事情是按时间和队列提取估计效应并绘制它们。在这种情况下,因为我们知道数据是如何产生的,所以我们知道应该期待什么。也就是说,每个组群在治疗前的效果必须为零,治疗 10 天后效果为 1,治疗后 10 天内效果从零上升到 1。

effects = (twfe_model.params[twfe_model.params.index.str.contains("treat")]
           .reset_index()
           .rename(columns={0:"param"})
           .assign(cohort=lambda d: d["index"].str.extract(r'C\(cohort\)\[(.*)\]:'))
           .assign(date=lambda d: d["index"].str.extract(r':C\(date\)\[(.*)\]'))
           .assign(date=lambda d: pd.to_datetime(d["date"]), cohort=lambda d: pd.to_datetime(d["cohort"])))

plt.figure(figsize=(10,4))
sns.lineplot(data=effects, x="date", y="param", hue="cohort")
plt.xticks(rotation=45)
plt.ylabel("Estimated Effect");

上图再次与我们的预期效果相吻合。它们完全符合我们上面描述的模式。

这已经非常不错了,但我们还可以做得更好。首先,请注意该模型有大量参数。由于我们的数据中有 100 个单位和大约 92 天,我们知道其中有 192 个参数是单位和时间效应。尽管如此,我们仍有 250 多个治疗效果参数。

如果我们假设治疗前的影响为零(没有预期),那么我们就可以从交互项中去掉治疗前的日期,从而减少参数的数量。Installs_{it}=\sum_{g=0}^G\sum_{t=g}^T\tau_{gt}D_{it}+\gamma_i+\theta_t+e_{it}

此外,我们还可以将对照组从交互作用中剔除,因为在治疗前,对照组的影响始终为零Installs_{it}=\sum_{G=q}^g\sum_{t=g}^T\tau_{gt}D_{it}+\gamma_i+\theta_t+e_{it} 其中 g<q 之前的队列被定义为对照队列。

不过请注意,用公式来实现这一点比较麻烦,所以我们必须先做一些特征工程。也就是说,我们将手工创建队列虚拟变量,其中一列在队列为 2021-06-01 时为 1,否则为 0;另一列在队列为 2021-07-15 时为 1,否则为 0。此外,我们还将为 2021-06-01 群组创建一个日期列,将该群组日期之前的所有日期整理为一个控制类别。我们还可以对 2021-07-15 组群的日期进行类似处理。代码如下

def feature_eng(df):
    return (
        df
        .assign(date_0601 = np.where(df["date"]>="2021-06-01", df["date"], "control"),
                date_0715 = np.where(df["date"]>="2021-07-15", df["date"], "control"),)
        .assign(cohort_0601 = (df["cohort"]=="2021-06-01").astype(float),
                cohort_0715 = (df["cohort"]=="2021-07-15").astype(float))
    )

formula = f"""installs ~ treat:cohort_0601:C(date_0601) 
                       + treat:cohort_0715:C(date_0715) 
                       + C(unit) + C(date)"""

twfe_model = smf.ols(formula, data=df_heter_str.pipe(feature_eng)).fit()

如果我们现在像以前一样进行反事实预测,我们可以看到估计效果仍然与真实效果完全吻合。这样做的好处是,我们的模型更加简单,只有大约 80 个治疗效果参数(请记住,其中 192 个参数是时间和单位固定效应)。

df_pred = (df_heter
           .assign(**{"installs_hat_0": twfe_model.predict(df_heter_str
                                                           .pipe(feature_eng)
                                                           .assign(**{"treat":0}))})
           .assign(**{"effect_hat": lambda d: d["installs"] - d["installs_hat_0"]}))


print(len(twfe_model.params))
print("True Effect: ", df_pred.query("treat==1")["tau"].mean())
print("Pred Effect: ", df_pred.query("treat==1")["effect_hat"].mean())
271
True Effect:  0.8544117647058823
Pred Effect:  0.8544117647059067

在绘制治疗效果参数图时,我们可以看到我们是如何从对照组群和治疗组群之前的日期中移除这些参数的。

effects = (twfe_model.params[twfe_model.params.index.str.contains("treat")]
           .reset_index()
           .rename(columns={0:"param"})
           .assign(cohort=lambda d: d["index"].str.extract(r':cohort_(.*):'),
                   date_0601=lambda d: d["index"].str.extract(r':C\(date_0601\)\[(.*)\]'),
                   date_0715=lambda d: d["index"].str.extract(r':C\(date_0715\)\[(.*)\]'))
           .assign(date=lambda d: pd.to_datetime(d["date_0601"].combine_first(d["date_0715"]), errors="coerce")))

           
plt.figure(figsize=(10,4))
sns.lineplot(data=effects.dropna(subset=["date"]), x="date", y="param", hue="cohort")
plt.xticks(rotation=45);

请注意,我们还可以更进一步,因为两个组群的效应遵循相同的形状。也就是说,我们可以限制模型,使两个组群的效果相同,只是随时间变化。为此,我们需要创建一列来表示治疗后的天数,就像在事件研究设计中一样。具体如下

days_after_treat=1(date>cohort)*(date - cohort)

然后,我们将它与治疗指标交互作用

installs ~ treat:C(days_after_treat) + C(unit) + C(date)

不过,我认为我们可以就此打住。不考虑队列异质性通常是个坏主意,因为治疗效果往往会随着日历时间而变化,而不仅仅是治疗后的时间。例如,可能在一段时间后,竞争对手复制了我们的功能,使其不再像以前那样具有强大的客户吸引力。在这种情况下,新功能对安装量的影响就会随着时间的推移而减弱。

除了显示随时间推移的效果外,我们还应该做的最后一件事就是绘制反事实图,看看它们是否处于感觉正确的位置。我知道这并不是对我们模型的科学验证,但相信我,这对我们的语气很有帮助。就是这样。

twfe_model_wrong = smf.ols("installs ~ treat + C(date) + C(unit)",
                           data=df_pred).fit()


df_pred = (df_pred
           .assign(**{"installs_hat_0_wrong": twfe_model_wrong.predict(df_pred.assign(**{"treat":0}))}))


plt.figure(figsize=(10,4))
sns.lineplot(
    data=(df_pred
          [(df_pred["cohort"].astype(str) > "2021-06-01") & (df_pred["date"].astype(str) >= "2021-06-01")]
          .groupby(["date"])["installs_hat_0"]
          .mean()
          .reset_index()),
    x="date",
    y = "installs_hat_0",
    ls="dotted",
    color="C3",
    label="counterfactual",
)

sns.lineplot(
    data=(df_pred
          .groupby(["cohort", "date"])["installs"]
          .mean()
          .reset_index()),
    x="date",
    y = "installs",
    hue="cohort",
    legend=None
)

plt.ylabel("Installs");

我们可以看到,反事实 Y0 预测值正好落在我们认为应该落在的地方,即非常接近对照组。这让我们感到非常欣慰。我们知道 TWFE 模型将治疗效果估计为 Y-\hat{Y}_0。也就是说,它只是将治疗队列的结果与反事实进行比较。既然反事实看起来没问题,我们就可以放心,治疗效果也可能没问题。

这是个好消息,但别以为我忘了答应过你的坏消息。问题是,虽然我们已经解决了 TWFE 的功能形式问题,但 DiD 和 TWFE 还有一个可以说是更大的问题,这与其独立性假设有关。

在使用 DiD 和 TWFE 时,我们经常会引用平行趋势假设,却没有真正思考过这一假设到底意味着什么。遗憾的是,平行趋势假设的限制性要比大多数人意识到的多得多,也不那么可信。不过,由于本章篇幅已经太长,我想就此结束也无妨,我们还可以享受一下 DiD 小胜的滋味。

Key Concepts

我认为可以说,我们终于不仅理解了 TWFE 的函数形式问题,而且还纠正了这个问题。我们追溯了问题的根源(时间异质性),并通过允许更大的灵活性来解决它。现在我们可以拿起饮料放松一下了,因为我们知道 TWFE 又可以安全地使用了。或者说?

我们永远不能忘记,TWFE(以及更广泛的 DiD)是函数形式和独立性假设的混合体。在本章中,我们只解决了函数形式的问题,但房间里还有一头大象:平行趋势假设。平行趋势是 DiD 所做的独立性假设。这是众所周知的。但我觉得我们并没有真正理解这一假设的含义。我们只是凭空引用它,好像这样就能证明它是正确的。不幸的是,平行趋势假设的要求远远超出了大多数人的认识。在接下来的章节中,我们将看到为什么会这样,以及我们能做些什么或是否能做些什么。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/744582.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

竞赛选题 python+大数据校园卡数据分析

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 基于yolov5的深度学习车牌识别系统实现 &#x1f947;学长这里给一个题目综合评分(每项满分5分) 难度系数&#xff1a;4分工作量&#xff1a;4分创新点&#xff1a;3分 该项目较为新颖&am…

短视频最佳时长:成都柏煜文化传媒有限公司

探索时间与内容之间的完美平衡 成都柏煜文化传媒有限公司 在数字媒体日益繁荣的今天&#xff0c;短视频已成为人们获取信息、娱乐休闲的重要形式。然而&#xff0c;关于短视频的最佳时长&#xff0c;一直是一个备受争议的话题。本文将探讨短视频时长的各种考量因素&#xff0…

基于MATLAB对线阵天线进行道尔夫—切比雪夫加权

相控阵天线——基于MATLAB对线阵进行道尔夫—切比雪夫加权 目录 前言 一、阵列天线的综合 二、道尔夫—切比雪夫综合 三、单元间距的改变对切比雪夫阵列方向图的影响 四、单元数的改变对切比雪夫阵列激励分布的影响 五、副瓣电平SLL对切比雪夫阵列激励幅度的影响 六、副…

深入理解Java中的Collectors(Stream流)

引言 在 Java 的 Stream API 中&#xff0c;Collectors 是一个非常强大的工具类&#xff0c;它提供了许多静态方法&#xff0c;用于将 Stream 的元素收集到集合、字符串或其他类型的结果中。使用 Collectors&#xff0c;我们可以轻松地进行数据聚合和转换操作。 文章目录 引言…

小区业主管理系统

摘 要 随着城市化进程的加速和人口的不断增加&#xff0c;小区的数量也在不断增加。小区作为城市居民居住的主要场所&#xff0c;其管理工作也变得越来越重要。传统的小区业主管理方式存在诸多问题&#xff0c;如信息传递不畅、业务处理效率低下等。因此&#xff0c;开发一个高…

Spring底层原理之FactoryBean Bean工厂 单例对象 多例对象

FactoryBean 在 Spring Framework 中&#xff0c;FactoryBean 是一个用于创建其他 Bean 实例的特殊工厂 Bean。它允许开发者自定义 Bean 的创建逻辑&#xff0c;从而更加灵活地管理和配置 Bean 的实例化过程。 FactoryBean 接口 FactoryBean 接口是 Spring 框架中的一个重要…

启动VMWare虚拟机报错

1. 无法打开内核设备“\\.\VMCIDev\VMX”: 操作成功完成。是否在安装 VMware Workstation 后重新引导? 模块“DevicePowerOn”启动失败。 未能启动虚拟机。 解决办法: 解决办法: 将 Ubuntu 64 位.vmx 找到vmci0.present"TRUE"这行改成 vmci0.present "FAL…

【AI编译器】triton学习:矩阵乘优化

Matrix Multiplication 主要内容&#xff1a; 块级矩阵乘法 多维指针算术 重新编排程序以提升L2缓存命 自动性能调整 Motivations 矩阵乘法是当今高性能计算系统的一个关键组件&#xff0c;在大多数情况下被用于构建硬件。由于该操作特别复杂&#xff0c;因此通常由软件提…

fail2ban自动屏蔽之jumpserver

fail2ban是一款实用软件&#xff0c;可以监视你的系统日志&#xff0c;然后匹配日志的错误信息&#xff08;正则式匹配&#xff09;执行相应的屏蔽动作。 jumpserver是一款开源堡垒机&#xff0c;其拥有一定的防护登录&#xff0c;也可以做登录限制&#xff0c;但是相对于防火…

湖南(用户画像)源点调研 适用于新产品开发的市场调研方法

湖南&#xff08;上市验证调研&#xff09;源点咨询认为&#xff1a;其实市场与用户研究的方法不管都什么花哨的名头&#xff0c;本质上只有两种&#xff1a;定量与定性。而对于新产品的开发最重要的就是掌握好定性的研究方法。 问&#xff1a;对于新产品开发我们面对的是什么…

js如何使得四舍五入的百分比之和为100%

在JavaScript中&#xff0c;如果你想要确保一组四舍五入后的百分比之和严格等于100%&#xff0c;那么你不能直接对每个百分比进行四舍五入&#xff0c;因为四舍五入会引入误差。但是&#xff0c;你可以采用一种策略&#xff0c;即先对所有的百分比进行常规的四舍五入&#xff0…

SNEC天合储能秀:全球首发多元场景一站式工商业储能融合解决方案

6月13日-15日&#xff0c;SNEC2024光伏与智慧能源展在上海隆重举行&#xff0c;来自全球95个国家和地区3000家国内外展商齐聚展会&#xff0c;5000行业专家共话产业发展。致力于成为全球光储智慧能源解决方案的领导者&#xff0c;天合光能&#xff08;展位号&#xff1a;7.2H-E…

线性和二次判别分析

线性判别分析 线性判别分析&#xff08;Linear Discriminant Analysis&#xff0c;LDA&#xff09;亦称 Fisher 判别分析。其基本思想是&#xff1a;将训练样本投影到低维超平面上&#xff0c;使得同类的样例尽可能近&#xff0c;不同类的样例尽可能远。在对新样本进行分类时&…

测试行业,你的未来路在何方?失业之外,暗藏的这个危机更可怕!

目前测试行业现状 近期飞书的大规模裁员&#xff0c;无疑为2024年伊始蒙上了一层阴影。再加上“共享员工”模式的兴起&#xff0c;对于身处互联网行业的从业者来说&#xff0c;无疑是雪上加霜。 此外&#xff0c;延续了2023年的情况&#xff0c;在求职平台如BOSS直聘上&#…

基于Java的宠物领养管理系统【附源码】

摘 要 近些年来&#xff0c;随着科技的飞速发展&#xff0c;互联网的普及逐渐延伸到各行各业中&#xff0c;给人们生活带来了十分的便利&#xff0c;宠物管理系统利用计算机网络实现信息化管理&#xff0c;使整个宠物领养的发展和服务水平有显著提升。 本文拟采用IDEA开发工具…

《编译原理》阅读笔记:p19-p24

《编译原理》学习第 4 天&#xff0c;p19-p24总结&#xff0c;总计 5 页。 一、技术总结 1.grouping of phases 这里谈到分组(group)&#xff0c;那么就会有一个疑问&#xff0c;分组的依据是什么&#xff1f;即根据什么来分组。 (1) front end & back end 编译器包含…

第三十一篇——大数据1:从四个特征把握大数据的本质

目录 一、背景介绍二、思路&方案三、过程1.思维导图2.文章中经典的句子理解3.学习之后对于投资市场的理解4.通过这篇文章结合我知道的东西我能想到什么&#xff1f; 四、总结五、升华 一、背景介绍 大数据的特征&#xff0c;如果我们没有一个清晰的边界以及明确的定位&…

如何找到合适的Python第三方库?

找合适的Python库其实很简单&#xff0c;按照以下三步法&#xff0c;你能找到90%的Python库。 1、百度谷歌搜索 明确自己的需求&#xff0c;用Python来干什么&#xff0c;力求简短明了。比如定位“数据分析”&#xff0c;然后去搜索关键词【Python数据分析第三方库】&#xf…

第二证券:港交所上市24周年 市值增长38倍

香港交易及结算所有限公司&#xff08;下称香港交易所&#xff09;于近来举办庆典活动&#xff0c;庆祝上市24周年。 据介绍&#xff0c;自2000年起&#xff0c;香港交易所逐步发展成为全球领先的商场营运机构&#xff0c;也成为连接中国内地与国际商场的主要桥梁。到2024年6月…

解决IMX6ULL GPIO扩展板PWM7/8中的pwm0/period后卡死问题

前言 本篇文章主要是记录解决百问网论坛上面设置 IMX6ULL GPIO扩展板PWM7/8中的pwm0/period后卡死问题&#xff0c;如下图&#xff1a; 一、查看原理图&#xff0c;找出对应引脚 在这里我们如何确定哪个扩展口中的引脚输出PWM波呢&#xff1f;我们可以通过查看原理图。 查看…