Causal Identification / Synthetic Control
合成控制与 SDID
合成控制把多个未处理单位加权成一个“影子处理组”,用政策前路径拟合来构造政策后的反事实。
Mechanism Lab
动画:合成控制如何用 donor 权重构造反事实
动画先显示 donor pool,再把权重推到合成路径上,最后比较处理单位实际政策后路径与合成反事实路径。
Step 1 / 5
Donor pool
先定义没有接受政策且制度环境可比的 donor pool。
j=2,...,J+1Animation Control
Reduced-motion users receive the same step states without continuous motion.
01 / 直觉
核心直觉
当只有一个城市、学校、地区或国家接受政策时,传统 DID 很难找到一个天然对照组;合成控制用 donor pool 加权造出一个更像处理组的对照。
权重不是为了让政策后结果接近,而是在政策前就把处理组的结果路径和协变量拟合好。
识别可信度来自政策前拟合、donor pool 合理性、没有其他同期冲击、placebo 检验和对权重/样本选择的透明报告。
02 / 数学
从 donor pool 权重到处理后反事实
01 / 数据结构
设单位 1 是处理单位,单位 2...J+1 是未处理 donor pool。T0 是政策前最后一期,T 是总期数。
Y_1t: treated unit outcome
Y_jt: donor unit outcome, j=2,...,J+1
t <= T0: pre-period, t > T0: post-period02 / 权重约束
合成控制权重通常非负且和为 1,因此合成单位是 donor pool 的凸组合,避免用负权重外推。
w_j >= 0, sum_{j=2}^{J+1} w_j = 103 / 预处理拟合目标
令 X1 是处理单位政策前特征,X0 是 donor pool 特征矩阵。选择权重 w,使加权 donor 尽量贴合处理单位的政策前特征。
w_hat = argmin_w (X_1 - X_0 w)^T V (X_1 - X_0 w)
s.t. w >= 0, 1^T w = 104 / 反事实路径
政策后如果处理单位没有接受政策,其反事实结果用 donor pool 的加权结果表示。
Y_1t(0)_hat = sum_{j=2}^{J+1} w_hat_j Y_jt, for t > T005 / 处理效应路径
每一期处理效应是处理单位实际结果减去合成反事实。也可对政策后时期取平均。
tau_t_hat = Y_1t - Y_1t(0)_hat
ATT_post = (1/(T-T0)) sum_{t=T0+1}^{T} tau_t_hat06 / Placebo 推断
把每个 donor 轮流假装成处理单位,重复合成控制。如果真实处理单位的政策后缺口相对 placebo 分布异常大,证据更可信。
ratio_i = RMSPE_post,i / RMSPE_pre,i07 / SDID 直觉
Synthetic DID 在单位权重之外加入时间权重,把合成控制的 donor 加权与 DID 的前后差分结合起来。
tau_SDID = (Y_1,post - omega^T Y_0,post) - (Y_1,pre - omega^T Y_0,pre) lambda03 / 代码
Python 代码:用约束优化估计合成控制权重
下面代码用 `scipy.optimize.minimize` 求非负且和为 1 的 donor 权重,然后生成合成路径、处理效应路径和 pre/post RMSPE。
import numpy as np
import pandas as pd
from scipy.optimize import minimize
# df columns:
# unit, year, outcome, treated_unit
treated_unit = "City A"
pre_years = range(2010, 2020)
post_years = range(2020, 2025)
panel = df.pivot(index="year", columns="unit", values="outcome").sort_index()
donors = [unit for unit in panel.columns if unit != treated_unit]
Y1_pre = panel.loc[pre_years, treated_unit].to_numpy()
Y0_pre = panel.loc[pre_years, donors].to_numpy()
def objective(weights):
synthetic_pre = Y0_pre @ weights
return np.mean((Y1_pre - synthetic_pre) ** 2)
n_donors = len(donors)
constraints = [{"type": "eq", "fun": lambda w: w.sum() - 1}]
bounds = [(0, 1)] * n_donors
start = np.repeat(1 / n_donors, n_donors)
result = minimize(objective, start, bounds=bounds, constraints=constraints)
weights = pd.Series(result.x, index=donors).sort_values(ascending=False)
synthetic_path = panel[donors] @ weights
effect_path = panel[treated_unit] - synthetic_path
pre_rmspe = np.sqrt(np.mean(effect_path.loc[pre_years] ** 2))
post_rmspe = np.sqrt(np.mean(effect_path.loc[post_years] ** 2))
print(weights[weights > 0.01])
print({"pre_rmspe": pre_rmspe, "post_rmspe": post_rmspe})
print(effect_path.loc[post_years])04 / 案例
案例:城市减排政策的单处理单位评估
- 研究问题:某城市 2020 年实施减排政策,是否降低了污染指数?只有一个城市先行试点,无法直接做大样本 DID。
- Donor pool 选取未实施类似政策、产业结构可比、没有同期重大冲击的城市。
- 关键图不是政策后差距,而是政策前真实城市路径与合成城市路径是否足够贴合。
- 报告应包括 donor 权重、政策前 RMSPE、处理效应路径、placebo 分布、剔除高权重 donor 的稳健性,以及 donor pool 选择理由。
05 / 风险
常见误区
参考资料
- Abadie, Diamond, and Hainmueller (2010), Synthetic Control Methods for Comparative Case Studieshttps://doi.org/10.1198/jasa.2009.ap08746
- Abadie (2021), Using Synthetic Controlshttps://doi.org/10.1257/jel.20191450
- Arkhangelsky et al. (2021), Synthetic Difference-in-Differenceshttps://doi.org/10.1257/aer.20190159