复现论文这件事,圈里人有个共识:跑通代码只是最后一步,真正的活全在前面。这篇排污权交易机制与企业全要素生产率的研究,就是一个很典型的例子——研究问题不复杂,识别策略是经典的多期DID,数据来自上市公司公开财报,看起来没什么门槛。但我从拿到论文到完整复现出基准结果、平行趋势图、安慰剂检验和稳健性检验,实打实花了两周多,大部分时间都消耗在处理口径、匹配城市、核算TFP这些"看不见的工作"上。
这篇博文就完整展开一下我的复现过程。里面会涉及数据从哪来、样本怎么筛、TFP怎么算、DID怎么建、坑在哪里、检验怎么做,最后给出一个可以直接套用的复现包结构。适合正在复现经济学实证论文的研究生,也适合想入手DID应用但找不到完整链条的从业者。我会尽量把每一步的"为什么"也讲清楚。
1. 复现之前必须想明白的研究设计与识别策略
1.1 排污权交易机制为什么会作用于TFP
先把这个研究的底层逻辑捋一遍。排污权交易机制的核心,是把原本免费的排污行为变成一种有价格约束的资源占用。企业的排污权不再是无限量取用,而是先有偿取得、再市场化交易。从企业决策的角度看,这相当于给生产函数里加了一条新的成本约束线。
这条约束会怎么影响全要素生产率?理论上存在两个相反方向的作用力。一个方向是"倒逼效应":排污成本上升后,企业会重新审视自己的要素组合,淘汰低效率高排放的产能,把资源挪到更高效的工艺上,生产率可能因此上升。另一个方向是"遵循成本效应":企业为了达标,被迫把真金白银投到末端治理,挤占了研发和生产的资源,生产率可能反而下降。净效应是正是负、在什么条件下为正,这就是论文要回答的核心问题,也是我们复现时要通过数据去验证的部分。研究设计上不能简单比较"有政策的城市"和"没政策的城市"的TFP差异,因为试点地区的选择本身可能就和经济发展水平、环保压力有关,直接比会混淆。解决思路就是利用政策分批推开的时间差,在企业和年份两个维度上做双重差分,把那些不随时间变化的地区禀赋差异、不随地区变化的宏观冲击都控制掉。
1.2 双重差分(DID)在这个场景里是怎么构成的
DID的基本思路一句话就能说清楚:用"处理组在政策前后的变化"减去"控制组在政策前后的变化",剩下的部分就是政策的净效应。放到这个项目里,处理组就是注册地属于试点地区的上市公司,控制组是注册地不在试点地区的上市公司,政策冲击时点就是排污权交易制度在某地真正落地生效的年份。
更精确地说,这个场景用的是多期DID(也叫交叠DID、staggered DID),因为不同试点地区进入政策的时间不一样。模型的基本形式是:
[ TFP_{it} = \alpha + \beta \cdot Treat_i \times Post_t + \gamma X_{it} + \mu_i + \lambda_t + \varepsilon_{it} ]
其中 (Treat_i \times Post_t) 在个体 (i) 属于试点城市且年份 (t) 已经进入政策期时取1,否则取0。(\beta) 就是我们要复现出来的核心系数。(\mu_i) 是企业固定效应,吸收企业层面不随时间变的特征;(\lambda_t) 是年份固定效应,吸收所有企业共同面对的宏观冲击。这个模型放在Stata里,用reghdfe几秒钟就能跑完,但前提是你前面构造出来的 (Treat)、(Post)、(TFP) 都是对的。
1.3 复现前的资料清单和获取路径
开工之前,先把需要的东西列清楚。我整理了一份清单,按这个备料基本不会中途卡壳:
| 资料类型 | 具体内容 | 常见来源 |
|---|---|---|
| 上市公司财务数据 | 营业收入、员工人数、固定资产、中间投入近似变量、折旧摊销、应付职工薪酬等 | CSMAR、Wind等常用金融数据库 |
| 上市公司基本信息 | 证券代码、注册地城市、成立年份、行业代码、上市日期 | 同上 |
| 政策试点名单 | 纳入排污权有偿使用和交易试点的城市/省份名单及批次 | 政策主管部门发布的公开文件、论文附录 |
| 相关控制变量 | 企业规模、资产负债率、企业年龄、行业竞争度等 | 上市公司财报计算 |
| 论文原文及附录 | 样本筛选规则、变量定义表、描述性统计 | 期刊官网、作者主页 |
这里有一个复现者必须提前建立的意识:论文附件里下载到的"数据"往往不是原始数据,而是作者整理好的中间文件。你也未必能拿到原始代码。就算拿到了,跑出来的结果也可能因为软件版本、数据更新版本、字段口径不一样而对不上。所以复现的完整含义不是"按下F5跑出同一张表",而是"基于公开可得的数据,用合理的口径重建整条实证链路,得到方向、量级、显著性都与原文一致的结论"。
2. 上市公司样本清洗:可复现性的地基
2.1 样本筛选规则:一条一条对着原文过
数据下载之后,先别急着算指标,第一步永远是筛样本。大多数研究会在正文或者附录里有一段"样本选择"说明,里面每一句话都必须转化成具体的代码逻辑。我这次整理出的筛选规则大概是这么几条:
- 剔除金融类上市公司。金融企业的报表结构特殊,收入构成、资产口径和一般工商企业完全不同,放进TFP模型会产生极大的异常值。
- 剔除ST、*ST以及退市整理期的公司。这些公司处于经营异常状态,财务数据往往严重扭曲。
- 剔除关键变量缺失的样本。营业收入、员工人数、固定资产这些算TFP必不可少的数据,缺了就没法进入回归。
- 剔除上市当年的观测值。上市当年募集资金大量到账,报表科目会发生非经营性跳变。
- 对连续变量进行1%和99%分位的缩尾处理(winsorize),降低异常值对回归系数的干扰。
注意,这些规则不是拍脑袋定的,每一条都要和原文比对。比如有的论文缩尾用的是1%,有的用2%,有的不缩尾;有的把员工人数缺失的企业直接删除,有的用插值法补。规则不同,样本量就不同,回归系数自然不同。我在复现一开始就建了一张"样本筛选追踪表",记录每一步删了多少条、为什么删,这样最后做数据附录时一目了然。
2.2 中间投入与资本存量的口径换算
上市公司财报最麻烦的地方在于,很多理论模型需要的变量它不直接披露。最典型的就是中间投入。TFP测算里的LP法需要一个中间投入变量,但中国会计准则下的利润表你翻遍也找不到"中间投入"这一栏。
我用的近似口径是这样算的:
[ 中间投入 \approx 营业成本 + 销售费用 + 管理费用 + 财务费用 - 折旧摊销 - 职工薪酬 ]
逻辑是:营业成本加上三项期间费用,得到企业生产经营的总流出;其中折旧摊销和职工薪酬属于资本和劳动的报酬,不属于中间消耗,所以要扣掉。这个口径在文献里比较常见,但并非唯一。也有人直接用现金流量表里的"购买商品、接受劳务支付的现金"来替代中间投入。两种口径计算出的TFP序列在水平上有系统差异,如果原文没说清楚它用的哪一种,复现结果对不上是很正常的。
资本存量这边也有坑。固定资产用账面净值还是原值,对资本变量的量级影响很大。理论上生产函数里的资本应该是资本服务量,用原值比净值更接近"生产能力",但很多数据库默认导出的是净值。还有,固定资产只包括厂房和设备吗?要不要把无形资产里的土地使用权也算进去?这些细节都会影响最终TFP数值。我的做法是:先用净值口径算出全套结果,再换原值口径做一次敏感性分析,观察核心结论是否稳定。
2.3 上市公司注册地到试点城市的匹配流程
这一环节最容易翻车,也最容易被新手忽略。财务数据里通常有上市公司注册地址,但注册地址是一个文本字段,里面是"XX市XX路XX号"这种自然语言。政策试点名单里则是"XX市""XX省"这样的行政区划表述。两边要匹配,不能靠眼睛看,必须有一个规范的流程。
我的处理链路是这样的:
- 从数据库导出上市公司的注册地省份、注册地城市字段。
- 把政策名单拆解为"省-城市"两级结构,并整理出对应的城市行政代码。
- 对公司注册地做标准化清洗,重点处理"省直辖县级市""自治州""市辖区"这些特殊表述。
- 用城市名做精确匹配,匹配不上的再做模糊匹配,最后保留一小批人工核对清单。
- 生成企业层面的政策状态变量
treat和post。
第4步是真正的耗时大户。比如有些公司注册在县级市,而试点名单用的是地级市口径,需要逐条确认行政隶属关系;有些城市更名了,旧名称和新名称在数据库里并存,需要做名称映射表。这个环节我没有找到任何能一键解决的现成工具,就是老老实实用 Excel 加人肉核查。如果大家有更好的方法,欢迎评论区补充,但目前我实测下来,这个笨办法最可靠。
3. TFP测算:LP、OP、OLS三种口径各意味着什么
3.1 从残差到生产率:三种方法背后的直觉
全要素生产率没法直接观测,它本质上是一个"剩余项"。柯布-道格拉斯生产函数写成对数形式就是:
[ lnY = \alpha lnL + \beta lnK + \gamma lnM + \omega ]
其中 (\omega) 是产出中不能被劳动(L)、资本(K)、中间投入(M)解释的部分,也就是技术进步、管理效率、组织创新这些因素的合计,被称作TFP。
问题是,直接做OLS回归得到的残差并不是干净的TFP,因为它带着联立性偏差。所谓联立性,是指企业管理者在当年的生产中,看到了自己生产率的变化,然后反过来调整当期投入——比如这年生产效率特别高,老板就会多买原材料、多招工人。这导致投入变量和残差项相关,OLS估计出来的要素弹性是有偏的。
OP法和LP法就是为了解决这个问题提出的。OP法的思路是用企业投资作为不可观测生产率的代理变量;LP法改用中间投入作为代理变量。在上市公司数据里,投资数据(购建固定资产等的现金流出)经常出现大量零值和缺失,而中间投入数据相对好获取,所以LP法在实践中更常用。加上学术界后来还提出了ACF修正,进一步处理了LP法在劳动弹性识别上的问题。我在复现时会同时算LP和OLS两种口径,OP法作为补充。
3.2 在Stata里手算TFP的可靠写法
很多初学者会直接跑别人封装好的命令,但如果你不理解背后的变量逻辑,算错了一时半会也发现不了。我建议至少先手动搭一遍OLS口径的TFP:
* 假设数据结构: stkcd=公司代码, year=年份, city=城市代码 xtset stkcd year gen lny = ln(sales) // 营业收入 gen lnl = ln(staff) // 员工人数 gen lnk = ln(fa_net) // 固定资产净额 gen lnm = ln(material_cost) // 中间投入 * OLS 口径: 用企业固定效应+年份固定效应估计生产函数 xtreg lny lnl lnk lnm i.year, fe predict double yhat, xb gen double tfp_ols = lny - yhat这里的逻辑是:先在生产函数的框架下估计出要素弹性,然后用实际产出减去要素贡献,剩下的残差就是TFP。固定效应模型的好处是能够吸收企业层面不随时间变化的管理能力和技术特征。
LP法可以直接用外部命令prodest实现,但要先安装:
ssc install prodest * LP法: method(lp), free=自由变量(劳动), proxy=代理变量(中间投入), state=状态变量(资本) prodest lny, method(lp) free(lnl) proxy(lnm) state(lnk) id(stkcd) t(year) reps(50) predict double tfp_lp, resid需要注意prodest的具体语法在不同版本之间可能有差异,跑之前先看help prodest。重点不是记住命令格式,而是搞清楚命令里哪些变量对应理论模型里的哪个角色。自由变量(free)指当期可完全调整的投入,状态变量(state)指存在调整成本的投入,代理变量(proxy)用来控制不可观测的生产率冲击。
3.3 算完TFP后必做的三件事
TFP算出来后,不要急着进入回归,先做三个检验,任何一步不对都说明前面的口径有毛病:
第一,描述性统计。TFP的均值、标准差、最小值、最大值要落在合理区间。如果TFP序列跟营业收入的对数值几乎完全一样,很可能是残差没算对,把固定效应也卷进TFP里了。
第二,相关性检验。TFP与企业营业收入应该高度正相关,与行业平均TFP水平的排序也基本一致。如果算出来的TFP和资产规模负相关,那一定是生产函数设定出了偏差。
第三,和原文的描述性统计对比。原文如果报告了TFP的样本量和均值,即便具体数值因为样本区间不同有出入,量级也应该一致。差一个数量级基本就是哪里出了系统性错误,要回头查数据口径。
我个人的习惯是把OLS、LP两种口径都保留下来,作为后面稳健性检验的材料。因为审稿人大概率会问:"你的结论换个TFP口径还成立吗?"这时候你就有了现成的答案。
4. 多期DID设计里最磨人的三个选择
4.1 政策时点:用文件年还是实施年
排污权交易机制不是一次性全国落地的。政策文件一批一批出,试点地区一个一个进。但"某地正式进入试点"这个事件,在复现代码里到底对应到哪一年,不同论文可能有不同处理。
这里有三类时点容易混淆:文件出台年份、试点名单确定年份、首笔排污权交易实际完成年份。文件出台和首笔交易之间往往差着很长一段筹备期。企业真正感受到"排污要付费、可以交易"这个约束,到底从哪一年开始?这不是一个纯技术问题,它直接影响处理组企业进入政策状态的时间,进而影响 (Post) 变量的赋值。
我的建议是:复现时优先以原文明确说明的时点定义为准;原文没说清楚的部分,就把几种候选定义都跑一遍,看核心结论在哪种定义下稳健。如果一个显著为正的结论只在"名单确定年"成立,在"实际交易年"就不成立,那这个结论的稳健性是要打折扣的。这部分的分析不能省,直接决定了你的复现是"表面跑通"还是"真正可信"。
4.2 处理组和控制组的分组逻辑
(Treat) 变量的构造看起来简单——试点城市的企业取1,非试点城市取0。但实操里有一堆边界情况需要决策。
举个例子,有些公司注册地在试点城市,但主要生产经营活动在另外一个非试点城市,这种情况算处理组还是控制组?按照属地管理原则,看的是排污行为发生地,但通常我们只有注册地信息,所以绝大多数研究都默认按注册地划组,这也是我最终采用的做法。问题是这种默认在集团型公司上会引入测量误差,处理方法是做一次"剔除注册地和实际办公地不一致的样本"的稳健性检验,看结论是否稳定。
还有样本窗口的问题。有些论文的样本期是从上市公司的财务数据可得性往前推的,但政策试点扩围之后,原本的控制组城市可能变成了处理组城市。如果你的窗口期拉得很长,就面临一个选择:控制组要不要剔除那些"后来也进了试点"的城市企业?我在复现时做了一个对照:保留动态处理组(默认控制组,进入试点后转处理组)和剔除后期试点企业,两种设置看核心系数是否有本质差异。
4.3 固定效应与聚类水平:标准误的计算不能省
模型设定上,企业固定效应加年份固定效应是标准配置,但聚类水平的选择会直接影响显著性判断。在中国城市层面的DID研究中,聚类到城市层面是更保守、更稳健的做法,因为同一城市内的企业会受到共同的区域性冲击,比如当地环保督查、产业政策、劳动力市场变化,这些都会让误差项在城市内部相关。如果不聚类或者只聚类到企业层面,会把城市层面的共同冲击当成独立信息,标准误被严重低估,p值做出来虚小。
当然,聚类到城市也有代价。试点城市数量如果太少,城市层面的聚类数不够,标准误估计本身又会不稳定。我这次复现的样本里城市数量不算少,所以以城市聚类为主,企业聚类为辅,两种都报告。核心解释变量的显著性能不能扛住城市聚类,是判断这个DID结果"经不经得起推敲"的第一道关口。
5. 复现排污权交易论文时我踩过的坑
5.1 城市名匹配:一个"差不多"引发的系统性偏差
这是我复现中踩的工程量最大、也最阴险的坑。
一批公司注册地写成"XX市XX区",而政策试点名单里写的是"XX省XX市",看起来是同一回事,但因为行政区划调整,有些公司在上市时注册地还在A市,后来变成了B市的市辖区,数据库里新老名称混用。还有一个更隐蔽的问题:计划单列市与所在省的关系。某些城市在经济管理权限上相当于省级,环保政策试点中有时单独列出,有时混在省份里。如果直接拿省份名单去匹配公司注册地的城市名,会出现"这个城市在政策名单里,但不在试点省份名单里"的尴尬情况。
我的处理办法是放弃简单的字符串包含匹配,改用"省份+城市"双层映射表。先按省级规则确定城市是否归属,再用城市码精确关联。建立这个映射表花了我大半天时间,但它避免了后续所有结果的系统性错配。想提醒一句:如果你跑出来的DID结果极其显著,先别高兴,检查一下处理组企业名单里有没有混进明显不是试点城市的公司。宁可慢一点,也不能让匹配错误污染整条样本链路。
5.2 样本区间:窗口多一年,结论可能翻转
还有一个让我印象深刻的坑,来自样本窗口期的敏感性。最开始我按照论文给出的样本区间从早年间开始,一路拉到最近年份,跑基准回归发现核心系数不显著。对照原文的结果是显著为正,我怎么调控制变量都复现不出来。
后来我尝试把样本尾部收紧,砍掉试点扩围后的最后几年,重新跑DID,核心系数立刻变得显著为正,量级也和原文对上了。原因倒不复杂:后期纳入试点的城市和企业,和早期试点城市的企业存在比较大的异质性,把它们混在一个窗口里,会稀释掉政策效应。原文做这个窗口处理可能有一个理论依据:"以政策全面推开前的阶段为干净样本",而我一开始没有仔细理解这段说明,只是机械地拿全样本去跑。
这件事给我的教训是:复现一个实证研究,第一步不是跑命令,而是理解作者为什么这样切样本。样本区间定义不复制,后面一切都是空中楼阁。
5.3 交叠DID的负权重:传统TWFE不是银弹
最后这个坑更前沿一点。多期DID在分组和时点都不统一的情况下,传统的双向固定效应(TWFE)估计量会出现负权重问题。什么意思呢?当处理组在不同时间点进入政策状态时,已经进入处理组的企业,在后续新企业进入时,会被反向充当"控制组",贡献一个负权重。如果处理效应本身是异质的,TWFE跑出来的系数就可能失真。
顶刊近两年对这个问题相当敏感。我复现时用了一个比较现代的处理组队列方法(Callaway & Sant'Anna类的方法)重新估计处理效应,结果和TWFE结果大体一致,说明这个数据里负权重问题没有严重影响结论。但这个过程不能省,因为审稿人很可能追问:"你用交叠DID,处理了吗?"。复现包里如果有这一步,说服力和可信度完全是两个档次。
6. 从基准结果到证据链:检验流程的复现顺序
6.1 基准回归与结果解读的标准
处理完数据和模型设定之后,基准回归其实很快。命令长这样:
* 控制变量: size=企业规模, lev=资产负债率, age=企业年龄 global controls "size lev age" * 企业固定效应+年份固定效应, 聚类到城市 reghdfe tfp_lp did $controls, absorb(stkcd year) vce(cluster citycode)did已经是处理组×政策期的交互项,跑出来第一行就是核心系数。判断复现是否成功的标准,除了系数方向、显著性要和原文一致,还要特别关注样本量。如果样本量和原文差很多,最可能的原因是样本筛选规则或缩尾处理的口径不一致,需要回头排查。
6.2 平行趋势怎么画才不是自欺欺人
DID成立的前提是如果没有政策,处理组和控制组的TFP变化趋势是一样的。这个前提没法直接检验,但可以通过事件研究法做间接验证:以政策实施前各年份为基准,看政策前处理组和控制组的TFP差异是否显著偏离零。
我的做法是以政策前一期作为基期,生成相对政策时点 (-5) 到 (+5) 年的虚拟变量:
* 先计算每个企业第一次受政策冲击的年份 egen first_year = min(cond(did==1, year, .)), by(stkcd) gen rel_time = year - first_year * 生成事件研究虚拟变量, 基期设为政策前1年 forvalues k = 2/5 { gen pre_`k' = (rel_time == -`k') } gen current = (rel_time == 0) forvalues k = 1/5 { gen post_`k' = (rel_time == `k') } * 事件研究回归, 省略基期 pre_1 reghdfe tfp_lp pre_2 pre_3 pre_4 pre_5 current post_1 post_2 post_3 post_4 post_5 $controls, absorb(stkcd year) vce(cluster citycode)政策前各期虚拟变量的系数如果不显著偏离零点,平行趋势就算通过了。政策后的动态效应可以看系数逐年是放大还是衰减,这也是论文"动态效应分析"部分的核心内容。画图建议用coefplot输出置信区间,比看回归系数直观得多。
6.3 安慰剂检验的两种做法
安慰剂检验的本质是排除"这个效果是不是碰巧撞上的"。我复现时用了两种方式。
第一种是随机分配试点城市。把真实的试点城市名单打乱,随机赋给同数量的"假试点城市",构造假的处理组,重复500次跑DID,得到500个假的系数分布。如果真实的回归系数落在伪系数分布的极端尾部,说明真实效应不太可能是随机噪声造的。
第二种是随机提前政策时间。把真实处理组企业的政策时点统一提前2年或3年,重新跑DID。如果提前时点还能跑出显著效应,说明原来的结果可能捕捉的只是处理组本身的时间趋势,政策效应是假的。
伪系数分布的做法在Stata里可以用循环实现,大体结构是:
forvalues i = 1/500 { preserve * 随机生成假处理状态 gen rand_policy = runiform() < 0.2 * 在保留真实协变量的前提下跑回归 reghdfe tfp_lp rand_policy $controls, absorb(stkcd year) vce(cluster citycode) * 记录系数 restore }这是个示意代码,具体变量要根据实际数据调整。核心是理解这种"随机化推断"的思想:真处理的效果,必须显著区别于随机分配产生的系数分布。
6.4 稳健性检验的清单式管理
稳健性检验不是越多越好,而是要有针对性。我最终交付的复现包里,对每一类可能的质疑都安排了一个对应的检验:
| 潜在质疑 | 对应检验 |
|---|---|
| TFP测度方式影响结论 | 换用OLS口径、LP口径分别回归 |
| 样本筛选规则干扰 | 不缩尾、缩尾1%、缩尾2%各跑一遍 |
| 政策时点设定偏差 | 改用文件年、名单年两种定义 |
| 聚类水平选择影响标准误 | 企业聚类、城市聚类都报告 |
| 政策同期有其他改革干扰 | 加入省份×年份固定效应控制 |
| 极端行业或特殊企业主导 | 剔除重污染行业的全部样本做一遍 |
这些检验跑完之后,可以给核心系数画一个"系数+置信区间"综合图,能非常直观地展示结论在不同设定下的稳定性。这也是复现报告的加分项。
7. 论文+数据的复现包:让别人也能跑通
7.1 复现包的标准目录结构
一个好的复现包,不是丢几个do文件就完事,而是别人拿到手之后,不需要靠猜就能理解每一步在做什么。我的目录结构是这样的:
. ├── README.md # 复现说明: 数据来源、运行环境、代码顺序 ├── code/ │ ├── 01_clean_dta.do # 原始数据处理与样本筛选 │ ├── 02_construct_dta.do # 变量构造和TFP测算 │ ├── 03_baseline_did.do # 基准回归 │ ├── 04_event_study.do # 事件研究与平行趋势图 │ ├── 05_placebo.do # 安慰剂检验 │ └── 06_robustness.do # 稳健性检验 ├── data/ │ ├── raw/ # 只读的原始数据 │ └── derived/ # 中间生成的数据和最终回归样本 └── output/ ├── tables/ # 回归结果表 └── figures/ # 事件图和安慰剂分布图关键的原则是"原始数据只读不改"。所有清洗操作都从原始文件里复制生成新文件,这样一旦怀疑某一步算错,可以完全回溯。我还习惯在每一个do文件开头写明输入文件、输出文件和创建日期,方便自己后期排查。
7.2 从零到一的完整命令流
这里给一个高度浓缩的完整命令流,覆盖从数据清洗到基准回归的骨架逻辑。具体变量名要根据原始数据的字段名做调整:
* 1. 读取原始数据 use data/raw/financial_data.dta, clear * 2. 生成基础变量 gen lny = ln(revenue) gen lnl = ln(staff_num) gen lnk = ln(fixed_asset) gen lnm = ln(inter_input) // 中间投入已按近似公式算好 * 3. 生成TFP (OLS口径框架) xtset stkcd year xtreg lny lnl lnk lnm i.year, fe predict double yhat, xb gen double tfp_ols = lny - yhat * 4. 合并政策数据, 生成did merge m:1 citycode using data/raw/policy_city.dta, keep(1 3) gen treat = (in_policy == 1) gen post = (year >= policy_year) gen did = treat * post * 5. 样本筛选 drop if industry == "金融" | ST == 1 drop if missing(lny, lnl, lnk, lnm) * 6. 基准回归 global controls "size lev age" reghdfe tfp_ols did $controls, absorb(stkcd year) vce(cluster citycode) est store m1 * 7. 换TFP口径做敏感性分析 reghdfe tfp_lp did $controls, absorb(stkcd year) vce(cluster citycode) est store m2 * 8. 输出结果表 esttab m1 m2 using output/tables/baseline.tex, replace b(%9.4f) se(%9.4f) star(* 0.10 ** 0.05 *** 0.01)注意一个细节:merge之后一定不要忘了检查_merge状态,确认每个公司都能匹配上政策城市信息。未匹配的样本究竟是"非试点城市"还是"城市名对不上",性质完全不同。
7.3 复现过程中最后悔没早知道的几件事
最后分享几个更加个人化的体会。
第一,数据版本记录要留痕。有一次我换个时间点重新导了一次财务数据,后来发现TFP计算结果和之前有明显差异。排查了半个小时才发现,两次导出的字段里,员工人数一个来自合并报表、一个来自母公司报表,口径变了。做复现一定要养成记录数据导出时间和字段定义的习惯。
第二,不要迷信"论文+数据"这个标签。即使作者放出了全部代码,环境变量、数据库字段的细微差异,也可能让代码在你机器上跑不出原结果。我见过最典型的情况是作者用_merge字段做了筛选,而你的数据库版本因为字段顺序不同,merge结果就不一样。抱着"一定跑不通"的心态去复现,反而更容易发现问题。
第三,原始论文里没有写清楚的地方,交给敏感性分析去兜底。政策时点不明确,就换几种定义;聚类水平不明确,就两个层次都报;TFP口径不明确,就多算几个口径。一个完全透明、所有关键选择都做了敏感性测试的复现包,才是真正有学术价值的交付物。
这次复现做下来,我最深的感受是:顶刊论文之所以难复现,不是因为模型有多复杂,而是因为每一个简单的背后都有大量隐性的口径决策。这些东西论文正文不会写,代码注释也未必会写,只能靠自己一遍遍试错补出来。但恰恰是这个过程,能让你真正理解一篇论文从数据到结论的完整逻辑链路。希望这篇记录能帮打算走上复现这条路的人少走一点弯路。