Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
67ac8a8
feat: add LWDiD estimator (Lee & Wooldridge 2025, 2026)
gorgeousfish Aug 18, 2026
73baea7
fix(vcov): close hc3 escape hatches across shared estimator surfaces …
igerber Aug 19, 2026
f77fe78
fix(lwdid): unify cohort normalization + treatment-design coherence (…
igerber Aug 19, 2026
aeb96a6
fix(lwdid): complete-case fixed-weight tau_omega + vcov-invariant rou…
igerber Aug 19, 2026
e183dc8
fix(lwdid): seasonal transforms fail closed (WS2)
igerber Aug 19, 2026
f8e3b4a
fix(lwdid): bootstrap resampling integrity + cluster wiring + df rule…
igerber Aug 19, 2026
e4f49ae
fix(lwdid): rebuild wild_cluster_bootstrap on the house WCR engine (WS4)
igerber Aug 19, 2026
eb524fa
fix(lwdid): PSM caliper never averages out-of-caliper controls (WS5)
igerber Aug 19, 2026
7491d3a
fix(lwdid): vcov contract restriction + per-surface inference policy …
igerber Aug 19, 2026
3bf8bf0
fix(lwdid): explicit NaN validation, contributing-sample metadata, ra…
igerber Aug 19, 2026
58d2f93
fix(lwdid): sensitivity honesty + plotting conventions + result-surfa…
igerber Aug 19, 2026
5c31948
chore(lwdid): housekeeping, docs sweep, notebook re-execution (WS11)
igerber Aug 19, 2026
16e6a16
style: pinned black/ruff/mypy pass over the fix wave (incl. the 3 con…
igerber Aug 19, 2026
1efc99e
fix(lwdid): sensitivity helpers accept control_group (was silently sw…
igerber Aug 19, 2026
9c2ae57
fix(lwdid): local-review round 1 - fail-closed inference guards + pro…
igerber Aug 19, 2026
60901b9
fix(lwdid): local-review round 2 - column-collision guard, n_jobs-inv…
igerber Aug 19, 2026
696df1b
fix(lwdid): local-review round 3 - calendar seasons, PSM contract clo…
igerber Aug 19, 2026
c2fbbe0
fix(lwdid): local-review round 4 - rank-aware advanced inference, NT …
igerber Aug 19, 2026
cb36fa7
fix(lwdid): local-review round 5 - post-fit inference replays the fit…
igerber Aug 19, 2026
55a9d8c
fix(lwdid+linalg): local-review round 6 - fweight leverage, fixed-win…
igerber Aug 19, 2026
eb1cf1a
fix(lwdid): local-review round 7 - replay follows the fitted design; …
igerber Aug 19, 2026
8828023
fix(lwdid): local-review round 8 - onset-derived calendar partition, …
igerber Aug 19, 2026
d2cb349
fix(lwdid): local-review round 9 - one event-time convention, onset p…
igerber Aug 19, 2026
c303ea5
fix(lwdid+linalg): local-review round 10 - hc3 guard ordering, sensit…
igerber Aug 19, 2026
1d756e0
fix(lwdid): local-review round 11 - propensity linearization, reduced…
igerber Aug 19, 2026
8f13ae1
fix(lwdid): local-review round 12 - rank-aware DR nuisances, identifi…
igerber Aug 19, 2026
6583f4a
fix(lwdid): local-review round 13 - scale-equilibrated influence brea…
igerber Aug 19, 2026
08df8bf
fix(lwdid): local-review round 14 - finite numeric time, normality-cl…
igerber Aug 19, 2026
42e8f68
docs(lwdid): local-review round 15 - constructor docstring completeness
igerber Aug 19, 2026
d480e38
fix(lwdid): local-review round 16 - degenerate event vcov exclusion, …
igerber Aug 19, 2026
0a05911
fix(lwdid): local-review round 17 - shared rank convention in the RA …
igerber Aug 19, 2026
59fe288
fix(lwdid): local-review round 18 - shared time-scale validator, RI v…
igerber Aug 19, 2026
8a24e25
fix(lwdid): local-review round 19 - PSM reduced-rank continuation; fa…
igerber Aug 19, 2026
7576a56
fix(lwdid): local-review round 20 - PSM df exemption, empty-cohort di…
igerber Aug 19, 2026
6deded2
fix(lwdid): local-review round 21 - hc2 surface fail-closed, headline…
igerber Aug 19, 2026
c0b8591
fix(lwdid): local-review round 22 - scalar alpha validation
igerber Aug 19, 2026
a62006f
fix(lwdid): local-review round 23 - contributing-unit cohort masses, …
igerber Aug 19, 2026
59146d1
fix(lwdid): local-review round 24 (final: Looks good) - three P2 nits
igerber Aug 20, 2026
0779ce6
docs(lwdid): CI-review round (mirror #690, verdict: Looks good) - qua…
igerber Aug 20, 2026
dacff9f
docs(lwdid): CI-review round 2 (verdict: Looks good) - PSM variance w…
igerber Aug 20, 2026
0a19715
docs(lwdid): CI-review round 3 (verdict: Looks good) - assumption-sco…
igerber Aug 20, 2026
a4edeea
docs(lwdid): CI-review round 4 (verdict: Looks good) - final categori…
igerber Aug 20, 2026
7b3328a
docs(lwdid): CI-review round 5 - pooled Walmart contrast labeled nonc…
igerber Aug 20, 2026
89da093
docs(lwdid): withdraw tutorial 27 from this PR (fresh notebook = trac…
igerber Aug 20, 2026
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
122 changes: 122 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,128 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
route on bootstrapped CS fits instead of the deprecated fit-time kwarg. The
sibling estimators' (EfficientDiD/ImputationDiD/TwoStageDiD/ContinuousDiD)
bootstrapped recompute gates are unchanged.
- **`LWDiD` (Lee & Wooldridge 2025, 2026 rolling-transformation DiD).** Unit-specific
demean/detrend (plus quarterly `demeanq`/`detrendq`) converts panel data to
cross-sectional transformed outcomes; supports common timing and staggered
adoption with never-treated / not-yet-treated controls,
`estimation_method` in `'reg'`/`'ipw'`/`'dr'`/`'psm'`, analytical
(`vcov_type` in `'classical'`/`'hc1'`/`'hc2'`/`'hc3'`) and cluster-robust
(constructor `cluster=`) inference, multiplier bootstrap, wild cluster
bootstrap, and randomization inference. Common-timing fits expose the same
post-fit event-study surface as staggered ones —
`results.aggregate('event_study')` returns per-period effects keyed by
event time relative to treatment onset, so no separate per-period fit
option exists.

### Changed
- **`LWDiD` API canonicalized to the v4 vocabulary agreed in PR #588's review**
(renames relative to the PR's earlier review rounds; nothing here was ever
released): `estimator=` -> `estimation_method=` with values `'ra'` -> `'reg'`
and `'ipwra'` -> `'dr'`; `vce=` -> `vcov_type=` with no `'cluster'` value —
cluster-robust (CR1) inference activates via the constructor `cluster=`
column instead; `bootstrap_seed=` -> `seed=` (default `None`);
`trim_threshold=` -> `pscore_trim=`. `vcov_type='hc3'` is computed through
the shared `diff_diff.linalg` HC machinery used by the other estimators,
and the `'hc0'`/`'hc4'` values are removed from the surface.
Unit-constancy validation is centralized and applies uniformly to
covariates and the cluster column across all estimation paths.

### Removed
- **`LWDiD` pre-v4 review-round surface** (never released): the `LW` alias,
the functional `lwdid()` wrapper, the `lwdid_trend_diagnostics` module
(including `recommend_transformation`), and the `overall_att` /
`period_effects` result fields together with the `period_specific` fit
option — per-period effects are served by the post-fit
`results.aggregate('event_study')` surface instead.

### Fixed
- **`LWDiD` maintainer fix wave** (post-acceptance validation campaign: 43
execution-verified findings, all resolved):
- Estimand: the `tau_omega` composite is complete-case with FIXED cohort
weights (treated units without a finite own-cohort post average and
controls not observing every surviving cohort's post window are
dropped with warnings and counters; the pre-fix code zero-filled
missing control entries and silently reweighted the treated side);
with any drops, `.att` is the influence-weighted cohort-mass point on
every variance route and the composite is exposed as
`att_tau_omega_complete_case`. `demeanq`/`detrendq` overall ATTs now
aggregate seasonal cohort ATTs (the composite silently substituted
the non-seasonal transforms); the seasonal transforms fail closed on
insufficient pre-periods and unobserved seasons.
- Inference: one reference-distribution policy per surface (one-cell
aggregates use the cell's residual t; multi-cell unclustered stay
large-sample; clustered use contributing-cluster G-1); sub-samples
with <2 clusters fail closed; the common-timing bootstrap resamples
positionally (row order/index labels no longer corrupt SEs), honors
`cluster=` via whole-cluster resampling, and reports the df it used;
`wild_cluster_bootstrap` was rebuilt on the house WCR engine
(test-inversion CI, CR1 se, strict-exceedance p; the intercept-only
null model, ULP tie handling, and the G=2 zero-SE escape are gone;
API: `n_bootstrap`/`alpha`, result fields renamed). The result-level
`wild_cluster_bootstrap()`/`randomization_test()` methods now REPLAY
the fitted estimation sample and exact RA design (no data arguments;
RI recomputes the treated covariate mean per permutation; the
replayed statistic is asserted equal to `.att` before caching) - the
prior signatures accepted arbitrary arrays and could cache p-values
for a different estimand than the fitted ATT. The seeded bootstrap
draws identical streams for every `n_jobs`, resamples only units
surviving the transformation, and preserves fail-closed NaN inference
when fewer than 2 effective clusters survive; `aggregate(balance_e=)`
is rejected (was silently ignored); both RI/WCR fit through the
rank-aware shared solver (a duplicated treatment column previously
yielded a finite minimum-norm ATT).
- Contracts: `vcov_type` is restricted to values with real behavior
(`ipw`/`dr`/`psm` accept `hc1` only; `cluster=` composes only with
`hc1`; `psm`+`cluster` rejected); NaN covariates/clusters are rejected
explicitly; PSM calipers never average out-of-caliper controls;
cohort encodings are normalized once (`inf`/beyond-window recode with
warnings, negative cohorts raise, validator and `fit()` agree);
sensitivity results are NaN-honest (`baseline_pvalue`, NaN
`significant_05` for failed specs, full-frame pre-validation, unknown
kwargs raise); staggered sample metadata counts contributing units;
rank-deficient designs rebuild the influence function on kept columns.
- Tutorial: the contribution's `27_lwdid.ipynb` is WITHDRAWN (its
Walmart narrative was built on a fabricated common onset with jobs
figures inconsistent with the staggered estimate; a fresh notebook
is a tracked follow-up).
- Shared surfaces: `hc3` escapes closed across siblings (DiD/MP-DiD
`absorb=` now full-dummy-routes hc3 like hc2; TWFE no longer crashes
misleadingly; SpilloverDiD rejects hc3 at construction with its own
reason) plus a structural roster guard. `LWDiDResults.to_latex()` and
the `lwdid_exceptions` shim removed (unreleased API).
- **`LWDiD` review-round fixes** (staggered contract and inference tightenings):
- Staggered classical/HC SEs now come from the joint influence function
across cohort-time cells (the LW 2026 eq. 7.19 pooled-regression basis),
accounting for correlation among cohort effects that share controls
instead of assuming independence.
- On unbalanced panels the overall ATT point estimate is unified so a
variance selection never moves it (gated to `rolling` in
`'demean'`/`'detrend'` with `control_group='never_treated'`,
`estimation_method='reg'`, and no covariates). Superseded in detail by
the maintainer fix wave below: the composite `tau_omega` is now
complete-case with fixed cohort weights, reported as `.att` only when
no unit is dropped, and the quarterly variants now aggregate SEASONAL
cohort ATTs on every variance route.
- t-test degrees of freedom are computed from one design-based rule across
common-timing and staggered paths instead of two inconsistent ones.
- All-eventually-treated panels under `control_group='not_yet_treated'`
raise `ValueError` instead of silently truncating the sample; staggered
`covariates` must be unit-constant, time-varying columns raise
`ValueError`.
- Randomization inference uses the inclusive Phipson-Smyth rule
p = (c+1)/(B+1) and counts ties as extreme (`>=`), so p is never 0 and
an all-tie permutation distribution yields p = 1.0.
- `estimation_method='dr'` without covariates warns (`UserWarning`) that it
reduces to regression adjustment instead of silently doing so.
- `sensitivity_analysis` gains a `not_estimable` robustness level (with a
warning) when the ratio cannot be computed — including the zero-baseline
case — instead of mislabeling it.
- `to_dict()` output is fully JSON-native, including datetime/Period
cohort and time labels (ISO-8601 / period strings, NaT -> None).
- Staggered fits accept datetime64 and Period time scales, and panels
mixing the two time families are rejected in both directions with a
clear `ValueError`; cluster variable equal to the unit column no longer
raises a spurious column-lookup error.

## [3.9.1] - 2026-08-17

Expand Down
2 changes: 2 additions & 0 deletions DEFERRED.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ provenance and AI-review deviation-documentation: a row here (or in

| Issue | Location | PR | Priority |
|-------|----------|----|----------|
| LWDiD PSM matching variance: implement the Abadie-Imbens (2006) matching variance (matched-control reuse + first-stage matching uncertainty) so `estimation_method='psm'` can report valid inference instead of the current fail-closed NaN tuple (point retained; naive var(diffs)/n was invalid under with-replacement reuse) | `diff_diff/lwdid.py` | #588 | Low |
| LWDiD cohort-relative sensitivity exclusions: `robustness_pre_periods` / `sensitivity_no_anticipation` currently reject multi-cohort staggered inputs because their exclusion windows are defined relative to the EARLIEST adoption (later cohorts' own pre periods fall inside the global post window and survive every restriction, mislabeling the specification). Supporting staggered inputs needs per-cohort window semantics (exclude the last k periods of each cohort's own `t < g` window before its transformation), which the current row-subset design cannot express — a per-cohort masking derivation + its aggregation contract | `diff_diff/lwdid_sensitivity.py` | #588 | Low |
| HonestDiD non-chronological declared partitions (native `MultiPeriodDiDResults` route): the Rambachan-Roth restriction matrices are built POSITIONALLY over the concatenated declared pre/post lists assuming one chronological boundary, but the native route accepts non-suffix `post_periods` / non-last-pre references and returns bounds whose restriction system does not match the Registry equations (pre-existing; surfaced by the Phase 3(a) calendar-route review, which fails closed instead). Fix = transform the declared partition into boundary form where a valid mapping exists, else reject on the native route too - needs the restriction-geometry derivation. REGISTRY HonestDiD Note records the limitation. | `diff_diff/honest_did.py` | 3(a) | Medium |
| `PlaceboTests` `boundary_gap` — a permutation randomization-inference margin (SE-audit item (b)); NOT computed anywhere in code today, so this is a new feature + result field, not a coverage lock. **User-locked 2026-07-09: defer until a derivation/paper source exists** — do not design or implement from scratch. | `tests/test_methodology_placebo.py`, `diff_diff/diagnostics.py` | SE-audit | Low |
| TwoStageDiD honest/pretrends container admission DEFERRED (decision revised from "widen" during the 2(b) PR-3b plan review): analytical fits carry the joint Gardner-GMM event-study covariance (M-092), but the pre-period coefficients are stage-1 residual MEANS — the reference horizon is dropped from the no-intercept Stage-2 design and the zero anchor row appended mechanically — not contrasts against a reference period, while HonestDiD's Δ^RM/Δ^SD arithmetic hard-codes the `delta_0 = 0` normalization into its boundary/bridge constraints. Admission needs either a Stage-2 re-estimation with the reference horizon in the design or a derived residual-to-reference normalization mapping (+ its variance transform). Both consumers' TypeErrors state the deferral; see the REGISTRY TwoStageDiD Note (d). | `diff_diff/honest_did.py`, `diff_diff/pretrends.py`, `diff_diff/two_stage_aggregation.py` | 2(b) PR-3b | Low |
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ Full guide: `diff_diff.get_llm_guide("practitioner")`.
- [WooldridgeDiD](https://diff-diff.readthedocs.io/en/stable/api/wooldridge_etwfe.html) - Wooldridge (2023, 2025) ETWFE: saturated OLS, logit/Poisson QMLE (ASF-based ATT). Alias `ETWFE`.
- [LPDiD](https://diff-diff.readthedocs.io/en/stable/api/lpdid.html) - Dube, Girardi, Jorda & Taylor (2025) Local Projections DiD: per-horizon long-difference event study on clean controls (no negative weighting), variance- or equally-weighted ATT, for absorbing or non-absorbing (reversible) treatment
- [ChangesInChanges](https://diff-diff.readthedocs.io/en/stable/api/changes_in_changes.html) - Athey & Imbens (2006) nonlinear/distributional DiD for the 2x2 design: full counterfactual distribution and quantile treatment effects via CDF transformation, plus the QDiD comparison estimator via `method="qdid"`; bootstrap inference; R qte parity. Alias `CiC`
- [LWDiD](https://diff-diff.readthedocs.io/en/stable/api/lwdid.html) - Lee & Wooldridge (2025, 2026) rolling-transformation DiD: unit-specific demean/detrend converts panel to cross-section, staggered adoption, `estimation_method` in `reg`/`ipw`/`dr`/`psm` (the papers' RA/IPW/IPWRA plus propensity-score matching), exact small-N inference on the classical collapsed regression
- [BaconDecomposition](https://diff-diff.readthedocs.io/en/stable/api/bacon.html) - Goodman-Bacon (2021) decomposition for diagnosing TWFE bias in staggered settings

## Diagnostics & Sensitivity
Expand Down
4 changes: 4 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ Related tracking surfaces:

| Issue | Location | Origin | Effort | Priority |
|-------|----------|--------|--------|----------|
| hc2/hc2_bm floor `1 - h_ii` at 1e-10 in the shared leverage meat, fabricating finite (if inflated) variances for leverage-one observations - hc3 now fails closed there (LWDiD fix wave) but the pre-existing hc2 family behavior is released surface; decide fail-closed vs keep-floor for hc2/hc2_bm | `diff_diff/linalg.py` | #588 | Quick | Low |
| Numeric between-period cohorts (e.g. `first_treat=4.5` with integer times) are rejected by LWDiD while CallawaySantAnna estimates them and LWDiD's own datetime/Period cohorts map to the next observed period — close the dtype asymmetry by adopting the next-observed-period mapping for numeric cohorts too (contract documented in REGISTRY cohort-encodings Note + `docs/api/lwdid.rst` Input Contract). Lands only after PR #588 merges | `diff_diff/lwdid.py` | #588 | Quick | Low |
| Expose cell-mass overall ATT (Stata `Post_avg` convention; = CS-simple on balanced panels) as an aggregate extra on LWDiD results — the fit's `.att` is the paper's `tau_omega` (cohort-mean-then-treated-weight, eq. 7.18); the authors' large-N display uses cell-mass weighting instead, and both are legitimate estimands (see the REGISTRY LWDiD Aggregation note). Lands only after PR #588 merges | `diff_diff/lwdid_results.py` | #588 | Quick | Low |
| Post-fit `aggregate()` for the staggered DDD container: `StaggeredTripleDiffResults` carries no `AggregationMixin`, which is why the phase-3(b) merge had to carry fit-time `aggregate=`/`balance_e=` onto the surviving `TripleDifference` (rows M-140/M-141) as the ONE documented exception to the section-6 aggregate-postfit program. Porting the container onto the M-122 aggregation contract retires both rows; note the bootstrapped-fit recompute levels will need replay or a fail-closed relay — solved for CS via the BootstrapReplaySpec state replay (the container port can adopt the same mechanism); EfficientDiD/ImputationDiD/TwoStageDiD/ContinuousDiD still track theirs. Until it lands, the DDD docs deliberately keep teaching the fit-time kwarg (the canonical route there) | `diff_diff/staggered_triple_diff_results.py`, `diff_diff/aggregation.py`, `docs/api/triple_diff.rst`, `docs/tutorials/08_triple_diff.ipynb` | 3(b) | Heavy | Medium |
| Staggered-DDD power support: `simulate_power`/`simulate_mde`/`simulate_sample_size` now REJECT a staggered-configured `TripleDifference` (both registered DDD generators emit 2x2x2 data and fit with `(group, partition, post)`, so a staggered config would be simulated under the wrong design). Support needs a staggered DDD DGP profile plus fit-kwargs builder, and a decision on whether the mode is selected by profile or by the estimator's own config | `diff_diff/power.py` | 3(b) | Mid | Low |
Expand Down Expand Up @@ -66,6 +68,8 @@ generic sparse-FE, QR+SVD rank-detection redundancy, `check_finite` bypass — m

| Issue | Location | Origin | Effort | Priority |
|-------|----------|--------|--------|----------|
| Author a fresh LWDiD tutorial notebook (the contribution's tutorial 27 was WITHDRAWN from PR #588 during CI review: its Walmart empirical narrative was built on a fabricated common onset with jobs translations that did not match the staggered estimate, and per-sentence repair failed across 5 review rounds). Build via the numbers-locked workflow: prototype in scripts, lock the numbers, then assemble and execute once; teach the staggered fit as the causal specification and keep any pooled contrast explicitly diagnostic | `docs/tutorials/` | #588 | Medium |
| LWDiD staggered fit recomputes cohort-wide work across surfaces: each cohort's rolling transformation is built once per `fit_staggered` cohort loop, but unit-level post summaries, control-eligibility sets, and the composite path's `ydot_by_cohort` are re-derived independently by the cell, aggregate, and `_composite_regression_aggregation` paths — runtime grows ~cohorts × panel size. Cache the per-cohort transformation + post summaries in one pass and reuse; add a many-cohort unbalanced-panel benchmark | `diff_diff/lwdid_staggered.py`, `diff_diff/lwdid.py` | #588 | Mid | Low |
| Reuse the demeaner's factorized codes in `absorbed_fe_rank`/`absorbed_fe_cr1_k_increment` instead of re-factorizing: at 186k rows the rank helper adds ~1.9 ms per absorbed fit (7.7% of the fastest Rust-served TWFE fit) and the K_reference increment ~3.2 ms per clustered-hc1 absorbed fit (~13%; see `docs/performance-plan.md` "Component-aware absorbed-FE rank"), and the helpers and `demean_by_groups` factorize the same group columns. Threading the codes through the call sites halves the factorize work; the `connected_components` call itself is ~1.1 ms. Deliberately not done in the correctness PRs. | `diff_diff/utils.py` | #variance-inventory | Quick | Low |
| `EfficientDiD` conditional path: the largest remaining O(n) stage is the sieve/nuisance construction outside the tiled pass (~9s at 10k). (The `_ridge_solve_weights` Python-prep shave landed 2026-07-07 — the `omega_stack[rest]` fancy-index copy and tail scatter are skipped when no row is zero-masked, byte-identical outputs; the `zero_mask` abs scan itself remains, needed for correctness.) | `efficient_did_covariates.py` | CS-scaling | Mid | Low |
| `_rq_fit` LP assembly is dense (`A_eq = [X, I, -I]` with dense identity blocks, rebuilt per cell fit): a `scipy.sparse` construction would cut memory and likely HiGHS time for large cells / bootstrap-heavy covariate CiC/QDiD fits. CAVEAT before doing it: a different matrix representation can change HiGHS's vertex selection at degenerate/tied QR optima - end-to-end covariate goldens are tie-selection-gated (fine), but the `qr_cases` tight coefficient matches may shift to the equal-loss branch; re-run the parity suite and re-calibrate if needed. | `diff_diff/changes_in_changes.py::_rq_fit` | covariates PR | Quick | Low |
Expand Down
5 changes: 5 additions & 0 deletions diff_diff/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,8 @@
)
from diff_diff.lpdid import LPDiD
from diff_diff.lpdid_results import LPDiDResults
from diff_diff.lwdid import LWDiD
from diff_diff.lwdid_results import LWDiDResults
from diff_diff.mmm import (
MeridianROIPrior,
meridian_calibration_mask,
Expand Down Expand Up @@ -459,6 +461,9 @@ def __getattr__(name: str) -> _Any:
# LPDiD (Local Projections DiD)
"LPDiD",
"LPDiDResults",
# LWDiD (Lee & Wooldridge rolling transformation DiD)
"LWDiD",
"LWDiDResults",
# Visualization
"plot_bacon",
"plot_event_study",
Expand Down
Loading
Loading