Skip to content

Commit 231dca9

Browse files
igerberclaude
andcommitted
Release 3.1.2: SDID scale fix, convergence warnings, roadmap refresh; remove deprecated SyntheticDiD params
Package four merged PRs (#312 SDID catastrophic cancellation at extreme Y scale, #313 roadmap refresh, #314 FE imputation non-convergence signaling, #315 Frank-Wolfe SC weight solver non-convergence signaling) as 3.1.2. Also remove the SyntheticDiD(lambda_reg=...) and SyntheticDiD(zeta=...) kwargs, which have been deprecated with DeprecationWarning since v2.3.1 (2026-02-10) in favor of zeta_omega / zeta_lambda; their warning messages announced removal in v3.1. Passing the old kwargs now raises TypeError at __init__ and ValueError: Unknown parameter at set_params. Internal ridge-regression helpers that accept a lambda_reg parameter (compute_synthetic_weights, rank_control_units, Rust FFI bindings) are unaffected. Version strings bumped in diff_diff/__init__.py, pyproject.toml, rust/Cargo.toml, and diff_diff/guides/llms-full.txt. CHANGELOG populated with Fixed / Changed / Removed sections and comparison-link footer. TODO.md's "Deprecated Code" entry removed now that the task is done. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 32a4d09 commit 231dca9

9 files changed

Lines changed: 19 additions & 107 deletions

File tree

CHANGELOG.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [3.1.2] - 2026-04-18
11+
12+
### Fixed
13+
- **SyntheticDiD catastrophic cancellation at extreme Y scale** (PR #312) - the Frank-Wolfe weight solver lost precision when outcome magnitudes were very large or very small; results are now numerically stable across scales.
14+
- **Non-convergence signaling in FE imputation alternating-projection solvers** (PR #314) - `ImputationDiD`, `TwoStageDiD`, and shared `within_transform` now emit a `ConvergenceWarning` when the alternating-projection / weighted-demean loop exits without meeting the tolerance. `max_iter` and `tol` are documented on `within_transform`.
15+
- **Non-convergence signaling in SyntheticDiD Frank-Wolfe solver** (PR #315) - the numpy-path Frank-Wolfe SC weight solver now emits a `ConvergenceWarning` when the loop exits without meeting `min_decrease`. Wrapper-level and `max_iter=0` regression tests added.
16+
1017
### Changed
11-
- Refresh `ROADMAP.md` to drop top-level phase numbering and reflect shipped state through v3.1.1. Absorbs dCDH into the Current State estimator list; adds Recently Shipped summary; reorganizes open work as Shipping Next / Under Consideration / AI-Agent Track / Long-term. Updates `docs/business-strategy.md`, `docs/survey-roadmap.md`, `docs/practitioner_decision_tree.rst`, `docs/choosing_estimator.rst`, `docs/api/chaisemartin_dhaultfoeuille.rst`, `README.md`, and `diff_diff/guides/llms-full.txt` to remove stale phase-deferral language now that the deferred items have shipped.
18+
- Refresh `ROADMAP.md` to drop top-level phase numbering and reflect shipped state through v3.1.1 (PR #313). Absorbs dCDH into the Current State estimator list; adds Recently Shipped summary; reorganizes open work as Shipping Next / Under Consideration / AI-Agent Track / Long-term. Updates `docs/business-strategy.md`, `docs/survey-roadmap.md`, `docs/practitioner_decision_tree.rst`, `docs/choosing_estimator.rst`, `docs/api/chaisemartin_dhaultfoeuille.rst`, `README.md`, and `diff_diff/guides/llms-full.txt` to remove stale phase-deferral language now that the deferred items have shipped.
19+
20+
### Removed
21+
- **`SyntheticDiD(lambda_reg=...)` and `SyntheticDiD(zeta=...)`** - deprecated since v2.3.1 (2026-02-10) in favor of `zeta_omega` / `zeta_lambda`, which match R `synthdid`'s unit-weight / time-weight split. Passing the old kwargs now raises `TypeError` at `__init__` and `ValueError: Unknown parameter` at `set_params`. Internal helpers that take a `lambda_reg` ridge parameter (`compute_synthetic_weights`, `rank_control_units`, Rust FFI bindings) are unaffected - they remain supported.
1222

1323
## [3.1.1] - 2026-04-16
1424

@@ -1298,6 +1308,7 @@ for the full feature history leading to this release.
12981308
[2.1.2]: https://github.com/igerber/diff-diff/compare/v2.1.1...v2.1.2
12991309
[2.1.1]: https://github.com/igerber/diff-diff/compare/v2.1.0...v2.1.1
13001310
[2.1.0]: https://github.com/igerber/diff-diff/compare/v2.0.3...v2.1.0
1311+
[3.1.2]: https://github.com/igerber/diff-diff/compare/v3.1.1...v3.1.2
13011312
[3.1.1]: https://github.com/igerber/diff-diff/compare/v3.1.0...v3.1.1
13021313
[3.1.0]: https://github.com/igerber/diff-diff/compare/v3.0.2...v3.1.0
13031314
[3.0.2]: https://github.com/igerber/diff-diff/compare/v3.0.1...v3.0.2

TODO.md

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -118,16 +118,6 @@ Different estimators compute SEs differently. Consider unified interface.
118118
Mypy reports 0 errors. All mixin `attr-defined` errors resolved via
119119
`TYPE_CHECKING`-guarded method stubs in bootstrap mixin classes.
120120

121-
## Deprecated Code
122-
123-
Deprecated parameters still present for backward compatibility:
124-
125-
- `lambda_reg` and `zeta` in `SyntheticDiD` (`synthetic_did.py`)
126-
- Deprecated in favor of `zeta_omega`/`zeta_lambda` parameters
127-
- Remove in v3.1
128-
129-
---
130-
131121
## Test Coverage
132122

133123
**Note**: 21 visualization tests are skipped when matplotlib unavailable—this is expected.

diff_diff/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@
231231
ETWFE = WooldridgeDiD
232232
DCDH = ChaisemartinDHaultfoeuille
233233

234-
__version__ = "3.1.1"
234+
__version__ = "3.1.2"
235235
__all__ = [
236236
# Estimators
237237
"DifferenceInDifferences",

diff_diff/guides/llms-full.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
> A Python library for Difference-in-Differences causal inference analysis. Provides sklearn-like estimators with statsmodels-style output for econometric analysis.
44

5-
- Version: 3.1.1
5+
- Version: 3.1.2
66
- Repository: https://github.com/igerber/diff-diff
77
- License: MIT
88
- Dependencies: numpy, pandas, scipy (no statsmodels dependency)

diff_diff/synthetic_did.py

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -141,26 +141,7 @@ def __init__(
141141
variance_method: str = "placebo",
142142
n_bootstrap: int = 200,
143143
seed: Optional[int] = None,
144-
# Deprecated — accepted for backward compat, ignored with warning
145-
lambda_reg: Optional[float] = None,
146-
zeta: Optional[float] = None,
147144
):
148-
if lambda_reg is not None:
149-
warnings.warn(
150-
"lambda_reg is deprecated and ignored. Regularization is now "
151-
"auto-computed from data. Use zeta_omega to override unit weight "
152-
"regularization. Will be removed in v3.1.",
153-
DeprecationWarning,
154-
stacklevel=2,
155-
)
156-
if zeta is not None:
157-
warnings.warn(
158-
"zeta is deprecated and ignored. Use zeta_lambda to override "
159-
"time weight regularization. Will be removed in v3.1.",
160-
DeprecationWarning,
161-
stacklevel=2,
162-
)
163-
164145
super().__init__(robust=True, cluster=None, alpha=alpha)
165146
self.zeta_omega = zeta_omega
166147
self.zeta_lambda = zeta_lambda
@@ -1465,17 +1446,8 @@ def get_params(self) -> Dict[str, Any]:
14651446

14661447
def set_params(self, **params) -> "SyntheticDiD":
14671448
"""Set estimator parameters."""
1468-
# Deprecated parameter names — emit warning and ignore
1469-
_deprecated = {"lambda_reg", "zeta"}
14701449
for key, value in params.items():
1471-
if key in _deprecated:
1472-
warnings.warn(
1473-
f"{key} is deprecated and ignored. Use zeta_omega/zeta_lambda "
1474-
f"instead. Will be removed in v3.1.",
1475-
DeprecationWarning,
1476-
stacklevel=2,
1477-
)
1478-
elif hasattr(self, key):
1450+
if hasattr(self, key):
14791451
setattr(self, key, value)
14801452
else:
14811453
raise ValueError(f"Unknown parameter: {key}")

pyproject.toml

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

55
[project]
66
name = "diff-diff"
7-
version = "3.1.1"
7+
version = "3.1.2"
88
description = "Difference-in-Differences causal inference with sklearn-like API. Callaway-Sant'Anna, Synthetic DiD, Honest DiD, event studies, parallel trends."
99
readme = "README.md"
1010
license = "MIT"

rust/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "diff_diff_rust"
3-
version = "3.1.1"
3+
version = "3.1.2"
44
edition = "2021"
55
rust-version = "1.84"
66
description = "Rust backend for diff-diff DiD library"

tests/test_estimators.py

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2758,27 +2758,6 @@ def test_get_set_params(self):
27582758
sdid.set_params(variance_method="bootstrap")
27592759
assert sdid.variance_method == "bootstrap"
27602760

2761-
def test_deprecated_params(self):
2762-
"""Test that old parameter names emit DeprecationWarning."""
2763-
import warnings as _warnings
2764-
2765-
with _warnings.catch_warnings(record=True) as w:
2766-
_warnings.simplefilter("always")
2767-
sdid = SyntheticDiD(lambda_reg=1.0, zeta=0.5)
2768-
dep_warnings = [x for x in w if issubclass(x.category, DeprecationWarning)]
2769-
assert len(dep_warnings) == 2
2770-
2771-
# Deprecated params are ignored — auto-computed regularization is used
2772-
assert sdid.zeta_omega is None
2773-
assert sdid.zeta_lambda is None
2774-
2775-
# set_params with deprecated names also warns
2776-
with _warnings.catch_warnings(record=True) as w:
2777-
_warnings.simplefilter("always")
2778-
sdid.set_params(lambda_reg=2.0)
2779-
dep_warnings = [x for x in w if issubclass(x.category, DeprecationWarning)]
2780-
assert len(dep_warnings) == 1
2781-
27822761
def test_missing_unit_column(self, sdid_panel_data):
27832762
"""Test error when unit column is missing."""
27842763
sdid = SyntheticDiD()

tests/test_methodology_sdid.py

Lines changed: 2 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1064,55 +1064,15 @@ def test_set_params_new_names(self):
10641064
assert sdid.zeta_omega == 2.0
10651065
assert sdid.zeta_lambda == 0.1
10661066

1067-
def test_set_params_deprecated_names_warn(self):
1068-
"""set_params with old names should emit DeprecationWarning."""
1069-
sdid = SyntheticDiD()
1070-
with warnings.catch_warnings(record=True) as w:
1071-
warnings.simplefilter("always")
1072-
sdid.set_params(lambda_reg=1.0)
1073-
dep_warnings = [x for x in w if issubclass(x.category, DeprecationWarning)]
1074-
assert len(dep_warnings) == 1
1075-
10761067
def test_set_params_unknown_raises(self):
10771068
"""set_params with unknown name should raise ValueError."""
10781069
sdid = SyntheticDiD()
10791070
with pytest.raises(ValueError, match="Unknown parameter"):
10801071
sdid.set_params(nonexistent_param=1.0)
10811072

10821073

1083-
class TestDeprecatedParams:
1084-
"""Test deprecated parameter handling in __init__."""
1085-
1086-
def test_lambda_reg_warns(self):
1087-
"""SyntheticDiD(lambda_reg=...) emits DeprecationWarning."""
1088-
with warnings.catch_warnings(record=True) as w:
1089-
warnings.simplefilter("always")
1090-
sdid = SyntheticDiD(lambda_reg=0.1)
1091-
dep = [x for x in w if issubclass(x.category, DeprecationWarning)]
1092-
assert len(dep) == 1
1093-
assert "lambda_reg" in str(dep[0].message)
1094-
1095-
# Deprecated param is ignored — auto-computed used
1096-
assert sdid.zeta_omega is None
1097-
1098-
def test_zeta_warns(self):
1099-
"""SyntheticDiD(zeta=...) emits DeprecationWarning."""
1100-
with warnings.catch_warnings(record=True) as w:
1101-
warnings.simplefilter("always")
1102-
sdid = SyntheticDiD(zeta=2.0)
1103-
dep = [x for x in w if issubclass(x.category, DeprecationWarning)]
1104-
assert len(dep) == 1
1105-
assert "zeta" in str(dep[0].message)
1106-
1107-
assert sdid.zeta_lambda is None
1108-
1109-
def test_both_deprecated_params(self):
1110-
"""Both deprecated params at once should emit two warnings."""
1111-
with warnings.catch_warnings(record=True) as w:
1112-
warnings.simplefilter("always")
1113-
SyntheticDiD(lambda_reg=0.5, zeta=1.5)
1114-
dep = [x for x in w if issubclass(x.category, DeprecationWarning)]
1115-
assert len(dep) == 2
1074+
class TestDefaultVarianceMethod:
1075+
"""Default variance_method sanity."""
11161076

11171077
def test_default_variance_method_is_placebo(self):
11181078
"""Default variance_method should be 'placebo' (matching R)."""

0 commit comments

Comments
 (0)