R Econometrics
Generate rigorous, modern, reproducible R code for causal inference and panel econometrics. Default to estimators that are robust to the failure modes of 2010-era TWFE, weak instruments, and naive cluster-robust inference.
Operating Principles
- Identification before code. State the estimand, the source of identifying variation, and the threats to identification before writing any model. Write these as comments at the top of the script.
- Modern estimators by default. Plain TWFE under staggered treatment is biased; weak IV with F just above 10 is unreliable; RDD without
rdrobust understates uncertainty. Use heterogeneity-robust DiD, weak-IV-robust inference, and bias-corrected RDD unless the user explicitly opts out.
- Match inference to design. Cluster at the level of treatment assignment or sampling. With few clusters (rule of thumb < 30), use wild cluster bootstrap, not cluster-robust SEs.
- Make every script runnable. Set seeds, declare paths, write to
results/, separate one-time data prep from estimation, and never silently drop observations.
- Show the alternatives. When two reasonable estimators exist (TWFE vs Callaway-Sant'Anna; CR1 vs wild bootstrap), report both and explain divergence.
Decision Policy
This skill follows the repo-wide Agent Policy.
ASK before proceeding (blocking):
- Estimand and identification strategy (RCT / IV / DiD / RDD / descriptive).
- Treatment timing — uniform shock vs staggered adoption (drives the choice between TWFE and CS / SA / BJS).
- Cluster level for inference (treatment-assignment level by default).
- Sample restrictions to apply (each is irreversible without re-running).
DEFAULT + flag (use this default; tell the user how to override):
feols from fixest over lm for panel models.
- Heterogeneity-robust DiD (Callaway-Sant'Anna by default) when timing is staggered; TWFE only when the user opts in.
rdrobust with MSE-optimal bandwidth for sharp RDD; report sensitivity at h/2, h, 2h.
- Wild cluster bootstrap (
fwildclusterboot) when there are fewer than 30 clusters.
- Output to
results/ (tables) and results/figures/ (vector PDF).
DOCUMENT and proceed (write into the decisions log):
- The chosen estimator within an approved family (e.g. CS vs SA vs BJS).
- The omitted period in event studies (default:
-1).
- Inference type (CR1, CR2, wild bootstrap) and cluster level.
- Any composition shifts that emerged (e.g. dropped never-treated cohorts because too small).
PROCEED items: set.seed(20240101), here::here() paths, theme_paper() for figures, asserting panel structure with n_distinct before estimation.
Pre-flight Checklist
Before writing code, confirm with the user — and write the answers as a header block:
- Estimand. ATT, LATE, ITT, sharp/fuzzy RD effect, or descriptive?
- Unit of observation and panel keys. Assert uniqueness (
fixest::demean will silently aggregate; dplyr::n_distinct(unit, time) should equal nrow(df) for a balanced panel).
- Treatment timing. Single shock, simultaneous adoption, staggered adoption, or reversible?
- Identifying variation. Which comparisons drive the coefficient? What is the implicit control group?
- Clustering level. Where is treatment assigned, or where does within-group correlation live?
- Sample restrictions. Document every filter and report the resulting N at each step.
- Output target. Working paper, slides, journal submission, or exploratory.
Estimator Decision Tree
Difference-in-Differences
Is treatment timing the same across all treated units?
├── YES (one shock, two groups)
│ └── feols(y ~ treat_post | unit + time, cluster = ~unit) # OK
│
└── NO (staggered adoption)
├── Are treatment effects plausibly homogeneous?
│ └── If you really believe this: TWFE with caveats.
│
└── Otherwise (the realistic case):
├── Callaway & Sant'Anna (did::att_gt + aggte)
├── Sun & Abraham (fixest::sunab)
├── Borusyak/Jaravel/Spiess (didimputation::did_imputation)
└── de Chaisemartin & D'Haultfoeuille (DIDmultiplegt)
Report at least two estimators when possible.
Instrumental Variables
Always estimate and report the first stage explicitly.
Effective first-stage F (Olea-Pflueger):
├── F >= 100 → conventional 2SLS inference is reliable
├── 10 <= F < 100 → conventional t is unreliable; report:
│ ├── Anderson-Rubin confidence set (ivmodel, ivDiag)
│ └── tF-adjusted CI (Lee 2022)
└── F < 10 → instrument is weak; do not interpret 2SLS as causal
without weak-IV-robust inference.
Panel IV: feols(y ~ x1 | fe | endog ~ z, data = df)
First stage: summary(model, stage = 1)
Regression Discontinuity
Sharp RDD:
├── Default: rdrobust::rdrobust(y, x, c = cutoff)
├── Visualization: rdrobust::rdplot(y, x, c = cutoff)
├── Density manipulation test: rddensity::rddensity(x, c = cutoff)
└── Bandwidth sensitivity: report estimates over [h/2, h, 2h]
Fuzzy RDD: rdrobust(y, x, c, fuzzy = treatment)
Never pick a bandwidth by eye. Use rdbwselect or rdrobust's MSE-optimal default.
Inference
Number of clusters G:
├── G >= 50 → vcov = cluster-robust (CR1) is fine
├── 30 <= G < 50 → use clubSandwich::vcovCR(..., type = "CR2")
└── G < 30 → use wild cluster bootstrap (fwildclusterboot::boottest)
Multi-way clustering only when there is real correlation along
both dimensions (e.g. industry x year shocks). Adding a second
dimension always inflates SEs and is not "more conservative" — it
is a different estimator.
Output Skeleton
Every analysis script should follow this structure:
# 0. Header: estimand, identification, sample, clustering, data version
# 1. Setup: packages, seed, paths
# 2. Load + assert panel structure (isid-equivalent)
# 3. Sample construction with documented filters and Ns
# 4. Descriptives (Table 1, balance table)
# 5. Main specification(s)
# 6. Robustness ladder (alt FE, alt cluster, alt sample, alt estimator)
# 7. Diagnostics (pre-trends, first-stage, density, placebo)
# 8. Tables: etable() or modelsummary() to results/
# 9. Figures: iplot(), coefplot() to results/
# 10. Save fitted models with versioned filenames
Common Pitfalls
- Plain TWFE with staggered timing — biased when effects are heterogeneous (Goodman-Bacon 2021, de Chaisemartin & D'Haultfoeuille 2020).
- Reporting first-stage F > 10 as a sufficient pre-test — biased; use AR or tF (Andrews, Stock & Sun 2019; Lee 2022).
- Cluster-robust SEs with a handful of clusters — undercoverage; use wild bootstrap (Cameron, Gelbach & Miller 2008; MacKinnon & Webb 2018).
- Picking an RDD bandwidth by eye — use
rdrobust / rdbwselect.
- Using
lm() for panel data — slow, no within transform, fragile clustering. Use feols.
- Quietly dropping observations — log every filter and report sample sizes.
- Treating a coefficient on
treated * post as the "DiD" without thinking about composition when treatment is staggered.
Additional Resources
reference.md — extended code patterns for each estimator family.
examples/ — runnable R scripts:
examples/did_twfe_baseline.R — single-shock DiD with fixest
examples/did_callaway_santanna.R — staggered DiD with did
examples/event_study_sun_abraham.R — heterogeneity-robust event study
examples/iv_weak_instruments.R — first-stage diagnostics, AR set, tF
examples/rdd_robust.R — sharp and fuzzy RDD with rdrobust
examples/wild_cluster_bootstrap.R — inference with few clusters
Requirements
- R >= 4.1
- Core:
fixest, modelsummary, dplyr, ggplot2
- Modern DiD:
did, didimputation, DIDmultiplegt
- IV:
ivmodel, ivDiag
- RDD:
rdrobust, rddensity
- Inference:
clubSandwich, fwildclusterboot, sandwich, lmtest
install.packages(c(
"fixest", "modelsummary", "dplyr", "ggplot2",
"did", "didimputation", "DIDmultiplegt",
"ivmodel", "ivDiag",
"rdrobust", "rddensity",
"clubSandwich", "fwildclusterboot", "sandwich", "lmtest"
))
References
- Goodman-Bacon (2021). Difference-in-Differences with Variation in Treatment Timing. JoE.
- Callaway & Sant'Anna (2021). Difference-in-Differences with Multiple Time Periods. JoE.
- Sun & Abraham (2021). Estimating Dynamic Treatment Effects in Event Studies with Heterogeneous Effects. JoE.
- Borusyak, Jaravel & Spiess (2024). Revisiting Event Study Designs: Robust and Efficient Estimation. ReStud.
- de Chaisemartin & D'Haultfoeuille (2020). Two-way Fixed Effects Estimators with Heterogeneous Treatment Effects. AER.
- Olea & Pflueger (2013). A Robust Test for Weak Instruments. JBES.
- Lee, McCrary, Moreira & Porter (2022). Valid t-ratio Inference for IV. AER.
- Calonico, Cattaneo & Titiunik (2014). Robust Nonparametric Confidence Intervals for RDD. Econometrica.
- MacKinnon & Webb (2018). The Wild Bootstrap for Few (Treated) Clusters. EJ.
- Roth, Sant'Anna, Bilinski & Poe (2023). What's Trending in Difference-in-Differences? JoE.
1---2name: r-econometrics3description: Generates rigorous, modern, reproducible R code for causal inference and panel econometrics with `fixest`, heterogeneity-robust DiD estimators (Callaway-Sant'Anna, Sun-Abraham, BJS, de Chaisemartin-D'Haultfoeuille), weak-IV-robust inference, optimal-bandwidth RDD via `rdrobust`, and wild cluster bootstrap. Use when the user asks for IV, DiD, event studies, RDD, TWFE, staggered treatment, clustered or wild-bootstrap inference, instrumental variables, parallel trends, first-stage F, AR confidence sets, or publication-ready R regression output.4---56# R Econometrics78Generate rigorous, modern, reproducible R code for causal inference and panel econometrics. Default to estimators that are robust to the failure modes of 2010-era TWFE, weak instruments, and naive cluster-robust inference.910## Operating Principles11121. **Identification before code.** State the estimand, the source of identifying variation, and the threats to identification before writing any model. Write these as comments at the top of the script.132. **Modern estimators by default.** Plain TWFE under staggered treatment is biased; weak IV with F just above 10 is unreliable; RDD without `rdrobust` understates uncertainty. Use heterogeneity-robust DiD, weak-IV-robust inference, and bias-corrected RDD unless the user explicitly opts out.143. **Match inference to design.** Cluster at the level of treatment assignment or sampling. With few clusters (rule of thumb < 30), use wild cluster bootstrap, not cluster-robust SEs.154. **Make every script runnable.** Set seeds, declare paths, write to `results/`, separate one-time data prep from estimation, and never silently drop observations.165. **Show the alternatives.** When two reasonable estimators exist (TWFE vs Callaway-Sant'Anna; CR1 vs wild bootstrap), report both and explain divergence.1718## Decision Policy1920This skill follows the repo-wide [Agent Policy](../../AGENT_POLICY.md).2122**ASK before proceeding** (blocking):23241. Estimand and identification strategy (RCT / IV / DiD / RDD / descriptive).252. Treatment timing — uniform shock vs staggered adoption (drives the choice between TWFE and CS / SA / BJS).263. Cluster level for inference (treatment-assignment level by default).274. Sample restrictions to apply (each is irreversible without re-running).2829**DEFAULT + flag** (use this default; tell the user how to override):3031- `feols` from `fixest` over `lm` for panel models.32- Heterogeneity-robust DiD (Callaway-Sant'Anna by default) when timing is staggered; TWFE only when the user opts in.33- `rdrobust` with MSE-optimal bandwidth for sharp RDD; report sensitivity at h/2, h, 2h.34- Wild cluster bootstrap (`fwildclusterboot`) when there are fewer than 30 clusters.35- Output to `results/` (tables) and `results/figures/` (vector PDF).3637**DOCUMENT and proceed** (write into the decisions log):3839- The chosen estimator within an approved family (e.g. CS vs SA vs BJS).40- The omitted period in event studies (default: `-1`).41- Inference type (CR1, CR2, wild bootstrap) and cluster level.42- Any composition shifts that emerged (e.g. dropped never-treated cohorts because too small).4344`PROCEED` items: `set.seed(20240101)`, `here::here()` paths, `theme_paper()` for figures, asserting panel structure with `n_distinct` before estimation.4546## Pre-flight Checklist4748Before writing code, confirm with the user — and write the answers as a header block:4950- **Estimand.** ATT, LATE, ITT, sharp/fuzzy RD effect, or descriptive?51- **Unit of observation and panel keys.** Assert uniqueness (`fixest::demean` will silently aggregate; `dplyr::n_distinct(unit, time)` should equal `nrow(df)` for a balanced panel).52- **Treatment timing.** Single shock, simultaneous adoption, staggered adoption, or reversible?53- **Identifying variation.** Which comparisons drive the coefficient? What is the implicit control group?54- **Clustering level.** Where is treatment assigned, or where does within-group correlation live?55- **Sample restrictions.** Document every filter and report the resulting N at each step.56- **Output target.** Working paper, slides, journal submission, or exploratory.5758## Estimator Decision Tree5960### Difference-in-Differences6162```63Is treatment timing the same across all treated units?64├── YES (one shock, two groups)65│ └── feols(y ~ treat_post | unit + time, cluster = ~unit) # OK66│67└── NO (staggered adoption)68 ├── Are treatment effects plausibly homogeneous?69 │ └── If you really believe this: TWFE with caveats.70 │71 └── Otherwise (the realistic case):72 ├── Callaway & Sant'Anna (did::att_gt + aggte)73 ├── Sun & Abraham (fixest::sunab)74 ├── Borusyak/Jaravel/Spiess (didimputation::did_imputation)75 └── de Chaisemartin & D'Haultfoeuille (DIDmultiplegt)7677 Report at least two estimators when possible.78```7980### Instrumental Variables8182```83Always estimate and report the first stage explicitly.8485Effective first-stage F (Olea-Pflueger):86├── F >= 100 → conventional 2SLS inference is reliable87├── 10 <= F < 100 → conventional t is unreliable; report:88│ ├── Anderson-Rubin confidence set (ivmodel, ivDiag)89│ └── tF-adjusted CI (Lee 2022)90└── F < 10 → instrument is weak; do not interpret 2SLS as causal91 without weak-IV-robust inference.9293Panel IV: feols(y ~ x1 | fe | endog ~ z, data = df)94First stage: summary(model, stage = 1)95```9697### Regression Discontinuity9899```100Sharp RDD:101├── Default: rdrobust::rdrobust(y, x, c = cutoff)102├── Visualization: rdrobust::rdplot(y, x, c = cutoff)103├── Density manipulation test: rddensity::rddensity(x, c = cutoff)104└── Bandwidth sensitivity: report estimates over [h/2, h, 2h]105106Fuzzy RDD: rdrobust(y, x, c, fuzzy = treatment)107108Never pick a bandwidth by eye. Use rdbwselect or rdrobust's MSE-optimal default.109```110111### Inference112113```114Number of clusters G:115├── G >= 50 → vcov = cluster-robust (CR1) is fine116├── 30 <= G < 50 → use clubSandwich::vcovCR(..., type = "CR2")117└── G < 30 → use wild cluster bootstrap (fwildclusterboot::boottest)118119Multi-way clustering only when there is real correlation along120both dimensions (e.g. industry x year shocks). Adding a second121dimension always inflates SEs and is not "more conservative" — it122is a different estimator.123```124125## Output Skeleton126127Every analysis script should follow this structure:128129```r130# 0. Header: estimand, identification, sample, clustering, data version131# 1. Setup: packages, seed, paths132# 2. Load + assert panel structure (isid-equivalent)133# 3. Sample construction with documented filters and Ns134# 4. Descriptives (Table 1, balance table)135# 5. Main specification(s)136# 6. Robustness ladder (alt FE, alt cluster, alt sample, alt estimator)137# 7. Diagnostics (pre-trends, first-stage, density, placebo)138# 8. Tables: etable() or modelsummary() to results/139# 9. Figures: iplot(), coefplot() to results/140# 10. Save fitted models with versioned filenames141```142143## Common Pitfalls144145- Plain TWFE with staggered timing — biased when effects are heterogeneous (Goodman-Bacon 2021, de Chaisemartin & D'Haultfoeuille 2020).146- Reporting first-stage F > 10 as a sufficient pre-test — biased; use AR or tF (Andrews, Stock & Sun 2019; Lee 2022).147- Cluster-robust SEs with a handful of clusters — undercoverage; use wild bootstrap (Cameron, Gelbach & Miller 2008; MacKinnon & Webb 2018).148- Picking an RDD bandwidth by eye — use `rdrobust` / `rdbwselect`.149- Using `lm()` for panel data — slow, no within transform, fragile clustering. Use `feols`.150- Quietly dropping observations — log every filter and report sample sizes.151- Treating a coefficient on `treated * post` as the "DiD" without thinking about composition when treatment is staggered.152153## Additional Resources154155- `reference.md` — extended code patterns for each estimator family.156- `examples/` — runnable R scripts:157 - `examples/did_twfe_baseline.R` — single-shock DiD with `fixest`158 - `examples/did_callaway_santanna.R` — staggered DiD with `did`159 - `examples/event_study_sun_abraham.R` — heterogeneity-robust event study160 - `examples/iv_weak_instruments.R` — first-stage diagnostics, AR set, tF161 - `examples/rdd_robust.R` — sharp and fuzzy RDD with `rdrobust`162 - `examples/wild_cluster_bootstrap.R` — inference with few clusters163164## Requirements165166- R >= 4.1167- Core: `fixest`, `modelsummary`, `dplyr`, `ggplot2`168- Modern DiD: `did`, `didimputation`, `DIDmultiplegt`169- IV: `ivmodel`, `ivDiag`170- RDD: `rdrobust`, `rddensity`171- Inference: `clubSandwich`, `fwildclusterboot`, `sandwich`, `lmtest`172173```r174install.packages(c(175 "fixest", "modelsummary", "dplyr", "ggplot2",176 "did", "didimputation", "DIDmultiplegt",177 "ivmodel", "ivDiag",178 "rdrobust", "rddensity",179 "clubSandwich", "fwildclusterboot", "sandwich", "lmtest"180))181```182183## References184185- Goodman-Bacon (2021). *Difference-in-Differences with Variation in Treatment Timing*. JoE.186- Callaway & Sant'Anna (2021). *Difference-in-Differences with Multiple Time Periods*. JoE.187- Sun & Abraham (2021). *Estimating Dynamic Treatment Effects in Event Studies with Heterogeneous Effects*. JoE.188- Borusyak, Jaravel & Spiess (2024). *Revisiting Event Study Designs: Robust and Efficient Estimation*. ReStud.189- de Chaisemartin & D'Haultfoeuille (2020). *Two-way Fixed Effects Estimators with Heterogeneous Treatment Effects*. AER.190- Olea & Pflueger (2013). *A Robust Test for Weak Instruments*. JBES.191- Lee, McCrary, Moreira & Porter (2022). *Valid t-ratio Inference for IV*. AER.192- Calonico, Cattaneo & Titiunik (2014). *Robust Nonparametric Confidence Intervals for RDD*. Econometrica.193- MacKinnon & Webb (2018). *The Wild Bootstrap for Few (Treated) Clusters*. EJ.194- Roth, Sant'Anna, Bilinski & Poe (2023). *What's Trending in Difference-in-Differences?* JoE.