Skip to content

Commit 01fcf8a

Browse files
authored
ENH add inverse_transform in *SparsePCA (#23905)
1 parent a81ba1d commit 01fcf8a

File tree

3 files changed

+64
-1
lines changed

3 files changed

+64
-1
lines changed

doc/whats_new/v1.2.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,11 @@ Changelog
125125
its memory footprint and runtime.
126126
:pr:`22268` by :user:`MohamedBsh <Bsh>`.
127127

128+
- |Enhancement| :class:`decomposition.SparsePCA` and
129+
:class:`decomposition.MiniBatchSparsePCA` now implements an `inverse_transform`
130+
function.
131+
:pr:`23905` by :user:`Guillaume Lemaitre <glemaitre>`.
132+
128133
:mod:`sklearn.decomposition`
129134
............................
130135

sklearn/decomposition/_sparse_pca.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
from ..utils import check_random_state
1010
from ..utils._param_validation import Hidden, Interval, StrOptions
11-
from ..utils.validation import check_is_fitted
11+
from ..utils.validation import check_array, check_is_fitted
1212
from ..linear_model import ridge_regression
1313
from ..base import BaseEstimator, TransformerMixin, _ClassNamePrefixFeaturesOutMixin
1414
from ._dict_learning import dict_learning, MiniBatchDictionaryLearning
@@ -115,6 +115,29 @@ def transform(self, X):
115115

116116
return U
117117

118+
def inverse_transform(self, X):
119+
"""Transform data from the latent space to the original space.
120+
121+
This inversion is an approximation due to the loss of information
122+
induced by the forward decomposition.
123+
124+
.. versionadded:: 1.2
125+
126+
Parameters
127+
----------
128+
X : ndarray of shape (n_samples, n_components)
129+
Data in the latent space.
130+
131+
Returns
132+
-------
133+
X_original : ndarray of shape (n_samples, n_features)
134+
Reconstructed data in the original space.
135+
"""
136+
check_is_fitted(self)
137+
X = check_array(X)
138+
139+
return (X @ self.components_) + self.mean_
140+
118141
@property
119142
def _n_features_out(self):
120143
"""Number of transformed output features."""

sklearn/decomposition/tests/test_sparse_pca.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,3 +313,38 @@ def test_spca_early_stopping(global_random_seed):
313313
max_iter=100, tol=1e-6, max_no_improvement=100, random_state=global_random_seed
314314
).fit(X)
315315
assert model_early_stopped.n_iter_ < model_not_early_stopped.n_iter_
316+
317+
318+
def test_sparse_pca_inverse_transform():
319+
"""Check that `inverse_transform` in `SparsePCA` and `PCA` are similar."""
320+
rng = np.random.RandomState(0)
321+
n_samples, n_features = 10, 5
322+
X = rng.randn(n_samples, n_features)
323+
324+
n_components = 2
325+
spca = SparsePCA(
326+
n_components=n_components, alpha=1e-12, ridge_alpha=1e-12, random_state=0
327+
)
328+
pca = PCA(n_components=n_components, random_state=0)
329+
X_trans_spca = spca.fit_transform(X)
330+
X_trans_pca = pca.fit_transform(X)
331+
assert_allclose(
332+
spca.inverse_transform(X_trans_spca), pca.inverse_transform(X_trans_pca)
333+
)
334+
335+
336+
@pytest.mark.parametrize("SPCA", [SparsePCA, MiniBatchSparsePCA])
337+
def test_transform_inverse_transform_round_trip(SPCA):
338+
"""Check the `transform` and `inverse_transform` round trip with no loss of
339+
information.
340+
"""
341+
rng = np.random.RandomState(0)
342+
n_samples, n_features = 10, 5
343+
X = rng.randn(n_samples, n_features)
344+
345+
n_components = n_features
346+
spca = SPCA(
347+
n_components=n_components, alpha=1e-12, ridge_alpha=1e-12, random_state=0
348+
)
349+
X_trans_spca = spca.fit_transform(X)
350+
assert_allclose(spca.inverse_transform(X_trans_spca), X)

0 commit comments

Comments
 (0)