Repository: scikit-learn/scikit-learn
Pull request: FEA Add metadata routing for RFE and RFECV
<!--
Thanks for contributing a pull request! Please ensure you have taken a look at
the contribution guidelines: https://github.com/scikit-learn/scikit-learn/blob/main/CONTRIBUTING.md
-->

#### Reference Issues/PRs
<!--
Example: Fixes #1234. See also #3456.
Please use keywords (e.g., Fixes) to create link to the issues or pull requests
you resolved, so that they will automatically be closed when your pull request
is merged. See https://github.com/blog/1506-closing-issues-via-pull-requests
-->
Towards #22893
Fixes #7308

#### What does this implement/fix? Explain your changes.
- Adds the routing of params to the fit methods of RFE and RFECV

#### Any other comments?
CC: @adrinjalali @glemaitre

<!--
Please be aware that we are a loose team of volunteers so patience is
necessary; assistance handling other issues is very welcome. We value
all user contributions, no matter how minor they are. If we are slow to
review, either the pull request needs some benchmarking, tinkering,
convincing, etc. or more likely the reviewers are simply busy. In either
case, we ask for your understanding during the review process.
For more information, see our FAQ on this topic:
https://scikit-learn.org/dev/faq.html#why-is-my-pull-request-not-getting-any-attention.

Thanks for contributing!
-->


Problem statement:
RFE/RFECV doesn't work with sample weights
As far as I can tell, `sklearn.feature_selection.RFE` has no way to pass sample weights to the estimator alongside the data.

I have fixed this in my code with:

``` diff
index bbe0cda..f5072b2 100644
--- a/sklearn/feature_selection/rfe.py
+++ b/sklearn/feature_selection/rfe.py
@@ -120,7 +120,7 @@ class RFE(BaseEstimator, MetaEstimatorMixin, SelectorMixin):
     def _estimator_type(self):
         return self.estimator._estimator_type

-    def fit(self, X, y):
+    def fit(self, X, y, **fit_params):
         """Fit the RFE model and then the underlying estimator on the selected
            features.

@@ -132,9 +132,9 @@ class RFE(BaseEstimator, MetaEstimatorMixin, SelectorMixin):
         y : array-like, shape = [n_samples]
             The target values.
         """
-        return self._fit(X, y)
+        return self._fit(X, y, **fit_params)

-    def _fit(self, X, y, step_score=None):
+    def _fit(self, X, y, step_score=None, **fit_params):
         X, y = check_X_y(X, y, "csc")
         # Initialization
         n_features = X.shape[1]
@@ -166,7 +166,7 @@ class RFE(BaseEstimator, MetaEstimatorMixin, SelectorMixin):
             if self.verbose > 0:
                 print("Fitting estimator with %d features." % np.sum(support_))

-            estimator.fit(X[:, features], y)
+            estimator.fit(X[:, features], y, **fit_params)

             # Get coefs
             if hasattr(estimator, 'coef_'):
```

Would this be a worthwhile contribution to scikit-learn?
#### Versions

<!--
Please run the following snippet and paste the output below.
import platform; print(platform.platform())
import sys; print("Python", sys.version)
import numpy; print("NumPy", numpy.__version__)
import scipy; print("SciPy", scipy.__version__)
import sklearn; print("Scikit-Learn", sklearn.__version__)
-->

``` python

In [1]: import platform; print(platform.platform())
Linux-3.13.0-63-generic-x86_64-with-Ubuntu-14.04-trusty

In [2]: import sys; print("Python", sys.version)
('Python', '2.7.6 (default, Jun 22 2015, 17:58:13) \n[GCC 4.8.2]')

In [3]: import numpy; print("NumPy", numpy.__version__)
('NumPy', '1.11.0')

In [4]: import scipy; print("SciPy", scipy.__version__)
('SciPy', '0.17.1')

In [5]: import sklearn; print("Scikit-Learn", sklearn.__version__)
('Scikit-Learn', '0.18.dev0')
```

<!-- Thanks for contributing! -->

TODO:

- [x] Add support for `sample_weight` in `RFE`
- [ ] Add support for `sample_weight` in `RFECV`

Hints from issue discussion:
yeah I think you can open a PR for that. You need to add tests, though.

Note that testing for sample weights usually involves checking that weights
correspond to repeating samples.

On 1 September 2016 at 03:33, Andreas Mueller notifications@github.com
wrote:

> yeah I think you can open a PR for that. You need to add tests, though.
> 
> —
> You are receiving this because you are subscribed to this thread.
> Reply to this email directly, view it on GitHub
> https://github.com/scikit-learn/scikit-learn/issues/7308#issuecomment-243839981,
> or mute the thread
> https://github.com/notifications/unsubscribe-auth/AAEz64VJO8sJbmnWKX_vPF61jQEWBmwwks5qlbrtgaJpZM4Jx1Lf
> .

Great, want to assign this to me @amueller? I'll sort out a PR with tests.

We don't use the "assignee" feature much, and for some reason it's not letting me assign. Go ahead and open a PR, reference this issue, then ping one of us after we've released 0.18: it's not likely to be looked at before then.

you can only "assign" contributors

Hey all, just an FIY, @ijpulidos and I worked on this over #20380 
@fbidu thank you so much for this! for what it's worth I think it would be fairly simple to add this also to RFECV (which just calls RFE), but I understand if that's outside of the scope of what you're working on (since you're already building/trying to merge)
@nathanwalker-sp you're welcome! Well, I agree with you and sure enough should be a simple change, but I'm not familiar with the practices adopted by this project to track these.

@glemaitre as the reviewer for my PR, what do you think? May I just go ahead and implement this there or is it better if we first merge #20380 and then create a new issue/PR pair to track RFECV?
@nathanwalker-sp yeah, @glemaitre already merged it. Can you please create a new issue referring back to this in order to track RFECV?
I am reopening this issue and will rename the title. Feel free to open a new PR.

Commit message: Add _deprecate_positional_args for groups

Patch to review:
diff --git a/doc/metadata_routing.rst b/doc/metadata_routing.rst
index 31dae6813bda5..a753269e1903a 100644
--- a/doc/metadata_routing.rst
+++ b/doc/metadata_routing.rst
@@ -284,6 +284,8 @@ Meta-estimators and functions supporting metadata routing:
 - :class:`sklearn.ensemble.VotingRegressor`
 - :class:`sklearn.ensemble.BaggingClassifier`
 - :class:`sklearn.ensemble.BaggingRegressor`
+- :class:`sklearn.feature_selection.RFE`
+- :class:`sklearn.feature_selection.RFECV`
 - :class:`sklearn.feature_selection.SelectFromModel`
 - :class:`sklearn.impute.IterativeImputer`
 - :class:`sklearn.linear_model.ElasticNetCV`
@@ -322,6 +324,4 @@ Meta-estimators and tools not supporting metadata routing yet:
 
 - :class:`sklearn.ensemble.AdaBoostClassifier`
 - :class:`sklearn.ensemble.AdaBoostRegressor`
-- :class:`sklearn.feature_selection.RFE`
-- :class:`sklearn.feature_selection.RFECV`
 - :class:`sklearn.feature_selection.SequentialFeatureSelector`
diff --git a/doc/whats_new/v1.6.rst b/doc/whats_new/v1.6.rst
index 3971f60eb5f4b..c8485c150b716 100644
--- a/doc/whats_new/v1.6.rst
+++ b/doc/whats_new/v1.6.rst
@@ -95,6 +95,10 @@ more details.
   for the `fit` method of its estimator and for its underlying CV splitter and scorer.
   :pr:`29266` by :user:`Adam Li <adam2392>`.
 
+- |Feature| :class:`feature_selection.RFE` and :class:`feature_selection.RFECV`
+  now support metadata routing.
+  :pr:`29312` by :user:`Omar Salman <OmarManzoor>`.
+
 Dropping support for building with setuptools
 ---------------------------------------------
 
diff --git a/sklearn/feature_selection/_rfe.py b/sklearn/feature_selection/_rfe.py
index be71efaf27a0e..45052927366a2 100644
--- a/sklearn/feature_selection/_rfe.py
+++ b/sklearn/feature_selection/_rfe.py
@@ -13,23 +13,37 @@
 from ..metrics import check_scoring
 from ..model_selection import check_cv
 from ..model_selection._validation import _score
-from ..utils._param_validation import HasMethods, Interval, RealNotInt
-from ..utils.metadata_routing import (
-    _raise_for_unsupported_routing,
-    _RoutingNotSupportedMixin,
+from ..utils import Bunch, metadata_routing
+from ..utils._metadata_requests import (
+    MetadataRouter,
+    MethodMapping,
+    _raise_for_params,
+    _routing_enabled,
+    process_routing,
 )
+from ..utils._param_validation import HasMethods, Interval, RealNotInt
 from ..utils.metaestimators import _safe_split, available_if
 from ..utils.parallel import Parallel, delayed
-from ..utils.validation import check_is_fitted
+from ..utils.validation import (
+    _check_method_params,
+    _deprecate_positional_args,
+    check_is_fitted,
+)
 from ._base import SelectorMixin, _get_feature_importances
 
 
-def _rfe_single_fit(rfe, estimator, X, y, train, test, scorer):
+def _rfe_single_fit(rfe, estimator, X, y, train, test, scorer, routed_params):
     """
     Return the score and n_features per step for a fit across one fold.
     """
     X_train, y_train = _safe_split(estimator, X, y, train)
     X_test, y_test = _safe_split(estimator, X, y, test, train)
+    fit_params = _check_method_params(
+        X, params=routed_params.estimator.fit, indices=train
+    )
+    score_params = _check_method_params(
+        X=X, params=routed_params.scorer.score, indices=test
+    )
 
     rfe._fit(
         X_train,
@@ -40,8 +54,9 @@ def _rfe_single_fit(rfe, estimator, X, y, train, test, scorer):
             X_test[:, features],
             y_test,
             scorer,
-            score_params=None,
+            score_params=score_params,
         ),
+        **fit_params,
     )
 
     return rfe.step_scores_, rfe.step_n_features_
@@ -66,7 +81,7 @@ def check(self):
     return check
 
 
-class RFE(_RoutingNotSupportedMixin, SelectorMixin, MetaEstimatorMixin, BaseEstimator):
+class RFE(SelectorMixin, MetaEstimatorMixin, BaseEstimator):
     """Feature ranking with recursive feature elimination.
 
     Given an external estimator that assigns weights to features (e.g., the
@@ -253,16 +268,31 @@ def fit(self, X, y, **fit_params):
             The target values.
 
         **fit_params : dict
-            Additional parameters passed to the `fit` method of the underlying
-            estimator.
+            - If `enable_metadata_routing=False` (default):
+
+                Parameters directly passed to the ``fit`` method of the
+                underlying estimator.
+
+            - If `enable_metadata_routing=True`:
+
+                Parameters safely routed to the ``fit`` method of the
+                underlying estimator.
+
+                .. versionchanged:: 1.6
+                    See :ref:`Metadata Routing User Guide <metadata_routing>`
+                    for more details.
 
         Returns
         -------
         self : object
             Fitted estimator.
         """
-        _raise_for_unsupported_routing(self, "fit", **fit_params)
-        return self._fit(X, y, **fit_params)
+        if _routing_enabled():
+            routed_params = process_routing(self, "fit", **fit_params)
+        else:
+            routed_params = Bunch(estimator=Bunch(fit=fit_params))
+
+        return self._fit(X, y, **routed_params.estimator.fit)
 
     def _fit(self, X, y, step_score=None, **fit_params):
         # Parameter step_score controls the calculation of self.step_scores_
@@ -358,7 +388,7 @@ def _fit(self, X, y, step_score=None, **fit_params):
         return self
 
     @available_if(_estimator_has("predict"))
-    def predict(self, X):
+    def predict(self, X, **predict_params):
         """Reduce X to the selected features and predict using the estimator.
 
         Parameters
@@ -366,16 +396,35 @@ def predict(self, X):
         X : array of shape [n_samples, n_features]
             The input samples.
 
+        **predict_params : dict
+            Parameters to route to the ``predict`` method of the
+            underlying estimator.
+
+            .. versionadded:: 1.6
+                Only available if `enable_metadata_routing=True`,
+                which can be set by using
+                ``sklearn.set_config(enable_metadata_routing=True)``.
+                See :ref:`Metadata Routing User Guide <metadata_routing>`
+                for more details.
+
         Returns
         -------
         y : array of shape [n_samples]
             The predicted target values.
         """
+        _raise_for_params(predict_params, self, "predict")
         check_is_fitted(self)
-        return self.estimator_.predict(self.transform(X))
+        if _routing_enabled():
+            routed_params = process_routing(self, "predict", **predict_params)
+        else:
+            routed_params = Bunch(estimator=Bunch(predict={}))
+
+        return self.estimator_.predict(
+            self.transform(X), **routed_params.estimator.predict
+        )
 
     @available_if(_estimator_has("score"))
-    def score(self, X, y, **fit_params):
+    def score(self, X, y, **score_params):
         """Reduce X to the selected features and return the score of the estimator.
 
         Parameters
@@ -386,11 +435,22 @@ def score(self, X, y, **fit_params):
         y : array of shape [n_samples]
             The target values.
 
-        **fit_params : dict
-            Parameters to pass to the `score` method of the underlying
-            estimator.
+        **score_params : dict
+            - If `enable_metadata_routing=False` (default):
+
+                Parameters directly passed to the ``score`` method of the
+                underlying estimator.
+
+                .. versionadded:: 1.0
+
+            - If `enable_metadata_routing=True`:
 
-            .. versionadded:: 1.0
+                Parameters safely routed to the `score` method of the
+                underlying estimator.
+
+                .. versionchanged:: 1.6
+                    See :ref:`Metadata Routing User Guide <metadata_routing>`
+                    for more details.
 
         Returns
         -------
@@ -399,7 +459,14 @@ def score(self, X, y, **fit_params):
             features returned by `rfe.transform(X)` and `y`.
         """
         check_is_fitted(self)
-        return self.estimator_.score(self.transform(X), y, **fit_params)
+        if _routing_enabled():
+            routed_params = process_routing(self, "score", **score_params)
+        else:
+            routed_params = Bunch(estimator=Bunch(score=score_params))
+
+        return self.estimator_.score(
+            self.transform(X), y, **routed_params.estimator.score
+        )
 
     def _get_support_mask(self):
         check_is_fitted(self)
@@ -478,6 +545,29 @@ def _more_tags(self):
 
         return tags
 
+    def get_metadata_routing(self):
+        """Get metadata routing of this object.
+
+        Please check :ref:`User Guide <metadata_routing>` 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(
+            estimator=self.estimator,
+            method_mapping=MethodMapping()
+            .add(caller="fit", callee="fit")
+            .add(caller="predict", callee="predict")
+            .add(caller="score", callee="score"),
+        )
+        return router
+
 
 class RFECV(RFE):
     """Recursive feature elimination with cross-validation to select features.
@@ -668,6 +758,7 @@ class RFECV(RFE):
         "n_jobs": [None, Integral],
     }
     _parameter_constraints.pop("n_features_to_select")
+    __metadata_request__fit = {"groups": metadata_routing.UNUSED}
 
     def __init__(
         self,
@@ -690,11 +781,13 @@ def __init__(
         self.n_jobs = n_jobs
         self.min_features_to_select = min_features_to_select
 
+    # TODO(1.8): remove `groups` from the signature after deprecation cycle.
+    @_deprecate_positional_args(version="1.8")
     @_fit_context(
         # RFECV.estimator is not validated yet
         prefer_skip_nested_validation=False
     )
-    def fit(self, X, y, groups=None):
+    def fit(self, X, y, *, groups=None, **params):
         """Fit the RFE model and automatically tune the number of selected features.
 
         Parameters
@@ -714,12 +807,23 @@ def fit(self, X, y, groups=None):
 
             .. versionadded:: 0.20
 
+        **params : dict of str -> object
+            Parameters passed to the ``fit`` method of the estimator,
+            the scorer, and the CV splitter.
+
+            ..versionadded:: 1.6
+                Only available if `enable_metadata_routing=True`,
+                which can be set by using
+                ``sklearn.set_config(enable_metadata_routing=True)``.
+                See :ref:`Metadata Routing User Guide <metadata_routing>`
+                for more details.
+
         Returns
         -------
         self : object
             Fitted estimator.
         """
-        _raise_for_unsupported_routing(self, "fit", groups=groups)
+        _raise_for_params(params, self, "fit")
         X, y = self._validate_data(
             X,
             y,
@@ -729,9 +833,19 @@ def fit(self, X, y, groups=None):
             multi_output=True,
         )
 
+        if _routing_enabled():
+            params.update({"groups": groups})
+            routed_params = process_routing(self, "fit", **params)
+        else:
+            routed_params = Bunch(
+                estimator=Bunch(fit={}),
+                splitter=Bunch(split={"groups": groups}),
+                scorer=Bunch(score={}),
+            )
+
         # Initialization
         cv = check_cv(self.cv, y, classifier=is_classifier(self.estimator))
-        scorer = check_scoring(self.estimator, scoring=self.scoring)
+        scorer = self._get_scorer()
 
         # Build an RFE object, which will evaluate and score each possible
         # feature count, down to self.min_features_to_select
@@ -772,8 +886,8 @@ def fit(self, X, y, groups=None):
             func = delayed(_rfe_single_fit)
 
         scores_features = parallel(
-            func(rfe, self.estimator, X, y, train, test, scorer)
-            for train, test in cv.split(X, y, groups)
+            func(rfe, self.estimator, X, y, train, test, scorer, routed_params)
+            for train, test in cv.split(X, y, **routed_params.splitter.split)
         )
         scores, step_n_features = zip(*scores_features)
 
@@ -793,14 +907,14 @@ def fit(self, X, y, groups=None):
             verbose=self.verbose,
         )
 
-        rfe.fit(X, y)
+        rfe.fit(X, y, **routed_params.estimator.fit)
 
         # Set final attributes
         self.support_ = rfe.support_
         self.n_features_ = rfe.n_features_
         self.ranking_ = rfe.ranking_
         self.estimator_ = clone(self.estimator)
-        self.estimator_.fit(self._transform(X), y)
+        self.estimator_.fit(self._transform(X), y, **routed_params.estimator.fit)
 
         # reverse to stay consistent with before
         scores_rev = scores[:, ::-1]
@@ -811,3 +925,42 @@ def fit(self, X, y, groups=None):
             "n_features": step_n_features_rev,
         }
         return self
+
+    def get_metadata_routing(self):
+        """Get metadata routing of this object.
+
+        Please check :ref:`User Guide <metadata_routing>` 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__)
+        router.add(
+            estimator=self.estimator,
+            method_mapping=MethodMapping()
+            .add(caller="fit", callee="fit")
+            .add(caller="predict", callee="predict")
+            .add(caller="score", callee="score"),
+        )
+        router.add(
+            splitter=check_cv(self.cv),
+            method_mapping=MethodMapping().add(
+                caller="fit",
+                callee="split",
+            ),
+        )
+        router.add(
+            scorer=self._get_scorer(),
+            method_mapping=MethodMapping().add(caller="fit", callee="score"),
+        )
+
+        return router
+
+    def _get_scorer(self):
+        return check_scoring(self.estimator, scoring=self.scoring)
diff --git a/sklearn/feature_selection/tests/test_rfe.py b/sklearn/feature_selection/tests/test_rfe.py
index a0610e990054f..28bee4121a45e 100644
--- a/sklearn/feature_selection/tests/test_rfe.py
+++ b/sklearn/feature_selection/tests/test_rfe.py
@@ -666,3 +666,36 @@ def test_rfe_n_features_to_select_warning(ClsRFE, param):
         # larger than the number of features present in the X variable
         clsrfe = ClsRFE(estimator=LogisticRegression(), **{param: 21})
         clsrfe.fit(X, y)
+
+
+def test_rfe_with_sample_weight():
+    """Test that `RFE` works correctly with sample weights."""
+    X, y = make_classification(random_state=0)
+    n_samples = X.shape[0]
+
+    # Assign the first half of the samples with twice the weight
+    sample_weight = np.ones_like(y)
+    sample_weight[: n_samples // 2] = 2
+
+    # Duplicate the first half of the data samples to replicate the effect
+    # of sample weights for comparison
+    X2 = np.concatenate([X, X[: n_samples // 2]], axis=0)
+    y2 = np.concatenate([y, y[: n_samples // 2]])
+
+    estimator = SVC(kernel="linear")
+
+    rfe_sw = RFE(estimator=estimator, step=0.1)
+    rfe_sw.fit(X, y, sample_weight=sample_weight)
+
+    rfe = RFE(estimator=estimator, step=0.1)
+    rfe.fit(X2, y2)
+
+    assert_array_equal(rfe_sw.ranking_, rfe.ranking_)
+
+    # Also verify that when sample weights are not doubled the results
+    # are different from the duplicated data
+    rfe_sw_2 = RFE(estimator=estimator, step=0.1)
+    sample_weight_2 = np.ones_like(y)
+    rfe_sw_2.fit(X, y, sample_weight=sample_weight_2)
+
+    assert not np.array_equal(rfe_sw_2.ranking_, rfe.ranking_)
diff --git a/sklearn/tests/metadata_routing_common.py b/sklearn/tests/metadata_routing_common.py
index 5fffec8fccecf..174164daada8c 100644
--- a/sklearn/tests/metadata_routing_common.py
+++ b/sklearn/tests/metadata_routing_common.py
@@ -201,6 +201,7 @@ def __init__(self, alpha=0.0):
 
     def fit(self, X, y):
         self.classes_ = np.unique(y)
+        self.coef_ = np.ones_like(X)
         return self
 
     def partial_fit(self, X, y, classes=None):
@@ -281,6 +282,7 @@ def fit(self, X, y, sample_weight="default", metadata="default"):
         )
 
         self.classes_ = np.unique(y)
+        self.coef_ = np.ones_like(X)
         return self
 
     def predict(self, X, sample_weight="default", metadata="default"):
diff --git a/sklearn/tests/test_metaestimators_metadata_routing.py b/sklearn/tests/test_metaestimators_metadata_routing.py
index 9aca241521ca0..78a887a6486ce 100644
--- a/sklearn/tests/test_metaestimators_metadata_routing.py
+++ b/sklearn/tests/test_metaestimators_metadata_routing.py
@@ -407,6 +407,26 @@ def enable_slep006():
         ],
         "method_mapping": {"fit": ["fit", "score"]},
     },
+    {
+        "metaestimator": RFE,
+        "estimator": "classifier",
+        "estimator_name": "estimator",
+        "X": X,
+        "y": y,
+        "estimator_routing_methods": ["fit", "predict", "score"],
+    },
+    {
+        "metaestimator": RFECV,
+        "estimator": "classifier",
+        "estimator_name": "estimator",
+        "estimator_routing_methods": ["fit"],
+        "cv_name": "cv",
+        "cv_routing_methods": ["fit"],
+        "scorer_name": "scoring",
+        "scorer_routing_methods": ["fit"],
+        "X": X,
+        "y": y,
+    },
 ]
 """List containing all metaestimators to be tested and their settings
 
@@ -448,8 +468,6 @@ def enable_slep006():
 UNSUPPORTED_ESTIMATORS = [
     AdaBoostClassifier(),
     AdaBoostRegressor(),
-    RFE(ConsumingClassifier()),
-    RFECV(ConsumingClassifier()),
     SequentialFeatureSelector(ConsumingClassifier()),
 ]
 


Code review comments:
diff_hunk: @@ -811,3 +925,42 @@ def fit(self, X, y, groups=None):
             "n_features": step_n_features_rev,
         }
         return self
+
+    def get_metadata_routing(self):
+        """Get metadata routing of this object.
+
+        Please check :ref:`User Guide <metadata_routing>` 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__)
+        router.add(
+            estimator=self.estimator,
+            method_mapping=MethodMapping()
+            .add(caller="fit", callee="fit")
+            .add(caller="predict", callee="predict")
+            .add(caller="score", callee="score"),
+        )
+        router.add(
+            splitter=check_cv(self.cv),
+            method_mapping=MethodMapping().add(
+                caller="fit",
+                callee="split",
+            ),
+        )
+        router.add(
+            scorer=self._get_scorer(),
+            method_mapping=MethodMapping().add(caller="fit", callee="score"),
+        )
+
+        return router
+
+    def _get_scorer(self):
+        return check_scoring(self.estimator, scoring=self.scoring)
path: sklearn/feature_selection/_rfe.py
text: @user1:
I think this is safer to use `get_scorer`:


```suggestion
        if self.scoring is None:
            scoring = "accuracy" if is_classifier(self.estimator) else "r2"
        else:
            scoring = self.scoring
        return get_scorer(scoring)
```

My issue with `check_scoring` is triggered when `self.scoring is None`. Indeed, we are going to delegate to the `estimator.score` method and thus we don't return a scorer.

I'm wondering if there is any side effect when returning the `estimator.score` that does not have the `score_params` in the signature. I assume that we should have the same error than when calling a `scorer` with an estimator that does not support it.

@author:
Yeah _PassThroughScorer breaks on 3 tests when scoring is None because of recursion depth exceeded meaning it gets stuck in an endless loop. The code you suggested seems fine, we just have to tweak the MockClassifier defined in the tests a bit to make the tests pass. Could you have a look at the latest changes to see if they look alright and then I can add the additional changelog.