From 01dd25a10bdbc89b4145dba24776b7583ec8d8ef Mon Sep 17 00:00:00 2001 From: Imran Ahamed Date: Sun, 14 Jun 2026 19:15:59 -0500 Subject: [PATCH] =?UTF-8?q?fix:=20complete=20sklearn=201.9=20support=20?= =?UTF-8?q?=E2=80=94=20drop=20self.alphas=20overwrite=20(#1032)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #1032 reports econml errors on import with scikit-learn 1.9. e546416 ("Fix scikit-learn 1.7+ FutureWarnings ...") added a >=1.7 dispatch in WeightedLassoCV / WeightedMultiTaskLassoCV that translates n_alphas= into alphas= on the super().__init__() call, which fixes the import-time TypeError. But it then ran self.alphas = alphas at the end of the dispatch, overwriting the value sklearn's __init__ had correctly recorded back to the constructor's alphas kwarg (None by default). On sklearn 1.7-1.8 the loose param-validation tolerated the resulting self.alphas = None. sklearn 1.9's stricter _param_validation rejects it, so SparseLinearDML.fit (via _DebiasedLasso.fit -> WeightedLassoCV) and DebiasedLasso.fit raise InvalidParameterError. Drop the overwrite. super().__init__(...) already records self.alphas from the translated value. self.n_alphas is still set so callers can introspect the original wrapper kwarg. Also bump scikit-learn pin from < 1.9 to < 1.10 so 1.9 installs. Adds test_default_alphas_fits_on_strict_sklearn covering both WeightedLassoCV and WeightedMultiTaskLassoCV; verifies on sklearn 1.9 locally that 48 tests pass across test_linear_model, test_dml, and test_treatment_featurization (no regressions). Signed-off-by: Imran Ahamed --- econml/sklearn_extensions/linear_model.py | 2 -- econml/tests/test_linear_model.py | 29 +++++++++++++++++++++++ pyproject.toml | 2 +- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/econml/sklearn_extensions/linear_model.py b/econml/sklearn_extensions/linear_model.py index 56091899c..2ccba55d6 100644 --- a/econml/sklearn_extensions/linear_model.py +++ b/econml/sklearn_extensions/linear_model.py @@ -437,7 +437,6 @@ def __init__(self, eps=1e-3, n_alphas=100, alphas=None, fit_intercept=True, cv=cv, verbose=verbose, n_jobs=n_jobs, positive=positive, random_state=random_state, selection=selection) self.n_alphas = n_alphas - self.alphas = alphas else: super().__init__( eps=eps, n_alphas=n_alphas, alphas=alphas, @@ -561,7 +560,6 @@ def __init__(self, eps=1e-3, n_alphas=100, alphas=None, fit_intercept=True, cv=cv, verbose=verbose, n_jobs=n_jobs, random_state=random_state, selection=selection) self.n_alphas = n_alphas - self.alphas = alphas else: super().__init__( eps=eps, n_alphas=n_alphas, alphas=alphas, diff --git a/econml/tests/test_linear_model.py b/econml/tests/test_linear_model.py index bf28a5a5b..0608e2f93 100644 --- a/econml/tests/test_linear_model.py +++ b/econml/tests/test_linear_model.py @@ -296,6 +296,35 @@ def test_wrapper_attributes(self): assert wrapper.max_iter == 100 assert wrapper.tol == 0.01 + def test_default_alphas_fits_on_strict_sklearn(self): + # Regression test for #1032. WeightedLassoCV.__init__ used to overwrite + # self.alphas = None after the version-dispatch correctly translated + # the default n_alphas=100 into alphas=100 on the super().__init__() + # call. sklearn 1.9's strict param validation rejected the resulting + # None and SparseLinearDML.fit / DebiasedLasso.fit raised + # InvalidParameterError. With default args, the dispatched alphas value + # must survive to fit time. + from packaging.version import parse + import sklearn + if parse(sklearn.__version__) < parse("1.7"): + self.skipTest("dispatch only active on sklearn 1.7+") + + for cls in (WeightedLassoCV, WeightedMultiTaskLassoCV): + est = cls() + assert est.alphas is not None, \ + f"{cls.__name__}.alphas was clobbered to None by __init__ (#1032)" + assert est.n_alphas == 100, \ + f"{cls.__name__}.n_alphas should be preserved at 100 (#1032)" + + rng = np.random.default_rng(0) + n = 300 + X = rng.normal(size=(n, 3)) + y_1d = rng.normal(size=n) + y_2d = rng.normal(size=(n, 2)) + # neither call should raise sklearn.InvalidParameterError + WeightedLassoCV(cv=3).fit(X, y_1d) + WeightedMultiTaskLassoCV(cv=3).fit(X, y_2d) + ################# # DebiasedLasso # ################# diff --git a/pyproject.toml b/pyproject.toml index 615ce8672..1fd51b14b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,7 @@ dependencies = [ # in addition to dependencies) "numba > 0.53.1", "scipy > 1.4.0", - "scikit-learn >= 1.0, < 1.9", + "scikit-learn >= 1.0, < 1.10", "sparse", "joblib >= 0.13.0", "statsmodels >= 0.10",