pybacondecomp、pysharpley2、pydiff——都是把 Stata 的后估计命令 1:1 翻译成 Python。有朋友问我怎么做到的,这篇就把过程说清楚。为什么要这么做
我的实证涉及上百万行样本,进行复杂检验时,使用 Stata 运行,往往每一次都要40分钟以上。原因有两方面,一是我自己电脑 CPU 算力不足,二是我的电脑的 Stata 仅支持两核心运行。而 Stata 的并行授权是按核心收费的,每多用一个 CPU 都要多掏钱。
偏偏这样的长时间任务往往需要多次运行,以检验结果,串行任务叠加串行运行,等着实在难受。
Python 就没有这个问题。多种方案都可以支持把全部核心用上,完全免费。一个 500 次置换检验的任务,串行要 80 秒,12 核并行 8 秒搞定。
但使用Python 的问题是,我常用的几个后估计命令在 Python 生态里根本找不到对应——没有人写过,或者写了但没人维护。以前遇到这种情况只能跑回 Stata,我现在的做法是把 .ado 源文件丢给 AI,让它直接复刻。
这件事能做的前提
直接说结论:基准回归层已经有很成熟的 Python 实现,这是一切的前提。
常用的计量命令在 Python 里基本都有对应:
reg→statsmodels.OLS()reghdfe→pyfixest.feols()ppmlhdfe→pyfixest.fepois()ivreg2→pyfixest.feols()csdid→pyfixest.did2s()esttab→pyfixest.etable()
有这个基础,复刻一个冷门的后估计命令就是在已有基础上做"最后一公里"。工作量不大,但如果纯靠人工逐行翻译 ado 文件,还是挺繁琐的。这正是 AI 能帮上忙的地方。
实际怎么操作:以 pybacondecomp 为例
第一步:找到 ado 文件
Stata 的大多数命令都是开源的,.ado 文件就是源代码。Goodman-Bacon 分解(bacondecomp)的 ado 文件大概 400 行,逻辑清晰,注释也算完整。
对应源代码:http://fmwww.bc.edu/repec/bocode/b/bacondecomp.ado
第二步:把 ado 文件上传,写清楚需求
我发的 prompt 大概是这样的:
"根据 pyfixest 的 API 接口实现对于这个 Stata ado 文件主程序
bacondecomp的 Python 复刻,实现输入和输出的完整实现。"
几个关键信息:指定后端是 pyfixest(不说清楚的话 AI 可能用 statsmodels 或 sklearn,然后对不上 FE 回归的接口);强调"完整",意思是不只是算法核心,输出格式和边界情况也要对应;范围限定在"主程序",把一个历史遗留的辅助子程序排除在外。
第三步:AI 做了什么
收到 ado 文件之后,AI 先调用了我预先制作的的 pyfixest Skill文档一个skill让AI学会使用python跑实证(里面记录了常用 API、公式语法、标准误选项)。然后它开始对照 ado 文件,把每一块逻辑翻译成 Python。
整个 ado 文件的骨架是这样的:解析命令参数 → 按处理时点把观测分组 → 对每对组合跑子样本的双向 FE 回归 → 用解析公式算各组的权重 → 汇总结果输出表格和图。
Stata 和 Python 在回归这一步的写法,放在一起看很直观:
* Stata ado 里的子样本面板回归qui xtreg `y' `x' if `touse' & `g'==`k', fescalar b_kl = _b[`x']# Python 对应写法fit = pf.feols(f"{y} ~ {x} | {id_var} + {time_var}", data=df.loc[mask],)b_kl = float(fit.coef()[x])语义完全一致,语法不同而已。大量这类"逻辑等价、语法完全不同"的转换,靠人工翻译容易出错,AI 处理起来反而更稳,因为它同时熟悉两种语言,能在上下文里追踪变量的含义。
最终输出长什么样
产出是一个约 500 行的 pybacondecomp.py,调用方式和 Stata 命令一一对应:
from pybacondecomp import bacondecomp, bacon_plotresult = bacondecomp( df = df, y = "outcome", tr = "treat", # binary, weakly increasing unit = "state", time = "year", n_jobs = -1, # parallel: use all cores)bacon_plot(result) # scatter: estimates vs. weightsresult.two_by_two 是一个 DataFrame,每行是一组 2×2 比较,包含比较类型、DD 系数、TWFE 权重,和 Stata 的 e(dd) 矩阵内容完全对应。
其他移植的 Stata 指令介绍
通过这个方法我制作了几个我近期使用的 Stata 指令的 Python 实现,你可以通过 pip 下载,如果发现错误也恳请指出,以下是对应的用法示例:
# pip install pybdiff# Test whether x1 and x2 coefficients differ between group 0 and 1from pybdiff import bdiffresult = bdiff( df = df, group = "treated", formula = "y ~ x1 + x2 | firm + year", vcov = {"CRV1": "firm"}, method = "permutation", # or "bootstrap" / "wald" reps = 500, n_jobs = -1, # parallel: use all cores)# pip install pyshapley2# Decompose R² across independent variablesfrom pyshapley2 import shapley2result = shapley2( df = df, depvar = "wage", indepvars = ["edu", "exp", "tenure"], stat = "r2", # or "r2_a", "ll", "aic", "bic" n_jobs = -1, # parallel: use all cores)result.summary() # Stata-style table output# pip install pybacondecomp# Decompose TWFE DiD into all 2×2 comparisonsfrom pybacondecomp import bacondecomp, bacon_plotresult = bacondecomp( df = df, y = "outcome", tr = "treat", # binary, weakly increasing unit = "state", time = "year", n_jobs = -1, # parallel: use all cores)bacon_plot(result) # scatter: estimates vs. weights几点实操经验
你需要先明白ado文件在做什么。 AI了解任务需要上下文,你指导AI完成任务同样需要上下文。
保存对应 Stata复现记录。 Python 可以做结果的快速验证,但是真正落到论文里的结论,起码当前必须需要 Stata 进行核对。
一定要指定后端库。 说"用 pyfixest"比"用 Python 做回归"有效得多,AI 需要知道具体 API,才能写出能直接运行的代码。
第一版输出是起点,不是终点。 遇到 AI 没处理好的逻辑,粘贴出来继续追问,通常一两轮就能修掉。
Smoke Test很重要。 当 AI 完成指令构建后,直接让它下载一个在线数据库汇报计算结果,并打包给出对应Stata do代码和dta文件,执行后进行结果对照。
成本相对可控。 平均移植一个 Stata 指令并调试到完整可用的成本花费大概在10元左右(使用 Sonnet 4.6 模型)
夜雨聆风