diff --git a/doc/whats_new/v1.1.rst b/doc/whats_new/v1.1.rst index 38aea32e776aa..3ae1c260570b2 100644 --- a/doc/whats_new/v1.1.rst +++ b/doc/whats_new/v1.1.rst @@ -413,6 +413,9 @@ Changelog preserve this dtype. :pr:`21534` by :user:`Andrew Knyazev `. +- |Enhancement| Adds `get_feature_names_out` to :class:`manifold.Isomap` + and :class:`manifold.LocallyLinearEmbedding`. :pr:`22254` by `Thomas Fan`_. + - |Fix| :func:`manifold.spectral_embedding` now uses Gaussian instead of the previous uniform on [0, 1] random initial approximations to eigenvectors in eigen_solvers `lobpcg` and `amg` to improve their numerical stability. diff --git a/sklearn/manifold/_isomap.py b/sklearn/manifold/_isomap.py index 312c1166522ea..98f0e719b7cdd 100644 --- a/sklearn/manifold/_isomap.py +++ b/sklearn/manifold/_isomap.py @@ -10,7 +10,7 @@ from scipy.sparse.csgraph import shortest_path from scipy.sparse.csgraph import connected_components -from ..base import BaseEstimator, TransformerMixin +from ..base import BaseEstimator, TransformerMixin, _ClassNamePrefixFeaturesOutMixin from ..neighbors import NearestNeighbors, kneighbors_graph from ..utils.validation import check_is_fitted from ..decomposition import KernelPCA @@ -19,7 +19,7 @@ from ..externals._packaging.version import parse as parse_version -class Isomap(TransformerMixin, BaseEstimator): +class Isomap(_ClassNamePrefixFeaturesOutMixin, TransformerMixin, BaseEstimator): """Isomap Embedding. Non-linear dimensionality reduction through Isometric Mapping @@ -257,6 +257,7 @@ def _fit_transform(self, X): G *= -0.5 self.embedding_ = self.kernel_pca_.fit_transform(G) + self._n_features_out = self.embedding_.shape[1] def reconstruction_error(self): """Compute the reconstruction error for the embedding. diff --git a/sklearn/manifold/_locally_linear.py b/sklearn/manifold/_locally_linear.py index 32fc3623f8cfb..a9c6ec350b912 100644 --- a/sklearn/manifold/_locally_linear.py +++ b/sklearn/manifold/_locally_linear.py @@ -9,7 +9,12 @@ from scipy.sparse import eye, csr_matrix from scipy.sparse.linalg import eigsh -from ..base import BaseEstimator, TransformerMixin, _UnstableArchMixin +from ..base import ( + BaseEstimator, + TransformerMixin, + _UnstableArchMixin, + _ClassNamePrefixFeaturesOutMixin, +) from ..utils import check_random_state, check_array from ..utils._arpack import _init_arpack_v0 from ..utils.extmath import stable_cumsum @@ -542,7 +547,12 @@ def locally_linear_embedding( ) -class LocallyLinearEmbedding(TransformerMixin, _UnstableArchMixin, BaseEstimator): +class LocallyLinearEmbedding( + _ClassNamePrefixFeaturesOutMixin, + TransformerMixin, + _UnstableArchMixin, + BaseEstimator, +): """Locally Linear Embedding. Read more in the :ref:`User Guide `. @@ -728,6 +738,7 @@ def _fit_transform(self, X): reg=self.reg, n_jobs=self.n_jobs, ) + self._n_features_out = self.embedding_.shape[1] def fit(self, X, y=None): """Compute the embedding vectors for data X. diff --git a/sklearn/manifold/tests/test_isomap.py b/sklearn/manifold/tests/test_isomap.py index fa2f2188e3d6e..68ea433a58b93 100644 --- a/sklearn/manifold/tests/test_isomap.py +++ b/sklearn/manifold/tests/test_isomap.py @@ -1,6 +1,10 @@ from itertools import product import numpy as np -from numpy.testing import assert_almost_equal, assert_array_almost_equal +from numpy.testing import ( + assert_almost_equal, + assert_array_almost_equal, + assert_array_equal, +) import pytest from sklearn import datasets @@ -8,6 +12,7 @@ from sklearn import neighbors from sklearn import pipeline from sklearn import preprocessing +from sklearn.datasets import make_blobs from sklearn.metrics.pairwise import pairwise_distances from scipy.sparse import rand as sparse_rand @@ -220,3 +225,14 @@ def test_multiple_connected_components_metric_precomputed(): X_graph = neighbors.kneighbors_graph(X, n_neighbors=2, mode="distance") with pytest.raises(RuntimeError, match="number of connected components"): manifold.Isomap(n_neighbors=1, metric="precomputed").fit(X_graph) + + +def test_get_feature_names_out(): + """Check get_feature_names_out for Isomap.""" + X, y = make_blobs(random_state=0, n_features=4) + n_components = 2 + + iso = manifold.Isomap(n_components=n_components) + iso.fit_transform(X) + names = iso.get_feature_names_out() + assert_array_equal([f"isomap{i}" for i in range(n_components)], names) diff --git a/sklearn/manifold/tests/test_locally_linear.py b/sklearn/manifold/tests/test_locally_linear.py index 520ba00df2e09..ff93a15c0704d 100644 --- a/sklearn/manifold/tests/test_locally_linear.py +++ b/sklearn/manifold/tests/test_locally_linear.py @@ -1,11 +1,16 @@ from itertools import product import numpy as np -from numpy.testing import assert_almost_equal, assert_array_almost_equal +from numpy.testing import ( + assert_almost_equal, + assert_array_almost_equal, + assert_array_equal, +) from scipy import linalg import pytest from sklearn import neighbors, manifold +from sklearn.datasets import make_blobs from sklearn.manifold._locally_linear import barycenter_kneighbors_graph from sklearn.utils._testing import ignore_warnings @@ -159,3 +164,16 @@ def test_integer_input(): for method in ["standard", "hessian", "modified", "ltsa"]: clf = manifold.LocallyLinearEmbedding(method=method, n_neighbors=10) clf.fit(X) # this previously raised a TypeError + + +def test_get_feature_names_out(): + """Check get_feature_names_out for LocallyLinearEmbedding.""" + X, y = make_blobs(random_state=0, n_features=4) + n_components = 2 + + iso = manifold.LocallyLinearEmbedding(n_components=n_components) + iso.fit(X) + names = iso.get_feature_names_out() + assert_array_equal( + [f"locallylinearembedding{i}" for i in range(n_components)], names + ) diff --git a/sklearn/tests/test_common.py b/sklearn/tests/test_common.py index de00fd713c5c7..de8818b53a477 100644 --- a/sklearn/tests/test_common.py +++ b/sklearn/tests/test_common.py @@ -385,7 +385,6 @@ def test_pandas_column_name_consistency(estimator): "isotonic", "kernel_approximation", "preprocessing", - "manifold", "neural_network", ]