Skip to content

Fix #30936: ElasticNet l1 ratio fails for float-only arrays #31107

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
May 7, 2025
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- :class:`feature_selection.SelectFromModel` now correctly works when the estimator
is an instance of :class:`linear_model.ElasticNetCV` with its `l1_ratio` parameter
being an array-like.
By :user:`Vasco Pereira <vasco-s-pereira>`.
15 changes: 11 additions & 4 deletions sklearn/feature_selection/_from_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,18 @@ def _calculate_threshold(estimator, importances, threshold):
est_name = estimator.__class__.__name__
is_l1_penalized = hasattr(estimator, "penalty") and estimator.penalty == "l1"
is_lasso = "Lasso" in est_name
is_elasticnet_l1_penalized = "ElasticNet" in est_name and (
(hasattr(estimator, "l1_ratio_") and np.isclose(estimator.l1_ratio_, 1.0))
or (hasattr(estimator, "l1_ratio") and np.isclose(estimator.l1_ratio, 1.0))
is_elasticnet_l1_penalized = est_name == "ElasticNet" and (
hasattr(estimator, "l1_ratio") and np.isclose(estimator.l1_ratio, 1.0)
)
if is_l1_penalized or is_lasso or is_elasticnet_l1_penalized:
is_elasticnetcv_l1_penalized = est_name == "ElasticNetCV" and (
hasattr(estimator, "l1_ratio_") and np.isclose(estimator.l1_ratio_, 1.0)
)
if (
is_l1_penalized
or is_lasso
or is_elasticnet_l1_penalized
or is_elasticnetcv_l1_penalized
):
# the natural default threshold is 0 when l1 penalty was used
threshold = 1e-5
else:
Expand Down
17 changes: 16 additions & 1 deletion sklearn/feature_selection/tests/test_from_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from sklearn import datasets
from sklearn.base import BaseEstimator
from sklearn.cross_decomposition import CCA, PLSCanonical, PLSRegression
from sklearn.datasets import make_friedman1
from sklearn.datasets import make_friedman1, make_regression
from sklearn.decomposition import PCA
from sklearn.ensemble import HistGradientBoostingClassifier, RandomForestClassifier
from sklearn.exceptions import NotFittedError
Expand Down Expand Up @@ -489,6 +489,21 @@ def test_prefit_max_features():
model.transform(data)


def test_get_feature_names_out_elasticnetcv():
"""Check if ElasticNetCV works with a list of floats.

Non-regression test for #30936."""
X, y = make_regression(n_features=5, n_informative=3, random_state=0)
estimator = ElasticNetCV(l1_ratio=[0.25, 0.5, 0.75], random_state=0)
selector = SelectFromModel(estimator=estimator)
selector.fit(X, y)

names_out = selector.get_feature_names_out()
mask = selector.get_support()
expected = np.array([f"x{i}" for i in range(X.shape[1])])[mask]
assert_array_equal(names_out, expected)


def test_prefit_get_feature_names_out():
"""Check the interaction between prefit and the feature names."""
clf = RandomForestClassifier(n_estimators=2, random_state=0)
Expand Down