From 9471da6ddcf069754a6d905113158ddd074a3ce2 Mon Sep 17 00:00:00 2001 From: "Thomas J. Fan" Date: Thu, 20 Jan 2022 13:45:50 -0500 Subject: [PATCH 1/3] ENH Adds get_feature_names to manifold module --- sklearn/manifold/_isomap.py | 5 +++-- sklearn/manifold/_locally_linear.py | 15 ++++++++++++-- sklearn/manifold/tests/test_isomap.py | 18 ++++++++++++++++- sklearn/manifold/tests/test_locally_linear.py | 20 ++++++++++++++++++- sklearn/tests/test_common.py | 1 - 5 files changed, 52 insertions(+), 7 deletions(-) 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 a8178a4219485..21ab5e5d362c3 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", "neighbors", "neural_network", ] From a43e5572ad0b0b41318240bcb7b58b07da46a262 Mon Sep 17 00:00:00 2001 From: "Thomas J. Fan" Date: Thu, 20 Jan 2022 14:01:35 -0500 Subject: [PATCH 2/3] DOC Adds whats new nubmer --- doc/whats_new/v1.1.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/whats_new/v1.1.rst b/doc/whats_new/v1.1.rst index 61e074e56a657..7176f53df299b 100644 --- a/doc/whats_new/v1.1.rst +++ b/doc/whats_new/v1.1.rst @@ -390,6 +390,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:`xxxxx` 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. From a516099fc9b15f7a524346baa80716331ed658ac Mon Sep 17 00:00:00 2001 From: "Thomas J. Fan" Date: Thu, 20 Jan 2022 14:03:02 -0500 Subject: [PATCH 3/3] DOC Adds whats new nubmer --- doc/whats_new/v1.1.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/whats_new/v1.1.rst b/doc/whats_new/v1.1.rst index 7176f53df299b..9ef1add1b367a 100644 --- a/doc/whats_new/v1.1.rst +++ b/doc/whats_new/v1.1.rst @@ -391,7 +391,7 @@ Changelog :pr:`21534` by :user:`Andrew Knyazev `. - |Enhancement| Adds `get_feature_names_out` to :class:`manifold.Isomap` - and :class:`manifold.LocallyLinearEmbedding`. :pr:`xxxxx` by `Thomas Fan`_. + 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