实证研究完整流程与论文复现 Replication Workflow
读一百篇论文,不如把一篇顶刊从头跑到尾。这一页把"复现一篇论文"拆成可执行的工程步骤:从解压复现包、读懂 README、追踪主程序调用链,到环境配置、运行主程序、核对输出、再到在别人代码上做自己的扩展。复现包不是论文的附属品——它是实证研究的"标准答案"。
01 为什么要学复现:复现包是实证研究的"标准答案"
读论文时,我们读到的是一篇被高度压缩的故事:一张主回归表、几张稳健性图、一段文字解释识别策略。但论文从原始数据到那张表之间,藏着几百个决策——哪些观测被删、缺失值怎么填、异常值怎么处理、行业代码如何统一、政策冲击变量怎么构造、固定效应按什么层级加、标准误聚在哪一层。这些决策在正文里通常只占半句话,甚至完全不提。审稿人看不到,读者更看不到。复现包(replication package)就是把这几百个决策全部摊开、写成可重跑的代码和数据。
把复现包当作"标准答案"来学,有三个立刻能拿到的好处。第一,它告诉你一篇顶刊论文在工程上到底长什么样——不是 abstract 里那个抽象的识别故事,而是 main.do 里第 37 行那个 drop if missing(industry)。第二,它是你自己写代码的"范文":变量命名、文件夹组织、注释风格、log 文件管理、表输出格式,都能直接照抄。第三,复现是训练"识别思维"最快的方式——你必须搞懂每一步为什么这么做,才能改得动它;而改不动,就说明你还没读懂。
反过来说,一个不会复现论文的研究者,写出来的实证几乎一定是脆弱的。因为他没见过"一篇被严格审稿、被公开复现的论文"在工程上有多讲究:从 set seed 到 esttab,从 cd 到 save, replace,每一行都有讲究。本站前面几页讲的是"方法是什么",这一页讲的是"顶刊的方法是怎么被代码组织起来的"。
复现不是"抄别人结果"。复现的目标有三:(1) 跑通——主表主图能复出来,数字对得上;(2) 读懂——每一步代码为什么这么写、删了谁、留了谁;(3) 改动——在别人的复现包上改一个变量、加一个稳健性、换一个样本,变成自己的小练习。第三点才是复现的真正价值。
02 复现包结构解读
一个规范的复现包,无论作者用 Stata、R 还是 Python,骨架都高度同构。你拿到一个 zip 解压后,应该立刻认出下面这些东西。如果缺了其中任何一个,这个复现包的质量就要打问号。
2.1 顶层结构:README + 主程序 + data/ + output/
打开复现包根目录,第一层通常是:
- README.md / README.txt:入口说明书。写清楚作者、论文标题、期刊、运行环境(Stata 版本 / R 版本 / Python 版本)、外部命令清单、运行顺序、预期运行时间、常见报错。这是你读复现包第一个要打开的文件。
- 主程序(main.do / run_all.do / master.R / run_all.py):一键从原始数据跑到最终表的脚本。它通常会
cd到根目录、依次调用各个 do 文件、最后输出表和图。 - code/ 或 do/ 文件夹:拆分好的子程序,通常按"数据清洗 → 描述统计 → 主回归 → 稳健性 → 图表输出"分文件。
- data/raw/ 或 raw_data/:未经任何处理的原始数据。这是你的起点——复现必须从 raw 重新跑到最终表,而不是直接用作者已经洗好的中间数据。
- data/derived/ 或 data/intermediate/:清洗过程中产生的中间数据(合并好的面板、生成的变量、缩尾后的数据)。
- data/analysis/:最终回归用的分析样本。这张表的行数应该和主回归表的 N 对得上。
- output/tables/ 与 output/figures/:最终的 .csv / .xls / .tex 表格和 .png / .pdf 图。
- log/:每次运行留下的日志文件,用来核对输出。
2.2 三层数据:raw → derived → analysis
这是复现包里最重要的分层思想。raw 是只读的,你不允许修改它;derived 是中间过程,可重新生成;analysis 是最终跑回归用的表。一个复现包如果把这三层混在一起——比如直接在 raw 数据上 replace、drop、save, replace——那它几乎一定跑不通,因为你没法重跑,也不知道作者到底删了谁。
对应到论文里:raw 数据是"原始观测",derived 是"清洗后的中间样本",analysis 是"最终回归样本"。三者之间每一步删了多少观测,都应该能在代码里数清楚——这就是下一页要讲的"样本构造 flow chart"。
| 层级 | 目录 | 可否修改 | 对应论文中的概念 |
|---|---|---|---|
| raw | data/raw/ | 只读,绝不修改 | 原始观测(如 CSMAR 下载的原始 dta) |
| derived | data/derived/ | 可由代码重新生成 | 清洗、合并、缩尾后的中间样本 |
| analysis | data/analysis/ | 可由代码重新生成 | 最终回归样本(N 与主表一致) |
03 如何识别主程序:从 README 到调用链
拿到一个复现包,作者常常给你 20 个 do 文件,你不知道该先跑哪个。识别主程序有三种办法,按可靠性排序:
- 看 README。规范的 README 会直接写"在 Stata 里运行
do main.do"。如果 README 没写,看下一条。 - 找名字里带 main / master / run_all / 00_master / setup 的文件。这类文件通常一开头就
clear all、cd到根目录、set more off、log using,然后一串do "code/01_clean.do"、do "code/02_merge.do"。 - 用 grep 搜调用关系。在 Stata 里或用终端
grep -rn 'do "' code/,找出哪个文件被最多文件调用、且它自己不调用别人——它就是叶子节点(最终输出表);反过来,谁调用了最多文件、自己不被别人调用,谁就是主程序。
找到主程序后,你要在脑子里画出一张调用链图:
main.do
├─ 00_setup.do ← cd, set seed, 安装外部命令, 全局宏
├─ 01_clean_raw.do ← 读 data/raw/*.dta → 清洗 → 存 data/derived/clean.dta
├─ 02_merge.do ← 合并多个 derived → 面板 → data/derived/panel.dta
├─ 03_sample.do ← 筛选最终样本 → data/analysis/final.dta
├─ 04_desc.do ← 描述性统计表 → output/tables/t1_desc.tex
├─ 05_main_reg.do ← 主回归 → output/tables/t2_main.tex
├─ 06_robust.do ← 稳健性 → output/tables/t3_robust.tex
└─ 07_figures.do ← 图 → output/figures/fig1.pdf
这张图一旦画出来,你就知道:data/raw 是源头,output/tables 是终点,中间每个 do 文件都是一条加工线。复现失败时,沿着这条线倒查——是哪个 do 文件产生的中间数据出了问题。
有些论文的复现包把主程序藏在 Replication.do、run.do、make.do 这种不起眼的名字里,甚至藏在二级文件夹。如果 README 没写、根目录看不出入口,就用上面第 3 条 grep 方法找。
04 复现五步走
ssc install reghdfe, replace、ssc install estout, replace、ssc install ftools, replace 等。把 cd 改成你自己机器上复现包的路径。永远不要直接改作者的 do 文件里的硬路径——在主程序最开头加一个全局宏,所有路径都引用它。data/raw/,保持作者命名。不要自己重命名——主程序里读的就是这个名字。do main.do。第一次跑几乎一定报错:路径错、外部命令没装、Stata 版本不兼容、某个变量名拼错。逐个修,不要跳着跑——必须从 raw 重新生成所有中间数据,否则你跑的不是"复现",是"作者已经洗好的半成品"。output/tables/ 里生成的表,和论文正文里的表逐列比对:系数大小、星号、N、R²。不需要每个数字都一模一样(版本差异、浮点误差会导致第三位小数不同),但主系数的符号、量级、显著性必须一致。对不上的列,回去查那一步 do 文件。05 案例:Lane (2025, QJE) "Manufacturing Revolutions"
Lane (2025) 是近年产业政策实证的代表作。这类基于新组装历史微观数据的 QJE 论文,其复现包通常遵循下面的一般模式(具体文件结构以作者在 QJE 补充材料 / 个人主页公开的 replication package 为准):
- raw 数据层:韩国经济统计年鉴、行业普查(industry census)、企业层面报表、投入产出表(input-output tables)、政策文件档案。这些是作者一手收集、数字化整理的历史数据,是论文最核心的"硬资产"。
- derived 数据层:把不同来源的行业代码统一到同一口径(韩国 SIC → 国际 ISIC),构造行业层面的产出、就业、资本、增加值面板;用投入产出矩阵构造下游/上游 exposure 变量。
- analysis 数据层:以行业 × 年为单位的最终面板,处理变量 = 该行业是否被 HCI targeted(或 exposure 强度),结果变量 = 产出增长、TFP、比较优势指数。
- 识别策略:利用政策在行业间的 cross-sectional 差异 + 时间上的引入/退出事件,类似事件研究(event-study)+ 双重差分思路;并通过 IO 网络把处理效应从直接 targeted 行业传导到下游用户行业。
- 输出层:主回归表(直接效应)、下游溢出表、动态效应图(event-study coefficients over time)、政策结束后的持续性图。
QJE 要求作者公开 replication package,通常放在 OUP 网站的论文补充材料(Supplementary material)区,或作者本人主页(Nathan Lane 在 Erasmus / Warwick 等机构的 faculty page)。下载后按本页第二节的结构对照阅读:先看 README,再找 main.do,再追踪 raw → derived → analysis 三层。如果你在复现中发现某一步和本页描述的一般模式不同,以实际代码为准——本页给的是"这类论文通常长什么样"的地图,不是该论文的精确目录。
06 常见错误与踩坑清单
错误 1:路径硬编码(Hard-coded paths)
作者在自己电脑上写的 cd "C:/Users/Nathan/Dropbox/HCI/replication",你拿到后直接跑,必然报错。正确做法:在 main.do 最开头定义一个全局宏:
* 在 main.do 开头,只改这一行
global root "C:/你的路径/Lane2025_replication"
cd "$root"
* 之后所有 do 文件都用 "$root/data/raw/..." 引用路径
* 不要在每个 do 文件里写死自己的电脑路径
错误 2:相对路径错误
主程序里写 do "code/01_clean.do",这个相对路径是相对于当前工作目录的。如果你手动 do code/01_clean.do 而不是从 main.do 调用,工作目录可能不在根目录,就会找不到文件。永远只从 main.do 跑,不要单独调子程序。
错误 3:随机种子未固定
复现包里如果有 bootstrap、随机分组、随机抽样,却没有 set seed,那你每次跑结果都会不一样——这不是复现错了,是代码本身不可复现。正确做法:主程序第一行就写 set seed 20250801(或作者指定的种子)。如果你自己做扩展,也必须固定种子,否则审稿人无法核对。
错误 4:外部命令版本依赖
作者用的是 Stata 17 + reghdfe v6.x,你装的是最新版 v7.x,语法可能不兼容(例如 absorb() 参数变化)。解决:先按 README 指定版本装;如果 README 没写,跑通后遇到报错,去 ssc describe reghdfe 看版本,必要时降级。自己写新代码时,在 log 开头用 which reghdfe 记录版本号。
错误 5:直接改 raw 数据
新手最常犯:打开 data/raw/industry.dta,发现一个变量名不对,直接 rename 然后 save, replace。从此你再也跑不出作者的结果,因为 raw 被污染了。铁律:raw 只读,所有清洗都在 derived 层做。
错误 6:跳过中间步骤直接跑最终回归
看到 05_main_reg.do 里有一行 use "data/analysis/final.dta",作者已经把 final.dta 放在包里了,你直接跑这一个文件,"结果对得上"——恭喜你,你什么都没复现。真正的复现必须从 raw 重新生成 final.dta,否则你只是在验证作者存好的那张表,而不是他的代码。
复现不是"跑通就算赢"。如果你跑出来的主系数和论文差了一倍、符号反了、星号没了,不要假装没看见。回到调用链,一步一步比对中间样本的 N、均值、关键变量分布——差异一定藏在某一步 drop if 里。
07 完整示例:一个最小复现包的目录树 + 主程序
下面用一个模拟的"某产业政策对企业产出影响"的复现包,演示规范的目录组织和主程序写法。你可以照这个骨架搭自己的项目。
目录树
replication_template/
├── README.md # 说明:作者、期刊、运行环境、运行顺序
├── main.do # 主程序(一键从 raw 跑到 output)
├── code/
│ ├── 00_setup.do # 全局设置
│ ├── 01_clean.do # 清洗 raw → derived
│ ├── 02_merge.do # 合并 → derived/panel.dta
│ ├── 03_sample.do # 构造分析样本 → analysis/final.dta
│ ├── 04_descriptive.do # 描述性统计 → output/tables/t1.tex
│ └── 05_main_reg.do # 主回归 → output/tables/t2.tex
├── data/
│ ├── raw/ # 只读,原始下载数据
│ │ ├── firm_raw.dta
│ │ └── policy_raw.dta
│ ├── derived/ # 中间数据(可重生成)
│ └── analysis/ # 最终回归样本
├── output/
│ ├── tables/
│ └── figures/
└── log/ # 每次运行的日志
main.do(带完整中文注释)
*==============================================================
* 主程序:从 raw 数据一键复现全部表格
* 用法:改下面 global root 为你本机路径,然后 do main.do
*==============================================================
clear all
set more off
set varabbrev off
set seed 20260911 // 固定随机种子,保证可复现
* --- 1. 路径设置(只改这一行)---
global root "C:/Users/你/Projects/replication_template"
cd "$root"
* --- 2. 新建输出文件夹(如不存在)---
capture mkdir "output/tables"
capture mkdir "output/figures"
capture mkdir "log"
* --- 3. 启动日志 ---
capture log close
log using "log/run_$S_DATE.log", replace
* --- 4. 安装并检查外部命令 ---
* (首次运行取消下面注释)
* ssc install reghdfe, replace
* ssc install estout, replace
which reghdfe
which estout
* --- 5. 按调用链依次执行 ---
do "code/00_setup.do"
do "code/01_clean.do" // raw -> derived/clean_firm.dta
do "code/02_merge.do" // + policy -> derived/panel.dta
do "code/03_sample.do" // -> analysis/final.dta
do "code/04_descriptive.do" // -> output/tables/t1_desc.tex
do "code/05_main_reg.do" // -> output/tables/t2_main.tex
* --- 6. 完成 ---
display _n "===== 全部跑完,请检查 output/tables/ ====="
log close
03_sample.do(样本构造,演示如何记录 N 变化)
*==============================================================
* 03_sample.do:从 derived/panel.dta 构造最终分析样本
* 每一步都用 count 记录 N,方便做 flow chart
*==============================================================
use "$root/data/derived/panel.dta", clear
* 初始 N
count
scalar N0 = r(N)
display "初始观测: " N0
* 步骤1:只保留制造业
keep if industry_sector == "manufacturing"
count
scalar N1 = r(N)
display "保留制造业后: " N1 " (删了 " N0-N1 ")"
* 步骤2:删除关键变量缺失
drop if missing(output, capital, labor, treat)
count
scalar N2 = r(N)
display "删除关键变量缺失后: " N2 " (删了 " N1-N2 ")"
* 步骤3:缩尾 1%/99%(在组内)
winsor2 output capital labor, cuts(1 99) replace by(year)
* 保存最终分析样本
save "$root/data/analysis/final.dta", replace
* 打印 flow chart
display _n "===== 样本构造 flow chart ====="
display "N0 原始面板: " N0
display "N1 制造业: " N1
display "N2 无缺失: " N2
下次开始一个新实证项目,先复制这个目录树和 main.do,把里面的模拟数据换成你自己的。从第一天就保持"raw 只读、derived 可重生成、analysis 是终点"的分层,三个月后你会感谢自己——因为你自己的项目也变成了一个别人能复现的复现包。