From 20ba66100ae9b98a1f427081e5e09057fc708b8c Mon Sep 17 00:00:00 2001 From: Robin Thibaut Date: Mon, 15 Mar 2021 10:32:14 +0100 Subject: [PATCH 1/8] Added new feature to inverse-transform new target values in PLS. (inverse_transform method in _pls.py) --- sklearn/cross_decomposition/_pls.py | 35 ++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/sklearn/cross_decomposition/_pls.py b/sklearn/cross_decomposition/_pls.py index 42d727b9ae2be..f3b9c7fa73ee2 100644 --- a/sklearn/cross_decomposition/_pls.py +++ b/sklearn/cross_decomposition/_pls.py @@ -334,7 +334,7 @@ def transform(self, X, Y=None, copy=True): return x_scores - def inverse_transform(self, X): + def inverse_transform(self, X=None, Y=None): """Transform data back to its original space. Parameters @@ -343,23 +343,38 @@ def inverse_transform(self, X): New data, where `n_samples` is the number of samples and `n_components` is the number of pls components. + Y : array-like of shape (n_samples, n_components) + New target, where `n_samples` is the number of samples + and `n_components` is the number of pls components. + Returns ------- - x_reconstructed : array-like of shape (n_samples, n_features) + 'X_reconstructed' if X given : array-like of shape (n_samples, n_features), + 'Y_reconstructed' if Y given : array-like of shape (n_samples, n_features) Notes ----- This transformation will only be exact if `n_components=n_features`. """ check_is_fitted(self) - X = check_array(X, dtype=FLOAT_DTYPES) - # From pls space to original space - X_reconstructed = np.matmul(X, self.x_loadings_.T) - - # Denormalize - X_reconstructed *= self._x_std - X_reconstructed += self._x_mean - return X_reconstructed + + if X is not None: + X = check_array(X, dtype=FLOAT_DTYPES) + # From pls space to original space + X_reconstructed = np.matmul(X, self.x_loadings_.T) + # Denormalize + X_reconstructed *= self._x_std + X_reconstructed += self._x_mean + return X_reconstructed + + if Y is not None: + Y = check_array(Y, dtype=FLOAT_DTYPES) + # From pls space to original space + Y_reconstructed = np.matmul(Y, self.y_loadings_.T) + # Denormalize + Y_reconstructed *= self._y_std + Y_reconstructed += self._y_mean + return Y_reconstructed def predict(self, X, copy=True): """Predict targets of given samples. From 176dd2b8e1b692a59f8e53a5c034c2c06601ce9d Mon Sep 17 00:00:00 2001 From: Robin Thibaut Date: Mon, 27 Sep 2021 11:19:52 +0200 Subject: [PATCH 2/8] Update sklearn/cross_decomposition/_pls.py Co-authored-by: Guillaume Lemaitre --- sklearn/cross_decomposition/_pls.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/sklearn/cross_decomposition/_pls.py b/sklearn/cross_decomposition/_pls.py index f3b9c7fa73ee2..6e0e91979dad6 100644 --- a/sklearn/cross_decomposition/_pls.py +++ b/sklearn/cross_decomposition/_pls.py @@ -357,16 +357,13 @@ def inverse_transform(self, X=None, Y=None): This transformation will only be exact if `n_components=n_features`. """ check_is_fitted(self) - - if X is not None: - X = check_array(X, dtype=FLOAT_DTYPES) - # From pls space to original space - X_reconstructed = np.matmul(X, self.x_loadings_.T) - # Denormalize - X_reconstructed *= self._x_std - X_reconstructed += self._x_mean - return X_reconstructed - + X = check_array(X, dtype=FLOAT_DTYPES) + # From pls space to original space + X_reconstructed = np.matmul(X, self.x_loadings_.T) + # Denormalize + X_reconstructed *= self._x_std + X_reconstructed += self._x_mean + if Y is not None: Y = check_array(Y, dtype=FLOAT_DTYPES) # From pls space to original space @@ -374,7 +371,9 @@ def inverse_transform(self, X=None, Y=None): # Denormalize Y_reconstructed *= self._y_std Y_reconstructed += self._y_mean - return Y_reconstructed + return X_reconstructed, Y_reconstructed + + return X_reconstructed def predict(self, X, copy=True): """Predict targets of given samples. From a68422ada549b632ccbb1639ab574fcc867ac07d Mon Sep 17 00:00:00 2001 From: Robin Thibaut Date: Mon, 27 Sep 2021 11:41:51 +0200 Subject: [PATCH 3/8] Added test for inverse transform of Y --- sklearn/cross_decomposition/_pls.py | 2 +- sklearn/cross_decomposition/tests/test_pls.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/sklearn/cross_decomposition/_pls.py b/sklearn/cross_decomposition/_pls.py index 6e0e91979dad6..5e76fafcba32d 100644 --- a/sklearn/cross_decomposition/_pls.py +++ b/sklearn/cross_decomposition/_pls.py @@ -334,7 +334,7 @@ def transform(self, X, Y=None, copy=True): return x_scores - def inverse_transform(self, X=None, Y=None): + def inverse_transform(self, X, Y=None): """Transform data back to its original space. Parameters diff --git a/sklearn/cross_decomposition/tests/test_pls.py b/sklearn/cross_decomposition/tests/test_pls.py index 04c791fd4154a..46ece671c9dc6 100644 --- a/sklearn/cross_decomposition/tests/test_pls.py +++ b/sklearn/cross_decomposition/tests/test_pls.py @@ -58,6 +58,8 @@ def test_pls_canonical_basics(): # Check that inverse_transform works X_back = pls.inverse_transform(Xt) assert_array_almost_equal(X_back, X) + _, Y_back = pls.inverse_transform(Xt, Yt) + assert_array_almost_equal(Y_back, Y) def test_sanity_check_pls_regression(): From 35bfbc50541d14cb0473181a89a61a96ef3f3f24 Mon Sep 17 00:00:00 2001 From: Robin Thibaut Date: Mon, 27 Sep 2021 16:50:40 +0200 Subject: [PATCH 4/8] Adapted docstring for "Returns" to follow sklearn's style. --- sklearn/cross_decomposition/_pls.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sklearn/cross_decomposition/_pls.py b/sklearn/cross_decomposition/_pls.py index 663ddd6422f90..9fc83439ce597 100644 --- a/sklearn/cross_decomposition/_pls.py +++ b/sklearn/cross_decomposition/_pls.py @@ -413,8 +413,8 @@ def inverse_transform(self, X, Y=None): Returns ------- - `X_reconstructed` if `Y` is not given, `(X_reconstructed, Y_reconstructed)` otherwise. - + X_reconstructed, Y_reconstructed : array-like or tuple of array-like + Return `X_reconstructed` if `Y` is not given, `(X_reconstructed, Y_reconstructed)` otherwise. Notes ----- From 0cf8fb58612d7ff7549fdc23a30f8b1cfea03897 Mon Sep 17 00:00:00 2001 From: Robin Thibaut Date: Thu, 7 Oct 2021 10:23:52 +0200 Subject: [PATCH 5/8] Update sklearn/cross_decomposition/_pls.py Update docstring for more consistency Co-authored-by: Thomas J. Fan --- sklearn/cross_decomposition/_pls.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sklearn/cross_decomposition/_pls.py b/sklearn/cross_decomposition/_pls.py index 9fc83439ce597..d5090e67af2a5 100644 --- a/sklearn/cross_decomposition/_pls.py +++ b/sklearn/cross_decomposition/_pls.py @@ -413,8 +413,11 @@ def inverse_transform(self, X, Y=None): Returns ------- - X_reconstructed, Y_reconstructed : array-like or tuple of array-like - Return `X_reconstructed` if `Y` is not given, `(X_reconstructed, Y_reconstructed)` otherwise. + X_reconstructed : ndarray of shape (n_samples, n_features) + Return the reconstructed `X` data. + + Y_reconstructed : ndarray of shape (n_samples, n_targets) + Return the reconstructed `X` target. Only returned when `Y` is given. Notes ----- From 70a3ea4434ae2a927d5869f65a979f88783328e8 Mon Sep 17 00:00:00 2001 From: Robin Thibaut Date: Thu, 7 Oct 2021 10:50:10 +0200 Subject: [PATCH 6/8] Update v1.1.rst --- doc/whats_new/v1.1.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/whats_new/v1.1.rst b/doc/whats_new/v1.1.rst index 93300bf67d4a5..bcd5951e10e5f 100644 --- a/doc/whats_new/v1.1.rst +++ b/doc/whats_new/v1.1.rst @@ -45,6 +45,12 @@ Changelog `pos_label` to specify the positive class label. :pr:`21032` by :user:`Guillaume Lemaitre `. +:mod:`sklearn.cross_decomposition` +.................................. + +- |Enhancement| :func:`cross_decomposition._PLS.inverse_transform` now allows reconstruction of a `X` target when a `Y` parameter is given. + :pr:`19680` by :user:`Robin Thibaut `. + :mod:`sklearn.ensemble` ........................... From b3861134dabf9e694c2077cac6d03a5885cdbc51 Mon Sep 17 00:00:00 2001 From: Robin Thibaut Date: Sat, 9 Oct 2021 15:49:07 +0200 Subject: [PATCH 7/8] Fixed linting issues --- sklearn/cross_decomposition/_pls.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sklearn/cross_decomposition/_pls.py b/sklearn/cross_decomposition/_pls.py index 98b81aa425aaa..3d4012e6050ff 100644 --- a/sklearn/cross_decomposition/_pls.py +++ b/sklearn/cross_decomposition/_pls.py @@ -415,7 +415,7 @@ def inverse_transform(self, X, Y=None): ------- X_reconstructed : ndarray of shape (n_samples, n_features) Return the reconstructed `X` data. - + Y_reconstructed : ndarray of shape (n_samples, n_targets) Return the reconstructed `X` target. Only returned when `Y` is given. @@ -430,7 +430,7 @@ def inverse_transform(self, X, Y=None): # Denormalize X_reconstructed *= self._x_std X_reconstructed += self._x_mean - + if Y is not None: Y = check_array(Y, dtype=FLOAT_DTYPES) # From pls space to original space @@ -439,7 +439,7 @@ def inverse_transform(self, X, Y=None): Y_reconstructed *= self._y_std Y_reconstructed += self._y_mean return X_reconstructed, Y_reconstructed - + return X_reconstructed def predict(self, X, copy=True): From 606d459cb81a0d45bc5cdc6e2adc9f120adc6cc3 Mon Sep 17 00:00:00 2001 From: Robin Thibaut Date: Tue, 12 Oct 2021 16:43:47 +0200 Subject: [PATCH 8/8] Update doc/whats_new/v1.1.rst Co-authored-by: Thomas J. Fan --- doc/whats_new/v1.1.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/whats_new/v1.1.rst b/doc/whats_new/v1.1.rst index 34aebad6c36ea..0282c83917cd5 100644 --- a/doc/whats_new/v1.1.rst +++ b/doc/whats_new/v1.1.rst @@ -48,8 +48,9 @@ Changelog :mod:`sklearn.cross_decomposition` .................................. -- |Enhancement| :func:`cross_decomposition._PLS.inverse_transform` now allows reconstruction of a `X` target when a `Y` parameter is given. - :pr:`19680` by :user:`Robin Thibaut `. +- |Enhancement| :func:`cross_decomposition._PLS.inverse_transform` now allows + reconstruction of a `X` target when a `Y` parameter is given. :pr:`19680` by + :user:`Robin Thibaut `. :mod:`sklearn.ensemble` ...........................