Skip to content

Commit 9230c8f

Browse files
igerberclaude
andcommitted
Revert SyntheticDiD deprecation removal; keep warning-only through 3.x
Per CI review feedback (#316): removing public kwargs under a patch version violates Semantic Versioning, which CHANGELOG.md explicitly claims to adhere to. Restore lambda_reg and zeta handling in SyntheticDiD.__init__ and set_params as warning-only, and bump the removal target in the DeprecationWarning text from "v3.1" to "v4.0.0". The 3.1.2 release now carries only the four fix/doc PRs (#312 SDID scale, #313 roadmap, #314 FE imputation convergence, #315 Frank-Wolfe convergence) with no breaking changes. - diff_diff/synthetic_did.py: restore deprecated kwargs + warnings (v4.0.0 text) - tests/test_methodology_sdid.py: restore TestDeprecatedParams class + set_params deprecation test - tests/test_estimators.py: restore test_deprecated_params - CHANGELOG.md: drop Removed section; add Changed entry documenting the v3.1 -> v4.0.0 bump in the removal target - TODO.md: restore Deprecated Code section with v4.0.0 removal target and SemVer rationale Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 231dca9 commit 9230c8f

5 files changed

Lines changed: 103 additions & 6 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1616

1717
### Changed
1818
- 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.
19+
- Bump the `SyntheticDiD(lambda_reg=...)` and `SyntheticDiD(zeta=...)` deprecation warnings' removal target from `v3.1` to `v4.0.0`. Removing public kwargs in a patch / minor release would violate Semantic Versioning; the deprecation stays warning-only throughout the `3.x` line and will be removed in the next major release. Use `zeta_omega` / `zeta_lambda` instead.
2220

2321
## [3.1.1] - 2026-04-16
2422

TODO.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,16 @@ 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 v4.0.0 (SemVer-safe: public kwarg removal requires a major bump)
128+
129+
---
130+
121131
## Test Coverage
122132

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

diff_diff/synthetic_did.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,26 @@ 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,
144147
):
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 v4.0.0.",
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 v4.0.0.",
160+
DeprecationWarning,
161+
stacklevel=2,
162+
)
163+
145164
super().__init__(robust=True, cluster=None, alpha=alpha)
146165
self.zeta_omega = zeta_omega
147166
self.zeta_lambda = zeta_lambda
@@ -1446,8 +1465,17 @@ def get_params(self) -> Dict[str, Any]:
14461465

14471466
def set_params(self, **params) -> "SyntheticDiD":
14481467
"""Set estimator parameters."""
1468+
# Deprecated parameter names — emit warning and ignore
1469+
_deprecated = {"lambda_reg", "zeta"}
14491470
for key, value in params.items():
1450-
if hasattr(self, key):
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 v4.0.0.",
1475+
DeprecationWarning,
1476+
stacklevel=2,
1477+
)
1478+
elif hasattr(self, key):
14511479
setattr(self, key, value)
14521480
else:
14531481
raise ValueError(f"Unknown parameter: {key}")

tests/test_estimators.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2758,6 +2758,27 @@ 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+
27612782
def test_missing_unit_column(self, sdid_panel_data):
27622783
"""Test error when unit column is missing."""
27632784
sdid = SyntheticDiD()

tests/test_methodology_sdid.py

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1064,15 +1064,55 @@ 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+
10671076
def test_set_params_unknown_raises(self):
10681077
"""set_params with unknown name should raise ValueError."""
10691078
sdid = SyntheticDiD()
10701079
with pytest.raises(ValueError, match="Unknown parameter"):
10711080
sdid.set_params(nonexistent_param=1.0)
10721081

10731082

1074-
class TestDefaultVarianceMethod:
1075-
"""Default variance_method sanity."""
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
10761116

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

0 commit comments

Comments
 (0)