CoolFace
Apppublic

Aluode/PerceptionLabPortable

sourceHugging Faceupdated 9mo agoView on Hugging Face
0likes
_base.py268 linesDownload Raw Back to feature_selection
1"""Generic feature selection mixin"""
2
3# Authors: The scikit-learn developers
4# SPDX-License-Identifier: BSD-3-Clause
5
6import warnings
7from abc import ABCMeta, abstractmethod
8from operator import attrgetter
9
10import numpy as np
11from scipy.sparse import csc_matrix, issparse
12
13from ..base import TransformerMixin
14from ..utils import _safe_indexing, check_array, safe_sqr
15from ..utils._set_output import _get_output_config
16from ..utils._tags import get_tags
17from ..utils.validation import (
18    _check_feature_names_in,
19    _is_pandas_df,
20    check_is_fitted,
21    validate_data,
22)
23
24
25class SelectorMixin(TransformerMixin, metaclass=ABCMeta):
26    """
27    Transformer mixin that performs feature selection given a support mask
28
29    This mixin provides a feature selector implementation with `transform` and
30    `inverse_transform` functionality given an implementation of
31    `_get_support_mask`.
32
33    Examples
34    --------
35    >>> import numpy as np
36    >>> from sklearn.datasets import load_iris
37    >>> from sklearn.base import BaseEstimator
38    >>> from sklearn.feature_selection import SelectorMixin
39    >>> class FeatureSelector(SelectorMixin, BaseEstimator):
40    ...    def fit(self, X, y=None):
41    ...        self.n_features_in_ = X.shape[1]
42    ...        return self
43    ...    def _get_support_mask(self):
44    ...        mask = np.zeros(self.n_features_in_, dtype=bool)
45    ...        mask[:2] = True  # select the first two features
46    ...        return mask
47    >>> X, y = load_iris(return_X_y=True)
48    >>> FeatureSelector().fit_transform(X, y).shape
49    (150, 2)
50    """
51
52    def get_support(self, indices=False):
53        """
54        Get a mask, or integer index, of the features selected.
55
56        Parameters
57        ----------
58        indices : bool, default=False
59            If True, the return value will be an array of integers, rather
60            than a boolean mask.
61
62        Returns
63        -------
64        support : array
65            An index that selects the retained features from a feature vector.
66            If `indices` is False, this is a boolean array of shape
67            [# input features], in which an element is True iff its
68            corresponding feature is selected for retention. If `indices` is
69            True, this is an integer array of shape [# output features] whose
70            values are indices into the input feature vector.
71        """
72        mask = self._get_support_mask()
73        return mask if not indices else np.nonzero(mask)[0]
74
75    @abstractmethod
76    def _get_support_mask(self):
77        """
78        Get the boolean mask indicating which features are selected
79
80        Returns
81        -------
82        support : boolean array of shape [# input features]
83            An element is True iff its corresponding feature is selected for
84            retention.
85        """
86
87    def transform(self, X):
88        """Reduce X to the selected features.
89
90        Parameters
91        ----------
92        X : array of shape [n_samples, n_features]
93            The input samples.
94
95        Returns
96        -------
97        X_r : array of shape [n_samples, n_selected_features]
98            The input samples with only the selected features.
99        """
100        # Preserve X when X is a dataframe and the output is configured to
101        # be pandas.
102        output_config_dense = _get_output_config("transform", estimator=self)["dense"]
103        preserve_X = output_config_dense != "default" and _is_pandas_df(X)
104
105        # note: we use get_tags instead of __sklearn_tags__ because this is a
106        # public Mixin.
107        X = validate_data(
108            self,
109            X,
110            dtype=None,
111            accept_sparse="csr",
112            ensure_all_finite=not get_tags(self).input_tags.allow_nan,
113            skip_check_array=preserve_X,
114            reset=False,
115        )
116        return self._transform(X)
117
118    def _transform(self, X):
119        """Reduce X to the selected features."""
120        mask = self.get_support()
121        if not mask.any():
122            warnings.warn(
123                (
124                    "No features were selected: either the data is"
125                    " too noisy or the selection test too strict."
126                ),
127                UserWarning,
128            )
129            if hasattr(X, "iloc"):
130                return X.iloc[:, :0]
131            return np.empty(0, dtype=X.dtype).reshape((X.shape[0], 0))
132        return _safe_indexing(X, mask, axis=1)
133
134    def inverse_transform(self, X):
135        """Reverse the transformation operation.
136
137        Parameters
138        ----------
139        X : array of shape [n_samples, n_selected_features]
140            The input samples.
141
142        Returns
143        -------
144        X_original : array of shape [n_samples, n_original_features]
145            `X` with columns of zeros inserted where features would have
146            been removed by :meth:`transform`.
147        """
148        if issparse(X):
149            X = X.tocsc()
150            # insert additional entries in indptr:
151            # e.g. if transform changed indptr from [0 2 6 7] to [0 2 3]
152            # col_nonzeros here will be [2 0 1] so indptr becomes [0 2 2 3]
153            it = self.inverse_transform(np.diff(X.indptr).reshape(1, -1))
154            col_nonzeros = it.ravel()
155            indptr = np.concatenate([[0], np.cumsum(col_nonzeros)])
156            Xt = csc_matrix(
157                (X.data, X.indices, indptr),
158                shape=(X.shape[0], len(indptr) - 1),
159                dtype=X.dtype,
160            )
161            return Xt
162
163        support = self.get_support()
164        X = check_array(X, dtype=None)
165        if support.sum() != X.shape[1]:
166            raise ValueError("X has a different shape than during fitting.")
167
168        if X.ndim == 1:
169            X = X[None, :]
170        Xt = np.zeros((X.shape[0], support.size), dtype=X.dtype)
171        Xt[:, support] = X
172        return Xt
173
174    def get_feature_names_out(self, input_features=None):
175        """Mask feature names according to selected features.
176
177        Parameters
178        ----------
179        input_features : array-like of str or None, default=None
180            Input features.
181
182            - If `input_features` is `None`, then `feature_names_in_` is
183              used as feature names in. If `feature_names_in_` is not defined,
184              then the following input feature names are generated:
185              `["x0", "x1", ..., "x(n_features_in_ - 1)"]`.
186            - If `input_features` is an array-like, then `input_features` must
187              match `feature_names_in_` if `feature_names_in_` is defined.
188
189        Returns
190        -------
191        feature_names_out : ndarray of str objects
192            Transformed feature names.
193        """
194        check_is_fitted(self)
195        input_features = _check_feature_names_in(self, input_features)
196        return input_features[self.get_support()]
197
198
199def _get_feature_importances(estimator, getter, transform_func=None, norm_order=1):
200    """
201    Retrieve and aggregate (ndim > 1)  the feature importances
202    from an estimator. Also optionally applies transformation.
203
204    Parameters
205    ----------
206    estimator : estimator
207        A scikit-learn estimator from which we want to get the feature
208        importances.
209
210    getter : "auto", str or callable
211        An attribute or a callable to get the feature importance. If `"auto"`,
212        `estimator` is expected to expose `coef_` or `feature_importances`.
213
214    transform_func : {"norm", "square"}, default=None
215        The transform to apply to the feature importances. By default (`None`)
216        no transformation is applied.
217
218    norm_order : int, default=1
219        The norm order to apply when `transform_func="norm"`. Only applied
220        when `importances.ndim > 1`.
221
222    Returns
223    -------
224    importances : ndarray of shape (n_features,)
225        The features importances, optionally transformed.
226    """
227    if isinstance(getter, str):
228        if getter == "auto":
229            if hasattr(estimator, "coef_"):
230                getter = attrgetter("coef_")
231            elif hasattr(estimator, "feature_importances_"):
232                getter = attrgetter("feature_importances_")
233            else:
234                raise ValueError(
235                    "when `importance_getter=='auto'`, the underlying "
236                    f"estimator {estimator.__class__.__name__} should have "
237                    "`coef_` or `feature_importances_` attribute. Either "
238                    "pass a fitted estimator to feature selector or call fit "
239                    "before calling transform."
240                )
241        else:
242            getter = attrgetter(getter)
243    elif not callable(getter):
244        raise ValueError("`importance_getter` has to be a string or `callable`")
245
246    importances = getter(estimator)
247
248    if transform_func is None:
249        return importances
250    elif transform_func == "norm":
251        if importances.ndim == 1:
252            importances = np.abs(importances)
253        else:
254            importances = np.linalg.norm(importances, axis=0, ord=norm_order)
255    elif transform_func == "square":
256        if importances.ndim == 1:
257            importances = safe_sqr(importances)
258        else:
259            importances = safe_sqr(importances).sum(axis=0)
260    else:
261        raise ValueError(
262            "Valid values for `transform_func` are "
263            "None, 'norm' and 'square'. Those two "
264            "transformation are only supported now"
265        )
266
267    return importances
268 
Aluode/PerceptionLabPortable · CoolFace