[go: up one dir, main page]

Skip to content
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

MNT SLEP6: raise NotImplementedError for meta-estimators not supporting metadata routing #27389

Merged
merged 18 commits into from
Sep 25, 2023
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
77 changes: 74 additions & 3 deletions doc/metadata_routing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ Metadata Routing

.. note::
The Metadata Routing API is experimental, and is not implemented yet for many
estimators. It may change without the usual deprecation cycle. By default
this feature is not enabled. You can enable this feature by setting the
``enable_metadata_routing`` flag to ``True``:
estimators. Please refer to the :ref:`list of supported and unsupported
models <metadata_routing_models>` for more information. It may change without
the usual deprecation cycle. By default this feature is not enabled. You can
enable this feature by setting the ``enable_metadata_routing`` flag to
``True``::

>>> import sklearn
>>> sklearn.set_config(enable_metadata_routing=True)
Expand Down Expand Up @@ -230,3 +232,72 @@ The issue can be fixed by explicitly setting the request value::
>>> lr = LogisticRegression().set_fit_request(
... sample_weight=True
... ).set_score_request(sample_weight=False)

At the end we disable the configuration flag for metadata routing::

>>> sklearn.set_config(enable_metadata_routing=False)

.. _metadata_routing_models:

Metadata Routing Support Status
*******************************
All consumers (i.e. simple estimators which only consume metadata and don't
route them) support metadata routing, meaning they can be used inside
meta-estimators which support metadata routing. However, development of support
for metadata routing for meta-estimators is in progress, and here is a list of
meta-estimators and tools which support and don't yet support metadata routing.


Meta-estimators and functions supporting metadata routing:

- :class:`sklearn.calibration.CalibratedClassifierCV`
- :class:`sklearn.compose.ColumnTransformer`
- :class:`sklearn.linear_model.LogisticRegressionCV`
- :class:`sklearn.model_selection.GridSearchCV`
- :class:`sklearn.model_selection.HalvingGridSearchCV`
- :class:`sklearn.model_selection.HalvingRandomSearchCV`
- :class:`sklearn.model_selection.RandomizedSearchCV`
- :func:`sklearn.model_selection.cross_validate`
- :func:`sklearn.model_selection.cross_val_score`
- :func:`sklearn.model_selection.cross_val_predict`
- :class:`sklearn.multioutput.ClassifierChain`
- :class:`sklearn.multioutput.MultiOutputClassifier`
- :class:`sklearn.multioutput.MultiOutputRegressor`
- :class:`sklearn.multioutput.RegressorChain`
- :class:`sklearn.pipeline.Pipeline`

Meta-estimators and tools not supporting metadata routing yet:

- :class:`sklearn.compose.TransformedTargetRegressor`
- :class:`sklearn.covariance.GraphicalLassoCV`
- :class:`sklearn.ensemble.AdaBoostClassifier`
- :class:`sklearn.ensemble.AdaBoostRegressor`
- :class:`sklearn.ensemble.BaggingClassifier`
- :class:`sklearn.ensemble.BaggingRegressor`
- :class:`sklearn.ensemble.StackingClassifier`
- :class:`sklearn.ensemble.StackingRegressor`
- :class:`sklearn.ensemble.VotingClassifier`
- :class:`sklearn.ensemble.VotingRegressor`
- :class:`sklearn.feature_selection.RFE`
- :class:`sklearn.feature_selection.RFECV`
- :class:`sklearn.feature_selection.SelectFromModel`
- :class:`sklearn.feature_selection.SequentialFeatureSelector`
- :class:`sklearn.impute.IterativeImputer`
- :class:`sklearn.linear_model.ElasticNetCV`
- :class:`sklearn.linear_model.LarsCV`
- :class:`sklearn.linear_model.LassoCV`
- :class:`sklearn.linear_model.LassoLarsCV`
- :class:`sklearn.linear_model.MultiTaskElasticNetCV`
- :class:`sklearn.linear_model.MultiTaskLassoCV`
- :class:`sklearn.linear_model.OrthogonalMatchingPursuitCV`
- :class:`sklearn.linear_model.RANSACRegressor`
- :class:`sklearn.linear_model.RidgeClassifierCV`
- :class:`sklearn.linear_model.RidgeCV`
- :class:`sklearn.model_selection.learning_curve`
- :class:`sklearn.model_selection.permutation_test_score`
- :class:`sklearn.model_selection.validation_curve`
- :class:`sklearn.multiclass.OneVsOneClassifier`
- :class:`sklearn.multiclass.OneVsRestClassifier`
- :class:`sklearn.multiclass.OutputCodeClassifier`
- :class:`sklearn.pipeline.FeatureUnion`
- :class:`sklearn.semi_supervised.SelfTrainingClassifier`
9 changes: 7 additions & 2 deletions doc/whats_new/v1.4.rst
Original file line number Diff line number Diff line change
Expand Up @@ -58,17 +58,22 @@ more details.
estimator's ``fit``, the CV splitter, and the scorer. :pr:`27058` by `Adrin
Jalali`_.

- |Enhancement| :class:`~compose.ColumnTransformer` now supports metadata routing
- |Feature| :class:`~compose.ColumnTransformer` now supports metadata routing
according to :ref:`metadata routing user guide <metadata_routing>`. :pr:`27005`
by `Adrin Jalali`_.

- |Enhancement| :class:`linear_model.LogisticRegressionCV` now supports
- |Feature| :class:`linear_model.LogisticRegressionCV` now supports
metadata routing. :meth:`linear_model.LogisticRegressionCV.fit` now
accepts ``**params`` which are passed to the underlying splitter and
scorer. :meth:`linear_model.LogisticRegressionCV.score` now accepts
``**score_params`` which are passed to the underlying scorer.
:pr:`26525` by :user:`Omar Salman <OmarManzoor>`.

- |Fix| All meta-estimators for which metadata routing is not yet implemented
now raise a `NotImplementedError` on `get_metadata_routing` and on `fit` if
adrinjalali marked this conversation as resolved.
Show resolved Hide resolved
metadata routing is enabled and any metadata is passed to them. :pr:`27389`
by `Adrin Jalali`_.

Support for SciPy sparse arrays
-------------------------------

Expand Down
9 changes: 8 additions & 1 deletion sklearn/compose/_target.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,18 @@
from ..utils import _safe_indexing, check_array
from ..utils._param_validation import HasMethods
from ..utils._tags import _safe_tags
from ..utils.metadata_routing import (
_raise_for_unsupported_routing,
_RoutingNotSupportedMixin,
)
from ..utils.validation import check_is_fitted

__all__ = ["TransformedTargetRegressor"]


class TransformedTargetRegressor(RegressorMixin, BaseEstimator):
class TransformedTargetRegressor(
_RoutingNotSupportedMixin, RegressorMixin, BaseEstimator
):
"""Meta-estimator to regress on a transformed target.

Useful for applying a non-linear transformation to the target `y` in
Expand Down Expand Up @@ -222,6 +228,7 @@ def fit(self, X, y, **fit_params):
self : object
Fitted estimator.
"""
_raise_for_unsupported_routing(self, "fit", **fit_params)
if y is None:
raise ValueError(
f"This {self.__class__.__name__} estimator "
Expand Down
3 changes: 2 additions & 1 deletion sklearn/covariance/_graph_lasso.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from ..linear_model import lars_path_gram
from ..model_selection import check_cv, cross_val_score
from ..utils._param_validation import Interval, StrOptions, validate_params
from ..utils.metadata_routing import _RoutingNotSupportedMixin
from ..utils.parallel import Parallel, delayed
from ..utils.validation import (
_is_arraylike_not_scalar,
Expand Down Expand Up @@ -705,7 +706,7 @@ def graphical_lasso_path(
return covariances_, precisions_


class GraphicalLassoCV(BaseGraphicalLasso):
class GraphicalLassoCV(_RoutingNotSupportedMixin, BaseGraphicalLasso):
"""Sparse inverse covariance w/ cross-validated choice of the l1 penalty.

See glossary entry for :term:`cross-validation estimator`.
Expand Down
9 changes: 7 additions & 2 deletions sklearn/ensemble/_bagging.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
from ..utils import check_random_state, column_or_1d, indices_to_mask
from ..utils._param_validation import HasMethods, Interval, RealNotInt, StrOptions
from ..utils._tags import _safe_tags
from ..utils.metadata_routing import (
_raise_for_unsupported_routing,
_RoutingNotSupportedMixin,
)
from ..utils.metaestimators import available_if
from ..utils.multiclass import check_classification_targets
from ..utils.parallel import Parallel, delayed
Expand Down Expand Up @@ -326,6 +330,7 @@ def fit(self, X, y, sample_weight=None):
self : object
Fitted estimator.
"""
_raise_for_unsupported_routing(self, "fit", sample_weight=sample_weight)
# Convert data (X is required to be 2d and indexable)
X, y = self._validate_data(
X,
Expand Down Expand Up @@ -542,7 +547,7 @@ def estimators_samples_(self):
return [sample_indices for _, sample_indices in self._get_estimators_indices()]


class BaggingClassifier(ClassifierMixin, BaseBagging):
class BaggingClassifier(_RoutingNotSupportedMixin, ClassifierMixin, BaseBagging):
"""A Bagging classifier.

A Bagging classifier is an ensemble meta-estimator that fits base
Expand Down Expand Up @@ -990,7 +995,7 @@ def _more_tags(self):
return {"allow_nan": _safe_tags(estimator, "allow_nan")}


class BaggingRegressor(RegressorMixin, BaseBagging):
class BaggingRegressor(_RoutingNotSupportedMixin, RegressorMixin, BaseBagging):
"""A Bagging regressor.

A Bagging regressor is an ensemble meta-estimator that fits base
Expand Down
10 changes: 8 additions & 2 deletions sklearn/ensemble/_stacking.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
from ..utils import Bunch
from ..utils._estimator_html_repr import _VisualBlock
from ..utils._param_validation import HasMethods, StrOptions
from ..utils.metadata_routing import (
_raise_for_unsupported_routing,
_RoutingNotSupportedMixin,
)
from ..utils.metaestimators import available_if
from ..utils.multiclass import check_classification_targets, type_of_target
from ..utils.parallel import Parallel, delayed
Expand Down Expand Up @@ -380,7 +384,7 @@ def _sk_visual_block_with_final_estimator(self, final_estimator):
return _VisualBlock("serial", (parallel, final_block), dash_wrapped=False)


class StackingClassifier(ClassifierMixin, _BaseStacking):
class StackingClassifier(_RoutingNotSupportedMixin, ClassifierMixin, _BaseStacking):
"""Stack of estimators with a final classifier.

Stacked generalization consists in stacking the output of individual
Expand Down Expand Up @@ -641,6 +645,7 @@ def fit(self, X, y, sample_weight=None):
self : object
Returns a fitted instance of estimator.
"""
_raise_for_unsupported_routing(self, "fit", sample_weight=sample_weight)
check_classification_targets(y)
if type_of_target(y) == "multilabel-indicator":
self._label_encoder = [LabelEncoder().fit(yk) for yk in y.T]
Expand Down Expand Up @@ -761,7 +766,7 @@ def _sk_visual_block_(self):
return super()._sk_visual_block_with_final_estimator(final_estimator)


class StackingRegressor(RegressorMixin, _BaseStacking):
class StackingRegressor(_RoutingNotSupportedMixin, RegressorMixin, _BaseStacking):
"""Stack of estimators with a final regressor.

Stacked generalization consists in stacking the output of individual
Expand Down Expand Up @@ -952,6 +957,7 @@ def fit(self, X, y, sample_weight=None):
self : object
Returns a fitted instance.
"""
_raise_for_unsupported_routing(self, "fit", sample_weight=sample_weight)
y = column_or_1d(y, warn=True)
return super().fit(X, y, sample_weight)

Expand Down
10 changes: 8 additions & 2 deletions sklearn/ensemble/_voting.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@
from ..utils import Bunch
from ..utils._estimator_html_repr import _VisualBlock
from ..utils._param_validation import StrOptions
from ..utils.metadata_routing import (
_raise_for_unsupported_routing,
_RoutingNotSupportedMixin,
)
from ..utils.metaestimators import available_if
from ..utils.multiclass import check_classification_targets
from ..utils.parallel import Parallel, delayed
Expand Down Expand Up @@ -152,7 +156,7 @@ def _more_tags(self):
return {"preserves_dtype": []}


class VotingClassifier(ClassifierMixin, _BaseVoting):
class VotingClassifier(_RoutingNotSupportedMixin, ClassifierMixin, _BaseVoting):
"""Soft Voting/Majority Rule classifier for unfitted estimators.

Read more in the :ref:`User Guide <voting_classifier>`.
Expand Down Expand Up @@ -336,6 +340,7 @@ def fit(self, X, y, sample_weight=None):
self : object
Returns the instance itself.
"""
_raise_for_unsupported_routing(self, "fit", sample_weight=sample_weight)
check_classification_targets(y)
if isinstance(y, np.ndarray) and len(y.shape) > 1 and y.shape[1] > 1:
raise NotImplementedError(
Expand Down Expand Up @@ -478,7 +483,7 @@ def get_feature_names_out(self, input_features=None):
return np.asarray(names_out, dtype=object)


class VotingRegressor(RegressorMixin, _BaseVoting):
class VotingRegressor(_RoutingNotSupportedMixin, RegressorMixin, _BaseVoting):
"""Prediction voting regressor for unfitted estimators.

A voting regressor is an ensemble meta-estimator that fits several base
Expand Down Expand Up @@ -601,6 +606,7 @@ def fit(self, X, y, sample_weight=None):
self : object
Fitted estimator.
"""
_raise_for_unsupported_routing(self, "fit", sample_weight=sample_weight)
y = column_or_1d(y, warn=True)
return super().fit(X, y, sample_weight)

Expand Down
11 changes: 9 additions & 2 deletions sklearn/ensemble/_weight_boosting.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@
from ..utils import _safe_indexing, check_random_state
from ..utils._param_validation import HasMethods, Interval, StrOptions
from ..utils.extmath import softmax, stable_cumsum
from ..utils.metadata_routing import (
_raise_for_unsupported_routing,
_RoutingNotSupportedMixin,
)
from ..utils.validation import (
_check_sample_weight,
_num_samples,
Expand Down Expand Up @@ -132,6 +136,7 @@ def fit(self, X, y, sample_weight=None):
self : object
Fitted estimator.
"""
_raise_for_unsupported_routing(self, "fit", sample_weight=sample_weight)
X, y = self._validate_data(
X,
y,
Expand Down Expand Up @@ -338,7 +343,9 @@ def _samme_proba(estimator, n_classes, X):
)


class AdaBoostClassifier(ClassifierMixin, BaseWeightBoosting):
class AdaBoostClassifier(
_RoutingNotSupportedMixin, ClassifierMixin, BaseWeightBoosting
):
"""An AdaBoost classifier.

An AdaBoost [1]_ classifier is a meta-estimator that begins by fitting a
Expand Down Expand Up @@ -980,7 +987,7 @@ def predict_log_proba(self, X):
return np.log(self.predict_proba(X))


class AdaBoostRegressor(RegressorMixin, BaseWeightBoosting):
class AdaBoostRegressor(_RoutingNotSupportedMixin, RegressorMixin, BaseWeightBoosting):
"""An AdaBoost regressor.

An AdaBoost [1] regressor is a meta-estimator that begins by fitting a
Expand Down
9 changes: 8 additions & 1 deletion sklearn/feature_selection/_from_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
from ..exceptions import NotFittedError
from ..utils._param_validation import HasMethods, Interval, Options
from ..utils._tags import _safe_tags
from ..utils.metadata_routing import (
_raise_for_unsupported_routing,
_RoutingNotSupportedMixin,
)
from ..utils.metaestimators import available_if
from ..utils.validation import _num_features, check_is_fitted, check_scalar
from ._base import SelectorMixin, _get_feature_importances
Expand Down Expand Up @@ -78,7 +82,9 @@ def _estimator_has(attr):
)


class SelectFromModel(MetaEstimatorMixin, SelectorMixin, BaseEstimator):
class SelectFromModel(
_RoutingNotSupportedMixin, MetaEstimatorMixin, SelectorMixin, BaseEstimator
):
"""Meta-transformer for selecting features based on importance weights.

.. versionadded:: 0.17
Expand Down Expand Up @@ -342,6 +348,7 @@ def fit(self, X, y=None, **fit_params):
self : object
Fitted estimator.
"""
_raise_for_unsupported_routing(self, "fit", **fit_params)
self._check_max_features(X)

if self.prefit:
Expand Down
8 changes: 7 additions & 1 deletion sklearn/feature_selection/_rfe.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
from ..model_selection._validation import _score
from ..utils._param_validation import HasMethods, Interval, RealNotInt
from ..utils._tags import _safe_tags
from ..utils.metadata_routing import (
_raise_for_unsupported_routing,
_RoutingNotSupportedMixin,
)
from ..utils.metaestimators import _safe_split, available_if
from ..utils.parallel import Parallel, delayed
from ..utils.validation import check_is_fitted
Expand Down Expand Up @@ -56,7 +60,7 @@ def _estimator_has(attr):
)


class RFE(SelectorMixin, MetaEstimatorMixin, BaseEstimator):
class RFE(_RoutingNotSupportedMixin, SelectorMixin, MetaEstimatorMixin, BaseEstimator):
"""Feature ranking with recursive feature elimination.

Given an external estimator that assigns weights to features (e.g., the
Expand Down Expand Up @@ -251,6 +255,7 @@ def fit(self, X, y, **fit_params):
self : object
Fitted estimator.
"""
_raise_for_unsupported_routing(self, "fit", **fit_params)
return self._fit(X, y, **fit_params)

def _fit(self, X, y, step_score=None, **fit_params):
Expand Down Expand Up @@ -680,6 +685,7 @@ def fit(self, X, y, groups=None):
self : object
Fitted estimator.
"""
_raise_for_unsupported_routing(self, "fit", groups=groups)
tags = self._get_tags()
X, y = self._validate_data(
X,
Expand Down
Loading
Loading