ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

双重差分DID完整指南:Stata实现平行趋势与安慰剂检验

双重差分DID完整指南:Stata实现平行趋势与安慰剂检验 读这篇文章的人多半是正在被实证论文折磨的学生或者刚接触政策评估的年轻研究者。打开Stata装好命令敲下一行did弹出一堆看不懂的输出然后陷入迷茫系数是哪个显著性怎么看平行趋势怎么检验审稿人问起来怎么答双重差分DID是政策评估里最常用、也最容易出错的方法之一。这篇文章不绕弯子直接说清楚从原理到落地操作的完整链路。数据应该长什么样、交互项怎么生成、基准回归用哪个命令、平行趋势如何画图证明、安慰剂检验怎么写代码、顶级期刊现在还在追问什么问题——按这个顺序走一遍你就能把DID真正跑通而不是停留在只知道命令的阶段。1. DID的核心逻辑为什么前后对比不够用先说原理但只说人话版。很多人第一次接触DID时会忘记一个基础问题我们到底在估计什么1.1 政策评估的困境一个反事实难题假设有一天某市突然实施了限行政策你要评估它是否降低了空气污染。最朴素的想法拿政策实施前的平均空气质量和实施后的平均空气质量比一比差多少就是政策效果。问题在于空气污染受季节、气象、产业结构调整、周边城市传输等多种因素影响。即便没有限行政策下个月的污染水平也可能自然下降。你看到的前后差异是政策效应和一系列时间趋势的混合体。这个混杂效应不剥离掉结论就不干净。所以研究者需要找一组对照组。这个对照组最好和限行城市高度相似——地理相邻、产业结构相近——但它没实施限行。这样对照组在同时期内的变化就可以代理如果限行城市没有政策会出现什么变化。用处理组的前后差异减去对照组的前后差异剔除了共同的时间趋势剩下的才是政策净效应。这就是双重差分名字的由来第一次差分消除个体异质性第二次差分消除时间趋势。1.2 一张2x2表看懂DID的本质把DID塞进一张四格表组别政策前政策后前后差异处理组ABB - A对照组CDD - C组间差异A - CB - D(B-A)-(D-C)右下角那个差分值就是DID估计量。它的数学表达式很简单[ DID ( \bar{Y}{T,after} - \bar{Y}{T,before} ) - ( \bar{Y}{C,after} - \bar{Y}{C,before} ) ]放在回归框架里变成大家最熟悉的形式[ Y_{it} \alpha \beta_1 Treat_i \beta_2 Post_t \beta_3 (Treat_i \times Post_t) \gamma X_{it} \epsilon_{it} ]其中(\beta_3)就是政策净效应。理解这个回归式的关键在于(Treat_i)吸收组别差异(Post_t)吸收时间趋势交互项才是政策效应。这在后面的Stata操作中反复出现。1.3 一个必须理解的前提平行趋势假定DID能成立核心不是数据有多好而是满足平行趋势假定如果没有政策干预处理组和对照组的结果变量应该沿着平行路径演变。这个假定绕不过去。没有它DID就只是两个差分的机械减法。理解平行趋势最好的方式是找一个生活类比。想象两棵相邻的树一棵施肥处理组一棵不施对照组。两棵树生长速度本来就差不多施肥后长得更快。你观察到的长势差异可以归因于肥料。但如果两棵树的初始土壤条件完全不同一棵在肥沃地一棵在贫瘠地即便不施肥生长速度也会不一样——这时候用对照树去模拟处理树如果没有肥料会怎样就不成立了。2. 数据准备先确认你的数据长什么样跑DID之前90%的问题出在数据形态上。见过太多人拿着一份只有几十行的截面数据就试图跑DID。先搞清楚DID需要什么数据。2.1 面板数据与重复截面数据DID最常见的载体是面板数据每个个体城市、企业、个人被追踪多个时期。面板数据里你能控制个体固定效应把不随时间变化的个体特征全部吸收掉这是DID最理想的形态。没有真实面板怎么办重复截面数据也可以做DID。比如每年调查对象不同但调查设计稳定你可以从不同年份中抽取政策实施前和政策实施后两波样本。此时没有个体固定效应可用但依然可以控制年份和组别效应。核心要求是每个细胞处理组-政策前、处理组-政策后、对照组-政策前、对照组-政策后都得有足够样本量。无论哪种数据关键变量有三类标识变量个体ID和年份/时期变量。分组变量该个体是否属于处理组(Treat)。时间变量该时期是否处于政策实施之后(Post)。2.2 构造交互项小心这种常见错误基准回归里的核心解释变量是(Treat \times Post)。在Stata中你可以用gen命令直接生成新变量gen did treat * post这是最简单直接的做法。但如果你在回归里写成下面这样reg y treat post treat#post, vce(cluster id)注意了treat#post和treat*post不完全等价。前者会生成全交互包括同一个变量的虚拟变量的展开形式在某些情况下会多出一堆变量。更稳妥的方式是显式定义交互项或者使用c.treat#c.post告诉Stata这是两个连续变量的乘积。实际操作中我更推荐先gen did treat * post然后在回归里直接放did变量简单明了不易出错。焦虑点提醒生成交互项之前先确认treat和post都是0/1二值变量。如果treat是连续变量比如政策强度那DID就变成了强度DID即连续型DID模型解释要相应调整。2.3 数据形态检查清单不管你是从原始数据库如中国工业企业数据库、CNRDS、CSMAR下载的数据还是自己构建的平衡面板建议在跑回归前先做这几步检查xtset id year确认面板声明成功没有报repeated time values错误。tab treat post看看四类样本量是否充足有没有出现某类细胞为0的情况。summarize y treat post did观察变量的量纲和取值范围确认没有异常值。这一步花十分钟回报巨大。很多人跑出来的DID结果极其诡异回查数据发现处理组和政策后时期的交叉项全是0——那还回归什么。3. Stata基准回归从命令到结果解读的完整梳理数据搞定进入核心环节运行DID回归。这里介绍三种命令本质估计量相同但使用场景和输出细节有差异。3.1 基础OLS回归写法最经典的写法直接对前文的回归方程对应reg y treat post did ctrl1 ctrl2, vce(cluster id)几个关键点逐一说明。第一标准误。DID回归的扰动项往往在个体内自相关所以一定要聚类到个体层面即政策评估的单元写成vce(cluster id)。如果你的处理变量在更高级别变化比如要研究省级政策处理组按省划分聚类层级至少要省级。Cameron等人的经验法则是聚类数量太少少于30或40个时聚类稳健标准误可能失效此时可考虑bootstrap或野生聚类自助法。第二控制变量。控制变量加入原则在DID里被经常误解。大家以为控制变量加得越多越好其实不然。加控制变量的目的是缓解释义时段变化的混杂因素而不是让R²更高。要警惕坏控制变量政策实施后受到影响的结果变量中介变量、事后变量不能进回归否则会引入偏差。这类问题在实证文献中被称为坏控制bad control问题。我的经验是基准回归先不加控制变量跑一遍再加时变控制变量跑一遍两个结果一起报既展示稳健性也避免控制变量选择争议。第三个体固定效应和时间固定效应。细心的读者会发现reg y treat post did里没有显式添加个体和年份固定效应。实际上广义DID通常用双向固定效应TWFE模型xtreg y did ctrl1, fe i(id) i(year) vce(cluster id)或者等价地reghdfe y did ctrl1, absorb(id year) vce(cluster id)reghdfe是目前最推荐的工具速度快内存占用低还便于吸收高维固定效应。记得先安装ssc install reghdfe这里注意在双向固定效应模型中treat和post的主效应已经被个体固定效应和时间固定效应吸收了所以只需放交互项did。这是初学者最容易困惑的点——为什么reg写法里有treat和post而xtreg写法里不需要了。3.2 diff命令傻瓜式但有用Stata社区还有一个外部命令diff专门为DID优化ssc install diff diff y, t(treat) p(post) cov(ctrl1 ctrl2) robustt()里面放处理组变量p()里面放政策前后变量cov()是控制变量。运行后输出会自动给出处理组前后均值、对照组前后均值、双重差分估计量和t统计量还会给出多种标准误版本常规、稳健、聚类等。说实话我论文初稿有时候也用diff快速扫一眼结果因为它输出格式很像教材方便检查估计量是否稳健。但正式写作时我更倾向reghdfe——控制更细致形式更清晰便于加固定效应和高维固定效应。3.3 结果解读β₃到底说明什么无论用哪种命令核心关注点都是交互项did的系数。这个系数为正表示政策导致结果变量显著上升为负则意味着政策降低结果变量。判断标准看系数大小、显著性水平p0.05或p0.01以及经济显著性系数量级是否有现实意义。以前阵子做一个最低工资调整评估为例交互项系数是-0.08p0.03。说明最低工资调整使得就业概率平均降低8个百分点。如果只报系数和p值不解释经济意义审稿人一定会挑刺。8个百分点在这种研究背景里是不小的效应但我也会告知读者这一效应主要集中在低技能劳动者群体平均效应掩盖了异质性。4. 平行趋势检验DID成立的生命线基准回归跑完系数好看还不够。同行评审看到DID结果时第一反应几乎都是你如何保证平行趋势这个关口过不去前面都是白搭。4.1 事件研究法把时间动态完全展开平行趋势检验使用最普遍的方式是事件研究法event study也被称为动态DID。基本思想不把政策效果压缩成一个单一系数而是把政策实施前后每一期的处理效应都估计出来。如果政策前的各期系数统计上不显著异于0说明处理组和对照组在政策前没有系统性差异平行趋势成立。实现方式把政策时间作为事件时间event time生成一系列虚拟变量比如pre4表示政策前第4期post3表示政策后第3期然后跑如下回归reghdfe y pre4 pre3 pre2 post1 post2 post3, absorb(id year) vce(cluster id)通常以政策前一期或政策当期作为基准组省略不加入模型否则会遇到完全共线性。注意我这里用公式表示某期效应实际操作中用gen pre2 (event_time -2)等命令逐期生成虚拟变量。还有一种更快的方式是用coefplot直接看系数图coefplot, keep(pre4 pre3 pre2 post1 post2 post3) vertical yline(0) xline(2.5)如果政策前各期系数置信区间跨越0线说明平行趋势得到直观支持。政策后的系数若是渐进上升或下降还能读出动态效应——政策起效的时滞、持续性如何。4.2 怎么做事件时间变量一个实操细节构造事件时间变量听起来简单但有个常见的坑政策时间不是同一个时点怎么办。比如你要评估的是各省陆续实施的某项试点不同省份的政策后第1期对应不同日历年份。标准做法是使用相对时间gen event_time year - policy_year if treat 1 replace event_time 0 if treat 0重点对于从未受政策影响的对照组event_time统一设为0或一个固定值并且不能参与事件研究虚拟变量的生成否则对照组被错误地当作政策当期。通常建议对照组单独处理保持treat0的样本在所有pre/post虚拟变量中都是0。4.3 平行趋势检验没通过怎么办先说结论如果平行趋势检验明显不通过别硬着头皮隐瞒或伪造结果。有几种正路可走。第一尝试调整样本期。政策实施前的长窗口中可能存在特定年份的冲击比如某一年突然有个全国性政策影响了所有地区。适当缩短事件窗口或添加省份特定线性时间趋势i.id#c.year有时能缓解。第二使用匹配方法预处理数据PSM-DID。通过倾向得分匹配把处理组和对照组的可观测特征拉近再在匹配样本上做DID。这在一定程度上缓解了可观测变量不平衡造成的平行趋势偏离但注意它不能解决不可观测的遗漏变量问题。第三使用合成控制法。如果对照组和处理组存在严重不可比性合成控制法用多个对照地区加权构造一个合成处理组它的平行趋势表现通常更好。5. 安慰剂检验证明政策效果不是窗外的运气平行趋势检验回答的是政策前是否可比安慰剂检验则回答政策效果是否可信——你是不是捡了一个统计上偶然出现的显著结果。5.1 常规思路一随机分配处理组原理把处理组标签在所有样本中随机打乱这样理论上就不存在真实的政策效应。重复几百次看看随机分配下的t值分布。如果真实的政策效应不是靠运气那么真实的t值应该处在随机分布的极端尾部偏大或偏小。具体操作步骤* 假设真实的t值为 real_t forvalues i 1/500 { gen random_treat runiform(0,1) sort random_treat gen pseudo_treat _n N_treat // 保证处理组样本量与原数据一致 * 在pseudo_treat和post构成的交互项下跑回归记录系数和t值 local t_pseudo _b[did]/_se[did] }手动写循环效率低更推荐用permutepermute did _b[did], reps(1000) seed(12345) saving(perm_results.dta) nodots: reghdfe y did, absorb(id year) vce(cluster id)跑完后把真实系数和安慰剂系数的分布对比画出系数分布图。如果真实系数在安慰剂分布的边缘说明你的结果不是随机噪声。5.2 常规思路二提前政策时间另一种安慰剂做法是虚构政策时点。比如真实政策发生在2018年你假设政策发生在2015年并删除2018年以后的样本以免真实政策污染然后跑DID。如果虚构政策产生了显著的假效果说明原结果可能捕捉的是假趋势而不是真实政策效应。这个做法的直觉是如果在政策还没发生之前处理组和对照组的差异已经显著那说明两组本身趋势不同真实政策效应可能是虚假的。5.3 画图展示安慰剂结果我在论文里最常用的展示方式是系数分布图。横轴为安慰剂估计系数纵轴为密度或核密度真实系数用一个竖直线标记。如果竖直线落在分布主体之外读者一眼就能看出政策效果是真实存在的。用twoway kdensity或者histogram都能快速绘制。不建议只报做了500次安慰剂平均系数接近0少数显著这种模糊说法。审稿人想看到的是分布、具体排除随机性的证据、设定的充分性。6. 从基准回归到发表级结果高阶问题和踩坑经验写完基准回归、平行趋势检验、安慰剂检验的代码理论上文章的实证部分已经能看了。但从能看到发表中间还隔着几座大山。这是我这几年被拒稿和审稿别人论文时攒下的体会。6.1 多期DID的时代不能再假装所有政策同一时点发生过去的DID教学常假设政策在某一时点统一实施处理组和对照组清晰分明。但现在越来越多的政策是逐步试点、分批推开的不同的处理组在不同的时点开始受政策影响。这种情形下传统双向固定效应模型面临着异质性处理效应的困扰——不同组别在不同时间接受处理时TWFE估计量可能不是个体处理效应的加权平均甚至可能出现负权重问题。如果是多期DID建议首先做Bacon分解Goodman-Bacon分解看看TWFE估计量如何由各种2x2比较构成。如果存在大量早处理组作为后处理组的对照组的情形考虑使用Callaway和SantAnna提出的csdid命令或者Sun和Abraham的eventstudyinteract命令。报告时同时给出TWFE和异质性稳健估计量作为对照。ssc install csdid csdid Y, ivar(id) time(year) gvar(first_treat_year) method(dripw) estat event这个命令输出的动态效应图比我手动写的event study图要规范得多推荐直接用。6.2 控制变量控制到什么程度前文说了坏控制问题。这里再补充一条经验控制变量一旦进入回归就要在论文里交代选取理由。常见的组合是地区层面的经济特征人均GDP、产业结构、人口密度、企业层面的基本特征规模、年龄、所有制。不要过度控制——控制变量过多会造成过度调整偏差尤其当控制变量与处理变量高度相关时标准误会被夸大估计量不再有效。如果审稿人质疑你们是否控制了XX变量最稳妥的策略是展示一个表格第一列不含控制变量第二列加一组第三列加另一组第四列全加。系数如果稳定你的结论就坚固系数如果翻来覆去变号问题就出在样本或模型上。6.3 聚类稳健标准误、Bootstrap法和幸存者偏差杂谈聚类层级选择是个高频争议点。处理变量在省级变化但聚类到省级时只有不到10个省聚类稳健标准误会过于激进聚类到更细的县级又可能低估标准误。实践中我倾向于至少提供两个层级的标准误比如省级和县级或者采用多层聚类。Stata 9.2及以上版本支持vce(cluster id1 id2)的双重聚类。另一个被低估的问题是进行DID的面板数据是否平衡。如果不平衡某些个体可能在政策前后都出现退出造成样本选择性。一个简单的处理是先检查各时期样本量差异如果是企业数据还要考虑企业进入退出带来的存活偏差必要时使用xtbalance或者估计面板attrition模型。6.4 结果呈现的小规范最后说论文写作中那些小事:表格里要标出标准误的聚类层级不要写括号内为标准误就完事。显著星星标到10%、5%、1%三级并说明是双尾检验。表格注释里写清楚样本量、R²、固定效应范围。如果用了reghdfe报告里要说明被吸收的控制变量数No. of absorbed FE。一个规范的结果表格能让审稿人在30秒内抓住你的识别策略而不是追问一堆细节。把这个做好了比任何花哨的计量方法都能提升论文的接受概率。工具命令汇总一下方便之后按图索骥目的Stata命令说明面板声明xtset id year回归前必做基准回归reghdfe Y did, absorb(id year) vce(cluster id)首选吸收高维固定效应快速诊断diff Y, t(treat) p(post) robust快速查看DID结果平行趋势reghdfe 事件时间虚拟变量政策前系数应不显著系数图coefplot画事件研究系数图随机安慰剂permute随机分配处理组做安慰剂多期DIDcsdid异质性稳健DID估计量Bacon分解bacondecomp诊断TWFE估计量构成我在实际做项目时最常提醒自己的就是那句老话DID只有两条路能走通——要么你的平行趋势足够漂亮要么你的政策冲击足够外生。跑一百遍回归不如把这两件事想透。这篇文章给了你从数据准备到最终表格的全部操作路径剩下的就是对着你的真实数据动手跑一遍遇到报错再回头查跑完再想解释。这个循环才是真正学会DID的路。
返回列表