复制安装命令
用 Codex 或 Claude 安装复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它先审查 Skill 页面再帮你安装。
复制前请先查看来源、License 和安全提示。
Security audit: baseline 52/52 CLEAN
用 Codex 或 Claude 安装复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它先审查 Skill 页面再帮你安装。
复制前请先查看来源、License 和安全提示。
来源文件:README.md
📌 文档结构(2026-07-22 起): 本文件是中文默认入口 —— banner + badges + 信任面 + 9 阶段流水线速览 + 76 行合集总表。 每个合集的完整描述、按用途分组、精确数字、验证方法在
docs/CONTENT_ZH.md(扩展正文,总表行内的→直接跳转到对应锚点)。English version:
README-en.md· 中文扩展正文:docs/CONTENT_ZH.md·README-zh-CN.md已弃用(重定向占位)
🌐 语言: English | 简体中文(默认) | 繁體中文 | 日本語 | 한국어
|
|
Stanford REAP × CoPaper.AI · 实证研究 AI 工具的学术工业级产品
由斯坦福实证研究方法论团队打造,覆盖从数据清洗到顶刊投稿的完整工作流
🚀 New here? Open the Skill Search → to filter all 1,096 skills by method, stage, language, and license. The 5-minute tour (
make quickstart) prints the same picture in your terminal.🇨🇳 中文用户从本文件开始(流水线速览 + 76 行总表),每个合集的完整描述见
docs/CONTENT_ZH.md。📖 English readers: seeREADME-en.md.
| Rigor lane | Count | Where |
|---|---|---|
| Numeric benchmark tasks — gold values recomputed from real data each run | 17 | benchmark/ |
| Behavioral eval scenarios / rubric items | 37 / 183 | eval-harness/ |
Full trust overview:
docs/TRUST.md·docs/RIGOR_COVERAGE.md
中文内容分两级维护,各司其职:
docs/CONTENT_ZH.md(扩展正文):每个合集的完整描述(#skill-NN 锚点)、按用途分组、精确数字、2 分钟验证、三层信任、旗舰流水线详解、贡献与引用。总表行内的 → 直接跳到对应锚点。README-en.md · README-zh-TW.md · README-ja.md · README-ko.md[!NOTE] 维护规则: 改合集总表 → 本文件与 CONTENT_ZH.md 的锚点表两处同步;改合集详情 / 分组 / 数字 → 只改
docs/CONTENT_ZH.md。统计数字(合集数 / skill 数)以catalog/skills.json为准,由make validate的 readme-stats 检查器守护。贡献者(Contributors): 提交前请在本地跑通完整门禁
make check(catalog 校验 + 链接 + 单元测试 + eval-harness + benchmark)。详见CONTRIBUTING.md。旧版归档:
README-zh-CN.md已弃用,仅作向后兼容的重定向占位。
AERS 不只是 76 个散装 skill —— 它能陪你走完一篇论文。 从模糊 idea → 选题精炼 → 文献综述 → 数据获取 → 识别策略 → 估计建模 → 稳健性审计 → 出版级表格 / 图形 → 写作与同行评审 → 降 AIGC → 投稿。端到端、全自动、每一步都可被人介入(中间任何一步你都可以接过去手工改方法、补变量、加稳健性,再让流水线自动接上跑)。
Paper-WorkFlow 是 AERS 的"指挥棒",它把上面 9 个阶段的 skill 串成 一条按键即运行的端到端流水线。
你在 IDE 入口给它一句自然语言:
"开一个新论文项目:空气污染与中国劳动力市场,CS 设计 + 省级面板"
它会自动按顺序调:
sp.csdid(...) 给出 CS-DID 估计草案 + 写出估计方程与识别假设sp.feols(...) + sp.honest_did(...)任何阶段你都可以手动介入 —— 上一阶段的产物全部落盘(产物-幂等 pipeline),你接过去改方法、补控制、加稳健性,再让流水线自动接下去跑。这就是"全自动 + 可介入"。
| ⭐ Skill | 在流水线里的角色 |
|---|---|
| 00 StatsPAI 🔥 | 因果引擎:900+ 函数,sp.causal(...) 一行跑闭环(DID / RD / IV / SCM / DML / matching) |
| 00.1 Full Empirical · Python 📘 | 显式 Python 栈(pandas / statsmodels / linearmodels / pyfixest) |
| 00.2 Full Empirical · Stata 📊 | 显式 Stata 栈(reghdfe / ivreg2 / csdid / sdid / rdrobust) |
| 00.3 Full Empirical · R 📗 | 显式 R 栈(tidyverse / fixest / did / HonestDiD)+ Quarto 渲染 |
| 48 de-AIGC-skills 🇨🇳🇬🇧 | 中英双语学术降 AIGC(Turnitin AI / GPTZero / 知网 / 万方) |
| 50 AER-skills 📕 | Top-5 经济学投稿套件:识别 → 稳健性 → R&R |
| 69 Paper-WorkFlow 🧭 | 元编排器,把上面 9 个阶段串成一键流水线 |
为什么挑这 7 个?因为它们的行为都被基准钉死了 —— 不是营销口径,是对着已知答案反复跑过验证过的(17 项数值 benchmark + 37 项行为评测 ↗)。
↴ 直跳到下方 76 行总表(每个合集带 #skill-NN 锚点)。如果你更关心"这些 skill 怎么用"而不是"有哪些 skill",看 📘 中文唯一权威正文 里的「按用途分组」与「旗舰流水线」两节。
00 → 72,编号连续无空缺)打开仓库 → 看见整座库。 全部 76 个合集 · 1,096 个 skill,每一个都已 vendor 进本仓库,由
catalog/skills.json跟踪。⭐ = Stanford REAP × CoPaper.AI 团队自研的 skill;其余为精选、经安全审计的社区作品。主题图例 — 🚀 全流程与编排器 · 🎯 因果推断与计量经济学 · 📚 文献与研究设计 · ✍️ 写作 / 编辑 / 去 AIGC · 📑 引用 / 复现 / 同行评审 · 🛠️ 数据 / 工具 / 基础设施
点击【→】 跳转到
docs/CONTENT_ZH.md中该合集的完整描述;点击合集名 直接打开其目录。
| # | 合集 | 一句话 | 详情 |
|---|---|---|---|
| ⭐ 00 | StatsPAI 🔥 | 因果引擎 · Agent-native Python DSL:sp.causal(...) 一行跑闭环(DID/RD/IV/SCM/DML,900+ 函数) | → |
| ⭐ 00.1 | Full Empirical · Python 📘 | 显式栈:pandas · statsmodels · linearmodels · pyfixest | → |
| ⭐ 00.2 | Full Empirical · Stata 📊 | reghdfe · ivreg2 · csdid · sdid · rdrobust 复现包 | → |
| ⭐ 00.3 | Full Empirical · R 📗 | tidyverse · fixest · did · HonestDiD + Quarto 渲染 | → |
| 01 | academic-paper-skills | 大纲 → 手稿写作 + 7 维审稿人模拟 | → |
| 02 | research-skills | 医学影像综述、提案、论文转幻灯片 | → |
| 03 | scientific-skills | 假设生成 + 28 个科学数据库 | → |
| 04 | scientific-writer | 引用管理 + 科学写作 | → |
| 05 | research-superpower | 系统化检索、筛选与引文溯源 | → |
| 06 | stats-paper-writing | 端到端 LaTeX 统计论文写作 | → |
| 07 | AI-Research-SKILLs | 发表级 ML 图表、LaTeX、引文核验 | → |
| 08 | latex-document-skill | 创建 / 编译任意 LaTeX 文档为 PDF | → |
| 09 | awesome-econ-ai | Python 面板数据分析(linearmodels) | → |
| 10 | causal-inference-mixtape | DID / IV / RDD / SCM 模板(Cunningham) | → |
| 11 | compound-science | 面向定量社会科学的贝叶斯估计 | → |
| 12 | claude-code-my-workflow | 提交 → PR → 合并的研究工作流(Emory) | → |
| 13 | MixtapeTools | Cunningham 的因果推断工具集与讲义 | → |
| 14 | research-starter | R 中的 IV / DiD / RDD,含完整诊断 | → |
| 15 | social-science-research | R 或 Python 端到端数据分析 | → |
| 16 | clo-author | 多代理数据分析(R / Stata / Python) | → |
| 17 | DAAF | 安全意识代理框架(32 条 deny rule) | → |
| 18 | stata-accounting | 来自 126 篇 JAR 论文的实测 Stata 范式 | → |
| 19 | vera-economic-intelligence | 经济情报 / 政策研究情报工作流 | → |
| 20 | python-econ-skill | DSGE / HANK 与定量经济计算 | → |
| 21 | AI-research-feedback | 用 AI 同行评审生成结构化反馈 | → |
| 22 | christopherkenny-skills | 面向 Quarto(.qmd)的 APSA 风格检查器 | → |
| 23 | baygent | 带护栏的 PyMC / Arviz 贝叶斯工作流 | → |
| 24 | academic-research-skills | 5 审稿人多视角论文评审 | → |
| 25 | Diverga | 研究问题精炼器(抗模式坍缩) | → |
| 26 | scholar | 统计算法设计与文档 | → |
| 27 | my_claude_skills | 经济学摘要写作指南 | → |
| 28 | paper-replicate-agent | 论文复现代理演示 | → |
| 29 | project20XXy | 可复现手稿 + notebook 项目 | → |
| 30 | zirui-song-claude-skills | Zirui Song 的研究辅助 Claude 技能集 | → |
| 31 | claude-code-skills | Python 面板数据分析 | → |
| 32 | stata-skill | 高性能 Stata C/C++ 插件 | → |
| 33 | claude-scholar | 研究全生命周期:选题 → 综述 → 实验 → 审稿回复 | → |
| 34 | research-companion | 头脑风暴、评估并决策研究方向 | → |
| 35 | academic-writing-skills | 面向投稿场所的工业 AI 文献研究 | → |
| 36 | literature-review-skill | 完整文献综述工作流(中文) | → |
| 37 | IlanStrauss-ai-skills | Ilan Strauss 经济学研究 AI 工作流 | → |
| 38 | academic-proofreader | 学术校对 | → |
| 39 | marginaleffects | 预测、斜率与比较(R / Python) | → |
| 40 | pyfixest | Python 中的快速固定效应估计 | → |
| 41 | sewage-econometrics-check | 10 项复现包审计 | → |
| 42 | ARIS | 自主「research-in-sleep」代理,端到端 | → |
| 43 | research-plugins | 478 个研究插件:数据可视化、领域、基础设施 | → |
| 44 | humanizer_academic | 为医学/学术手稿去 AI 味(23 类模式) | → |
| 45 | deslop | 去除 AI 写作痕迹(5 维评分) | → |
| 46 | stop-slop | 三层 AI 痕迹检测与改写 | → |
| 47 | avoid-ai-writing | 审计 → 改写 → 二次审计 AI 味(留痕) | → |
| ⭐ 48 | de-AIGC-skills 🇨🇳🇬🇧 | 中英双语学术降 AIGC(Turnitin AI / GPTZero / 知网 / 万方) | → |
| 49 | humanize-chinese | 检测并人性化 AI 生成的中文文本 | → |
| ⭐ 50 | AER-skills 📕 | Top-5 经济学投稿套件:识别 → 稳健性 → R&R | → |
| 51 | CausalPy | 贝叶斯准实验(PyMC Labs) | → |
| 52 | slr-prisma | 系统文献综述,PRISMA 2020 | → |
| 53 | thematic-analysis | Braun & Clarke 六阶段定性主题分析 | → |
| 54 | open-science-skills | 引用一致性、DOI 与论据支撑审计 | → |
| 55 | r-skills | R 中用 brms 做贝叶斯推断 | → |
| 56 | econ-writing-skill | 综合 50+ 顶级指南的经济学写作 | → |
| 57 | edgartools | 查询与分析 SEC 文件 | → |
| 58 | econstack | 政策简报(UK GES / AU Treasury) | → |
| 59 | openalex-skill | 通过 OpenAlex 查询 2.4 亿+ 学术作品 | → |
| 60 | superpapers | 综合性实证研究支持套件 | → |
| 61 | research-methods | 与预注册匹配的验证性检验 | → |
| 62 | citation-checker | 对照 CrossRef / S2 / OpenAlex 核验引用 | → |
| 63 | scientific-agent-skills | DoWhy 识别–估计–反驳框架 | → |
| 64 | mcp-stata | 20 个 Stata 因果推断与复现 skill | → |
| 65 | game-theory-paper-writer | 生成并压力测试博弈论论文 | → |
| 66 | empirical-research-skills | 面向大型面板的 R 性能优化 | → |
| 67 | econfin-workflow-toolkit | 中国公司金融实证工作流,从提案到论文 | → |
| 68 | research-productivity-skills | 论文检索、SSRN、DOI 查询、下载 | → |
| ⭐ 69 | Paper-WorkFlow 🧭 | 元编排器,串起整个社会科学论文流水线 | → |
| 70 | ssci-polish ✍️ | SSCI / SCI 英文论文语言润色(语法、可读性、学术语气) | → |
| ⭐ 71 | lit-review-agent-tools 🔍 | 文献综述工具选型 + 一键安装运行(MinerU / PaperQA2 / ASReview / STORM / MCP 服务器) | → |
| ⭐ 72 | Kaggle Research 🧪 | 通过官方 CLI 安全检索 Kaggle 资源、限界下载公开数据并保留审计证据 | → |
想看更详细的描述(主题分类、字段、统计)? 见
docs/CONTENT_ZH.md中标注#skill-NN锚点的同一张表 —— 它是每个合集的完整描述所在的扩展正文。
自 2026-04 首次发布以来的主干里程碑(完整提交记录见 Commits 与 CHANGELOG.md):
---
config:
gitGraph:
rotateCommitLabel: false
---
gitGraph TB:
commit id: "2026-04 首次发布"
branch community
commit id: "2026-05 首个社区 PR"
checkout main
merge community
commit id: "2026-05 更名 AERS"
commit id: "2026-06 插件市场"
commit id: "2026-06 全库路由器"
commit id: "2026-07 首个 tag" tag: "v2026.07"
branch kaggle
commit id: "2026-07 Kaggle 集成"
checkout main
merge kaggle
commit id: "2026-08 de-AIGC 双语"
Star 增长曲线(非提交数)· 由 scripts/build-star-history.py 从 GitHub API 生成并提交入库
如果 AERS 对你的工作有帮助,请引用它(CITATION.cff)并点个 Star,让更多研究者看到。
AI 是放大器,不是替代品。它替你做最耗时的"搬砖",你保留最核心的"判断"。
|
|
Stanford REAP × CoPaper.AI · 实证研究 AI 工具的学术工业级产品
![]() 扫码访问 copaper.ai |
![]() 关注公众号「CoPaper.AI」 |
内置 20 个方法论 skill · 20 分钟完成实证论文 · 自研 StatsPAI(900+ 函数 / MIT 开源)
name: python-econ-computing
description: Use when writing Python code for DSGE models, HANK models, numerical economic computation, causal inference, or quantitative economic data analysisBest practices for macroeconomic modeling (DSGE/HANK), causal inference, and data analysis in Python. Core principle: vectorize first, accelerate loops with Numba, keep code structure aligned with economic theory.
| Use Case | Preferred Libraries |
|---|---|
| Numerical core | numpy, scipy |
| Loop acceleration | numba (@njit, @njit(parallel=True)) |
| Economics toolkit | quantecon |
| HANK / sequence space | sequence_jacobian (SSJ) |
| Heterogeneous agents | HARK |
| Linear models with FE | pyfixest (pip install pyfixest) |
| DID / DD / DDD | diff-diff (pip install diff-diff) |
| IV / 2SLS / GMM | linearmodels (or pyfixest for panel IV with FE) |
| RD / RDD / RKD | rdrobust, rddensity, rdlocrand |
| Synthetic Control | pysynth, synth_control, sdid |
| Matching | causalml, pymatch, econml |
| Causal ML / DML | econml, dowhy |
| Data manipulation | pandas, polars (large datasets) |
| Visualization | matplotlib, seaborn |
import numpy as np
from scipy.linalg import ordqz
def solve_bk(A, B, n_fwd):
"""
Solve linear DSGE: A E_t[x_{t+1}] = B x_t + C eps_t
n_fwd: number of forward-looking variables
Returns decision rule matrix P such that x_t = P x_{t-1} + ...
"""
AA, BB, alpha, beta, Q, Z = ordqz(A, B, sort='ouc')
n = A.shape[0]
Z21 = Z[n - n_fwd:, :n - n_fwd]
Z22 = Z[n - n_fwd:, n - n_fwd:]
P = -np.linalg.solve(Z22, Z21)
return P
quantecon.lqcontrol for LQ problemsperturbpy or manual implementationscipy.optimize.fsolve / rootimport sequence_jacobian as sj
# 1. Define steady-state blocks
@sj.simple
def household_ss(r, w, beta, sigma):
# Return steady-state aggregates
...
# 2. Build DAG
model = sj.create_model([household_block, firm_block, market_clearing],
name='HANK')
# 3. Solve steady state
ss = model.solve_steady_state(calibration, unknowns, targets)
# 4. Compute Jacobians → solve transition dynamics
G = model.solve_jacobian(ss, unknowns, targets, T=300)
from numba import njit
import numpy as np
@njit
def vfi(V0, a_grid, y_grid, r, beta, sigma, tol=1e-8, max_iter=1000):
"""Heterogeneous agent VFI over asset grid × income grid"""
n_a, n_y = len(a_grid), len(y_grid)
V = V0.copy()
policy = np.zeros((n_a, n_y))
for it in range(max_iter):
V_new = np.empty_like(V)
for ia in range(n_a):
for iy in range(n_y):
best_val = -1e10
best_a = 0
for ia2 in range(n_a):
c = (1 + r) * a_grid[ia] + y_grid[iy] - a_grid[ia2]
if c <= 0:
continue
u = c ** (1 - sigma) / (1 - sigma)
val = u + beta * V[:, iy].mean() # use transition matrix in practice
if val > best_val:
best_val = val
best_a = ia2
V_new[ia, iy] = best_val
policy[ia, iy] = a_grid[best_a]
if np.max(np.abs(V_new - V)) < tol:
break
V = V_new
return V, policy
def iterate_distribution(policy_idx, trans_mat, dist0, T=500):
"""Iterate joint distribution to steady state given policy indices and income transition matrix"""
dist = dist0.copy()
n_a, n_y = dist.shape
for _ in range(T):
dist_new = np.zeros_like(dist)
for iy in range(n_y):
for iy2 in range(n_y):
dist_new[policy_idx[:, iy], iy2] += dist[:, iy] * trans_mat[iy, iy2]
dist = dist_new
return dist
Rule: For any OLS/Poisson/Logit with fixed effects, use pyfixest. It mirrors R's fixest syntax.
import pyfixest as pf
# OLS with unit + time FE, cluster-robust SEs
fit = pf.feols("y ~ treat_post | unit + year",
data=df, vcov={"CRV1": "id"})
fit.summary()
# Multiple high-dimensional FE (Frisch-Waugh absorbed)
fit = pf.feols("y ~ x1 + x2 | unit + year + industry",
data=df, vcov={"CRV1": "id"})
# Wild cluster bootstrap (few clusters, <50)
fit = pf.feols("y ~ treat_post | unit + year",
data=df, vcov={"CRV1": "id"})
fit.wildboottest(param="treat_post", B=9999, seed=42)
# Event study via i() syntax
fit = pf.feols("y ~ i(rel_year, ref=-1) | unit + year",
data=df, vcov={"CRV1": "id"})
pf.iplot(fit) # event study plot
# Poisson (count / log-linear) with FE
fit_pois = pf.fepois("y ~ treat_post | unit + year",
data=df, vcov={"CRV1": "id"})
# Access results
fit.coef() # coefficient estimates
fit.se() # standard errors
fit.pvalue() # p-values
fit.confint() # confidence intervals
fit._N # number of observations
| Use case | Use |
|---|---|
| OLS / WLS with any FE | pyfixest |
| Poisson / logit with FE | pyfixest |
| Wild bootstrap | pyfixest |
| Time-series ARIMA, VAR | statsmodels |
| MLE / GLM without FE | statsmodels |
Rule: For any DiD, DD, DDD, or staggered difference-in-differences design, use diff-diff and follow the General Empirical Workflow below.
Source: https://github.com/igerber/diff-diff | https://github.com/wenddymacro/A-General-Empirical-Workflow-for-DID
| Alias | Class | Use When |
|---|---|---|
DiD | DifferenceInDifferences | Basic 2×2 DiD |
TWFE | TwoWayFixedEffects | Standard panel DiD |
EventStudy | MultiPeriodDiD | Dynamic effects / event study |
CS | CallawaySantAnna | Staggered adoption, heterogeneous effects |
SA | SunAbraham | Staggered, avoids negative weights |
BJS | ImputationDiD | Borusyak et al. imputation approach |
SDiD | SyntheticDiD | Synthetic DiD |
DDD | TripleDifference | Triple difference |
from diff_diff import DiD, TWFE, EventStudy, CS, SA, BJS, DDD
# Common fit() arguments
results = estimator.fit(
data,
outcome='y', # dependent variable
treatment='treated', # binary treatment indicator
time='post', # binary post-period (or period var for panel)
unit='id', # unit identifier (panel)
covariates=['x1','x2'],# control variables
absorb=['region'], # high-dim fixed effects (within-transform)
cluster='id', # clustered standard errors
robust=True, # HC1 robust SEs
inference='wild_bootstrap', # for few clusters (<50)
n_bootstrap=999,
)
# Results
results.att # ATT estimate
results.se # standard error
results.p_value
results.conf_int # confidence interval tuple
results.print_summary()
results.to_dataframe()
from diff_diff import DDD
ddd = DDD()
results = ddd.fit(
data,
outcome='y',
treatment='treated',
time='post',
third_diff='group_var', # third differencing dimension
cluster='id',
)
Follow this workflow for every DID/DD/DDD paper or analysis.
Covariate types:
| Type | Form | Purpose |
|---|---|---|
| Covariates | Pre-treatment, time-invariant | Condition parallel trends |
| Control variables | Baseline × time trend | Absorb residual heterogeneity |
State and justify:
Document policy assignment mechanism; cite policy documents for exogeneity.
Model: $$Y_{it} = \alpha + \beta(\text{Treat}i \times \text{Post}{it}) + \gamma W_i + \delta(Z_i^{pre} \times t) + \mu_i + \lambda_t + \varepsilon_{it}$$
Run six progressive specifications (M1–M6):
| Model | Unit FE | Time FE | Covariates | Baseline×Trend | Regional FE | Unit Trend |
|---|---|---|---|---|---|---|
| M1 | ✓ | — | — | — | — | — |
| M2 | ✓ | ✓ | — | — | — | — |
| M3 | ✓ | ✓ | ✓ | ✓ | — | — |
| M4 | ✓ | ✓ | ✓ | ✓ | ✓ | — |
| M5 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| M6 | ✓ | ✓ | ✓ | ✓ | Industry×Year | ✓ |
Coefficient stability across M1→M6 supports identification. Report Oster (2019) δ* (selection bias ratio); |δ*| > 1 = basic robustness, |δ*| > 2 = strong.
import pyfixest as pf
# M1: unit FE only
pf.feols("y ~ treat_post | unit", data=df, vcov={"CRV1": "id"}).summary()
# M2: unit + time FE
pf.feols("y ~ treat_post | unit + year", data=df, vcov={"CRV1": "id"}).summary()
# M3–M4: add covariates and baseline×trend
pf.feols("y ~ treat_post + x1 + x2 + baseline:year | unit + year",
data=df, vcov={"CRV1": "id"}).summary()
# M5: + regional FE
pf.feols("y ~ treat_post + x1 + x2 + baseline:year | unit + year + region",
data=df, vcov={"CRV1": "id"}).summary()
# M6: + industry×year FE
pf.feols("y ~ treat_post + x1 + x2 + baseline:year | unit + industry^year",
data=df, vcov={"CRV1": "id"}).summary()
from diff_diff import EventStudy
es = EventStudy()
res = es.fit(data, outcome='y', treatment='treated',
unit='id', time='year', base_period=-1,
cluster='id')
res.plot() # shows pre/post coefficients with CIs
Plot standards:
Run all estimators for staggered designs:
from diff_diff import CS, SA, BJS
# Callaway & Sant'Anna (2021)
cs = CS().fit(data, outcome='y', unit='id', time='year',
cohort='treat_year', control_group='never_treated', cluster='id')
# Sun & Abraham (2021)
sa = SA().fit(data, outcome='y', unit='id', time='year',
cohort='treat_year', cluster='id')
# Borusyak et al. (2024) imputation
bjs = BJS().fit(data, outcome='y', unit='id', time='year',
cohort='treat_year', horizons=range(5), cluster='id')
Use Rambachan-Roth (2023) bounds to quantify robustness to parallel trends violations.
# After event study, extract pre/post coefficients and covariance
# Pass to HonestDiD (R package via rpy2, or use diff-diff's built-in honest DiD)
from diff_diff import EventStudy
es = EventStudy()
res = es.fit(data, ..., honest_did=True,
sensitivity_constraint='smoothness')
res.plot_honest_did() # shows identified set under relaxed PT assumption
| Threat | Test |
|---|---|
| Spatial spillovers | Geographic placebo; effect by distance from treated units |
| Anticipation effects | Pre-period event-study coefficients ≈ 0 |
| Policy overlap | Exclude or control for concurrent policies |
from diff_diff import DiD
from diff_diff.diagnostics import PlaceboTest, GoodmanBaconDecomposition
# Goodman-Bacon decomposition (TWFE bias diagnosis)
gb = GoodmanBaconDecomposition().fit(data, outcome='y', treatment='treated',
unit='id', time='year')
gb.plot()
# Placebo tests
placebo = PlaceboTest(method='fake_timing').fit(data, ...)
placebo = PlaceboTest(method='permutation', n_permutations=500).fit(data, ...)
# Subsample / specification robustness
for subsample_mask in subsamples:
res = CS().fit(data[subsample_mask], ...)
Standard robustness battery:
# Triple difference for effect heterogeneity by subgroup Z
from diff_diff import DDD
ddd = DDD().fit(data, outcome='y', treatment='treated',
time='post', third_diff='high_exposure', cluster='id')
# Interaction-based heterogeneity in TWFE
from diff_diff import TWFE
res = TWFE().fit(data, outcome='y',
treatment='treat_post',
interactions=['treat_post:firm_size'],
absorb=['id', 'year'], cluster='id')
1. Data & balance
2. Identification assumptions
3. Baseline specs M1–M6 + Oster δ*
4. Event study (TWFE + CS + SA + BJS)
5. HonestDiD sensitivity
6. Alternative explanations
7. Robustness battery
8. Heterogeneous effects (DDD / interactions)
9. Mechanisms
10. Welfare implications
What is your identification strategy?
├── Policy/treatment with parallel trends → DID (see above)
├── Exogenous instrument for endogenous X → IV
├── Discontinuity in assignment rule → RD / RKD
├── Control units that can be reweighted → Synthetic Control
├── Selection on observables → Matching / IPW
└── High-dimensional / ML setting → DML / Causal Forest
Library: linearmodels (preferred over statsmodels for panel IV)
Key assumptions: Relevance (F > 10, ideally > 104 per Lee et al. 2022), Exclusion restriction, Independence.
from linearmodels.iv import IV2SLS, IVGMM, IVLIML
# Basic 2SLS: y ~ X_exog + [X_endog ~ Z_instruments]
res = IV2SLS(dependent=y,
exog=X_exog, # included exogenous (+ constant)
endog=X_endog, # endogenous regressors
instruments=Z).fit(cov_type='robust')
# Panel IV with fixed effects
from linearmodels import PanelOLS, BetweenOLS
from linearmodels.iv import IV2SLS
# absorb FE first (within transform), then IV on residuals
# or use linearmodels.panel with IV support
# GMM (efficient with heteroskedasticity)
res = IVGMM(y, X_exog, X_endog, Z).fit(cov_type='robust')
# LIML (less biased with weak instruments)
res = IVLIML(y, X_exog, X_endog, Z).fit(cov_type='robust')
# Key diagnostics
print(res.first_stage) # first-stage F-statistic
print(res.wooldridge_score) # endogeneity test (H0: OLS consistent)
print(res.sargan) # overidentification test (J-stat, requires overid)
| Test | What it checks | Pass if |
|---|---|---|
| First-stage F | Instrument relevance | F > 104 (Lee et al.) or > 10 (rule of thumb) |
| Cragg-Donald / Kleibergen-Paap | Weak instrument (multiple endog) | > Stock-Yogo critical values |
| Sargan-Hansen J-test | Overidentification (exclusion) | p > 0.1 (can't reject validity) |
| Hausman / Wooldridge | Endogeneity of X | p < 0.05 → IV needed |
| Reduced form | Instrument affects outcome | Should be significant |
# Anderson-Rubin confidence set (robust to weak instruments)
from linearmodels.iv import IV2SLS
res = IV2SLS(y, X_exog, X_endog, Z).fit(cov_type='robust')
print(res.anderson_rubin) # AR test, valid even with weak instruments
# Conley spatial HAC SEs (geographic instruments)
res = IV2SLS(y, X_exog, X_endog, Z).fit(cov_type='kernel', bandwidth=5)
# Bartik instrument: Z_i = sum_k s_{ik} * g_k
# s_{ik}: industry share of unit i; g_k: national industry growth
import numpy as np
def bartik_instrument(shares, growth):
"""
shares: (n_units, n_industries)
growth: (n_industries,)
returns: (n_units,) Bartik instrument
"""
return shares @ growth
Library: rdrobust (Python port of R package)
Key assumption: Units cannot precisely manipulate the running variable around the cutoff.
from rdrobust import rdrobust, rdbwselect, rdplot
# Sharp RD
res = rdrobust(y, x, c=cutoff) # default: MSE-optimal bandwidth, local linear
res = rdrobust(y, x, c=0,
kernel='triangular', # triangular (default) / uniform / epanechnikov
bwselect='mserd', # MSE-optimal (default); 'cerrd' for coverage
vce='hc1', # robust SEs
cluster=cluster_var)
print(res)
# Fuzzy RD (instrument = 1[x >= c])
res_fuzzy = rdrobust(y, x, c=0,
fuzzy=treatment_var) # IV-style, estimates LATE
# Bandwidth selection
bw = rdbwselect(y, x, c=0, bwselect='mserd')
print(bw.bws) # optimal bandwidth
# Visualization
rdplot(y, x, c=0) # binned scatter with polynomial fit
from rddensity import rddensity
from rdrobust import rdrobust
# 1. McCrary density test (H0: no manipulation at cutoff)
den = rddensity(x, c=cutoff)
print(den.test) # p > 0.05: no evidence of manipulation
# 2. Covariate smoothness (placebo on pre-determined covariates)
for cov in baseline_covariates:
res = rdrobust(cov, x, c=cutoff)
print(f'{cov}: {res.coef[0]:.3f} (p={res.pv[2]:.3f})') # should be insignificant
# 3. Placebo cutoffs (should find no effect at fake cutoffs)
for fake_c in [cutoff - 0.5, cutoff + 0.5]:
res = rdrobust(y, x, c=fake_c)
print(f'Placebo c={fake_c}: {res.coef[0]:.3f}')
# 4. Sensitivity to bandwidth
for h in [bw_opt * 0.5, bw_opt * 0.75, bw_opt, bw_opt * 1.25, bw_opt * 1.5]:
res = rdrobust(y, x, c=cutoff, h=h)
print(f'h={h:.2f}: {res.coef[0]:.3f}')
# 5. Donut hole (exclude units very close to cutoff)
mask = np.abs(x - cutoff) > donut_radius
res_donut = rdrobust(y[mask], x[mask], c=cutoff)
# RKD: identifies effect via kink (slope discontinuity) rather than level jump
res_rkd = rdrobust(y, x, c=cutoff, deriv=1) # deriv=1 estimates slope discontinuity
Use when: Few treated units (often N=1), long pre-treatment panel, no obvious control group.
Libraries: pysynth, synth_control (pip), or manual implementation via scipy.optimize.
# --- Option 1: pysynth ---
from pysynth import Synth
sc = Synth()
sc.fit(
dataprep={
'foo_table': df,
'predictors': ['gdp', 'trade', 'invest'],
'predictors_op': 'mean',
'time_predictors_prior': list(range(1980, 1990)),
'special_predictors': [('gdp', [1985, 1988], 'mean')],
'dependent': 'gdp',
'unit_variable': 'country',
'time_variable': 'year',
'treatment_identifier': 'basque',
'controls_identifier': control_countries,
'time_optimize_ssr': list(range(1960, 1990)),
'time_plot': list(range(1960, 1998)),
}
)
sc.plot(['trends', 'weights', 'gaps'])
# --- Option 2: manual (scipy) ---
from scipy.optimize import minimize
import numpy as np
def synth_loss(w, Y_pre_control, Y_pre_treated):
"""Minimize pre-treatment fit: ||Y_treated - Y_control @ w||^2"""
return np.sum((Y_pre_treated - Y_pre_control @ w) ** 2)
n_controls = Y_pre_control.shape[1]
w0 = np.ones(n_controls) / n_controls
constraints = [{'type': 'eq', 'fun': lambda w: w.sum() - 1}]
bounds = [(0, 1)] * n_controls
res = minimize(synth_loss, w0,
args=(Y_pre_control, Y_pre_treated),
method='SLSQP',
bounds=bounds,
constraints=constraints)
w_opt = res.x
Y_synth = Y_post_control @ w_opt
gap = Y_post_treated - Y_synth
# Pre-treatment fit (RMSPE)
rmspe_pre = np.sqrt(np.mean((Y_pre_treated - Y_pre_control @ w_opt)**2))
# Placebo tests: apply SC to each control unit, compute distribution of gaps
placebo_gaps = []
for ctrl in control_units:
Y_treated_placebo = Y_pre[:, ctrl_idx]
Y_control_placebo = np.delete(Y_pre, ctrl_idx, axis=1)
# ... fit and store gap
placebo_gaps.append(gap_placebo)
# Ratio: treated RMSPE_post / RMSPE_pre vs. controls (Abadie et al. 2010)
ratio_treated = rmspe_post / rmspe_pre
# Inference: fraction of placebos with ratio >= ratio_treated → p-value
# In-time placebo: apply SC using period before actual treatment as fake treatment
# In-space placebo: already done above
# Combines SC weights with DiD — robust to both parallel trends violations and
# imperfect pre-treatment fit
from diff_diff import SDiD
sdid = SDiD()
res = sdid.fit(data, outcome='y', treatment='treated',
unit='id', time='year', cluster='id')
res.print_summary()
Use when: Selection on observables; rich baseline covariate data.
Estimands: ATT (treated vs. matched controls), ATE (population average).
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
import numpy as np
# 1. Estimate propensity score
X_scaled = StandardScaler().fit_transform(X_covariates)
ps_model = LogisticRegression(C=1.0, max_iter=1000)
ps_model.fit(X_scaled, treatment)
p_score = ps_model.predict_proba(X_scaled)[:, 1]
# 2. Check overlap / common support
import matplotlib.pyplot as plt
plt.hist(p_score[treatment==1], alpha=0.5, label='Treated', bins=30)
plt.hist(p_score[treatment==0], alpha=0.5, label='Control', bins=30)
plt.legend(); plt.xlabel('Propensity Score')
# Trim tails: drop obs with p_score outside [0.05, 0.95]
mask = (p_score >= 0.05) & (p_score <= 0.95)
from econml.dr import LinearDRLearner
from sklearn.linear_model import LassoCV, LogisticRegressionCV
# Doubly robust (AIPW) — consistent if either outcome or propensity model correct
dr = LinearDRLearner(
model_regression=LassoCV(), # outcome model
model_propensity=LogisticRegressionCV(), # propensity model
featurizer=None
)
dr.fit(Y, T, X=X_het, W=X_controls) # X: effect modifiers, W: controls
ate = dr.ate(X_het)
print(dr.ate_interval(X_het)) # confidence interval
from causalml.match import NearestNeighborMatch
from causalml.propensity import ElasticNetPropensityModel
# Propensity score matching
pm = ElasticNetPropensityModel()
ps = pm.fit_predict(X_covariates, treatment)
matcher = NearestNeighborMatch(replace=False, ratio=1, random_state=42)
matched = matcher.match(data=df, treatment_col='treated', score_cols=['ps'])
# ATT on matched sample
att = matched[matched.treated==1]['y'].mean() - matched[matched.treated==0]['y'].mean()
# OR: Mahalanobis distance matching (better for low-dimensional X)
from pymatch.Matcher import Matcher
m = Matcher(test=df[df.treated==1], control=df[df.treated==0],
yvar='y', exclude=['id'])
m.fit_scores(balance=True, nmodels=10)
m.predict_scores()
m.match(method='min', nmatches=1, threshold=0.001)
m.assess_balance(actual=True)
# Standardized mean differences (SMD) before/after matching
def smd(x_treat, x_control):
return (x_treat.mean() - x_control.mean()) / np.sqrt(
(x_treat.var() + x_control.var()) / 2
)
for col in covariates:
before = smd(df[df.treated==1][col], df[df.treated==0][col])
after = smd(matched[matched.treated==1][col], matched[matched.treated==0][col])
print(f'{col}: SMD before={before:.3f}, after={after:.3f}')
# Target: |SMD| < 0.1 after matching
# Love plot
import matplotlib.pyplot as plt
smds_before = [...]
smds_after = [...]
plt.scatter(smds_before, covariates, label='Before', marker='o')
plt.scatter(smds_after, covariates, label='After', marker='s')
plt.axvline(0, color='k', lw=0.5); plt.axvline(0.1, color='r', ls='--')
plt.legend(); plt.xlabel('Standardized Mean Difference')
# Reweight controls to exactly match treated means (and optionally variances)
# Install: pip install ebal
from ebal import ebal
# Balances moments of X_controls exactly — no propensity model needed
weights = ebal(X_control=X[treatment==0],
X_treated=X[treatment==1],
moments=1) # 1=means, 2=means+variances
# Use weights in weighted regression
import pyfixest as pf
df["w"] = np.where(treatment == 1, 1.0, weights)
res = pf.feols("y ~ treated", data=df, weights="w", vcov={"CRV1": "id"})
Use when: High-dimensional controls; heterogeneous treatment effects; flexible functional form.
from econml.dml import LinearDML, CausalForestDML, NonParamDML
from econml.dr import ForestDRLearner
from sklearn.ensemble import GradientBoostingRegressor, GradientBoostingClassifier
from sklearn.linear_model import LassoCV, LogisticRegressionCV
# --- Linear DML (Partially Linear Robinson model) ---
dml = LinearDML(
model_y=LassoCV(), # outcome residualization
model_t=LassoCV(), # treatment residualization
discrete_treatment=False,
cv=5,
)
dml.fit(Y, T, X=X_het, W=X_controls)
print(dml.ate(), dml.ate_interval())
# --- Causal Forest (nonparametric CATE) ---
cf = CausalForestDML(
model_y=GradientBoostingRegressor(),
model_t=GradientBoostingRegressor(),
n_estimators=1000,
min_samples_leaf=5,
max_depth=5,
discrete_treatment=False,
cv=5,
)
cf.fit(Y, T, X=X_het, W=X_controls)
# Heterogeneous effects
tau_hat = cf.effect(X_het) # CATE for each unit
lb, ub = cf.effect_interval(X_het) # 95% CI
# Feature importance for heterogeneity
cf.feature_importances_ # which X drives heterogeneity
# Best linear predictor of CATE
blp = cf.ate_inference(X_het)
blp.summary_frame()
# --- IV + DML (DRIV for endogenous treatment) ---
from econml.iv.dr import LinearDRIV
driv = LinearDRIV(
model_y_xw=LassoCV(),
model_t_xw=LassoCV(),
model_z=LogisticRegressionCV(), # instrument model
discrete_instrument=True,
)
driv.fit(Y, T, Z=Z_instrument, X=X_het, W=X_controls)
| Setting | Method | Key Library |
|---|---|---|
| OLS / WLS / Poisson with FE | Linear models | pyfixest |
| Panel + policy shock, parallel trends | DID / TWFE / CS / SA | diff-diff + pyfixest |
| Staggered adoption | CS, SA, BJS | diff-diff |
| Exogenous instrument | 2SLS / GMM / LIML | linearmodels (or pyfixest for panel IV) |
| Weak instrument concern | AR confidence set, LIML | linearmodels |
| Cutoff assignment rule | Sharp / Fuzzy RD | rdrobust |
| Slope discontinuity | RKD | rdrobust (deriv=1) |
| N=1 treated, long panel | Synthetic Control | pysynth / manual |
| SC + panel structure | Synthetic DiD | diff-diff (SDiD) |
| Selection on observables | PSM / IPW / EB | causalml, ebal |
| High-dim controls, binary T | AIPW / DR-Learner | econml |
| Heterogeneous effects | Causal Forest | econml |
| Endogenous T + heterogeneity | DRIV | econml |
from scipy.optimize import brentq, root
# Scalar: prefer brentq (robust)
r_star = brentq(lambda r: asset_market_clearing(r, params), -0.05, 0.1)
# Multivariate
sol = root(equilibrium_system, x0=initial_guess, method='hybr', tol=1e-10)
import quantecon as qe
# Tauchen: AR(1) log y' = rho log y + sigma_e * eps
mc = qe.tauchen(rho, sigma_e, n=7)
y_grid = np.exp(mc.state_values)
trans_mat = mc.P
# Rouwenhorst (better for high persistence)
mc = qe.rouwenhorst(n=7, rho=rho, sigma=sigma_e)
# 1. Vectorize with numpy first
# 2. Must loop → @njit
# 3. Parallelizable outer loop → @njit(parallel=True) + prange
# 4. Sparse structure → scipy.sparse
from numba import njit, prange
@njit(parallel=True)
def parallel_vfi(V, a_grid, y_grid, beta, sigma):
n_a = len(a_grid)
V_new = np.empty_like(V)
for ia in prange(n_a):
...
return V_new
| Mistake | Correct Approach |
|---|---|
| TWFE with staggered treatment | Use CS / SA / BJS to avoid negative-weight bias |
| DID without clustered SEs | cluster='id' in diff_diff |
| Few clusters (<50) | inference='wild_bootstrap' in diff-diff |
| IV: not checking first-stage F | Always print res.first_stage; F > 104 preferred |
| IV: J-test p < 0.05 with overid | Instrument likely invalid; reconsider exclusion restriction |
| RD: single bandwidth choice | Show robustness across multiple bandwidths |
| RD: not testing density at cutoff | Run McCrary / rddensity test always |
| Matching: not checking balance | Report SMD before/after; target |SMD| < 0.1 |
| Matching: ignoring common support | Trim p-score outside [0.05, 0.95] |
| SC: poor pre-treatment fit | RMSPE_pre high → SC weights unreliable; report fit explicitly |
| VFI inner loops without Numba | Decorate with @njit |
| Uniform grid for income | Tauchen / Rouwenhorst discretization |
| Linear asset grid | Log/exponential spacing near borrowing constraint |
| Not checking solver convergence | Inspect sol.success and residuals |
DID: Pre-period event study coefficients ≈ 0; Goodman-Bacon decomposition for TWFE weight check
IV: First-stage F > 104; reduced form significant; J-test p > 0.1 (overid); AR confidence set if weak instruments
RD: Density test p > 0.05; covariates smooth at cutoff; robust to bandwidth choice
SC: Pre-treatment RMSPE small; placebo RMSPE ratio (post/pre) for inference
Matching: |SMD| < 0.1 after matching; Love plot; common support overlap
DSGE/HANK: All market-clearing residuals < 1e-8; VFI: plot max|V_{n+1} - V_n|; Distribution: assert np.isclose(dist.sum(), 1.0); Jacobian: np.allclose(J_analytic, J_fd, rtol=1e-4)
评论 (0)
暂无评论,成为第一个评论者吧!