“全面创新改革试验区DID(2007—2023)”这个标题,懂行的人一看就知道,这是一篇典型的政策评估实证研究:用双重差分法(DID)去识别某个区域创新试点政策的经济效应,样本区间拉到了2007到2023年。很多做区域经济、产业政策、创新管理方向的研究生和年轻学者,拿到类似课题后最头疼的往往不是跑回归本身,而是从数据清洗、变量构造、模型设定到稳健性检验这一整套流程怎么串起来,中间任何一个环节出错,结论都可能站不住脚。
这篇文章我会把这个题目拆开揉碎讲清楚:为什么用DID、样本区间怎么定、变量怎么造、基准回归代码怎么写、平行趋势和安慰剂检验怎么落地,以及我在实际跑数据时踩过的那些坑。内容偏微观实操,适合经管类硕博、科研院所研究人员,以及所有需要处理“区域试点政策评估”类课题的人参考。
1. 项目整体设计与思路拆解
1.1 标题背后到底是什么研究
“全面创新改革试验区DID(2007-2023)”,这句话拆开看有三个信息点:
- 全面创新改革试验区:研究对象,一个区域性的试点政策,重点在“创新”和“改革”两个词上。这类试验区通常以城市或城市群为基本单元,分批设立,不同批次之间有时间差。
- DID:核心识别策略,利用政策作为准自然实验,比较处理组(试验区城市)和对照组(非试验区城市)在政策前后结果变量的差异。
- 2007-2023:样本区间,通常意味着一个跨度17年的大面板数据。
整个研究的核心问题就是:试验区政策到底对当地的创新产出、产业升级或经济增长产生了什么影响?这个问题本身并不复杂,复杂的是如何在非随机的政策安排下,把政策效应从混杂因素里剥离出来。
DID之所以在这个场景下好用,是因为它同时做了两次差分。第一次比较处理组在政策前后的变化,第二次比较对照组在政策前后的变化,两者相减,就消除了那些不随时间变化的组间固有差异(比如沿海和内陆、大城市和小城市),也消除了随时间变化的共同冲击(比如宏观周期、全国性趋势)。剩下那个交互项系数,就是净政策效应。这个概念用大白话说就是:你先看看自己吃了药之后体温降了多少,再看看隔壁没吃药的人自然退了多少,两者一减,才是药的真正效果。
1.2 样本区间为什么从2007划到2023
区间选择不是随便拍的。很多刚接触DID的人上来就会问:既然政策是2015年前后推出来的,那我只要2012到2018年的数据不就行了吗?实际上,只有政策前后的短窗口,是远远不够的。
拿2007-2023这个区间来说,它的核心价值在于给了研究者足够的政策前窗口和事件后窗口。政策前有7到8年的数据,你才能做平行趋势检验——也就是验证处理组和对照组在政策发生之前,结果变量的变化轨迹是否一致。这是DID最基本也最重要的前提假设。如果只拿政策前两年的数据,趋势有没有偏离根本看不出来。
区间长还有一个额外好处:可以做事件研究法,把政策效应逐年画出来。这不仅仅是检验平行趋势的工具,还能直观展示政策的动态影响。比如政策是第一年见效还是第三年才见效,是持续递增还是先升后降,这些问题在总体平均效应里是看不到的,但画一张动态效应图就能一目了然。
2023年这个节点也比较好理解,对于微观或者城市层面的公开数据来说,那基本是实证研究中当期能够获取完整数据的最新年份。即便你把数据更新到2024甚至2025,结论方向大概率也不会本质性改变,但2007-2023已经覆盖了两个完整的经济周期阶段,数据规模足够支撑稳健性检验。
1.3 渐进试点带来的识别挑战
这里必须多说一句:全面创新改革试验区不是同一天全部批复的,而是分批推进的。这意味着政策的“处理时点”在不同城市之间不一样。这种情形在计量上叫交错DID(staggered DID)。
交错DID听起来只是把post变量改成“城市i在t年是否已经进入处理状态”,但实际操作中有一个非常隐蔽的问题:较早被处理的组会变成较晚被处理组的“对照组”,而较晚被处理的组反过来也会变成早期组的“对照组”。一旦政策效应存在动态变化,这种互相充当对照的做法会让传统双向固定效应估计出来的系数产生偏误。这就是过去几年计量经济学界反复讨论的“坏对照组”问题。
所以这个项目的设计应该分两层:第一层,用传统DID交互项做基准回归,拿到一个大概的效应量级;第二层,用事件研究和其他异质性稳健估计量(比如Callaway-Sant’Anna方法)来检验结论是否稳健。我个人的习惯是,基准结果可以保守一点,但检验部分一定要做全,这样才能扛住审稿人的质疑。
2. 数据准备与变量构造
2.1 数据来源与面板样本构建
做区域政策评估,数据来源不外乎几类:统计年鉴、城市数据库、专利数据库、企业工商数据。具体要看结果变量选什么。如果关注创新产出,最常用的是城市层面的专利授权量、专利申请量,也可以进一步细分到发明专利、实用新型;如果关注经济绩效,可以用实际GDP、人均GDP、全要素生产率;如果关注结构调整,可以用第二三产业占比、高技术产业产值等。
把城市数据整理成面板,需要注意几个前置问题:
- 数据口径统一:早年统计指标的口径和近年可能不一样,比如GDP核算方法、规模以上工业统计范围,都需要仔细核对指标说明。
- 行政区划调整:2007到2023年之间,有些城市的区划发生过调整,比如撤县设区、地级市升格等,如果不处理,面板会断裂。
- 价格平减:凡是涉及货币计量的变量,都需要以某一年为基期做价格调整,否则跨年比较没有意义。
我建议把样本建成非平衡面板,不需要强行填补缺失值,因为固定效应模型本身能容纳非平衡面板。但你一定要确认每个城市在政策前后都有足够的数据点,特别是处理组,政策前至少要有3年以上观测值,否则连趋势都画不出来。
2.2 核心变量定义与口径选择
DID模型里的核心变量有三个:处理组标识、政策时间标识、交互项。看起来简单,真正做的时候全是对细节的考验。
处理组标识(treat):一个城市是否被纳入全面创新改革试验区。这个变量在处理期内不随时间变化,所以它本身在固定效应模型里会被吸收掉。这里最容易犯的错是:如果你用的是xtreg fe,却发现treat变量显示“omitted”,不用慌,这不是bug,而是因为treat不随时间变化,被个体固定效应完全吸收了。
政策时间标识(post):政策在哪一年算“开始”?原则上应该以正式批复文件的时间为准,但实际操作中要小心政策预期效应。如果企业在批复之前就已经知道消息并开始调整行为,那么结果变量可能在正式批复前一年就已经出现反应。稳妥的做法是,以批复当年作为政策起点,同时把批复前一年作为预期期单独放一个虚拟变量,检验是否存在预期效应。
交互项(did):常规做法是did = treat × post,系数就是平均处理效应。但在多批次推进背景下,我更倾向于不手动生成post,而是用一个变量treatment_status = treat × (year >= policy_year)来表示“该城市在该年份是否已经处于处理状态”。
结果变量(y):建议至少用两个维度的指标互相印证。比如主回归用发明专利授权量(取对数),稳健性检验用人均GDP或全要素生产率。单一指标容易被质疑,多指标交叉验证才稳。
控制变量(X):通常包括经济发展水平(人均GDP对数)、财政支出强度、人力资本(高校在校生人数占比)、金融深化程度(存贷款余额/GDP)、基础设施(人均道路面积)、外商直接投资强度等。控制变量不需要贪多,把那些同时影响政策选择和结果变量的因素控制住即可,控制太多反而会引入坏控制变量问题。
2.3 数据清洗中容易踩的坑
我做这类项目踩过的坑大概能写半页纸,最典型的几个:
第一个是城市名称匹配问题。不同数据库对同一城市的称谓可能不同,比如“市辖区”和“全市”指标混在一起,或者有的数据库用城市代码,有的用城市名称。合并之前必须先把城市名称标准化,最好统一到行政区划代码,同时保留一个城市名称列用于核对。
第二个是缺失值处理。很多数据库对早期年份的指标缺失得厉害,我不建议用均值填补、插值法这类方法去制造数据。比较可靠的做法是:如果某个城市的核心指标缺失超过30%,直接把这个城市从样本中剔除;如果是中间个别年份缺失,可以用相邻年份的线性插值,但要在稳健性检验中报告不插值的结果。
第三个是极端值。像专利授权量这种变量,个别大城市动辄几万件,小城市可能只有几十件,分布严重右偏。一般做法是对连续变量取对数,同时对控制变量在1%和99%分位上做缩尾处理,避免个别异常城市拉动整个回归。
3. 模型设定与实证策略
3.1 基准DID模型的设定逻辑
基准模型是双向固定效应:
Y_it = β × (treat_i × post_t) + γX_it + μ_i + λ_t + ε_it
其中μ_i是城市固定效应,λ_t是年份固定效应。城市固定效应控制所有不随时间变化的城市特征,年份固定效应控制所有城市共同面临的宏观冲击。β就是政策效应的核心估计量。
这里有一个容易被忽略的概念问题:为什么有了城市和年份固定效应,还要随机扰动项聚类到城市层面?因为同一个城市在多年间的扰动项通常是相关的,如果不处理,标准误会严重低估,导致t值虚高。实际操作中,vce(cluster city_id)是标配。如果样本量够大,还可以考虑城市和年份双向聚类,但一般城市层面聚类已经够用。
还有一点:既然有了双向固定效应,控制变量X加不加,其实对β的影响通常不大——这正是DID干净的地方。但如果政策落地和某些城市特征相关,加入控制变量可以减少扰动项方差,让标准误更小。判断控制变量是否合格有一个简单的标准:它在政策前应该基本平衡,或者说它不能被政策本身反向影响。
3.2 平行趋势检验与事件研究
平行趋势检验是DID研究里审稿人必看的内容。做法是把政策前的年份分成若干期,生成一系列虚拟变量,放在模型里做回归。最常见的是事件研究模型:
Y_it = Σβ_k × (treat_i × D_kit) + γX_it + μ_i + λ_t + ε_it
其中D_kit表示距离政策实施k年的虚拟变量,k取负值表示政策前,正值表示政策后。通常把政策前第一期(k=-1)作为基期省略,其他年份的系数画成图。
看图的要点有两个。第一,政策前各期的系数估计值应该在0附近,且置信区间包含0,这代表处理组和对照组在政策前没有显著差异趋势。第二,政策后的系数如果显著为正或负,说明政策效应开始显现,而且可以看到效应是瞬时出现还是逐渐累积。
我在实际操作中遇到过一种情况:政策前的点估计基本不显著,但个别年份(比如k=-4)置信区间勉强擦边。这时候不用慌,追加一个联合检验,看政策前所有虚拟变量是否联合为零。只要联合检验的F统计量不显著,平行趋势假设就可以接受。
3.3 安慰剂检验与稳健性验证
平行趋势检验只能排除可观测趋势的差异,无法排除不可观测因素。所以还要做安慰剂检验。思路很简单:人为把政策时间提前2到3年,或者随机抽取一部分城市作为假处理组,重新跑一遍DID。如果提前时间后的“政策效应”显著了,说明原结果可能来自某些共同趋势而不是真实政策;如果随机抽取的处理组跑出来的t值大部分不显著,就能说明原结论不是碰巧跑出来的。
随机化安慰剂检验的具体操作我可以给个思路:先把所有非处理组城市作为候选池,随机抽取和真实处理组相同数量的城市作为假处理组,生成假的did交互项,跑回归记录系数和t值,重复300到500次。然后画出t值的分布图,看真实回归的t值是不是落在分布之外。如果落在分布边缘,说明真实效应可能只是随机波动的产物,那就危险了。
其他稳健性检验还包括:更换结果变量、更换聚类层级、剔除特定样本(比如当年行政级别较高的城市)、排除其他同期政策干扰、用PSM-DID重新匹配对照组等。不需要全做,但至少要把审稿人最容易问到的两三项提前准备妥当。
4. 实操过程与关键代码实现
4.1 面板数据整理与预处理
第一步永远是把数据整理成长格式,也就是每一行是一个城市在某一年份的观测。Excel里那种一个城市占一行、年份横着排的宽格式是做不了面板回归的。
数据结构大概是下面这样:
| city_id | year | treat | post | did | y | x1 | x2 |
|---|---|---|---|---|---|---|---|
| 110000 | 2007 | 1 | 0 | 0 | 12.35 | 10.21 | 0.18 |
| 110000 | 2008 | 1 | 0 | 0 | 12.41 | 10.25 | 0.19 |
| 110000 | 2015 | 1 | 1 | 1 | 12.92 | 10.48 | 0.22 |
| 120000 | 2007 | 0 | 0 | 0 | 11.05 | 9.87 | 0.15 |
然后设定面板:
xtset city_id year这一步很重要,如果面板没设定对,后面所有xtreg、xttest等命令都会报错或给出错误结果。
生成交互项时,我建议拆开来生成,别在回归命令里现场乘:
gen did = treat * post从工程角度看,单独生成一列可以随时检查有没有错误值,也方便后续做异质性分析时重新组合。
4.2 基准回归与标准误选择
基准回归我通常跑两种设定,第一种是基本盘,第二种是完整版:
* 简洁版:只有核心交互项和双向固定效应 xtreg y did i.year, fe vce(cluster city_id) * 完整版:加入控制变量 xtreg y did x1 x2 x3 i.year, fe vce(cluster city_id)这里解释一下为什么没有把treat单独放进去:xtreg加上fe选项后,模型内部做了组内离差变换,所有不随时间变化的变量都会被消掉,treat自然包含在内。如果你用的是reg y treat post did i.city_id i.year,那treat也会被系列虚拟变量吸收,结果是一样的,只是会非常占内存。
看回归结果时,重点看did那一行的系数和标准误。系数表示政策带来的平均效应,比如0.15就代表试验区城市相比非试验区城市,在政策后结果变量多增长了15%。标准误决定显著性,如果聚类标准误明显大于普通标准误,说明城市内扰动项确实存在正相关,这一点务必在论文里写清楚。
4.3 动态效应图与安慰剂检验代码
事件研究是DID项目里最出效果的部分。我常用的代码思路是:先算出每个城市相对政策时点的时间距离,再生成一系列虚拟变量。
* 生成相对时点:政策前为负,政策当年为0,政策后为正 gen rel_time = year - policy_year if treat == 1 replace rel_time = -99 if treat == 0 * 生成事件虚拟变量(这里以-5到5年为例,基期设为-1) forvalues j = -5/5 { gen D_`j' = (rel_time == `j') if treat == 1 replace D_`j' = 0 if treat == 0 } * 去掉基期D_-1,放入回归 xtreg y D_-5 D_-4 D_-3 D_-2 D_0 D_1 D_2 D_3 D_4 D_5 x1 x2 i.year, fe vce(cluster city_id)用coefplot命令可以快速画图:
coefplot, keep(D_*) vertical /// yline(0) xline(6, lpattern(dash)) /// ci((95 90)) /// xlabel(, labsize(small)) /// title("动态处理效应")注意xline的位置取决于回归中变量的排列顺序,这里虚拟变量是按时间顺序生成的,基期被跳过,所以政策时点在排列中的位置要自己数清楚。
安慰剂检验可以写一个循环:
* 伪随机抽城市500次,记录t值 forvalues i = 1/500 { preserve * 随机抽取与处理组数量相同的城市作为伪处理组 gen rand = runiform() sort rand gen fake_treat = 0 replace fake_treat = 1 in 1/N_treat * 生成新的交互项并回归 gen fake_did = fake_treat * post xtreg y fake_did x1 x2 i.year, fe vce(cluster city_id) * 提取t值并保存 mat t_`i' = _b[fake_did] / _se[fake_did] restore }500次循环跑下来,把保存的t值画成核密度图,加上真实回归的t值竖线。如果竖线落在分布之外,美丽的安慰剂检验就做完了。
5. 常见问题与排查技巧实录
5.1 政策时点不统一的情况
多批次政策最麻烦的地方在于:不同城市进入处理状态的时间不同。如果直接设定统一的post,比如“2015年及以后为1”,就会出现某些城市还没有被批复就被当成处理组的情况,这会压低估计值。
正确做法是生成一个“该城市该年是否已处于处理状态”的变量。在数据表里增加一列policy_year,表示各处理组城市的实际政策年份,然后:
gen post_city = (year >= policy_year) if treat == 1 replace post_city = 0 if treat == 0 gen did = treat * post_city用这个did去做回归,本质上就是多期DID。如果担心交错DID的偏误问题,至少要把事件研究的结果和基准结果对比着看,两者方向一致就不用太担心。
5.2 标准误聚类与多重政策干扰
标准误的选择直接影响显著性判断。我见过不少初稿在城市层面聚类不显著,换成稳健标准误就显著了,审稿人一旦要求换回聚类标准误,结论马上就塌了。所以从一开始就应该用聚类标准误。如果城市数量比较少(比如只有几十个),聚类标准误可能偏小,可以考虑用wild cluster bootstrap方法修正,这在行业里已经是比较常规的稳健性操作。
多重政策干扰也是个高频问题。样本期内可能有其他经济特区、自贸区、自主创新示范区等政策同时落地,这些政策同样会影响处理组城市。排查办法是:把这些政策变量也加入回归,或者在稳健性检验中剔除同时受其他政策影响的城市。如果处理组城市里有一大批同时进入了其他政策范围,那要明确在论文里讨论结果可能受到多重政策叠加影响。
5.3 结果不显著和对照组合理性
DID跑出来不显著不一定代表政策没有效果,可能是对照组选得不好。最典型的问题:试验区多数集中在经济体量大、科教资源丰富的城市,而对照组包含了大量欠发达城市,这就导致处理组和对照组在政策前就不具备可比性。
改进思路是做一个PSM-DID:先在政策前年份用城市特征预测进入处理组的概率,然后按倾向得分给每个处理组城市匹配一个特征最接近的对照组城市,再在匹配后样本上跑DID。匹配的作用不是替代DID,而是让对照组在可观测特征上与处理组更接近,提升可比性。
5.4 代码与数据调试经验
最后写几条工程层面的调试经验。如果你发现did系数一直在回归结果里显示omitted,先检查是否treat或者post变量存在全为0或全为1的情况;如果事件研究图里政策前系数的置信区间异常宽,先检查是不是某些政策前年份只有极少数的处理组城市有数据;如果xtreg报错说面板不平衡没关系,但如果报错说“repeated time values in panel”,那一定是你把年份变量重复了,去查是不是数据合并的时候有重复行。
这类问题大多数时候不是模型的问题,而是数据管理的问题。我通常会花70%的时间在数据清洗和变量构造上,真正跑回归的时间反而是最短的。数据干净了,模型自然就跑得动。
6. 写在最后的一些体会
做“全面创新改革试验区DID(2007-2023)”这个项目,我最大的感受是:方法本身不复杂,复杂的是把一个看似标准的研究设计做到让审稿人挑不出刺。平行趋势画好了,安慰剂做完了,坏对照组问题也回应了,这个题目的大框架就算是立住了。至于系数显著不显著,反而没有那么重要——实证研究的价值在于把真实效应估计出来,而真实世界里的政策效果并不都是正向的。
最后再分享一个小技巧:所有生成变量的命令,尽量保留一份do文件,每个变量都加注释说明生成逻辑。这个项目从数据处理到最终成稿,中间至少会经历三轮大的修改,没有清晰的代码注释,返工的时候你可能会怀疑这些数据是不是自己处理的。数据会骗人,代码注释不会。