Skip to content

Commit 953febf

Browse files
chore(release): bump version to 0.7.0
Consolidates the v0.7.0 and previous v0.7.1 CHANGELOG sections into a single v0.7.0 release, matching the bumped version in pyproject.toml and the package __version__. No code changes. Contents of this release (summary): * `sp.callaway_santanna` with DR/IPW/REG, anticipation, and panel=False for repeated cross-sections (with optional covariate residualisation). * `sp.aggte` with four aggregations and Mammen multiplier-bootstrap uniform confidence bands. * `sp.cs_report` one-call report card + `CSReport.plot/.to_text/ .to_markdown/.to_latex/.to_excel` export surface and `save_to='prefix'` bundle dump. * `sp.ggdid`, `sp.honest_did`, `sp.breakdown_m`, `sp.bjs_pretrend_joint`, dCDH joint placebo Wald + avg cumulative effect. * Nine pre-release audit fixes, including the critical aggte(type='dynamic').estimate semantics bug (was averaging pre + post; now averages post only). Testing * 170 DiD tests + 1017 total repo tests; zero regressions across the Ubuntu / macOS / Windows × Python 3.9–3.12 CI matrix. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent b76fdb7 commit 953febf

3 files changed

Lines changed: 99 additions & 78 deletions

File tree

CHANGELOG.md

Lines changed: 97 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -2,103 +2,123 @@
22

33
All 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

557
Focused release reaching feature parity with the R `did` / `HonestDiD`
568
packages and the Python `csdid` / `differences` packages for staggered
579
Difference-in-Differences. All core algorithms are reimplemented from
5810
the 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 → RambachanRoth 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

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "StatsPAI"
7-
version = "0.6.2"
7+
version = "0.7.0"
88
description = "The Agent-Native Causal Inference & Econometrics Toolkit for Python"
99
readme = "README.md"
1010
license = {text = "MIT"}

src/statspai/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
>>> sp.outreg2(result, filename="results.xlsx")
2323
"""
2424

25-
__version__ = "0.6.2"
25+
__version__ = "0.7.0"
2626
__author__ = "Bryce Wang"
2727
__email__ = "bryce@copaper.ai"
2828

0 commit comments

Comments
 (0)