👇 连享会 · 推文导航 | www.lianxh.cn
连享会:2026五一论文班 · 线上时间:5月2-4日嘉宾:郭士祺 (上海交通大学)、戚树森 (厦门大学)、李学恒 (中山大学)咨询:王老师 18903405450(微信)

本次课程框架为:因果证据链的构建 → 在顶刊论文中感受和校准这套因果证据链判断 → AI Agent 辅助实现完成
从「会跑回归」到「会做研究设计」。 从 RCT 出发,覆盖 DID、固定效应、工具变量的识别逻辑与论证策略,重点不在方法本身,而在于如何围绕一个研究问题构建完整的因果证据链——这是 AI 替代不了、也最值得花时间打磨的核心能力。
顶刊论文,原作者精讲。 戚树森老师亲自讲解自己参与完成的 Review of Finance 论文,覆盖选题缘起、识别策略设计、审稿意见的回应过程——不只是呈现结果,而是还原论文背后真实的决策过程。
用 AI Agent 真正解放执行环节。 学完这门课,你将掌握两套可以直接上手的 AI Agent 工作流——文献综述和论文修改——并理解如何把自己的研究方法论写成 Skill,让 Agent 按照你的标准自动执行,而不是每次重新解释。
作者: 李俊奇 (中南财经政法大学)邮箱:jqlee2003@126.com
说明:本文整合了 EconML 用户手册等多方资源中有关动态双重机器学习的内容,EconML 官方网址: -Link-
在现实研究中,我们经常面临如下问题:
机器学习方法虽强大,但直接用于因果推断容易产生偏误。双重机器学习(Double Machine Learning, DML)提供了一种实现稳健因果估计的新思路:使用正交化技术消除混杂变量对核心参数估计的偏差,通过两阶段建模来分离核心参数与冗余参数,最终实现无偏估计。
然而,许多实际问题具有时间动态性,处理效应可能随时间变化,仅仅使用双重机器学习是不够的。
例如,很多现实问题(医疗、农业、市场营销)中,干预(treatment)是动态多次的:
这些场景下,因果效应的估计必须考虑多阶段干预下的动态机制,即每一期的处理不仅对当期结果产生影响,还会影响后续的状态变量与处理选择。因此,我们面临的是一个典型的动态因果推断问题。
(Lewis and Syrgkanis, 2021) 结合 Double Machine Learning(DML)与 Structural Nested Mean Models(SNMMs)中 g-Estimation 框架,提出一套可兼容任意机器学习模型、具有偏差修正能力的动态因果推断方法,即动态双重机器学习(Dynamic Double Machine Learning, i.e. DDML)。
DDML 可有效应用于高维协变量背景下的处理效应估计,扩展了 DML 的使用范围。与传统静态模型不同,DDML 允许处理变量随时间变化,且通过结合机器学习模型(如随机森林、梯度提升树)和非参数估计方法,显著提升了估计的灵活性与准确性。同时 DDML 保持与最终模型相关的许多有利的统计特性(如小均方误差、渐近正态性、置信区间的构建)。
DDML 适用于以下情况:观察到所有潜在的动态混杂因素/控制因素(在收集的数据和观察到的结果中同时对适应性治疗决策产生直接影响的因素),要么是由于数量过多(高维)导致经典统计方法无法适用,要么是参数(非参数)函数无法令人满意地模拟这些因素对治疗和结果的影响。此时,后两个问题可以通过机器学习技术来解决 (Lewis and Syrgkanis, 2021)。

本文主要使用 EconML 实现 DDML。
EconML 是微软研究院的 Alice 团队开发的 Python 工具包,可应用机器学习技术从观察或实验数据中估算个性化因果效应。
EconML 中提供的一套估算方法代表了因果机器学习的最新进展。通过将单个机器学习步骤纳入可解释的因果模型,这些方法提高了假设预测的可靠性,使广大用户能够更快、更轻松地进行因果分析。
该工具包集成了众多现代因果学习方法,其中几项由微软研究院的 ALICE 项目自行开发,还有许多其他技术来自该领域的领先团队。包括动态双重机器学习,如 (Lewis and Syrgkanis, 2021);双重机器学习,如 (Chernozhukov et al., 2017; Chernozhukov et al., 2018);因果森林,如 (Wager and Athey, 2018; Athey et al., 2019);深度工具变量 (Hartford et al.,2017) 等;非参数工具变量 (Newey and Powell, 2003);元学习器 (Künzel et al., 2017) 等。
该库将所有这些不同的技术汇集到一个通用的 Python API 中,极大简化了用户的建模流程,是因果推断应用实践的重要利器。

在EconML中,开发者实现了“条件平均处理效应”(Conditional Average Treatment Effect,CATE) 的四种估计方式:
这里我们主要介绍动态双重机器学习模块 DynamicDML 。
在Python终端输入如下代码下载 EconML 库:
pip install EconML同时推荐安装如下依赖包:
pip install scikit-learn pandas numpy matplotlib seabornscikit-learn 用于机器学习pandas 用于数据分析numpy 用于数组计算matplotlib 和 seaborn 用于数据可视化DDML 模型旨在估计随时间变化的处理效应,适用于平衡面板数据数据。其核心思想是:
例如,如下数据格式可以使用 DDML :

(注:将数据传递给 DynamicDML 估计器时,上面的 “公司” 列与拟合时的 “组” 参数相对应。上面的 “年份” 列不应传入,因为它会从 “公司” 列推断出来)
如果组内成员没有同时出现,则假定数据集中的第一个组实例对应于该组的第一个时期,第二个组实例对应于该组的第二个时期,例如:

在该数据集中,第 1 行对应于 “A” 组的第一期,第 4 行对应于 “A” 组的第二期。
具体来说,数据需要满足以下特点:
假设你拥有观测性数据(或来自A/B测试的实验性历史数据),在这些数据中,每个个体单位随着时间推移接受了多个不同的处理或干预(),同时记录了最终的结果变量(),以及所有可能影响处理选择的协变量()。这些协变量不仅影响了处理决策(),还可能直接影响结果变量()(即 是所谓的控制变量或混杂变量),且这些信息都被完整记录在数据集中。
如果你的研究目标是理解处理()对结果变量()的影响,并且希望根据样本的可观测特征(,可以认为是混杂变量)来刻画这种影响关系,那么就可以使用 DDML。下面是一个例子(此例只是调用 DynamicDML 的数据格式,若要运行,需要读入自己的数据之后,并替换为相应的变量名):
from EconML.panel.dml import DynamicDMLest = DynamicDML()est.fit(y_dyn, T_dyn, X=X_dyn, W=W_dyn, groups=groups)DynamicDML 类是对传统 Double ML 方法的一种扩展,专门用于处理随时间顺序分阶段赋值的多期处理问题。该估计方法能够调整每期处理对未来结果变量的因果影响。数据生成过程可以建模为一个马尔可夫决策过程 ,其中:
该模型对数据生成过程作出了如下结构方程假设:
其中:
(Lewis and Syrgkanis, 2021) 在论文中绘制了如下的马尔可夫模型因果图用于理解这个过程:

关于模型设定与更多理论假设,详见 (Lewis and Syrgkanis, 2021)和连享会往期推文 因果推断:双重机器学习-ddml。
本文以 Python 教程为主,因此不再过多赘述模型的理论部分。
(Lewis and Syrgkanis, 2021) 提出了 DDML 的两阶段算法。其中在第一阶段以交叉拟合的方式拟合和评估一系列回归和分类模型,在第二阶段解决简单的线性方程组或简单的平方损失最小化问题。该方法还允许简单的样本分割/交叉拟合方法,允许在第一阶段中使用任意的机器学习方法。
动态双重机器学习(Dynamic Double Machine Learning, DDML)是传统双重机器学习在 时序场景 下的扩展。其核心目标是通过两阶段估计,消除混杂变量 的影响,从而准确识别动态处理效应。
(Lewis and Syrgkanis, 2021) 中的 Algorithm1 伪代码展现了 DDML 的基础动态效应估计过程,可以用于处理高维状态变量。文章中还提到了其他扩展算法,感兴趣的读者可点击文末链接进行阅读。

中文版:
Dynamic DML 伪代码(Algorithm 1,摘自 Lewis and Syrgkanis, 2021):
说明: DDML 的关键在于用机器学习灵活建模协变量与处理/结果的关系,通过交叉拟合消除过拟合偏差,最终用残差回归获得无偏的动态因果效应估计。
本文演示代码基于 EconML 官方 notebooks 进行汉化。-Link-
假设你已经安装了第2部分提到的全部依赖包(如果没有,请看2.2节),在你运行下面提到的代码时,仍然可能出现下面这个问题(注意:以下代码是在 Jupyter Notebook 中进行测试的,没有遇到这个问题说明官方可能已经修复此 bug,可直接跳过 4.1 节):
# 主要使用的包from EconML.panel.dml import DynamicDMLfrom EconML.tests.dgp import DynamicPanelDGP, add_vlines# 这里的 tests 模块需要手动复制进去,这是 2025-03-28 官方最新上传的。# 即使 pip install EconML 下载最新的 EconML 的包,也暂时不能解决这个 bug(后期官方更新之后可能会修复此 bug)# 辅助包import numpy as npfrom sklearn.linear_model import LassoCV, MultiTaskLassoCVimport matplotlib.pyplot as plt%matplotlib inline会出现如下报错:
---------------------------------------------------------------------------ModuleNotFoundError Traceback (most recent call last)Cell In[1], line 3 1 # Main imports 2 from econml.panel.dml import DynamicDML----> 3 from econml.tests.dgp import DynamicPanelDGP, add_vlines 4 5 # Helper imports 6 import numpy as npModuleNotFoundError: No module named 'econml.tests'报错说 econml 中没有 tests 这个模块,可是我们刚刚明明下载了 econml 包,应该是含有这个模块才对。
那我们到官方的 GitHub 中一探究竟(需要魔法才能访问这个链接),-Link-:

打开 econml 文件夹,可以看到 tests 这个模块是不久前才更新的,所以可能还没有把最新的代码封装到 econml 包中。
我们点击 Download ZIP 将官方的代码压缩包下载到本地,便于后续操作:

Python 的包通常安装在特定的文件夹中,所以我们需要在自己电脑本地已经安装的 Python 包里,找到 econml 这个包,并把官方更新的 tests 文件夹复制进去。
可以通过以下代码找到 Python 下载包的文件夹地址
import siteprint(site.getsitepackages())我的电脑(使用 mac 进行测试,且本文所有代码是在 work 这个虚拟环境下运行的)就会返回如下地址:
['/opt/anaconda3/envs/work/lib/python3.11/site-packages']对于 mac 用户,可以打开 Finder 然后按 command + shift + G 将 /opt/anaconda3/envs/work/lib/python3.11/site-packages(这里替换成你自己的路径) 复制进去,然后仔细寻找一番,便可以找到已经安装的 econml 包。
然后将前面下载的官方代码中的 tests 文件夹进行复制,粘贴到 econml 文件夹下,最终达到的效果应该是这样:

然后就可以正常运行下面的代码了。
在本节中,我们展示 DynamicDML 在合成数据和观测数据上的性能。
我们考虑一个基于马尔可夫过程的处理模型来生成数据。
在下述示例中,变量定义如下:
数据生成机制为:
其中,初始条件为 、,且噪声项 、、 独立同分布于正态分布 。
进一步设定如下:
我们生成一条单条时间序列,长度为 。
# 定义数据生成过程(DGP)参数np.random.seed(123) # 固定随机种子,确保实验可重复n_panels = 5000# 面板单位数量(即独立个体/样本组数量)n_periods = 3# 每个面板单位包含的时间期数n_treatments = 2# 每期的处理变量数量n_x = 100# 每期的特征变量与控制变量总数s_x = 10# 控制变量的稀疏度(即真正影响结果的控制变量数量)s_t = 10# 处理变量的稀疏支撑集大小(即真正影响处理的特征数量)# 生成数据dgp = DynamicPanelDGP(n_periods, n_treatments, n_x).create_instance( s_x, random_seed=12345)Y, T, X, W, groups = dgp.observational_data(n_panels, s_t=s_t, random_seed=12345)true_effect = dgp.true_effect# 定义 DynamicDML 估计器对象est = DynamicDML( model_y=LassoCV(cv=3, max_iter=1000), # 用 LassoCV 拟合结果变量 Y(交叉验证次数为3,最大迭代步1000) model_t=MultiTaskLassoCV(cv=3, max_iter=1000),# 用 MultiTaskLassoCV 同时拟合多维处理变量 T(交叉验证次数为3,最大迭代步数1000) cv=3# 外层交叉验证次数设为3,用于样本分折训练 nuisance models)# 训练估计器est.fit(Y, T, X=None, W=W, groups=groups)# 计算并输出:所有时期的处理变量对最后一期结果变量的平均处理效应(ATE)print(f"Average effect of default policy: {est.ate():0.2f}")# 计算目标政策(target policy)相对于基准政策(baseline policy)的效应# 必须为每一期指定一个处理值(即每期的处理决策路径)baseline_policy = np.zeros((1, n_periods * n_treatments)) # 基准政策:所有时期、所有处理变量均为0(不施加处理)target_policy = np.ones((1, n_periods * n_treatments)) # 目标政策:所有时期、所有处理变量均为1(完全施加处理)# 调用 DynamicDML 的 effect 方法,估计目标政策相对于基准政策的效应差异eff = est.effect(T0=baseline_policy, T1=target_policy)# 输出目标政策相对于基准政策的因果效应(保留两位小数)print(f"Effect of target policy over baseline policy: {eff[0]:0.2f}")接下来是进行逐期处理:
# 逐期处理效应(Period-specific treatment effects)for i, theta in enumerate(est.intercept_.reshape(-1, n_treatments)):# 输出:第 i+1 期的每个处理变量,对最终一期结果变量的边际效应 print(f"Marginal effect of a treatments in period {i+1} on period {n_periods} outcome: {theta}")# 输出逐期处理效应(Period Treatment Effects)及其置信区间(Confidence Intervals)est.summary()# 计算各期处理效应的置信区间(Confidence Intervals)conf_ints = est.intercept__interval(alpha=0.05)# alpha 设置成 0.01 就是 99% 的置信区间可以得到如下的 CATE 估计、p值和置信区间等:

# 绘制各期处理效应及其置信区间(error bars)与真实效应对比图plt.figure(figsize=(15, 5)) # 设置图像大小为15×5英寸# 绘制 DynamicDML 估计的处理效应及其置信区间plt.errorbar( np.arange(n_periods * n_treatments) - .04, # x轴坐标(每个处理变量),稍微左移避免重叠 est.intercept_, # DynamicDML 估计的效应点 yerr=(conf_ints[1] - est.intercept_, # 上误差条 est.intercept_ - conf_ints[0]), # 下误差条 fmt='o', # 散点样式 label='DynamicDML'# 图例标签)# 绘制真实处理效应(Ground truth),用于与估计值对比plt.errorbar( np.arange(n_periods * n_treatments), true_effect.flatten(), # 真实效应,展开成一维 fmt='o', alpha=0.6, # 设置透明度 label='Ground truth'# 图例标签)# 在每个时期交界处画虚线分隔(帮助视觉区分不同时间段)for t in np.arange(1, n_periods): plt.axvline(x=t * n_treatments - .5, linestyle='--', alpha=.4)# 设置 x 轴刻度和刻度标签plt.xticks( [t * n_treatments - .5 + n_treatments/2for t in range(n_periods)], # 每个时间段中间的位置 ["$\\theta_{}$".format(t) for t in range(n_periods)] # 标签)plt.gca().set_xlim([-.5, n_periods * n_treatments - .5]) # 设置x轴范围plt.ylabel("Effect") # 设置y轴标签plt.legend() # 显示图例plt.show() # 显示图像画出图如下:

我们还可以估计初始时期某些特征子集 的取值所引起的处理效应异质性(Treatment Effect Heterogeneity)。
目前,异质性估计仅支持基于初始状态特征(initial state features)进行建模。
这种设定特别适用于分析单位个体在时间上不变的特征(如出生年份、基础教育程度等)对处理效应的异质性影响。在这种情况下,可以简单地将每期的协变量 设置为一些在所有时期保持不变的单位特征的重复值。
当然,也可以传入随时间变化的特征作为协变量,此时这些特征的时间变化部分将作为控制变量(time-varying controls)使用,但需要注意的是:异质性估计只基于初始时期的状态特征进行建模,不会随时间动态调整。
# 定义额外的数据生成过程(DGP)参数het_strength = 0.5# 异质性强度(heterogeneity strength),控制处理效应对特征的敏感程度het_inds = np.arange(n_x - n_treatments, n_x) # 指定产生异质性的特征变量索引(靠近特征集尾部)# 生成模拟数据dgp = DynamicPanelDGP(n_periods, n_treatments, n_x).create_instance( s_x, # 控制变量稀疏度(真正影响结果的控制变量数量) hetero_strength=het_strength, # 设置处理效应异质性的强度 hetero_inds=het_inds, # 指定哪一组特征引入异质性 random_seed=12# 随机种子,确保实验可重复)# 从生成的DGP实例中抽取观测数据Y, T, X, W, groups = dgp.observational_data( n_panels, # 面板单位数量 s_t=s_t, # 处理稀疏度 random_seed=1# 再次设置随机种子)# 记录下:真实的均值处理效应 (ATE) 以及真实的异质性处理效应 (heterogeneous effect)ate_effect = dgp.true_effecthet_effect = dgp.true_hetero_effect[:, het_inds + 1] # 注意索引偏移(+1)# 定义 DynamicDML 估计器对象,用于估计动态处理效应与异质性效应est = DynamicDML( model_y=LassoCV(cv=3), # 采用 LassoCV(交叉验证3折)拟合结果变量 Y,适应高维协变量 model_t=MultiTaskLassoCV(cv=3), # 采用 MultiTaskLassoCV 同时拟合多期、多维处理变量 T cv=3# 外层 cross-fitting 交叉验证设为3折,提高估计稳健性)# 训练估计器est.fit(Y, T, X=X, W=W, groups=groups, inference="auto")# 结果展示est.summary()最终输出结果如下:

# 测试点的平均处理效应X_test = X[np.arange(0, 25, 3)]print(f"Average effect of default policy:{est.ate(X=X_test):0.2f}")# 目标政策相对于基准政策的效应# 必须为每个时期指定一个处理baseline_policy = np.zeros((1, n_periods * n_treatments))target_policy = np.ones((1, n_periods * n_treatments))eff = est.effect(X=X_test, T0=baseline_policy, T1=target_policy)print("Effect of target policy over baseline policy for test set:\n", eff)# 系数:截距为 n_treatments*n_periods coef_ 的形状(n_treatments*n_periods, n_hetero_inds),# 前 n_treatment 行来自第一期,后 n_treatment 行来自第二期。est.intercept_, est.coef_# 置信区间conf_ints_intercept = est.intercept__interval(alpha=0.05)conf_ints_coef = est.coef__interval(alpha=0.05)代码如下:
true_effect_inds = []for t in range(n_treatments): true_effect_inds += [t * (1 + n_x)] + (list(t * (1 + n_x) + 1 + het_inds) if len(het_inds)>0else [])true_effect_params = dgp.true_hetero_effect[:, true_effect_inds]true_effect_params = true_effect_params.reshape((n_treatments*n_periods, 1 + het_inds.shape[0]))# 拼接矩阵param_hat = np.hstack([est.intercept_.reshape(-1, 1), est.coef_])lower = np.hstack([conf_ints_intercept[0].reshape(-1, 1), conf_ints_coef[0]])upper = np.hstack([conf_ints_intercept[1].reshape(-1, 1), conf_ints_coef[1]])# 画图plt.figure(figsize=(15, 5))plt.errorbar(np.arange(n_periods * (len(het_inds) + 1) * n_treatments), true_effect_params.flatten(), fmt='*', label='Ground Truth')plt.errorbar(np.arange(n_periods * (len(het_inds) + 1) * n_treatments), param_hat.flatten(), yerr=((upper - param_hat).flatten(), (param_hat - lower).flatten()), fmt='o', label='DynamicDML')add_vlines(n_periods, n_treatments, het_inds)plt.legend()plt.show()最终画出图如下:
EconML 官网:-Link-Note:产生如下推文列表的 Stata 命令为:
lianxh 机器学习, nocat安装最新版lianxh命令:ssc install lianxh, replace
Drukker, 刘迪, 2020, Stata Blogs - An introduction to the lasso in Stata (拉索回归简介), 连享会 No.117.

连享会:2026五一论文班 · 线上时间:5月2-4日嘉宾:郭士祺 (上海交通大学)、戚树森 (厦门大学)、李学恒 (中山大学)咨询:王老师 18903405450(微信)

Note:扫一扫进入“连享会微信小店”,你想学的课程在这里······

尊敬的老师 / 亲爱的同学们:
连享会致力于不断优化和丰富课程内容,以确保每位学员都能获得最有价值的学习体验。为了更精准地满足您的学习需求,我们诚挚地邀请您参与到我们的课程规划中来。 请您在下面的问卷中,分享您 感兴趣的学习主题或您希望深入了解的知识领域 。您的每一条建议都是我们宝贵的资源,将直接影响到我们课程的改进和创新。 我们期待您的反馈,因为您的参与和支持是我们不断前进的动力。感谢您抽出宝贵时间,与我们共同塑造更加精彩的学习旅程!https://www.wjx.cn/vm/YgPfdsJ.aspx# 再次感谢大家宝贵的意见!

New! Stata 搜索神器:
lianxh和songblGIF 动图介绍搜: 推文、数据分享、期刊论文、重现代码 ……👉 安装:. ssc install lianxh. ssc install songbl👉 使用:. lianxh DID 倍分法. songbl all

🍏 关于我们
