diff --git a/doc/whats_new/v1.6.rst b/doc/whats_new/v1.6.rst index 00235842b4dc0..16ba232c310a0 100644 --- a/doc/whats_new/v1.6.rst +++ b/doc/whats_new/v1.6.rst @@ -257,6 +257,11 @@ Changelog for the calculation of test scores. :pr:`29419` by :user:`Shruti Nath `. +- |Fix| :class:`linear_model.RidgeCV` now properly supports custom multioutput scorers + by letting the scorer manage the multioutput averaging. Previously, the predictions + and true targets were both squeezed to a 1D array before computing the error. + :pr:`29884` by :user:`Guillaume Lemaitre `. + - |Fix| :class:`linear_model.RidgeCV` now properly uses predictions on the same scale as the target seen during `fit`. These predictions are stored in `cv_results_` when `scoring != None`. Previously, the predictions were rescaled by the square root of the diff --git a/sklearn/linear_model/tests/test_ridge.py b/sklearn/linear_model/tests/test_ridge.py index 32c6f6aebf568..f846c24b7b39f 100644 --- a/sklearn/linear_model/tests/test_ridge.py +++ b/sklearn/linear_model/tests/test_ridge.py @@ -2326,6 +2326,36 @@ def test_ridge_cv_multioutput_sample_weight(global_random_seed): assert_allclose(ridge_cv.best_score_, -mean_squared_error(y, y_pred_loo)) +def test_ridge_cv_custom_multioutput_scorer(): + """Check that `RidgeCV` works properly with a custom multioutput scorer.""" + X, y = make_regression(n_targets=2, random_state=0) + + def custom_error(y_true, y_pred): + errors = (y_true - y_pred) ** 2 + mean_errors = np.mean(errors, axis=0) + if mean_errors.ndim == 1: + # case of multioutput + return -np.average(mean_errors, weights=[2, 1]) + # single output - this part of the code should not be reached in the case of + # multioutput scoring + return -mean_errors # pragma: no cover + + def custom_multioutput_scorer(estimator, X, y): + """Multioutput score that give twice more importance to the second target.""" + return -custom_error(y, estimator.predict(X)) + + ridge_cv = RidgeCV(scoring=custom_multioutput_scorer) + ridge_cv.fit(X, y) + + cv = LeaveOneOut() + ridge = Ridge(alpha=ridge_cv.alpha_) + y_pred_loo = np.squeeze( + [ridge.fit(X[train], y[train]).predict(X[test]) for train, test in cv.split(X)] + ) + + assert_allclose(ridge_cv.best_score_, -custom_error(y, y_pred_loo)) + + # Metadata Routing Tests # ======================