Staggered DiD Tools: Event Studies and ATT Aggregation
Overview
Provides tools for difference-in-differences (DiD) estimation and visualization with staggered adoption. Includes run_es() for event-study curves (dynamic effects by relative time) and calc_att() for aggregated ATT estimation (overall, by cohort, by calendar time). Supports multiple modern estimators: Callaway-Sant'Anna (2021), Sun-Abraham (2021), Borusyak-Jaravel-Spiess (2024), Wooldridge TWM, and Deb et al. FLEX.
Bug fixes
- **
run_did()reportednrow(data)instead of the estimation sample size.**attr(., "N")andN_treated(and the printed "N = … obs") counted all rows ofdata, including thosefixest::feols()drops for missing values, disagreeing withnobs(model),broom::glance(), andrun_es()(which already report the fitted sample).N,N_units, andN_treatednow describe the estimation sample, recovered from the model'sobs_selection. On data without missing values the numbers are unchanged.
New features
- **
calc_att()results can now be tabulated.**att_resultgainsbroom::tidy()andbroom::glance()methods, so aggregated ATTs (overall, by-cohort, or by-time; CS or BJS) can be passed tomodelsummary::modelsummary()and rendered withtinytable. Several estimators — e.g.run_did()TWFE, CS, and BJS — can be stacked side by side in one table. Thetermcolumn is derived from the aggregation type ("ATT","Cohort <g>","Time <t>") so rows align across estimators.
Show all changes in 0.12.0Hide the rest of the changes in 0.12.0
- **
run_es(..., rel_time = <col>)**: pass a pre-built event-time column (time relative to treatment) — e.g. theTime_to_Treatmentfrompaneltools/fect'sget.cohort()— andrun_es()uses it verbatim as the event-study factor (i(rel_time, treatment, ref = baseline)) instead of computing relative time fromtime/timing. This normalises the previoustime = event_time, timing = 0idiom and, unlike that idiom, keeps never-treated controls (NArel_time) in the estimation sample. Requiresestimator = "twfe"andmethod = "classic"; the default calendar-difference behaviour is unchanged. See the new **Details** section of?run_esfor the three relative-time conventions (calendar difference, within-unit rank, pre-built) and when they diverge in sparse/unbalanced panels.
Diagnostics
- **CS** now names cohorts that are silently dropped because their base period
g-1is not observed (a real hazard in sparse panels, where a cohort can vanish from the by-cohort and event-study aggregates), and exposes them asattr(result, "dropped_cohorts")on bothrun_es()andcalc_att(). - **SA** warns when the saturated design is degenerate (a large share of cohort×period interactions dropped as collinear, or implausibly large CATT standard errors), which is easy to miss with sparse/irregular time.
- **BJS** now reports
attr(result, "n_unimputed")(andn_treated_obs): the number of treated observations whose counterfactual could not be imputed and were excluded from aggregation, on bothrun_es()andcalc_att().
Internals — reduced fixest dependency
The long-term plan of trimming the fixest dependency (see CLAUDE.md)
begins in this release. Two internal engines now replace fixest::feols()
in most estimators; **all user-facing APIs, output columns, and numerics are
unchanged** — every change here is verified against the previous
fixest-based results to numerical agreement (tolerance = 1e-6, typically
much closer).
- **BJS Step 1** (fixed-effects nuisance regression) is now estimated by an internal alternating-projections solver (
src/fe_solver.cpp) instead offixest::feols(y ~ 0 | unit + time)+fixest::fixef(). It reproducesfixest's estimation sample exactly, including the iterative removal of singleton fixed-effect levels (dropping one singleton can create another). - **SA, TWM, FLEX, and
compute_contamination_weights()** now run their saturated cohort × period regressions through a new internal FE-OLS engine (R/fe_ols.R,src/demean_kway.cpp,src/fe_ols_kernels.cpp): k-way fixed-effects demeaning, keep-first collinearity dropping, and iid / heteroskedasticity-robust (HC1) / one-way cluster-robust standard errors that reproducefixest's default small-sample corrections exactly (ssc(adj = TRUE, cluster.adj = TRUE), nested-fixed-effect accounting). Requests the internal engine cannot reproduce exactly — multi-way clustering, customvcov_args, or fixed-effect specifications that are not plain columns — are transparently delegated tofixest, so every code path keeps returning identical results. - The regressions are parallelised with OpenMP (falls back to sequential execution where OpenMP is unavailable) and are within a few percent of
fixest's multi-threaded speed on saturated designs with hundreds of cohort × period columns. Thread count defaults to half the detected physical cores and can be set withoptions(fixes.nthreads = <n>). fixestremains the estimation engine for classic TWFE /method = "sunab"inrun_es(), forrun_did(), and as the fallback described above; it is still a required (Imports) dependency.parallel(base R) is now also imported, for core-count detection only.
From NEWS.md in the release
Install
Health
- OK13 OK2026-08-05
- NOTE12 OK, 1 NOTE2026-08-01
- OK13 OK2026-06-09
- ERROR12 OK, 1 ERROR2026-06-08
- OK14 OK2026-03-10
Documentation
- Help pages
- 15
- Examples that run
- 0%8 never run · 2 hold no code
- Exported functions with examples
- 50%
- Documented parameters
- 91%
- Return-value docs
- 100%
- Help pages citing references
- 13%
Downloads
Repository
- PRs merged (all time)
- 83%
- Median days to close, 50 most recently updated closed issues
- not measured
- Median days to close, 50 most recently updated closed PRs
- not measured
7 development-tooling and community-health practices detected across 3 families in the upstream repository
Checks run against github.com/yo5uke/fixes on 2026-10-18.
The R CMD check workflow runs on Linux, macOS and Windows, and on R-devel
GitHub Pages site last deployed 2026-09-21 · The repository has a site deploy workflow
This repository shows signs of AI coding tools.
- Claude Codefirst seen 2025-10-31Seen in the repository: .claude, CLAUDE.mdNamed in .Rbuildignore: .claude, CLAUDE.mdNamed in .gitignore: .claudePull request it wrote: opened by a project memberCommits: 1 by its own account · at least 55 crediting it in the message
- GitHub Copilotfirst seen 2026-07-04Pull request: opened by its own accountCommits: 4 by its own account
- Claude Fable 522 commits · Jun to Jul 2026
- Claude Sonnet 4.621 commits · May 2026
- Claude Opus 4.88 commits · May to Jun 2026
- Claude Sonnet 51 commit · Jul 2026
Dependencies
Code & Tests
People & History
Pick two releases to compare their code metrics.
- 0.12.0Latest
- 0.11.22026-06-11 · diff ↗ ·
added 6 exports
calc_att, compute_contamination_weights, honest_sensitivity, plot_contamination_weights, plot_honest, run_did - 0.8.1
- 0.7.2
- RR 4.6.0 released · 2026-04-24
- 0.5.02025-07-07 · diff ↗
- 0.4.12025-06-30 · diff ↗
- 0.4.02025-05-25 · diff ↗
- 0.2.12025-05-10 · diff ↗
- 0.2.02025-04-23 · diff ↗
- RR 4.5.0 released · 2025-04-11
- 0.1.02025-03-18
- RR 4.4.0 released · 2024-04-24
Package metadata
- First published
- 2025-03-18
- Total releases
- 10 / 1 yrs
- License
- MIT + file LICENSE OSI
- Minimum R
- ≥ 4.1.0
- Download size
- 866 KB
Cite
Cite this package
Run in R for the authors' preferred citation:
citation("fixes")This package has no citation file, so citation() returns this entry built from DESCRIPTION.
Cite the R Observatory
For a number measured here: a download total, a coverage figure, an archival date.
From data release v2026-10-21, which the citation names so these numbers can be found later. More on citing and the projects behind them.