|
2 | 2 |
|
3 | 3 | All notable changes to StatsPAI will be documented in this file. |
4 | 4 |
|
| 5 | +## [0.7.1] - 2026-04-15 |
| 6 | + |
| 7 | +DID-focused polish release. Brings the Wooldridge (2021) ETWFE |
| 8 | +implementation to full feature parity with the R `etwfe` package, |
| 9 | +adds a one-call method-robustness workflow, and closes 12 issues |
| 10 | +uncovered by an internal code review round. All 27 new / updated |
| 11 | +DID tests pass (`pytest tests/test_did_summary.py`). |
| 12 | + |
| 13 | +### Added — ETWFE full parity with R `etwfe` |
| 14 | + |
| 15 | +- **`sp.etwfe()` explicit API** aligned with R `etwfe` (McDermott 2023) |
| 16 | + naming. Thin alias over `wooldridge_did()` with a full argument- |
| 17 | + mapping table in the docstring. |
| 18 | +- **`xvar=` covariate heterogeneity** (single string or list of names). |
| 19 | + Adds per-cohort × post × `(x_j − mean(x_j))` interactions; `detail` |
| 20 | + gains `slope_<x>` / `slope_<x>_se` / `slope_<x>_pvalue` columns. |
| 21 | + Baseline ATT is reported at the sample means of every covariate. |
| 22 | +- **`panel=False` repeated cross-section mode** — replaces unit FE |
| 23 | + with cohort + time dummies (R `etwfe(ivar=NULL)` equivalent). |
| 24 | +- **`cgroup='nevertreated'`** — per-cohort regressions restricted to |
| 25 | + (cohort g) ∪ (never-treated); cohort-size-weighted aggregation |
| 26 | + (R `etwfe(cgroup='never')` equivalent). Default `'notyet'` preserves |
| 27 | + prior ETWFE behaviour. |
| 28 | +- **`sp.etwfe_emfx(result, type=…)`** — R `etwfe::emfx`-equivalent |
| 29 | + four aggregations: `'simple'`, `'group'`, `'event'`, `'calendar'`. |
| 30 | + `include_leads=True` returns full event-time output including pre- |
| 31 | + treatment leads for pre-trend inspection (`rel_time = -1` is the |
| 32 | + reference category). |
| 33 | + |
| 34 | +### Added — one-call DID method-robustness workflow |
| 35 | + |
| 36 | +- **`sp.did_summary()`** — fits five modern staggered-DID estimators |
| 37 | + (CS, SA, BJS, ETWFE, Stacked) to the same data and returns a tidy |
| 38 | + comparison table with per-method (estimate, SE, p, 95 % CI). Mean |
| 39 | + across methods + cross-method SD flag method-sensitivity of results. |
| 40 | +- **`include_sensitivity=True`** — attaches the Rambachan-Roth (2023) |
| 41 | + breakdown `M*` to the CS row, giving a three-way robustness readout |
| 42 | + in a single call. |
| 43 | +- **`sp.did_summary_plot()`** — forest plot of per-method estimates |
| 44 | + with cross-method mean line; `sort_by='estimate'` supported. |
| 45 | +- **`sp.did_summary_to_markdown()` / `_to_latex()`** — publication- |
| 46 | + ready exports (GFM tables / booktabs LaTeX with auto-escaped |
| 47 | + ampersands). |
| 48 | +- **`sp.did_report(save_to=dir)`** — one-call bundle that writes |
| 49 | + `did_summary.txt` / `.md` / `.tex` / `.png` / `.json` to a folder. |
| 50 | + |
| 51 | +### Fixed — 12 issues from the internal code review |
| 52 | + |
| 53 | +Blockers (C-severity): |
| 54 | + |
| 55 | +- `etwfe(xvar=…)` now raises a clear `ValueError` when the covariate |
| 56 | + is all-NaN or (near-)constant. Previously returned `n_obs = 0, |
| 57 | + estimate = 0` silently. |
| 58 | +- `etwfe(panel=False, cgroup='nevertreated')` now raises a crisp |
| 59 | + `NotImplementedError` instead of silently falling back to |
| 60 | + `'notyet'`. |
| 61 | +- `did_summary` now validates column names up front (raises |
| 62 | + `KeyError` listing missing columns) and only catches narrow |
| 63 | + estimator-side exceptions inside the fit loop; user typos in |
| 64 | + `controls=` / `cluster=` surface as proper errors. |
| 65 | +- `did_summary` results round-trip cleanly through stdlib |
| 66 | + serialisation (`DIDSummaryResult(CausalResult)` subclass with a |
| 67 | + real `.summary()` method, replacing the prior closure-bound |
| 68 | + instance attribute). |
| 69 | + |
| 70 | +High-severity: |
| 71 | + |
| 72 | +- `etwfe_emfx(type='event'/'calendar')` now computes SEs via the |
| 73 | + delta method on the stored event-study vcov instead of the |
| 74 | + independent-coefficient approximation. `model_info['se_method']` |
| 75 | + advertises which path was used. |
| 76 | +- `etwfe_emfx(type='group')` headline `se` / `pvalue` / `ci` are now |
| 77 | + populated (match the underlying fit's overall ATT exactly). |
| 78 | +- Validation for `did_summary_plot` / `_to_markdown` / `_to_latex` |
| 79 | + aligned on a single sentinel `model_info['_did_summary_marker']`. |
| 80 | +- `_etwfe_never_only` no longer leaves a `_ft_cache` helper column |
| 81 | + on the caller's DataFrame. |
| 82 | +- Slope indexing in `_etwfe_with_xvar` is now name-keyed |
| 83 | + (`coef_index` dict); regression test verifies swapping |
| 84 | + `xvar=['x1','x2']` vs `['x2','x1']` produces identical slopes per |
| 85 | + name. |
| 86 | +- `etwfe(panel=False)` with rank-deficient designs emits a |
| 87 | + `RuntimeWarning` pointing at concrete remedies (previously fell |
| 88 | + through to `pinv` silently). |
| 89 | + |
| 90 | +### Tests |
| 91 | + |
| 92 | +- New test module `tests/test_did_summary.py` — 27 cases covering |
| 93 | + consistency with direct estimator calls, export formats, forest |
| 94 | + plot rendering, `etwfe_emfx` round-trips, xvar / panel / cgroup |
| 95 | + options, the 12 review fixes, and the `include_leads` mode. |
| 96 | + |
5 | 97 | ## [0.7.0] - 2026-04-14 |
6 | 98 |
|
7 | 99 | Focused release reaching feature parity with the R `did` / `HonestDiD` |
|
0 commit comments