From 1daa77258805294d979d561e65b0cf95847dd161 Mon Sep 17 00:00:00 2001 From: Charlie-XIAO Date: Sun, 18 Jun 2023 20:11:24 +0800 Subject: [PATCH 1/4] ENH ravel prediction of PLSRegression when fitted on 1d y --- doc/whats_new/v1.3.rst | 6 ++++++ sklearn/cross_decomposition/_pls.py | 7 +++++-- sklearn/cross_decomposition/tests/test_pls.py | 14 ++++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/doc/whats_new/v1.3.rst b/doc/whats_new/v1.3.rst index 7bdbe7841f0d4..e74f6a46be60d 100644 --- a/doc/whats_new/v1.3.rst +++ b/doc/whats_new/v1.3.rst @@ -276,6 +276,12 @@ Changelog :func:`covariance.graphical_lasso_path`, and :class:`covariance.GraphicalLassoCV`. :pr:`26033` by :user:`Genesis Valencia `. +:mod:`sklearn.cross_decomposition` +.................................. + +- |Enhancement| :class:`cross_decomposition.PLSRegression` now automatically ravels + the output of `predict` if fitted with one dimensional `y`. + :mod:`sklearn.datasets` ....................... diff --git a/sklearn/cross_decomposition/_pls.py b/sklearn/cross_decomposition/_pls.py index da395d8f060fb..815e1804f10de 100644 --- a/sklearn/cross_decomposition/_pls.py +++ b/sklearn/cross_decomposition/_pls.py @@ -236,7 +236,10 @@ def fit(self, X, Y): Y, input_name="Y", dtype=np.float64, copy=self.copy, ensure_2d=False ) if Y.ndim == 1: + self._predict_1d = True Y = Y.reshape(-1, 1) + else: + self._predict_1d = False n = X.shape[0] p = X.shape[1] @@ -467,8 +470,8 @@ def predict(self, X, copy=True): # Normalize X -= self._x_mean X /= self._x_std - Ypred = X @ self.coef_.T - return Ypred + self.intercept_ + Ypred = X @ self.coef_.T + self.intercept_ + return Ypred.ravel() if self._predict_1d else Ypred def fit_transform(self, X, y=None): """Learn and apply the dimension reduction on the train data. diff --git a/sklearn/cross_decomposition/tests/test_pls.py b/sklearn/cross_decomposition/tests/test_pls.py index 8f4840c9b9f21..add67cc950a3a 100644 --- a/sklearn/cross_decomposition/tests/test_pls.py +++ b/sklearn/cross_decomposition/tests/test_pls.py @@ -622,3 +622,17 @@ def test_pls_set_output(Klass): assert isinstance(y_trans, np.ndarray) assert isinstance(X_trans, pd.DataFrame) assert_array_equal(X_trans.columns, est.get_feature_names_out()) + + +def test_pls_regression_fit_1d_y(): + """Check that when fitting with 1d y, prediction should also be 1d. + + Non-regression test for Issue #26549. + """ + X = np.array([[1, 1], [2, 4], [3, 9], [4, 16], [5, 25], [6, 36]]) + y = np.array([2, 6, 12, 20, 30, 42]) + expected = y.copy() + + plsr = PLSRegression().fit(X, y) + y_pred = plsr.predict(X) + assert_allclose(y_pred, expected) From b391e126889f52b024a8379caabb7d6a8bfd36a0 Mon Sep 17 00:00:00 2001 From: Charlie-XIAO Date: Sun, 18 Jun 2023 20:16:26 +0800 Subject: [PATCH 2/4] changelof entry updated --- doc/whats_new/v1.3.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/whats_new/v1.3.rst b/doc/whats_new/v1.3.rst index e74f6a46be60d..cdcde6561ff30 100644 --- a/doc/whats_new/v1.3.rst +++ b/doc/whats_new/v1.3.rst @@ -281,6 +281,7 @@ Changelog - |Enhancement| :class:`cross_decomposition.PLSRegression` now automatically ravels the output of `predict` if fitted with one dimensional `y`. + :pr:`26602` by :user:`Yao Xiao `. :mod:`sklearn.datasets` ....................... From f12c6201c1ce829f2b9068322b4ee0c45e53fafc Mon Sep 17 00:00:00 2001 From: Charlie-XIAO Date: Fri, 21 Jul 2023 15:00:14 +0800 Subject: [PATCH 3/4] resolved conversations --- doc/whats_new/v1.3.rst | 7 ------- doc/whats_new/v1.4.rst | 7 +++++++ sklearn/cross_decomposition/tests/test_pls.py | 10 +++++++++- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/doc/whats_new/v1.3.rst b/doc/whats_new/v1.3.rst index 0e7a900f8b988..8d39ca2fed143 100644 --- a/doc/whats_new/v1.3.rst +++ b/doc/whats_new/v1.3.rst @@ -305,13 +305,6 @@ Changelog :func:`covariance.graphical_lasso`, and :class:`covariance.GraphicalLassoCV`. :pr:`26033` by :user:`Genesis Valencia `. -:mod:`sklearn.cross_decomposition` -.................................. - -- |Enhancement| :class:`cross_decomposition.PLSRegression` now automatically ravels - the output of `predict` if fitted with one dimensional `y`. - :pr:`26602` by :user:`Yao Xiao `. - :mod:`sklearn.datasets` ....................... diff --git a/doc/whats_new/v1.4.rst b/doc/whats_new/v1.4.rst index ff3be92064fe8..dcd61fcbcb370 100644 --- a/doc/whats_new/v1.4.rst +++ b/doc/whats_new/v1.4.rst @@ -66,6 +66,13 @@ Changelog - |Enhancement| :func:`base.clone` now supports `dict` as input and creates a copy. :pr:`26786` by `Adrin Jalali`_. +:mod:`sklearn.cross_decomposition` +.................................. + +- |Fix| :class:`cross_decomposition.PLSRegression` now automatically ravels the output + of `predict` if fitted with one dimensional `y`. + :pr:`26602` by :user:`Yao Xiao `. + :mod:`sklearn.decomposition` ............................ diff --git a/sklearn/cross_decomposition/tests/test_pls.py b/sklearn/cross_decomposition/tests/test_pls.py index 3d79ed2de7ee2..4aab597b0499b 100644 --- a/sklearn/cross_decomposition/tests/test_pls.py +++ b/sklearn/cross_decomposition/tests/test_pls.py @@ -12,7 +12,9 @@ _svd_flip_1d, ) from sklearn.datasets import load_linnerud, make_regression +from sklearn.ensemble import VotingRegressor from sklearn.exceptions import ConvergenceWarning +from sklearn.linear_model import LinearRegression from sklearn.utils import check_random_state from sklearn.utils.extmath import svd_flip @@ -624,7 +626,7 @@ def test_pls_set_output(Klass): def test_pls_regression_fit_1d_y(): - """Check that when fitting with 1d y, prediction should also be 1d. + """Check that when fitting with 1d `y`, prediction should also be 1d. Non-regression test for Issue #26549. """ @@ -635,3 +637,9 @@ def test_pls_regression_fit_1d_y(): plsr = PLSRegression().fit(X, y) y_pred = plsr.predict(X) assert_allclose(y_pred, expected) + + # Check that it works in VotingRegressor + lr = LinearRegression().fit(X, y) + vr = VotingRegressor([("lr", lr), ("plsr", plsr)]) + y_pred = vr.fit(X, y).predict(X) + assert_allclose(y_pred, expected) From 53d4907836402206b61e8c9ae254c940599d4e02 Mon Sep 17 00:00:00 2001 From: Charlie-XIAO Date: Sun, 23 Jul 2023 16:29:02 +0800 Subject: [PATCH 4/4] resolved conversations --- sklearn/cross_decomposition/tests/test_pls.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sklearn/cross_decomposition/tests/test_pls.py b/sklearn/cross_decomposition/tests/test_pls.py index 4aab597b0499b..b8b5cbaa0f275 100644 --- a/sklearn/cross_decomposition/tests/test_pls.py +++ b/sklearn/cross_decomposition/tests/test_pls.py @@ -636,10 +636,11 @@ def test_pls_regression_fit_1d_y(): plsr = PLSRegression().fit(X, y) y_pred = plsr.predict(X) - assert_allclose(y_pred, expected) + assert y_pred.shape == expected.shape # Check that it works in VotingRegressor lr = LinearRegression().fit(X, y) vr = VotingRegressor([("lr", lr), ("plsr", plsr)]) y_pred = vr.fit(X, y).predict(X) + assert y_pred.shape == expected.shape assert_allclose(y_pred, expected)