diff --git a/doc/metadata_routing.rst b/doc/metadata_routing.rst index 27000a192ab21..d8a47927512e4 100644 --- a/doc/metadata_routing.rst +++ b/doc/metadata_routing.rst @@ -276,6 +276,7 @@ Meta-estimators and functions supporting metadata routing: - :class:`sklearn.calibration.CalibratedClassifierCV` - :class:`sklearn.compose.ColumnTransformer` +- :class:`sklearn.compose.TransformedTargetRegressor` - :class:`sklearn.covariance.GraphicalLassoCV` - :class:`sklearn.ensemble.StackingClassifier` - :class:`sklearn.ensemble.StackingRegressor` @@ -316,7 +317,6 @@ Meta-estimators and functions supporting metadata routing: Meta-estimators and tools not supporting metadata routing yet: -- :class:`sklearn.compose.TransformedTargetRegressor` - :class:`sklearn.ensemble.AdaBoostClassifier` - :class:`sklearn.ensemble.AdaBoostRegressor` - :class:`sklearn.feature_selection.RFE` diff --git a/doc/whats_new/v1.6.rst b/doc/whats_new/v1.6.rst index 3fd07fd51578e..adc2cd259085d 100644 --- a/doc/whats_new/v1.6.rst +++ b/doc/whats_new/v1.6.rst @@ -60,6 +60,11 @@ more details. ``**fit_params`` to the underlying estimators via their `fit` methods. :pr:`28701` by :user:`Stefanie Senger `. +- |Feature| :class:`compose.TransformedTargetRegressor` now supports metadata + routing in its `fit` and `predict` methods and routes the corresponding + params to the underlying regressor. + :pr:`29136` by :user:`Omar Salman `. + Dropping official support for PyPy ---------------------------------- diff --git a/sklearn/compose/_target.py b/sklearn/compose/_target.py index 3e6c94df8267a..4db174770e333 100644 --- a/sklearn/compose/_target.py +++ b/sklearn/compose/_target.py @@ -8,12 +8,18 @@ from ..base import BaseEstimator, RegressorMixin, _fit_context, clone from ..exceptions import NotFittedError +from ..linear_model import LinearRegression from ..preprocessing import FunctionTransformer -from ..utils import _safe_indexing, check_array +from ..utils import Bunch, _safe_indexing, check_array +from ..utils._metadata_requests import ( + MetadataRouter, + MethodMapping, + _routing_enabled, + process_routing, +) 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 @@ -230,15 +236,25 @@ def fit(self, X, y, **fit_params): Target values. **fit_params : dict - Parameters passed to the `fit` method of the underlying - regressor. + - If `enable_metadata_routing=False` (default): + + Parameters directly passed to the `fit` method of the + underlying regressor. + + - If `enable_metadata_routing=True`: + + Parameters safely routed to the `fit` method of the + underlying regressor. + + .. versionchanged:: 1.6 + See :ref:`Metadata Routing User Guide ` for + more details. Returns ------- self : object Fitted estimator. """ - _raise_for_unsupported_routing(self, "fit", **fit_params) if y is None: raise ValueError( f"This {self.__class__.__name__} estimator " @@ -274,14 +290,13 @@ def fit(self, X, y, **fit_params): if y_trans.ndim == 2 and y_trans.shape[1] == 1: y_trans = y_trans.squeeze(axis=1) - if self.regressor is None: - from ..linear_model import LinearRegression - - self.regressor_ = LinearRegression() + self.regressor_ = self._get_regressor(get_clone=True) + if _routing_enabled(): + routed_params = process_routing(self, "fit", **fit_params) else: - self.regressor_ = clone(self.regressor) + routed_params = Bunch(regressor=Bunch(fit=fit_params)) - self.regressor_.fit(X, y_trans, **fit_params) + self.regressor_.fit(X, y_trans, **routed_params.regressor.fit) if hasattr(self.regressor_, "feature_names_in_"): self.feature_names_in_ = self.regressor_.feature_names_in_ @@ -300,8 +315,19 @@ def predict(self, X, **predict_params): Samples. **predict_params : dict of str -> object - Parameters passed to the `predict` method of the underlying - regressor. + - If `enable_metadata_routing=False` (default): + + Parameters directly passed to the `predict` method of the + underlying regressor. + + - If `enable_metadata_routing=True`: + + Parameters safely routed to the `predict` method of the + underlying regressor. + + .. versionchanged:: 1.6 + See :ref:`Metadata Routing User Guide ` + for more details. Returns ------- @@ -309,7 +335,12 @@ def predict(self, X, **predict_params): Predicted values. """ check_is_fitted(self) - pred = self.regressor_.predict(X, **predict_params) + if _routing_enabled(): + routed_params = process_routing(self, "predict", **predict_params) + else: + routed_params = Bunch(regressor=Bunch(predict=predict_params)) + + pred = self.regressor_.predict(X, **routed_params.regressor.predict) if pred.ndim == 1: pred_trans = self.transformer_.inverse_transform(pred.reshape(-1, 1)) else: @@ -324,11 +355,7 @@ def predict(self, X, **predict_params): return pred_trans def _more_tags(self): - regressor = self.regressor - if regressor is None: - from ..linear_model import LinearRegression - - regressor = LinearRegression() + regressor = self._get_regressor() return { "poor_score": True, @@ -350,3 +377,31 @@ def n_features_in_(self): ) from nfe return self.regressor_.n_features_in_ + + def get_metadata_routing(self): + """Get metadata routing of this object. + + Please check :ref:`User Guide ` on how the routing + mechanism works. + + .. versionadded:: 1.6 + + Returns + ------- + routing : MetadataRouter + A :class:`~sklearn.utils.metadata_routing.MetadataRouter` encapsulating + routing information. + """ + router = MetadataRouter(owner=self.__class__.__name__).add( + regressor=self._get_regressor(), + method_mapping=MethodMapping() + .add(caller="fit", callee="fit") + .add(caller="predict", callee="predict"), + ) + return router + + def _get_regressor(self, get_clone=False): + if self.regressor is None: + return LinearRegression() + + return clone(self.regressor) if get_clone else self.regressor diff --git a/sklearn/tests/test_metaestimators_metadata_routing.py b/sklearn/tests/test_metaestimators_metadata_routing.py index 8bfb7b0663c18..a1cc807bd2a7e 100644 --- a/sklearn/tests/test_metaestimators_metadata_routing.py +++ b/sklearn/tests/test_metaestimators_metadata_routing.py @@ -382,6 +382,14 @@ def enable_slep006(): "cv_name": "cv", "cv_routing_methods": ["fit"], }, + { + "metaestimator": TransformedTargetRegressor, + "estimator": "regressor", + "estimator_name": "regressor", + "X": X, + "y": y, + "estimator_routing_methods": ["fit", "predict"], + }, ] """List containing all metaestimators to be tested and their settings @@ -427,7 +435,6 @@ def enable_slep006(): RFECV(ConsumingClassifier()), SelfTrainingClassifier(ConsumingClassifier()), SequentialFeatureSelector(ConsumingClassifier()), - TransformedTargetRegressor(), ]