Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ Related tracking surfaces:
| ContinuousDiD post-fit bootstrap-ES REPLAY: since 4.0 PR 3c a bootstrapped fit relays the event-study surface computed at fit (M-025), so the gap is a genuine post-fit replay (fresh draws, no fit-side cost, a route for pre-4.0 pickles); a seeded replay is tractable - the multiplier draws are seeded (`np.random.default_rng(self.seed)`) - but needs the FULL per-cell `_bootstrap_info` (bread/ee_treated/Psi_eval/dPsi_*/beta_pred) the pruned kit deliberately drops, so shipping it means a kit-payload change with its own memory contract | `diff_diff/continuous_did_aggregation.py`, `diff_diff/continuous_did_results.py` | 2(b) PR-3c | Mid | Low |
| practitioner `step_name="heterogeneity"` producer-side collisions: three OTHER estimators' advice steps reuse the key with non-heterogeneity labels (`:975` ContinuousDiD dose-response, `:1022` Triple placebo-group, `:1413` LPDiD WAS arrays), so DiagnosticReport's heterogeneity completion silently drops that unrelated advice from `next_steps` via `_filter_steps` - the same latent collision fixed for StackedDiD in M-024 (renamed to `sub_experiment_balance`). Renaming these changes those estimators' report output; audit + rename with per-estimator pins. | `diff_diff/practitioner.py` | 2(b) PR-2 review R9 | Quick | Low |
| PreTrendsPower `violation='linear'` on CS `base_period='varying'` input targets the wrong alternative: `δ_pre = M · \|t\|` assumes level coefficients against a common reference, but varying-base pre-treatment effects are consecutive-period comparisons (constant increments under a linear trend). Both CS-sourced routes now WARN (REGISTRY PreTrendsPower Note), and universal-base GAPPED grids fail closed via the `reference_event_times` common-reference guard; what remains is the varying-base resolution - either transforming the violation vector through each coefficient's actual base mapping (needs per-horizon base provenance) or requiring `base_period='universal'` for the linear benchmark - a per-estimator methodology decision with a hand-calculated linear-violation gate | `diff_diff/pretrends.py` | 2(b) PR-1 R5 | Mid | Medium |
| `run_placebo_test`/`run_all_placebo_tests` expose ONE overloaded `time` param (calendar for timing/group tests, 0/1 post dummy forwarded into the M-137/M-138 callees - works only because a two-period 0/1 calendar column is both); redesign the wrapper signatures (dual `time=`/`post=` params or a split) so each callee gets an honestly-named argument | `diff_diff/diagnostics.py` | 2(c)-ii PR-B | Mid | Medium |
| `absorbed_fe_rank` N>=3 general rank: the helper keeps `sum(levels-1)` for 3+ absorbed dims, exact for independent connected dims but an over-count for duplicated/nested triples (measured `a(5),b(4),c==b(4)`: true 7 vs formula 10) and for disconnected N-way graphs. Two-way is component-exact. Deriving general N-way FE rank is a hypergraph problem; do it with a reference (fixest's `fixef.rm`/reghdfe df_a) rather than a guess. See `docs/methodology/variance-conventions.md` D3. | `diff_diff/utils.py` | #variance-inventory | Mid | Low |
| `SyntheticControl` conformal (CWZ 2021) AR / innovation-permutation path (Lemmas 5-7) for time-series proxies — the residual-permutation shortcut is only valid for time-permutation-invariant proxies (SC/Lasso/DiD); an AR proxy needs innovation permutation. | `diff_diff/conformal.py`, `diff_diff/synthetic_control_results.py` | CWZ-2021 | Heavy | Low |
| Re-run the R-dependent benchmark refresh so `docs/benchmarks.rst`'s TWFE "SE Rel Diff 0.1%" cell reflects the 3.9 K_reference convergence (expected 0.1% -> 0.0%; the table is generated, never hand-edited — the movement is noted in the CHANGELOG entry). | `docs/benchmarks.rst`, `benchmarks/R/` | #variance-inventory | Quick | Low |
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/speed_review/bench_reversible_dcdh.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def inspect_placebo():
r = results["dcdh"]
results["placebo_summary"] = {
"placebo_effect": getattr(r, "placebo_effect", None),
"overall_att": getattr(r, "overall_att", None),
"att": getattr(r, "att", None),
"joiners_att": getattr(r, "joiners_att", None),
"leavers_att": getattr(r, "leavers_att", None),
}
Expand Down
2 changes: 1 addition & 1 deletion changelog.d/20261018-v4-cut-framing.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@
keeps due enforcement unchanged. The id snapshot and `ROW_COUNT_FLOOR` now include
`M-148`/`M-149` (shipped by #838 without extending them) and `M-150` (132 rows).
`M-091`'s roster records the four later-born `Diagnostic` containers; `M-050`'s
`code_refs` record that `diff_diff/_deprecation.py` survives 4.0 as 5.0-window
`code_refs` record that `diff_diff/_deprecation.py` survives 4.0 as pickle-migration
infrastructure; `M-045`/`M-115` name the legacy-CR1 remap branches their removal
deletes. A `maint/3.13` branch is cut at `v3.13.0` for 3.13.x patches.
6 changes: 2 additions & 4 deletions changelog.d/20261018-v4-pr1-conformance.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,11 @@
- **`RDDensityTestResult.to_dict()`**: JSON-serializable scalars with native casts (NaN
inference passes through), the `*_conventional` block `None` unless `report_all=True`, and
the bandwidth-selection table nested under `bandwidths` only when it was estimated.
- `diff_diff._deprecation.deprecated_field_property` takes `removed_in=` so the warning and the
generated docstring name the row's actual removal release.

### Deprecated
- **`LWDiDResults.pvalue` and `LWDiDResults.ci`** (ledger rows `M-151`, `M-152`): the
3.10-born alias properties for `p_value` / `conf_int` now emit a `FutureWarning` and are
removed in 5.0. Read `.p_value` / `.conf_int`.
3.10-born alias properties for `p_value` / `conf_int` were first shimmed with a `FutureWarning`
and then removed outright at 4.0 (item 9b of the cut). Read `.p_value` / `.conf_int`.

### Breaking Changes
- **`LWDiDResults.to_dataframe()` is reshaped onto the shared results schema** (ledger row
Expand Down
5 changes: 3 additions & 2 deletions changelog.d/20261018-v4-pr2-shim-removals.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,6 @@
- `tests/test_v4_matrix.py` removed-status probes: a function row that resolves to a submodule
counts as absent; a param row absent from the explicit signature counts as removed despite
`**kwargs`. Naming-guard allowlist extended for the kept body-local aliases, `__setstate__` keys
and shared vocabulary. The `diff_diff._deprecation` helpers survive for the remaining
M-031/M-082, PR-3 and 5.0-window shims.
and shared vocabulary. The `diff_diff._deprecation` helpers survived this PR for the remaining
M-031/M-082 and PR-3 shims (item 9b later reduced the module to its pickle-migration and
required-arg helpers).
5 changes: 3 additions & 2 deletions changelog.d/20261025-v4-pr4c-ii-remove-mpd.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@
(`M-083`). Through 3.x `time=` was the deprecated alias for the 0/1 post indicator and
emitted a `FutureWarning` (`M-082`); since 4.0 `time` means the event-study calendar column
only. Supplying both `post=` and `time=` keeps raising the "pass only post=" error.
- **`EventStudyResults.period_effects` is a `FutureWarning` compatibility view** (`M-016`;
removed in 5.0): on a calendar surface it returns the former calendar-keyed dict of
- **`EventStudyResults.period_effects` is removed** (`M-016`; the compatibility view
this item first shipped was retired at 4.0 by item 9b - no release carried it). Through
the pre-release window, on a calendar surface it returned the former calendar-keyed dict of
per-period records (`period, effect, se, t_stat, p_value, conf_int, is_significant,
significance_stars`, reference period omitted; `is_significant` is evaluated at the fit's
`alpha`, where the removed record hard-coded 0.05; duplicate labels raise). Relative-time
Expand Down
4 changes: 2 additions & 2 deletions changelog.d/20261026-v4-pr5b-storage-flip.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
### Breaking Changes

- **4.0 cut, storage flip of the headline quintet (`M-050`..`M-058`).** `CallawaySantAnnaResults` (and `DMLDiDResults`), `SunAbrahamResults`, `ImputationDiDResults`, `TwoStageDiDResults`, `StackedDiDResults`, `EfficientDiDResults`, `WooldridgeDiDResults`, `ChaisemartinDHaultfoeuilleResults` and `ContinuousDiDResults` now store the canonical `att` / `se` / `t_stat` / `p_value` / `conf_int` as their native dataclass fields (renamed in slot, so the positional constructor layout is unchanged) and take those names as constructor keywords (`overall_att=` raises `TypeError`). The 3.x `overall_att` / `overall_se` / `overall_t_stat` / `overall_p_value` / `overall_conf_int` names - and on `ContinuousDiDResults` all nine legacy spellings, the `overall_att_*` fields and the former silent `overall_se`-style aliases - are read-only properties that emit a `FutureWarning` and are removed at 5.0; they are absent from `dataclasses.fields()`, `asdict()` and `to_dict()`. 3.x pickles load unchanged (each container's `__setstate__` rewrites the legacy keys on load). The bootstrap draw containers (`bootstrap_results.overall_att_se` and siblings) are unchanged, and `StaggeredTripleDiffResults` keeps its `overall_*` storage until the DDD container unification.
- **4.0 cut, storage flip of the headline quintet (`M-050`..`M-058`).** `CallawaySantAnnaResults` (and `DMLDiDResults`), `SunAbrahamResults`, `ImputationDiDResults`, `TwoStageDiDResults`, `StackedDiDResults`, `EfficientDiDResults`, `WooldridgeDiDResults`, `ChaisemartinDHaultfoeuilleResults` and `ContinuousDiDResults` now store the canonical `att` / `se` / `t_stat` / `p_value` / `conf_int` as their native dataclass fields (renamed in slot, so the positional constructor layout is unchanged) and take those names as constructor keywords (`overall_att=` raises `TypeError`). The 3.x `overall_att` / `overall_se` / `overall_t_stat` / `overall_p_value` / `overall_conf_int` names - and on `ContinuousDiDResults` all nine legacy spellings, the `overall_att_*` fields and the former silent `overall_se`-style aliases - are removed at 4.0 (item 9b retired the `FutureWarning` property shims this item first shipped - reading one raises `AttributeError`); they are absent from `dataclasses.fields()`, `asdict()` and `to_dict()`. 3.x pickles load unchanged (each container's `__setstate__` rewrites the legacy keys on load). The bootstrap draw containers (`bootstrap_results.overall_att_se` and siblings) are unchanged, and `StaggeredTripleDiffResults` keeps its `overall_*` storage until the DDD container unification.
- **Report schemas.** `DiagnosticReport.to_dict()` reports `headline_metric.name` and `target_parameter.headline_attribute` as `"att"` for the nine containers above (`"overall_att"` under 3.x); `DIAGNOSTIC_REPORT_SCHEMA_VERSION` 2.0 -> 3.0. `BusinessReport.to_dict()` reports `target_parameter.headline_attribute` likewise; `BUSINESS_REPORT_SCHEMA_VERSION` 3.0 -> 4.0 (its `headline` block carries no name key). `StaggeredTripleDiffResults` keeps `headline_attribute: "overall_att"` until item 6.

### Internal

- Ledger rows `M-050`..`M-058` flip to `shimmed` with `tests/test_v4_storage_flip.py` as their `test_ref`; `diff_diff._deprecation` gains `LEGACY_OVERALL_QUINTET` and `migrate_renamed_state_keys` (5.0-window infrastructure); `DMLDiDResults` re-declares the five shims so the warning names the subclass (naming-guard allowlist reason reworded); `_extract_scalar_headline`'s legacy alias row is deleted; `tests/test_result_aliases.py` pins the post-flip direction (SDDD kept in the 3.x direction); the API autosummary stubs are regenerated; the bootstrap draw containers' payload names are recorded as a REGISTRY `Note` (user decision 2026-10-26); spec item 5 carries its SHIPPED note.
- Ledger rows `M-050`..`M-058` flip to `shimmed` with `tests/test_v4_storage_flip.py` as their `test_ref`; `diff_diff._deprecation` gains `LEGACY_OVERALL_QUINTET` and `migrate_renamed_state_keys` (the pickle-migration infrastructure); `DMLDiDResults` re-declares the five shims so the warning names the subclass (naming-guard allowlist reason reworded); `_extract_scalar_headline`'s legacy alias row is deleted; `tests/test_result_aliases.py` pins the post-flip direction (SDDD kept in the 3.x direction); the API autosummary stubs are regenerated; the bootstrap draw containers' payload names are recorded as a REGISTRY `Note` (user decision 2026-10-26); spec item 5 carries its SHIPPED note.
52 changes: 52 additions & 0 deletions changelog.d/20261101-v4-pr9b-shim-retirement-placebo-time.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
### Breaking Changes

- **4.0 cut, item 9b: the compatibility shims scheduled for a 5.0 removal are removed at 4.0** (user
decision - no 4.x debt; none of these `FutureWarning` shims ever reached a release, so the released
3.x names break without a warned release - `docs/migration-4.0.md` is the notice). The 3.x
`overall_att` / `overall_se` / `overall_t_stat` / `overall_p_value` / `overall_conf_int` names on
`CallawaySantAnnaResults` (and `DMLDiDResults`), `SunAbrahamResults`, `ImputationDiDResults`,
`TwoStageDiDResults`, `StackedDiDResults`, `EfficientDiDResults`, `WooldridgeDiDResults`,
`ChaisemartinDHaultfoeuilleResults` and - all nine spellings, `overall_att_*` included -
`ContinuousDiDResults` raise `AttributeError`; read the canonical `att` / `se` / `t_stat` /
`p_value` / `conf_int` (`M-050`..`M-058`). 3.x pickles still load (the `__setstate__` key migration
stays). `EventStudyResults.period_effects` is gone; read `to_dataframe()` or the columnar arrays
(`M-016`). `LWDiDResults.pvalue` / `.ci` are gone; read `.p_value` / `.conf_int` (`M-151`, `M-152`).
- **`SyntheticControl(inner_max_iter=, inner_min_decrease=)` are removed** (`M-148`, `M-149`; the
`DeprecationWarning` shipped in 3.13.0): passing either is a `TypeError`, and the positional
parameters after them (`standardize`, `alpha`, `seed`, `v_cv_t0`) move two slots left - pass
them by keyword.
- **`run_placebo_test` / `run_all_placebo_tests`: `time` is the calendar column only and
`post_periods` is required for every `test_type`** (`M-156`, `M-157`; spec section 8 rule 1). The
0/1 post dummy the permutation and leave-one-out tests need is derived as
`data[time].isin(post_periods)`; a missing `time` column, missing/empty `post_periods`, missing
values in `time`, `post_periods` values absent from the column, or a pre-existing `_placebo_post`
column raise `ValueError` (on the suite wrapper before any test runs). Calls that passed the 0/1
indicator as `time=` with `post_periods=[1]` return identical numbers; calls that omitted
`post_periods` must add it.

### Fixed

- `run_all_placebo_tests` and `run_placebo_test(test_type="permutation" | "leave_one_out")` no
longer fail on a multi-period calendar column: through 3.x the wrappers forwarded `time` itself as
the 0/1 post dummy, so `DifferenceInDifferences.fit` rejected it and the suite silently reported
the two tests as error dicts (tutorial 04's placebo cell printed `ERROR` lines). The dummy is now
derived from `post_periods`.

### Internal

- Ledger rows `M-016`, `M-050`..`M-058`, `M-151`, `M-152` flip to `removed` at 4.0 with
`deprecated_in: null` (no release warned); `M-148`/`M-149` flip to `removed` with
`deprecated_in: 3.13.0`; new behavior rows `M-156`/`M-157` (`tests/test_diagnostics.py`); the
ledger header, spec ladder, section 5/8/9/11 notes and the phase-5 roster carry dated amendments.
`diff_diff._deprecation` is reduced to the pickle-migration (`LEGACY_OVERALL_QUINTET`,
`migrate_renamed_state_keys`, `migrate_event_study_reference_sentinel`) and required-arg
(`NOT_SUPPLIED`, `require_arg`) helpers - `deprecated_field_property`, `resolve_renamed_kwarg`,
`warn_deprecated_kwarg` and `deprecated_kwarg_message` are deleted with their last consumers.
With the descriptors gone, assigning a legacy name to a results instance creates a shadow
attribute instead of raising (the non-slotted dataclasses) - the naming guard's consumer sweep
is the in-repo defense. Naming guard: the `DMLDiDResults.overall_att` surface entry and the
`_deprecation.py` `placebo_effects` consumer entry are deleted, the two placebo wrappers join the
rule-1 calendar `time` inventory, and the retired token families' remaining in-repo hits
(body-local `overall_att` names, the kept `__setstate__` key maps, methodology history) are
allowlisted. API autosummary stubs regenerated; TODO.md's placebo-wrapper redesign row closed;
tutorial 17 re-executed (its stored practitioner snippet echoed the old `time='post'` advice).
Loading
Loading