乐于分享
好东西不私藏

用 AI 把 Stata 命令复刻成 Python,比你想的容易得多

用 AI 把 Stata 命令复刻成 Python,比你想的容易得多
最近一直在逼着自己用Python做实证,在过程中为解决部分Stata命令没有对应Python实现的问题,便做了这几个小工具——pybacondecomppysharpley2pydiff——都是把 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. weights

result.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 模型)