尧图精选

Stata双重差分法全流程:平行趋势检验、PSM-DID与安慰剂检验实操指南

🕒 发布时间:2026/9/18 14:19:40 📁 来源:尧图网络
1. 为什么DID值得你花时间啃下来如果你正在读这篇东西大概率是手里已经有一份面板数据导师或者上级让你“跑个DID看看政策效果”然后你打开Stata面对一堆命令和检验有点不知道从哪下手。别慌这太正常了。DID双重差分法这几年在经济学、管理学、公共政策评估里几乎成了标配工具但真正能把平行趋势检验、PSM匹配、安慰剂检验这一整套流程跑通、跑对、跑出能写进论文的结果其实没想象中那么简单。我自己第一次做DID的时候犯过一个很典型的错误直接reg y treated##post跑完就交差了结果被审稿人一句“平行趋势检验在哪里”问得哑口无言。后来补做平行趋势发现处理组和对照组在政策前就有显著差异整个结果的可信度直接崩掉。从那以后我才明白DID的核心不是那个交互项系数而是识别假设是否成立。平行趋势是DID的命根子PSM-DID是补救手段安慰剂检验是最后一道防线。这三样东西串起来才构成一个完整的、能经得起推敲的实证流程。这篇内容适合谁看如果你是刚接触DID的研究生、需要做政策评估的分析师或者已经跑过DID但结果总被质疑的从业者那接下来的内容应该能帮你把整个流程理清楚。我会从数据准备讲到平行趋势检验再到PSM-DID的实现和安慰剂检验每一步都配上Stata代码和实操中踩过的坑。不堆公式重点讲清楚“为什么这么做”和“怎么做才不会翻车”。2. DID的核心逻辑与数据准备2.1 DID到底在识别什么双重差分法的基本思想其实很朴素政策实施后处理组的变化减去对照组的变化就是政策效应。用公式写出来就是[ \hat{\beta}{DID} (\bar{Y}{treat,post} - \bar{Y}{treat,pre}) - (\bar{Y}{control,post} - \bar{Y}_{control,pre}) ]这个式子背后有一个关键假设如果没有政策干预处理组和对照组的时间趋势应该是一样的。这就是平行趋势假设。注意它要求的不是两组在政策前水平相同而是趋势相同。水平不同可以用固定效应吸收掉但趋势不同就没法用常规DID解决了。我经常用这样一个类比来解释假设你想知道给一片庄稼施肥的效果。你选了A地块施肥B地块不施肥。收获时A地块增产了20%B地块增产了5%。那施肥的净效果是15%吗不一定。如果A地块本来土壤就更好、光照更充足那这15%里可能有一部分是地块本身条件带来的。DID要做的就是通过比较两块地在施肥前后的产量变化把那些不随时间变化的地块特征剔除掉。但前提是施肥前两块地的产量增长趋势得差不多否则你没法判断增产到底来自肥料还是来自原本就更好的生长势头。2.2 数据结构的硬性要求做DID数据必须是面板数据也就是同一个体在多个时间点上的观测。宽泛地说有两种常见结构个体-年份面板比如30个省份、10年的数据每个省份每年一条记录个体-月份面板比如企业层面的月度数据用于评估某项政策在某个时点后的效果Stata里做DID数据需要满足几个条件。第一要有明确的个体标识变量比如id或province_id。第二要有时间变量比如year。第三要有处理组和对照组的区分变量通常叫treat处理组取1对照组取0。第四要有政策实施后的时间虚拟变量通常叫post政策后取1政策前取0。这里有一个新手特别容易犯的错误把post定义错了。post不是简单的时间大于某个年份而是“对于处理组而言政策已经实施的时期”。如果政策是2015年在全国推行的但只有部分省份是试点那post应该定义为treat1 year2015。如果所有个体都在同一时间受到政策冲击那就是标准DID如果不同个体受到冲击的时间不同那就是多期DID需要用did命令或者手动构造交互项。2.3 数据清洗的实操要点在跑任何回归之前数据清洗这一步绝对不能省。我见过太多人因为数据没处理好跑出来的结果自己都不敢信。几个关键检查点第一缺失值处理。DID对缺失值很敏感尤其是被解释变量缺失。如果某个省份某一年GDP数据缺失直接删掉这条观测可能导致样本选择偏误。我的做法是先用misstable summarize看看缺失情况如果缺失比例低于5%可以考虑删除如果超过10%就得考虑插值或者用其他方法补全。第二异常值检查。用summarize看被解释变量的分布如果最大值是最小值的几百倍可能存在异常值。可以用缩尾处理winsor2命令把上下1%的极端值替换掉。但要注意缩尾不是万能的如果异常值本身有经济含义缩尾反而会掩盖真实效应。第三平衡面板 vs 非平衡面板。标准DID通常要求平衡面板也就是每个个体在每个时期都有观测。如果数据是非平衡的Stata的xtreg会自动处理但解释起来会麻烦一些。我一般会先用xtset id year设定面板结构然后用xtdescribe看看面板是否平衡。* 设定面板结构 xtset province_id year * 检查面板平衡性 xtdescribe * 查看缺失情况 misstable summarize y x1 x2 x3 * 缩尾处理 winsor2 y x1 x2, cuts(1 99) replace注意缩尾处理会改变数据分布做完之后一定要重新跑描述性统计确认变量范围合理。3. 平行趋势检验DID的生死线3.1 平行趋势检验的原理平行趋势检验的核心思想是在政策实施之前处理组和对照组的结果变量应该遵循相似的时间趋势。如果政策前两组就已经分道扬镳那政策后的差异就不能归因于政策本身。检验方法主要有两种事件研究法和时间趋势图。事件研究法更正式也是论文里最常被要求做的。它的做法是把政策实施前后每一期都生成一个虚拟变量然后看政策前的系数是否显著异于零。如果政策前的系数都不显著说明平行趋势假设成立。具体来说假设政策在2015年实施数据覆盖2010-2020年。以2014年为基期省略掉生成2010、2011、2012、2013、2015、2016...2020这些年份的虚拟变量然后与treat交互放进回归方程[ Y_{it} \alpha \sum_{k \neq 2014} \beta_k \cdot treat_i \cdot year_k \gamma X_{it} \mu_i \lambda_t \varepsilon_{it} ]如果2010-2013年的(\beta_k)都不显著说明政策前处理组和对照组没有显著差异平行趋势成立。3.2 Stata实现事件研究法在Stata里实现事件研究法关键是生成相对时间变量。假设政策实施年份是2015年那么* 生成相对时间变量 gen rel_time year - 2015 * 生成年份虚拟变量与treat的交互项 * 以政策前一年rel_time -1为基期 forvalues i 5(-1)2 { gen pre_i (rel_time -i) * treat } forvalues i 0/5 { gen post_i (rel_time i) * treat } * 回归 xtreg y pre_5 pre_4 pre_3 pre_2 post_0 post_1 post_2 post_3 post_4 post_5 x1 x2 i.year, fe robust跑完之后用coefplot把系数画出来直观展示政策前后的动态效应。* 安装coefplot ssc install coefplot, replace * 画图 coefplot, keep(pre_5 pre_4 pre_3 pre_2 post_0 post_1 post_2 post_3 post_4 post_5) /// vertical yline(0, lcolor(red) lpattern(dash)) /// xline(4.5, lcolor(gray) lpattern(dash)) /// ytitle(政策效应) xtitle(相对政策年份) /// title(平行趋势检验) /// addplot(line b at) /// ciopts(recast(rcap))这张图里横轴是相对政策年份纵轴是估计系数。如果政策前的点都在零线附近且置信区间包含零政策后的点显著偏离零那就说明平行趋势成立且政策效应显著。3.3 平行趋势不成立怎么办如果检验发现政策前系数显著说明平行趋势不成立。这时候有几条路可以走第一条路加控制变量。有时候平行趋势不成立是因为遗漏了某些随时间变化的变量。比如处理组和对照组的经济发展水平不同而经济发展水平本身会影响结果变量。加入GDP增长率、人口密度等控制变量后如果平行趋势改善了那说明原来的问题只是遗漏变量。第二条路改变样本区间。如果政策前某几年趋势差异特别大可以考虑缩短政策前的窗口期。比如原来用2010-2020年发现2010-2012年趋势差异大那就只用2013-2020年。但这样做会损失样本量而且需要说明理由。第三条路PSM-DID。如果加了控制变量还是不行那就用倾向得分匹配PSM来构造一个在政策前与处理组尽可能相似的对照组。这就是下一节要讲的内容。实操心得平行趋势检验的图一定要画得好看。审稿人第一眼看的就是这张图。坐标轴标签要清楚零线要明显政策实施年份要用竖线标出来。我一般会把图导出为PDF格式保证清晰度。4. PSM-DID当平行趋势不成立时的补救方案4.1 PSM-DID的适用场景PSM-DID不是万能的它解决的是可观测变量的选择偏误。也就是说如果处理组和对照组的差异可以由一些可观测的特征如企业规模、行业、地区经济发展水平解释那PSM可以通过匹配这些特征构造一个“反事实”的对照组。但PSM-DID有一个前提匹配变量必须是政策前确定的或者至少不受政策影响。如果匹配变量本身受到政策冲击那匹配就失去了意义。比如你研究最低工资政策对企业创新的影响用企业规模作为匹配变量但企业规模可能因为最低工资政策而变化那就不能用政策后的规模来匹配。4.2 PSM匹配的实操步骤PSM的第一步是估计倾向得分。通常用logit模型以treat为因变量以政策前的协变量为自变量* 估计倾向得分 logit treat size age leverage growth i.industry i.year, robust * 预测倾向得分 predict pscore, pr * 检查共同支撑域 sum pscore if treat 1 sum pscore if treat 0共同支撑域检查很重要。如果处理组的倾向得分范围是0.3-0.8对照组是0.1-0.5那重叠区域只有0.3-0.5匹配质量会受影响。可以用psgraph画图看看分布。* 画倾向得分分布图 psgraph, treated(treat) pscore(pscore)匹配方法有好几种最近邻匹配、半径匹配、核匹配。我一般先用最近邻匹配nearest neighbor因为最直观。但最近邻匹配可能会用到距离很远的样本所以通常会加一个卡尺caliper比如0.05或0.01。* 最近邻匹配卡尺0.05 psmatch2 treat, pscore(pscore) neighbor(1) caliper(0.05) common * 检查匹配平衡性 pstest size age leverage growth, both graphpstest会输出匹配前后处理组和对照组的均值差异。如果匹配后所有变量的标准化偏差standardized bias都小于10%说明匹配效果不错。如果还有变量偏差很大要么调整匹配方法要么重新考虑匹配变量。4.3 基于匹配样本的DID回归匹配完成后用匹配后的样本跑DID。注意PSM-DID的回归和普通DID略有不同需要加上权重_weight并且标准误要聚类到个体层面。* 基于匹配样本的DID xtreg y treat##post x1 x2 i.year [pweight _weight], fe robust或者用diff命令diff y, treated(treat) period(post) cov(x1 x2) pscore(pscore) kernel id(id)注意PSM-DID的结果解释要谨慎。匹配后的样本不再是随机样本所以系数反映的是“处理组平均处理效应”ATT而不是总体平均处理效应ATE。写论文的时候要明确说明这一点。4.4 PSM-DID的常见误区第一个误区是匹配变量用政策后的值。这是最致命的错误。匹配变量必须是政策前确定的或者至少是政策前测量的。如果用了政策后的值匹配本身就内生化了政策效应结果不可信。第二个误区是匹配后不检查平衡性。有些人跑完psmatch2直接就跑DID不看pstest。结果匹配后处理组和对照组还是有显著差异那匹配等于白做。第三个误区是过度依赖PSM。PSM只能解决可观测变量的选择偏误如果存在不可观测变量的选择偏误PSM无能为力。这时候需要考虑工具变量、断点回归等其他方法。5. 安慰剂检验给结果上最后一道保险5.1 安慰剂检验的逻辑安慰剂检验的核心思想是如果我把政策实施时间提前几年或者随机分配处理组还能得到显著的政策效应吗如果还能说明原来的结果可能是偶然的或者存在其他混淆因素。常见的安慰剂检验有三种时间安慰剂把政策实施时间提前2-3年重新跑DID。如果系数不显著说明原来的效应确实来自政策。个体安慰剂随机抽取与处理组数量相同的个体作为“伪处理组”重新跑DID。重复500次或1000次看真实系数在随机系数分布中的位置。变量安慰剂用一个理论上不受政策影响的变量作为被解释变量重新跑DID。如果系数不显著说明结果不是由共同趋势驱动的。5.2 时间安慰剂检验的Stata实现时间安慰剂检验最简单也最常用。假设真实政策在2015年我把政策时间提前到2012年只用2010-2014年的数据跑DID* 时间安慰剂检验 preserve keep if year 2014 gen fake_post (year 2012) * treat xtreg y fake_post x1 x2 i.year, fe robust restore如果fake_post的系数不显著说明政策前的趋势差异不足以产生显著结果原来的效应可信。5.3 随机分配处理组的安慰剂检验这种检验更严格但实现起来稍微复杂一些。思路是从全样本中随机抽取N个个体作为“伪处理组”N等于真实处理组的数量。然后跑DID记录系数。重复500次画出系数分布图看真实系数是否在分布的两端。* 随机分配处理组的安慰剂检验 program define placebo_did, rclass preserve * 随机打乱treat变量 gen random_treat runiform() sort random_treat gen fake_treat (_n 处理组数量) * 生成交互项 gen fake_did fake_treat * post * 回归 xtreg y fake_did x1 x2 i.year, fe robust return scalar coef _b[fake_did] restore end * 重复500次 simulate coef r(coef), reps(500) seed(12345): placebo_did * 画分布图 hist coef, xline(真实系数, lcolor(red) lpattern(dash)) /// title(安慰剂检验) xtitle(估计系数) ytitle(频数)如果真实系数落在分布的两端比如小于5%分位数或大于95%分位数说明真实效应不太可能是随机产生的。5.4 安慰剂检验的注意事项第一随机抽样的次数要足够多。500次是底线1000次更稳妥。次数太少分布不稳定。第二随机种子要固定。用seed()设定随机种子保证结果可复现。第三安慰剂检验不能替代平行趋势检验。安慰剂检验是补充证据不是平行趋势的替代品。如果平行趋势本身不成立安慰剂检验做得再好也没用。实操心得安慰剂检验的图要画得清楚。真实系数用红色虚线标出来分布用直方图或核密度图。如果真实系数在分布之外那说明结果很稳健。我一般还会报告p值也就是随机系数大于真实系数的比例。6. 完整流程复盘与常见问题速查6.1 从数据到结果的全流程把前面的内容串起来一个完整的DID分析流程大概是这样的数据准备导入数据设定面板结构处理缺失值和异常值描述性统计看处理组和对照组在政策前的均值差异平行趋势检验事件研究法画图判断是否成立基准DID回归如果平行趋势成立直接跑双向固定效应PSM-DID如果平行趋势不成立先做PSM匹配再跑DID安慰剂检验时间安慰剂或随机分配处理组稳健性检验更换被解释变量、改变样本区间、加入更多控制变量每一步的输出都要保存好写论文的时候需要报告。6.2 常见问题与排查技巧问题可能原因解决方法平行趋势检验政策前系数显著处理组和对照组趋势不同加控制变量、缩短窗口期、PSM-DIDPSM匹配后平衡性不好匹配变量选择不当更换匹配变量、调整卡尺、换匹配方法DID系数不显著政策效应弱、样本量小、模型设定错误检查模型设定、扩大样本、换被解释变量标准误过大聚类层级不对、异方差聚类到个体层面、用robust标准误安慰剂检验真实系数在分布中间结果可能是偶然的重新检查数据、考虑其他识别策略6.3 几个容易被忽略的细节第一个细节聚类标准误的层级。DID回归的标准误通常要聚类到个体层面因为同一个体不同时期的误差项可能存在序列相关。如果聚类层级不对标准误会被低估t值虚高。Stata里用xtreg自动处理了个体层面的聚类但如果是reg命令需要手动加cluster(id)。第二个细节固定效应的选择。双向固定效应个体固定效应时间固定效应是标配但有时候加入个体趋势项individual-specific time trend会更稳健。比如i.year##c.treat或者i.year##i.treat允许处理组和对照组有不同的时间趋势。但这样做会消耗更多自由度样本量小的时候要谨慎。第三个细节多期DID的处理。如果不同个体受到政策冲击的时间不同不能用简单的treat##post而应该用did命令或者手动构造treat和post的交互项。did命令是Stata 17以后才有的老版本可以用csdid或者did_multiplegt。* 多期DID did y, time(year) treatment(treat)第四个细节结果的可视化。除了平行趋势图还可以画处理组和对照组的均值时间趋势图直观展示两组在政策前后的变化。用twoway命令* 均值时间趋势图 preserve collapse (mean) y, by(year treat) twoway (line y year if treat 1, lcolor(blue)) /// (line y year if treat 0, lcolor(red)), /// xline(2015, lcolor(gray) lpattern(dash)) /// legend(label(1 处理组) label(2 对照组)) /// title(处理组与对照组的时间趋势) restore这张图放在论文里审稿人一眼就能看出两组在政策前是否平行。6.4 写论文时的报告规范最后说一下写论文时的报告规范。DID部分的表格通常包括基准回归结果至少两列不加控制变量、加控制变量平行趋势检验图PSM匹配平衡性检验表安慰剂检验图稳健性检验表表格里要报告系数、标准误、显著性水平、样本量、R方。标准误要用括号括起来显著性用星号标注。平行趋势图要标注政策实施年份安慰剂检验图要标注真实系数位置。我个人的习惯是所有回归结果都用esttab输出到Word或LaTeX方便直接粘贴到论文里。* 输出回归结果 esttab model1 model2 using results.rtf, /// replace star(* 0.1 ** 0.05 *** 0.01) /// se ar2 nogap /// title(DID回归结果)这套流程跑下来从数据清洗到最终结果大概需要两到三天。如果数据质量好、平行趋势成立可能一天就能搞定。但如果平行趋势不成立、需要做PSM-DID时间会拉长。我自己的经验是前期数据清洗和检验花的时间越多后期写论文和应对审稿人就越轻松。那些一上来就跑回归的人往往在审稿阶段被问得焦头烂额。最后再分享一个小技巧如果你用的是多期DIDStata 17以上的版本可以直接用did命令它会自动处理异质性处理效应的问题。如果是老版本可以用csdid或者did_multiplegt。这些命令的语法和xtreg略有不同但核心逻辑是一样的。跑之前记得先ssc install安装。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →