22
33All notable changes to StatsPAI will be documented in this file.
44
5- ## [ 0.7.1] - 2026-04-14
6-
7- Follow-up release to 0.7.0: export surface, visualisations, and the
8- last remaining capability gap vs ` csdid ` / ` differences ` — repeated
9- cross-sections. Full DiD test suite: 114 → 142 (+28).
10-
11- ### Added
12-
13- - ** Repeated cross-sections** support for ` callaway_santanna() ` via
14- the new ` panel=False ` argument. Uses the unconditional 2×2
15- cell-mean DID with observation-level influence functions
16- (CS2021 eq. 2.4, RCS version), so ` aggte ` / ` cs_report ` / ` ggdid `
17- / ` honest_did ` all work on RCS results with no code changes —
18- Mammen uniform bands, four aggregations, and Rambachan–Roth
19- breakdown M\* all compose downstream. Scope of the initial
20- implementation: ` estimator='reg' ` , ` control_group='nevertreated' ` ,
21- no covariates (other paths raise ` NotImplementedError ` with an
22- actionable message).
23- - ** ` CSReport.plot() ` ** — one-call 2×2 summary figure: event study
24- with uniform band (top-left), θ(g) per-cohort (top-right), θ(t)
25- per-calendar-time (bottom-left), Rambachan–Roth breakdown M\* bars
26- (bottom-right). Re-uses ` ggdid() ` via a minimal adapter so no
27- bootstrap draws are recomputed.
28- - ** ` CSReport.to_markdown() ` ** — GitHub-Flavoured Markdown export
29- with proper integer-column rendering and a configurable
30- ` float_format ` . Ready to paste into PRs, blog posts, or Jupyter
31- Markdown cells.
32- - ** ` CSReport.to_latex() ` ** — publication-ready booktabs fragment
33- wrapped in a ` table ` float. Zero jinja2 dependency (hand-rolled
34- booktabs renderer), auto-escapes LaTeX special characters,
35- right-aligns numerics and left-aligns strings, typesets the
36- pre-trend Wald in math mode.
37- - ** ` CSReport.to_excel() ` ** — six-sheet workbook: ` Summary ` ,
38- ` Dynamic ` , ` Group ` , ` Calendar ` , ` Breakdown ` , ` Meta ` . Engine
39- autoselect (openpyxl → xlsxwriter) with a clear ImportError when
40- neither is installed.
41- - ** ` cs_report(..., save_to='prefix') ` ** — one-call dump of the
42- full export matrix: writes ` <prefix>.{txt,md,tex,xlsx,png} ` in
43- a single invocation, auto-creating missing parent directories.
44- Optional dependencies (openpyxl, matplotlib) are skipped silently
45- so a minimal install still produces text + md + tex.
46-
47- ### Changed
48-
49- - ** README** : the DiD parity matrix now reflects full RCS support
50- and adds new rows for ` .to_markdown() ` / ` .to_latex() ` /
51- ` .to_excel() ` / ` save_to= ` / ` .plot() ` .
52-
535## [ 0.7.0] - 2026-04-14
546
557Focused release reaching feature parity with the R ` did ` / ` HonestDiD `
568packages and the Python ` csdid ` / ` differences ` packages for staggered
579Difference-in-Differences. All core algorithms are reimplemented from
5810the original papers — ** no wrappers, no runtime dependencies on upstream
59- DID packages** . DiD test count: 47 → 114.
11+ DID packages** . Full DiD test suite: 47 → 170+ (including three rounds
12+ of post-implementation audit that surfaced and fixed 9 bugs before
13+ release).
6014
61- ### Added
15+ ### Added — Core estimation
6216
63- - ** ` sp.did. aggte(result, type=...) ` ** — unified aggregation layer for
17+ - ** ` sp.aggte(result, type=...) ` ** — unified aggregation layer for
6418 ` callaway_santanna() ` results. Four aggregation schemes (` simple ` ,
65- ` dynamic ` , ` group ` , ` calendar ` ) backed by a single weighted-influence-
66- function engine. Callaway & Sant'Anna (2021) Section 4.
19+ ` dynamic ` , ` group ` , ` calendar ` ) backed by a single weighted-
20+ influence- function engine. Callaway & Sant'Anna (2021) Section 4.
6721- ** Mammen (1993) multiplier bootstrap** — IQR-rescaled pointwise
68- standard errors * and* simultaneous (uniform / sup-t) confidence bands
69- over the aggregation dimension. Matches the uniform-band behaviour
70- of the R ` did::aggte ` function.
22+ standard errors * and* simultaneous (uniform / sup-t) confidence
23+ bands over the aggregation dimension. Matches the uniform-band
24+ behaviour of the R ` did::aggte ` function.
7125- ** ` balance_e ` / ` min_e ` / ` max_e ` ** — event-study cohort balancing
7226 and window truncation (CS2021 eq. 3.8).
73- - ** ` anticipation=δ ` ** parameter on ` callaway_santanna() ` — shifts the
74- base period back by δ periods per CS2021 §3.2.
75- - ** ` sp.did.cs_report(data, ...) ` ** — one-call report card. Runs the
27+ - ** ` anticipation=δ ` ** parameter on ` callaway_santanna() ` — shifts
28+ the base period back by δ periods per CS2021 §3.2.
29+ - ** Repeated cross-sections** support via ` callaway_santanna(panel=False) `
30+ — unconditional 2×2 cell-mean DID with observation-level influence
31+ functions (CS2021 eq. 2.4, RCS version). Optional covariate
32+ residualisation with ` x=[...] ` for regression adjustment. All
33+ downstream modules (` aggte ` , ` cs_report ` , ` ggdid ` , ` honest_did ` )
34+ work on RCS results with no code changes.
35+ - ** dCDH joint inference** (` did_multiplegt ` ) — ` joint_placebo_test `
36+ (Wald χ² across placebo lags with bootstrap covariance, dCDH 2024
37+ §3.3) and ` avg_cumulative_effect ` (mean of dynamic[ 0..L] with
38+ SE preserving cross-horizon covariance, dCDH 2024 §3.4).
39+ - ** ` sp.bjs_pretrend_joint() ` ** — cluster-bootstrap joint Wald pre-
40+ trend test for BJS imputation results. Upgrades the default
41+ sum-of-z² test (which assumes pre-period independence) to a full
42+ covariance-aware statistic.
43+
44+ ### Added — Reporting & visualisation
45+
46+ - ** ` sp.cs_report(data, ...) ` ** — one-call report card. Runs the
7647 full pipeline (ATT(g,t) → four aggregations with uniform bands →
77- pre-trend Wald → Rambachan- Roth breakdown M\* for every post event
48+ pre-trend Wald → Rambachan– Roth breakdown M\* for every post event
7849 time) under a single bootstrap seed and pretty-prints the result.
7950 Returns a structured ` CSReport ` dataclass.
80- - ** ` sp.did. ggdid(result) ` ** — plot routine for ` aggte() ` output,
51+ - ** ` sp.ggdid(result) ` ** — plot routine for ` aggte() ` output,
8152 mirroring R ` did::ggdid ` . Auto-dispatches on aggregation type;
8253 uniform band overlaid on pointwise CI.
83- - ** dCDH joint inference** (` did_multiplegt ` ) — ` joint_placebo_test `
84- (Wald χ² across placebo lags with bootstrap covariance, dCDH 2024
85- §3.3) and ` avg_cumulative_effect ` (mean of dynamic[ 0..L] with
86- SE preserving cross-horizon covariance, dCDH 2024 §3.4).
54+ - ** ` CSReport.plot() ` ** — one-call 2×2 summary figure: event study
55+ with uniform band (top-left), θ(g) per-cohort (top-right), θ(t)
56+ per-calendar-time (bottom-left), Rambachan–Roth breakdown M\*
57+ bars (bottom-right).
58+ - ** ` CSReport.to_markdown() ` ** — GitHub-flavoured Markdown export
59+ with proper integer-column rendering and a configurable
60+ ` float_format ` .
61+ - ** ` CSReport.to_latex() ` ** — publication-ready booktabs fragment
62+ wrapped in a ` table ` float. Zero ` jinja2 ` dependency (hand-rolled
63+ booktabs renderer); auto-escapes LaTeX special characters.
64+ - ** ` CSReport.to_excel() ` ** — six-sheet workbook (` Summary ` ,
65+ ` Dynamic ` , ` Group ` , ` Calendar ` , ` Breakdown ` , ` Meta ` ). Engine
66+ autoselect (openpyxl → xlsxwriter) with a clear ImportError when
67+ neither is installed.
68+ - ** ` cs_report(..., save_to='prefix') ` ** — one-call dump of the
69+ full export matrix: writes ` <prefix>.{txt,md,tex,xlsx,png} ` in
70+ a single invocation, auto-creating missing parent directories.
71+ Optional dependencies (openpyxl, matplotlib) are skipped silently
72+ so a minimal install still produces text + md + tex.
73+ - ** ` sp.did(..., aggregation='dynamic', n_boot=..., random_state=...) ` **
74+ — the top-level dispatcher now forwards CS-style arguments
75+ (` aggregation ` , ` panel ` , ` anticipation ` ) and can pipe a CS result
76+ straight through ` aggte() ` in a single call.
8777
8878### Changed
8979
90- - ** ` sun_abraham() ` inference layer rewritten** — replaces the former
91- ad-hoc ` √(σ²/(total·T)) ` approximation with a Liang-Zeger cluster-
92- robust sandwich ` (X'X)⁻¹ Σ_c X_c' u_c u_c' X_c (X'X)⁻¹ ` (small-sample
93- adjusted), delta-method IW aggregation SEs ` w' V_β w ` , iterative
94- two-way within transformation (correct on unbalanced panels), and
95- optional ` control_group='lastcohort' ` per SA 2021 §6.
96- - ** ` sp.did.honest_did() ` / ` breakdown_m() ` made polymorphic** — now
97- accept both the legacy ` callaway_santanna() ` / ` sun_abraham() ` result
98- format (event study in ` model_info ` ) and the new ` aggte(type='dynamic') `
80+ - ** ` sun_abraham() ` inference layer rewritten** — replaces the
81+ former ad-hoc ` √(σ²/(total·T)) ` approximation with a Liang–Zeger
82+ cluster-robust sandwich ` (X'X)⁻¹ Σ_c X_c' u_c u_c' X_c (X'X)⁻¹ `
83+ (small-sample adjusted), delta-method IW aggregation SEs
84+ ` w' V_β w ` , iterative two-way within transformation (correct on
85+ unbalanced panels), and optional ` control_group='lastcohort' ` per
86+ SA 2021 §6.
87+ - ** ` sp.honest_did() ` / ` sp.breakdown_m() ` made polymorphic** — now
88+ accept the legacy ` callaway_santanna() ` / ` sun_abraham() ` format
89+ (event study in ` model_info ` ) * and* the new ` aggte(type='dynamic') `
9990 format (event study in ` detail ` with Mammen uniform bands). The
100- idiomatic pipeline ` cs → aggte → honest_did → breakdown_m ` now runs
101- end-to-end with no manual plumbing.
91+ idiomatic pipeline ` cs → aggte → honest_did → breakdown_m ` now
92+ runs end-to-end with no manual plumbing.
93+ - ** README DiD parity matrix** added, comparing StatsPAI against
94+ ` csdid ` , ` differences ` , and R ` did ` + ` HonestDiD ` across 15
95+ capabilities.
96+
97+ ### Fixed (from pre-release audit rounds)
98+
99+ - ** Critical — ` aggte(type='dynamic').estimate ` ** previously averaged
100+ pre- * and* post-treatment event times into the overall ATT,
101+ polluting the headline number with placebo signal. Now averages
102+ only e ≥ 0, matching R ` did::aggte ` 's print convention. On a
103+ typical DGP the bug shifted the reported overall by nearly a
104+ factor of 2.
105+ - ** LaTeX escape non-idempotence** in ` CSReport.to_latex() ` :
106+ ` \ ` → ` \textbackslash{} ` followed by ` { ` → ` \{ ` mangled the
107+ just-inserted braces. Fixed with a single-pass ` re.sub ` .
108+ - ** ` cs_report(save_to='~/study/…') ` ** did not expand ` ~ ` ; fixed
109+ via ` os.path.expanduser ` .
110+ - ** ` cs_report(sa_result) ` / ` aggte(sa_result) ` ** raised cryptic
111+ ` KeyError: 'group' ` ; both entry points now detect non-CS input
112+ up-front and raise a clear ` ValueError ` .
113+ - ** ` cs_report(pre_fitted_cs, estimator=…) ` ** silently ignored the
114+ override; now emits a ` UserWarning ` listing every shadowed arg.
115+ - ** ` sp.did(method='2x2', aggregation='dynamic') ` ** silently ignored
116+ CS-only arguments; now raises an informative ` ValueError ` .
117+ - ** ` bjs_pretrend_joint ` ** swallowed all exceptions as "bootstrap
118+ failed"; now narrows to expected failure modes and re-raises
119+ unexpected errors with context.
120+ - ** ` matplotlib.use('Agg') ` ** in ` _save_report_bundle ` no longer
121+ switches the backend unconditionally (respects Jupyter sessions).
102122
103123### References
104124
@@ -109,6 +129,7 @@ DID packages**. DiD test count: 47 → 114.
109129- de Chaisemartin, C. and D'Haultfoeuille, X. (2020). * AER* 110(9).
110130- de Chaisemartin, C. and D'Haultfoeuille, X. (2024). * RESt* , forthcoming.
111131- Rambachan, A. and Roth, J. (2023). * Rev. Econ. Studies* 90(5).
132+ - Borusyak, K., Jaravel, X. and Spiess, J. (2024). * ReStud* 91(6).
112133
113134## [ 0.6.2] - 2026-04-12
114135
0 commit comments