CoolFace
Apppublic

Aluode/PerceptionLabPortable

sourceHugging Faceupdated 9mo agoView on Hugging Face
0likes
base.py1370 linesDownload Raw Back to sklearn
1"""Base classes for all estimators and various utility functions."""
2
3# Authors: The scikit-learn developers
4# SPDX-License-Identifier: BSD-3-Clause
5
6import copy
7import functools
8import inspect
9import platform
10import re
11import warnings
12from collections import defaultdict
13
14import numpy as np
15
16from . import __version__
17from ._config import config_context, get_config
18from .exceptions import InconsistentVersionWarning
19from .utils._metadata_requests import _MetadataRequester, _routing_enabled
20from .utils._missing import is_scalar_nan
21from .utils._param_validation import validate_parameter_constraints
22from .utils._repr_html.base import ReprHTMLMixin, _HTMLDocumentationLinkMixin
23from .utils._repr_html.estimator import estimator_html_repr
24from .utils._repr_html.params import ParamsDict
25from .utils._set_output import _SetOutputMixin
26from .utils._tags import (
27    ClassifierTags,
28    RegressorTags,
29    Tags,
30    TargetTags,
31    TransformerTags,
32    get_tags,
33)
34from .utils.fixes import _IS_32BIT
35from .utils.validation import (
36    _check_feature_names_in,
37    _generate_get_feature_names_out,
38    _is_fitted,
39    check_array,
40    check_is_fitted,
41)
42
43
44def clone(estimator, *, safe=True):
45    """Construct a new unfitted estimator with the same parameters.
46
47    Clone does a deep copy of the model in an estimator
48    without actually copying attached data. It returns a new estimator
49    with the same parameters that has not been fitted on any data.
50
51    .. versionchanged:: 1.3
52        Delegates to `estimator.__sklearn_clone__` if the method exists.
53
54    Parameters
55    ----------
56    estimator : {list, tuple, set} of estimator instance or a single \
57            estimator instance
58        The estimator or group of estimators to be cloned.
59    safe : bool, default=True
60        If safe is False, clone will fall back to a deep copy on objects
61        that are not estimators. Ignored if `estimator.__sklearn_clone__`
62        exists.
63
64    Returns
65    -------
66    estimator : object
67        The deep copy of the input, an estimator if input is an estimator.
68
69    Notes
70    -----
71    If the estimator's `random_state` parameter is an integer (or if the
72    estimator doesn't have a `random_state` parameter), an *exact clone* is
73    returned: the clone and the original estimator will give the exact same
74    results. Otherwise, *statistical clone* is returned: the clone might
75    return different results from the original estimator. More details can be
76    found in :ref:`randomness`.
77
78    Examples
79    --------
80    >>> from sklearn.base import clone
81    >>> from sklearn.linear_model import LogisticRegression
82    >>> X = [[-1, 0], [0, 1], [0, -1], [1, 0]]
83    >>> y = [0, 0, 1, 1]
84    >>> classifier = LogisticRegression().fit(X, y)
85    >>> cloned_classifier = clone(classifier)
86    >>> hasattr(classifier, "classes_")
87    True
88    >>> hasattr(cloned_classifier, "classes_")
89    False
90    >>> classifier is cloned_classifier
91    False
92    """
93    if hasattr(estimator, "__sklearn_clone__") and not inspect.isclass(estimator):
94        return estimator.__sklearn_clone__()
95    return _clone_parametrized(estimator, safe=safe)
96
97
98def _clone_parametrized(estimator, *, safe=True):
99    """Default implementation of clone. See :func:`sklearn.base.clone` for details."""
100
101    estimator_type = type(estimator)
102    if estimator_type is dict:
103        return {k: clone(v, safe=safe) for k, v in estimator.items()}
104    elif estimator_type in (list, tuple, set, frozenset):
105        return estimator_type([clone(e, safe=safe) for e in estimator])
106    elif not hasattr(estimator, "get_params") or isinstance(estimator, type):
107        if not safe:
108            return copy.deepcopy(estimator)
109        else:
110            if isinstance(estimator, type):
111                raise TypeError(
112                    "Cannot clone object. "
113                    "You should provide an instance of "
114                    "scikit-learn estimator instead of a class."
115                )
116            else:
117                raise TypeError(
118                    "Cannot clone object '%s' (type %s): "
119                    "it does not seem to be a scikit-learn "
120                    "estimator as it does not implement a "
121                    "'get_params' method." % (repr(estimator), type(estimator))
122                )
123
124    klass = estimator.__class__
125    new_object_params = estimator.get_params(deep=False)
126    for name, param in new_object_params.items():
127        new_object_params[name] = clone(param, safe=False)
128
129    new_object = klass(**new_object_params)
130    try:
131        new_object._metadata_request = copy.deepcopy(estimator._metadata_request)
132    except AttributeError:
133        pass
134
135    params_set = new_object.get_params(deep=False)
136
137    # quick sanity check of the parameters of the clone
138    for name in new_object_params:
139        param1 = new_object_params[name]
140        param2 = params_set[name]
141        if param1 is not param2:
142            raise RuntimeError(
143                "Cannot clone object %s, as the constructor "
144                "either does not set or modifies parameter %s" % (estimator, name)
145            )
146
147    # _sklearn_output_config is used by `set_output` to configure the output
148    # container of an estimator.
149    if hasattr(estimator, "_sklearn_output_config"):
150        new_object._sklearn_output_config = copy.deepcopy(
151            estimator._sklearn_output_config
152        )
153    return new_object
154
155
156class BaseEstimator(ReprHTMLMixin, _HTMLDocumentationLinkMixin, _MetadataRequester):
157    """Base class for all estimators in scikit-learn.
158
159    Inheriting from this class provides default implementations of:
160
161    - setting and getting parameters used by `GridSearchCV` and friends;
162    - textual and HTML representation displayed in terminals and IDEs;
163    - estimator serialization;
164    - parameters validation;
165    - data validation;
166    - feature names validation.
167
168    Read more in the :ref:`User Guide <rolling_your_own_estimator>`.
169
170
171    Notes
172    -----
173    All estimators should specify all the parameters that can be set
174    at the class level in their ``__init__`` as explicit keyword
175    arguments (no ``*args`` or ``**kwargs``).
176
177    Examples
178    --------
179    >>> import numpy as np
180    >>> from sklearn.base import BaseEstimator
181    >>> class MyEstimator(BaseEstimator):
182    ...     def __init__(self, *, param=1):
183    ...         self.param = param
184    ...     def fit(self, X, y=None):
185    ...         self.is_fitted_ = True
186    ...         return self
187    ...     def predict(self, X):
188    ...         return np.full(shape=X.shape[0], fill_value=self.param)
189    >>> estimator = MyEstimator(param=2)
190    >>> estimator.get_params()
191    {'param': 2}
192    >>> X = np.array([[1, 2], [2, 3], [3, 4]])
193    >>> y = np.array([1, 0, 1])
194    >>> estimator.fit(X, y).predict(X)
195    array([2, 2, 2])
196    >>> estimator.set_params(param=3).fit(X, y).predict(X)
197    array([3, 3, 3])
198    """
199
200    _html_repr = estimator_html_repr
201
202    @classmethod
203    def _get_param_names(cls):
204        """Get parameter names for the estimator"""
205        # fetch the constructor or the original constructor before
206        # deprecation wrapping if any
207        init = getattr(cls.__init__, "deprecated_original", cls.__init__)
208        if init is object.__init__:
209            # No explicit constructor to introspect
210            return []
211
212        # introspect the constructor arguments to find the model parameters
213        # to represent
214        init_signature = inspect.signature(init)
215        # Consider the constructor parameters excluding 'self'
216        parameters = [
217            p
218            for p in init_signature.parameters.values()
219            if p.name != "self" and p.kind != p.VAR_KEYWORD
220        ]
221        for p in parameters:
222            if p.kind == p.VAR_POSITIONAL:
223                raise RuntimeError(
224                    "scikit-learn estimators should always "
225                    "specify their parameters in the signature"
226                    " of their __init__ (no varargs)."
227                    " %s with constructor %s doesn't "
228                    " follow this convention." % (cls, init_signature)
229                )
230        # Extract and sort argument names excluding 'self'
231        return sorted([p.name for p in parameters])
232
233    def get_params(self, deep=True):
234        """
235        Get parameters for this estimator.
236
237        Parameters
238        ----------
239        deep : bool, default=True
240            If True, will return the parameters for this estimator and
241            contained subobjects that are estimators.
242
243        Returns
244        -------
245        params : dict
246            Parameter names mapped to their values.
247        """
248        out = dict()
249        for key in self._get_param_names():
250            value = getattr(self, key)
251            if deep and hasattr(value, "get_params") and not isinstance(value, type):
252                deep_items = value.get_params().items()
253                out.update((key + "__" + k, val) for k, val in deep_items)
254            out[key] = value
255        return out
256
257    def _get_params_html(self, deep=True):
258        """
259        Get parameters for this estimator with a specific HTML representation.
260
261        Parameters
262        ----------
263        deep : bool, default=True
264            If True, will return the parameters for this estimator and
265            contained subobjects that are estimators.
266
267        Returns
268        -------
269        params : ParamsDict
270            Parameter names mapped to their values. We return a `ParamsDict`
271            dictionary, which renders a specific HTML representation in table
272            form.
273        """
274        out = self.get_params(deep=deep)
275
276        init_func = getattr(self.__init__, "deprecated_original", self.__init__)
277        init_default_params = inspect.signature(init_func).parameters
278        init_default_params = {
279            name: param.default for name, param in init_default_params.items()
280        }
281
282        def is_non_default(param_name, param_value):
283            """Finds the parameters that have been set by the user."""
284            if param_name not in init_default_params:
285                # happens if k is part of a **kwargs
286                return True
287            if init_default_params[param_name] == inspect._empty:
288                # k has no default value
289                return True
290            # avoid calling repr on nested estimators
291            if isinstance(param_value, BaseEstimator) and type(param_value) is not type(
292                init_default_params[param_name]
293            ):
294                return True
295            if not np.array_equal(
296                param_value, init_default_params[param_name]
297            ) and not (
298                is_scalar_nan(init_default_params[param_name])
299                and is_scalar_nan(param_value)
300            ):
301                return True
302
303            return False
304
305        # reorder the parameters from `self.get_params` using the `__init__`
306        # signature
307        remaining_params = [name for name in out if name not in init_default_params]
308        ordered_out = {name: out[name] for name in init_default_params if name in out}
309        ordered_out.update({name: out[name] for name in remaining_params})
310
311        non_default_ls = tuple(
312            [name for name, value in ordered_out.items() if is_non_default(name, value)]
313        )
314
315        return ParamsDict(ordered_out, non_default=non_default_ls)
316
317    def set_params(self, **params):
318        """Set the parameters of this estimator.
319
320        The method works on simple estimators as well as on nested objects
321        (such as :class:`~sklearn.pipeline.Pipeline`). The latter have
322        parameters of the form ``<component>__<parameter>`` so that it's
323        possible to update each component of a nested object.
324
325        Parameters
326        ----------
327        **params : dict
328            Estimator parameters.
329
330        Returns
331        -------
332        self : estimator instance
333            Estimator instance.
334        """
335        if not params:
336            # Simple optimization to gain speed (inspect is slow)
337            return self
338        valid_params = self.get_params(deep=True)
339
340        nested_params = defaultdict(dict)  # grouped by prefix
341        for key, value in params.items():
342            key, delim, sub_key = key.partition("__")
343            if key not in valid_params:
344                local_valid_params = self._get_param_names()
345                raise ValueError(
346                    f"Invalid parameter {key!r} for estimator {self}. "
347                    f"Valid parameters are: {local_valid_params!r}."
348                )
349
350            if delim:
351                nested_params[key][sub_key] = value
352            else:
353                setattr(self, key, value)
354                valid_params[key] = value
355
356        for key, sub_params in nested_params.items():
357            valid_params[key].set_params(**sub_params)
358
359        return self
360
361    def __sklearn_clone__(self):
362        return _clone_parametrized(self)
363
364    def __repr__(self, N_CHAR_MAX=700):
365        # N_CHAR_MAX is the (approximate) maximum number of non-blank
366        # characters to render. We pass it as an optional parameter to ease
367        # the tests.
368
369        from .utils._pprint import _EstimatorPrettyPrinter
370
371        N_MAX_ELEMENTS_TO_SHOW = 30  # number of elements to show in sequences
372
373        # use ellipsis for sequences with a lot of elements
374        pp = _EstimatorPrettyPrinter(
375            compact=True,
376            indent=1,
377            indent_at_name=True,
378            n_max_elements_to_show=N_MAX_ELEMENTS_TO_SHOW,
379        )
380
381        repr_ = pp.pformat(self)
382
383        # Use bruteforce ellipsis when there are a lot of non-blank characters
384        n_nonblank = len("".join(repr_.split()))
385        if n_nonblank > N_CHAR_MAX:
386            lim = N_CHAR_MAX // 2  # apprx number of chars to keep on both ends
387            regex = r"^(\s*\S){%d}" % lim
388            # The regex '^(\s*\S){%d}' % n
389            # matches from the start of the string until the nth non-blank
390            # character:
391            # - ^ matches the start of string
392            # - (pattern){n} matches n repetitions of pattern
393            # - \s*\S matches a non-blank char following zero or more blanks
394            left_lim = re.match(regex, repr_).end()
395            right_lim = re.match(regex, repr_[::-1]).end()
396
397            if "\n" in repr_[left_lim:-right_lim]:
398                # The left side and right side aren't on the same line.
399                # To avoid weird cuts, e.g.:
400                # categoric...ore',
401                # we need to start the right side with an appropriate newline
402                # character so that it renders properly as:
403                # categoric...
404                # handle_unknown='ignore',
405                # so we add [^\n]*\n which matches until the next \n
406                regex += r"[^\n]*\n"
407                right_lim = re.match(regex, repr_[::-1]).end()
408
409            ellipsis = "..."
410            if left_lim + len(ellipsis) < len(repr_) - right_lim:
411                # Only add ellipsis if it results in a shorter repr
412                repr_ = repr_[:left_lim] + "..." + repr_[-right_lim:]
413
414        return repr_
415
416    def __getstate__(self):
417        if getattr(self, "__slots__", None):
418            raise TypeError(
419                "You cannot use `__slots__` in objects inheriting from "
420                "`sklearn.base.BaseEstimator`."
421            )
422
423        try:
424            state = super().__getstate__()
425            if state is None:
426                # For Python 3.11+, empty instance (no `__slots__`,
427                # and `__dict__`) will return a state equal to `None`.
428                state = self.__dict__.copy()
429        except AttributeError:
430            # Python < 3.11
431            state = self.__dict__.copy()
432
433        if type(self).__module__.startswith("sklearn."):
434            return dict(state.items(), _sklearn_version=__version__)
435        else:
436            return state
437
438    def __setstate__(self, state):
439        if type(self).__module__.startswith("sklearn."):
440            pickle_version = state.pop("_sklearn_version", "pre-0.18")
441            if pickle_version != __version__:
442                warnings.warn(
443                    InconsistentVersionWarning(
444                        estimator_name=self.__class__.__name__,
445                        current_sklearn_version=__version__,
446                        original_sklearn_version=pickle_version,
447                    ),
448                )
449        try:
450            super().__setstate__(state)
451        except AttributeError:
452            self.__dict__.update(state)
453
454    def __sklearn_tags__(self):
455        return Tags(
456            estimator_type=None,
457            target_tags=TargetTags(required=False),
458            transformer_tags=None,
459            regressor_tags=None,
460            classifier_tags=None,
461        )
462
463    def _validate_params(self):
464        """Validate types and values of constructor parameters
465
466        The expected type and values must be defined in the `_parameter_constraints`
467        class attribute, which is a dictionary `param_name: list of constraints`. See
468        the docstring of `validate_parameter_constraints` for a description of the
469        accepted constraints.
470        """
471        validate_parameter_constraints(
472            self._parameter_constraints,
473            self.get_params(deep=False),
474            caller_name=self.__class__.__name__,
475        )
476
477
478class ClassifierMixin:
479    """Mixin class for all classifiers in scikit-learn.
480
481    This mixin defines the following functionality:
482
483    - set estimator type to `"classifier"` through the `estimator_type` tag;
484    - `score` method that default to :func:`~sklearn.metrics.accuracy_score`.
485    - enforce that `fit` requires `y` to be passed through the `requires_y` tag,
486      which is done by setting the classifier type tag.
487
488    Read more in the :ref:`User Guide <rolling_your_own_estimator>`.
489
490    Examples
491    --------
492    >>> import numpy as np
493    >>> from sklearn.base import BaseEstimator, ClassifierMixin
494    >>> # Mixin classes should always be on the left-hand side for a correct MRO
495    >>> class MyEstimator(ClassifierMixin, BaseEstimator):
496    ...     def __init__(self, *, param=1):
497    ...         self.param = param
498    ...     def fit(self, X, y=None):
499    ...         self.is_fitted_ = True
500    ...         return self
501    ...     def predict(self, X):
502    ...         return np.full(shape=X.shape[0], fill_value=self.param)
503    >>> estimator = MyEstimator(param=1)
504    >>> X = np.array([[1, 2], [2, 3], [3, 4]])
505    >>> y = np.array([1, 0, 1])
506    >>> estimator.fit(X, y).predict(X)
507    array([1, 1, 1])
508    >>> estimator.score(X, y)
509    0.66...
510    """
511
512    # TODO(1.8): Remove this attribute
513    _estimator_type = "classifier"
514
515    def __sklearn_tags__(self):
516        tags = super().__sklearn_tags__()
517        tags.estimator_type = "classifier"
518        tags.classifier_tags = ClassifierTags()
519        tags.target_tags.required = True
520        return tags
521
522    def score(self, X, y, sample_weight=None):
523        """
524        Return :ref:`accuracy <accuracy_score>` on provided data and labels.
525
526        In multi-label classification, this is the subset accuracy
527        which is a harsh metric since you require for each sample that
528        each label set be correctly predicted.
529
530        Parameters
531        ----------
532        X : array-like of shape (n_samples, n_features)
533            Test samples.
534
535        y : array-like of shape (n_samples,) or (n_samples, n_outputs)
536            True labels for `X`.
537
538        sample_weight : array-like of shape (n_samples,), default=None
539            Sample weights.
540
541        Returns
542        -------
543        score : float
544            Mean accuracy of ``self.predict(X)`` w.r.t. `y`.
545        """
546        from .metrics import accuracy_score
547
548        return accuracy_score(y, self.predict(X), sample_weight=sample_weight)
549
550
551class RegressorMixin:
552    """Mixin class for all regression estimators in scikit-learn.
553
554    This mixin defines the following functionality:
555
556    - set estimator type to `"regressor"` through the `estimator_type` tag;
557    - `score` method that default to :func:`~sklearn.metrics.r2_score`.
558    - enforce that `fit` requires `y` to be passed through the `requires_y` tag,
559      which is done by setting the regressor type tag.
560
561    Read more in the :ref:`User Guide <rolling_your_own_estimator>`.
562
563    Examples
564    --------
565    >>> import numpy as np
566    >>> from sklearn.base import BaseEstimator, RegressorMixin
567    >>> # Mixin classes should always be on the left-hand side for a correct MRO
568    >>> class MyEstimator(RegressorMixin, BaseEstimator):
569    ...     def __init__(self, *, param=1):
570    ...         self.param = param
571    ...     def fit(self, X, y=None):
572    ...         self.is_fitted_ = True
573    ...         return self
574    ...     def predict(self, X):
575    ...         return np.full(shape=X.shape[0], fill_value=self.param)
576    >>> estimator = MyEstimator(param=0)
577    >>> X = np.array([[1, 2], [2, 3], [3, 4]])
578    >>> y = np.array([-1, 0, 1])
579    >>> estimator.fit(X, y).predict(X)
580    array([0, 0, 0])
581    >>> estimator.score(X, y)
582    0.0
583    """
584
585    # TODO(1.8): Remove this attribute
586    _estimator_type = "regressor"
587
588    def __sklearn_tags__(self):
589        tags = super().__sklearn_tags__()
590        tags.estimator_type = "regressor"
591        tags.regressor_tags = RegressorTags()
592        tags.target_tags.required = True
593        return tags
594
595    def score(self, X, y, sample_weight=None):
596        """Return :ref:`coefficient of determination <r2_score>` on test data.
597
598        The coefficient of determination, :math:`R^2`, is defined as
599        :math:`(1 - \\frac{u}{v})`, where :math:`u` is the residual
600        sum of squares ``((y_true - y_pred)** 2).sum()`` and :math:`v`
601        is the total sum of squares ``((y_true - y_true.mean()) ** 2).sum()``.
602        The best possible score is 1.0 and it can be negative (because the
603        model can be arbitrarily worse). A constant model that always predicts
604        the expected value of `y`, disregarding the input features, would get
605        a :math:`R^2` score of 0.0.
606
607        Parameters
608        ----------
609        X : array-like of shape (n_samples, n_features)
610            Test samples. For some estimators this may be a precomputed
611            kernel matrix or a list of generic objects instead with shape
612            ``(n_samples, n_samples_fitted)``, where ``n_samples_fitted``
613            is the number of samples used in the fitting for the estimator.
614
615        y : array-like of shape (n_samples,) or (n_samples, n_outputs)
616            True values for `X`.
617
618        sample_weight : array-like of shape (n_samples,), default=None
619            Sample weights.
620
621        Returns
622        -------
623        score : float
624            :math:`R^2` of ``self.predict(X)`` w.r.t. `y`.
625
626        Notes
627        -----
628        The :math:`R^2` score used when calling ``score`` on a regressor uses
629        ``multioutput='uniform_average'`` from version 0.23 to keep consistent
630        with default value of :func:`~sklearn.metrics.r2_score`.
631        This influences the ``score`` method of all the multioutput
632        regressors (except for
633        :class:`~sklearn.multioutput.MultiOutputRegressor`).
634        """
635
636        from .metrics import r2_score
637
638        y_pred = self.predict(X)
639        return r2_score(y, y_pred, sample_weight=sample_weight)
640
641
642class ClusterMixin:
643    """Mixin class for all cluster estimators in scikit-learn.
644
645    - set estimator type to `"clusterer"` through the `estimator_type` tag;
646    - `fit_predict` method returning the cluster labels associated to each sample.
647
648    Examples
649    --------
650    >>> import numpy as np
651    >>> from sklearn.base import BaseEstimator, ClusterMixin
652    >>> class MyClusterer(ClusterMixin, BaseEstimator):
653    ...     def fit(self, X, y=None):
654    ...         self.labels_ = np.ones(shape=(len(X),), dtype=np.int64)
655    ...         return self
656    >>> X = [[1, 2], [2, 3], [3, 4]]
657    >>> MyClusterer().fit_predict(X)
658    array([1, 1, 1])
659    """
660
661    # TODO(1.8): Remove this attribute
662    _estimator_type = "clusterer"
663
664    def __sklearn_tags__(self):
665        tags = super().__sklearn_tags__()
666        tags.estimator_type = "clusterer"
667        if tags.transformer_tags is not None:
668            tags.transformer_tags.preserves_dtype = []
669        return tags
670
671    def fit_predict(self, X, y=None, **kwargs):
672        """
673        Perform clustering on `X` and returns cluster labels.
674
675        Parameters
676        ----------
677        X : array-like of shape (n_samples, n_features)
678            Input data.
679
680        y : Ignored
681            Not used, present for API consistency by convention.
682
683        **kwargs : dict
684            Arguments to be passed to ``fit``.
685
686            .. versionadded:: 1.4
687
688        Returns
689        -------
690        labels : ndarray of shape (n_samples,), dtype=np.int64
691            Cluster labels.
692        """
693        # non-optimized default implementation; override when a better
694        # method is possible for a given clustering algorithm
695        self.fit(X, **kwargs)
696        return self.labels_
697
698
699class BiclusterMixin:
700    """Mixin class for all bicluster estimators in scikit-learn.
701
702    This mixin defines the following functionality:
703
704    - `biclusters_` property that returns the row and column indicators;
705    - `get_indices` method that returns the row and column indices of a bicluster;
706    - `get_shape` method that returns the shape of a bicluster;
707    - `get_submatrix` method that returns the submatrix corresponding to a bicluster.
708
709    Examples
710    --------
711    >>> import numpy as np
712    >>> from sklearn.base import BaseEstimator, BiclusterMixin
713    >>> class DummyBiClustering(BiclusterMixin, BaseEstimator):
714    ...     def fit(self, X, y=None):
715    ...         self.rows_ = np.ones(shape=(1, X.shape[0]), dtype=bool)
716    ...         self.columns_ = np.ones(shape=(1, X.shape[1]), dtype=bool)
717    ...         return self
718    >>> X = np.array([[1, 1], [2, 1], [1, 0],
719    ...               [4, 7], [3, 5], [3, 6]])
720    >>> bicluster = DummyBiClustering().fit(X)
721    >>> hasattr(bicluster, "biclusters_")
722    True
723    >>> bicluster.get_indices(0)
724    (array([0, 1, 2, 3, 4, 5]), array([0, 1]))
725    """
726
727    @property
728    def biclusters_(self):
729        """Convenient way to get row and column indicators together.
730
731        Returns the ``rows_`` and ``columns_`` members.
732        """
733        return self.rows_, self.columns_
734
735    def get_indices(self, i):
736        """Row and column indices of the `i`'th bicluster.
737
738        Only works if ``rows_`` and ``columns_`` attributes exist.
739
740        Parameters
741        ----------
742        i : int
743            The index of the cluster.
744
745        Returns
746        -------
747        row_ind : ndarray, dtype=np.intp
748            Indices of rows in the dataset that belong to the bicluster.
749        col_ind : ndarray, dtype=np.intp
750            Indices of columns in the dataset that belong to the bicluster.
751        """
752        rows = self.rows_[i]
753        columns = self.columns_[i]
754        return np.nonzero(rows)[0], np.nonzero(columns)[0]
755
756    def get_shape(self, i):
757        """Shape of the `i`'th bicluster.
758
759        Parameters
760        ----------
761        i : int
762            The index of the cluster.
763
764        Returns
765        -------
766        n_rows : int
767            Number of rows in the bicluster.
768
769        n_cols : int
770            Number of columns in the bicluster.
771        """
772        indices = self.get_indices(i)
773        return tuple(len(i) for i in indices)
774
775    def get_submatrix(self, i, data):
776        """Return the submatrix corresponding to bicluster `i`.
777
778        Parameters
779        ----------
780        i : int
781            The index of the cluster.
782        data : array-like of shape (n_samples, n_features)
783            The data.
784
785        Returns
786        -------
787        submatrix : ndarray of shape (n_rows, n_cols)
788            The submatrix corresponding to bicluster `i`.
789
790        Notes
791        -----
792        Works with sparse matrices. Only works if ``rows_`` and
793        ``columns_`` attributes exist.
794        """
795
796        data = check_array(data, accept_sparse="csr")
797        row_ind, col_ind = self.get_indices(i)
798        return data[row_ind[:, np.newaxis], col_ind]
799
800
801class TransformerMixin(_SetOutputMixin):
802    """Mixin class for all transformers in scikit-learn.
803
804    This mixin defines the following functionality:
805
806    - a `fit_transform` method that delegates to `fit` and `transform`;
807    - a `set_output` method to output `X` as a specific container type.
808
809    If :term:`get_feature_names_out` is defined, then :class:`BaseEstimator` will
810    automatically wrap `transform` and `fit_transform` to follow the `set_output`
811    API. See the :ref:`developer_api_set_output` for details.
812
813    :class:`OneToOneFeatureMixin` and
814    :class:`ClassNamePrefixFeaturesOutMixin` are helpful mixins for
815    defining :term:`get_feature_names_out`.
816
817    Examples
818    --------
819    >>> import numpy as np
820    >>> from sklearn.base import BaseEstimator, TransformerMixin
821    >>> class MyTransformer(TransformerMixin, BaseEstimator):
822    ...     def __init__(self, *, param=1):
823    ...         self.param = param
824    ...     def fit(self, X, y=None):
825    ...         return self
826    ...     def transform(self, X):
827    ...         return np.full(shape=len(X), fill_value=self.param)
828    >>> transformer = MyTransformer()
829    >>> X = [[1, 2], [2, 3], [3, 4]]
830    >>> transformer.fit_transform(X)
831    array([1, 1, 1])
832    """
833
834    def __sklearn_tags__(self):
835        tags = super().__sklearn_tags__()
836        tags.transformer_tags = TransformerTags()
837        return tags
838
839    def fit_transform(self, X, y=None, **fit_params):
840        """
841        Fit to data, then transform it.
842
843        Fits transformer to `X` and `y` with optional parameters `fit_params`
844        and returns a transformed version of `X`.
845
846        Parameters
847        ----------
848        X : array-like of shape (n_samples, n_features)
849            Input samples.
850
851        y :  array-like of shape (n_samples,) or (n_samples, n_outputs), \
852                default=None
853            Target values (None for unsupervised transformations).
854
855        **fit_params : dict
856            Additional fit parameters.
857
858        Returns
859        -------
860        X_new : ndarray array of shape (n_samples, n_features_new)
861            Transformed array.
862        """
863        # non-optimized default implementation; override when a better
864        # method is possible for a given clustering algorithm
865
866        # we do not route parameters here, since consumers don't route. But
867        # since it's possible for a `transform` method to also consume
868        # metadata, we check if that's the case, and we raise a warning telling
869        # users that they should implement a custom `fit_transform` method
870        # to forward metadata to `transform` as well.
871        #
872        # For that, we calculate routing and check if anything would be routed
873        # to `transform` if we were to route them.
874        if _routing_enabled():
875            transform_params = self.get_metadata_routing().consumes(
876                method="transform", params=fit_params.keys()
877            )
878            if transform_params:
879                warnings.warn(
880                    (
881                        f"This object ({self.__class__.__name__}) has a `transform`"
882                        " method which consumes metadata, but `fit_transform` does not"
883                        " forward metadata to `transform`. Please implement a custom"
884                        " `fit_transform` method to forward metadata to `transform` as"
885                        " well. Alternatively, you can explicitly do"
886                        " `set_transform_request`and set all values to `False` to"
887                        " disable metadata routed to `transform`, if that's an option."
888                    ),
889                    UserWarning,
890                )
891
892        if y is None:
893            # fit method of arity 1 (unsupervised transformation)
894            return self.fit(X, **fit_params).transform(X)
895        else:
896            # fit method of arity 2 (supervised transformation)
897            return self.fit(X, y, **fit_params).transform(X)
898
899
900class OneToOneFeatureMixin:
901    """Provides `get_feature_names_out` for simple transformers.
902
903    This mixin assumes there's a 1-to-1 correspondence between input features
904    and output features, such as :class:`~sklearn.preprocessing.StandardScaler`.
905
906    Examples
907    --------
908    >>> import numpy as np
909    >>> from sklearn.base import OneToOneFeatureMixin, BaseEstimator
910    >>> class MyEstimator(OneToOneFeatureMixin, BaseEstimator):
911    ...     def fit(self, X, y=None):
912    ...         self.n_features_in_ = X.shape[1]
913    ...         return self
914    >>> X = np.array([[1, 2], [3, 4]])
915    >>> MyEstimator().fit(X).get_feature_names_out()
916    array(['x0', 'x1'], dtype=object)
917    """
918
919    def get_feature_names_out(self, input_features=None):
920        """Get output feature names for transformation.
921
922        Parameters
923        ----------
924        input_features : array-like of str or None, default=None
925            Input features.
926
927            - If `input_features` is `None`, then `feature_names_in_` is
928              used as feature names in. If `feature_names_in_` is not defined,
929              then the following input feature names are generated:
930              `["x0", "x1", ..., "x(n_features_in_ - 1)"]`.
931            - If `input_features` is an array-like, then `input_features` must
932              match `feature_names_in_` if `feature_names_in_` is defined.
933
934        Returns
935        -------
936        feature_names_out : ndarray of str objects
937            Same as input features.
938        """
939        # Note that passing attributes="n_features_in_" forces check_is_fitted
940        # to check if the attribute is present. Otherwise it will pass on
941        # stateless estimators (requires_fit=False)
942        check_is_fitted(self, attributes="n_features_in_")
943        return _check_feature_names_in(self, input_features)
944
945
946class ClassNamePrefixFeaturesOutMixin:
947    """Mixin class for transformers that generate their own names by prefixing.
948
949    This mixin is useful when the transformer needs to generate its own feature
950    names out, such as :class:`~sklearn.decomposition.PCA`. For example, if
951    :class:`~sklearn.decomposition.PCA` outputs 3 features, then the generated feature
952    names out are: `["pca0", "pca1", "pca2"]`.
953
954    This mixin assumes that a `_n_features_out` attribute is defined when the
955    transformer is fitted. `_n_features_out` is the number of output features
956    that the transformer will return in `transform` of `fit_transform`.
957
958    Examples
959    --------
960    >>> import numpy as np
961    >>> from sklearn.base import ClassNamePrefixFeaturesOutMixin, BaseEstimator
962    >>> class MyEstimator(ClassNamePrefixFeaturesOutMixin, BaseEstimator):
963    ...     def fit(self, X, y=None):
964    ...         self._n_features_out = X.shape[1]
965    ...         return self
966    >>> X = np.array([[1, 2], [3, 4]])
967    >>> MyEstimator().fit(X).get_feature_names_out()
968    array(['myestimator0', 'myestimator1'], dtype=object)
969    """
970
971    def get_feature_names_out(self, input_features=None):
972        """Get output feature names for transformation.
973
974        The feature names out will prefixed by the lowercased class name. For
975        example, if the transformer outputs 3 features, then the feature names
976        out are: `["class_name0", "class_name1", "class_name2"]`.
977
978        Parameters
979        ----------
980        input_features : array-like of str or None, default=None
981            Only used to validate feature names with the names seen in `fit`.
982
983        Returns
984        -------
985        feature_names_out : ndarray of str objects
986            Transformed feature names.
987        """
988        check_is_fitted(self, "_n_features_out")
989        return _generate_get_feature_names_out(
990            self, self._n_features_out, input_features=input_features
991        )
992
993
994class DensityMixin:
995    """Mixin class for all density estimators in scikit-learn.
996
997    This mixin defines the following functionality:
998
999    - sets estimator type to `"density_estimator"` through the `estimator_type` tag;
1000    - `score` method that default that do no-op.
1001
1002    Examples
1003    --------
1004    >>> from sklearn.base import DensityMixin
1005    >>> class MyEstimator(DensityMixin):
1006    ...     def fit(self, X, y=None):
1007    ...         self.is_fitted_ = True
1008    ...         return self
1009    >>> estimator = MyEstimator()
1010    >>> hasattr(estimator, "score")
1011    True
1012    """
1013
1014    # TODO(1.8): Remove this attribute
1015    _estimator_type = "DensityEstimator"
1016
1017    def __sklearn_tags__(self):
1018        tags = super().__sklearn_tags__()
1019        tags.estimator_type = "density_estimator"
1020        return tags
1021
1022    def score(self, X, y=None):
1023        """Return the score of the model on the data `X`.
1024
1025        Parameters
1026        ----------
1027        X : array-like of shape (n_samples, n_features)
1028            Test samples.
1029
1030        y : Ignored
1031            Not used, present for API consistency by convention.
1032
1033        Returns
1034        -------
1035        score : float
1036        """
1037        pass
1038
1039
1040class OutlierMixin:
1041    """Mixin class for all outlier detection estimators in scikit-learn.
1042
1043    This mixin defines the following functionality:
1044
1045    - set estimator type to `"outlier_detector"` through the `estimator_type` tag;
1046    - `fit_predict` method that default to `fit` and `predict`.
1047
1048    Examples
1049    --------
1050    >>> import numpy as np
1051    >>> from sklearn.base import BaseEstimator, OutlierMixin
1052    >>> class MyEstimator(OutlierMixin):
1053    ...     def fit(self, X, y=None):
1054    ...         self.is_fitted_ = True
1055    ...         return self
1056    ...     def predict(self, X):
1057    ...         return np.ones(shape=len(X))
1058    >>> estimator = MyEstimator()
1059    >>> X = np.array([[1, 2], [2, 3], [3, 4]])
1060    >>> estimator.fit_predict(X)
1061    array([1., 1., 1.])
1062    """
1063
1064    # TODO(1.8): Remove this attribute
1065    _estimator_type = "outlier_detector"
1066
1067    def __sklearn_tags__(self):
1068        tags = super().__sklearn_tags__()
1069        tags.estimator_type = "outlier_detector"
1070        return tags
1071
1072    def fit_predict(self, X, y=None, **kwargs):
1073        """Perform fit on X and returns labels for X.
1074
1075        Returns -1 for outliers and 1 for inliers.
1076
1077        Parameters
1078        ----------
1079        X : {array-like, sparse matrix} of shape (n_samples, n_features)
1080            The input samples.
1081
1082        y : Ignored
1083            Not used, present for API consistency by convention.
1084
1085        **kwargs : dict
1086            Arguments to be passed to ``fit``.
1087
1088            .. versionadded:: 1.4
1089
1090        Returns
1091        -------
1092        y : ndarray of shape (n_samples,)
1093            1 for inliers, -1 for outliers.
1094        """
1095        # we do not route parameters here, since consumers don't route. But
1096        # since it's possible for a `predict` method to also consume
1097        # metadata, we check if that's the case, and we raise a warning telling
1098        # users that they should implement a custom `fit_predict` method
1099        # to forward metadata to `predict` as well.
1100        #
1101        # For that, we calculate routing and check if anything would be routed
1102        # to `predict` if we were to route them.
1103        if _routing_enabled():
1104            transform_params = self.get_metadata_routing().consumes(
1105                method="predict", params=kwargs.keys()
1106            )
1107            if transform_params:
1108                warnings.warn(
1109                    (
1110                        f"This object ({self.__class__.__name__}) has a `predict` "
1111                        "method which consumes metadata, but `fit_predict` does not "
1112                        "forward metadata to `predict`. Please implement a custom "
1113                        "`fit_predict` method to forward metadata to `predict` as well."
1114                        "Alternatively, you can explicitly do `set_predict_request`"
1115                        "and set all values to `False` to disable metadata routed to "
1116                        "`predict`, if that's an option."
1117                    ),
1118                    UserWarning,
1119                )
1120
1121        # override for transductive outlier detectors like LocalOulierFactor
1122        return self.fit(X, **kwargs).predict(X)
1123
1124
1125class MetaEstimatorMixin:
1126    """Mixin class for all meta estimators in scikit-learn.
1127
1128    This mixin is empty, and only exists to indicate that the estimator is a
1129    meta-estimator.
1130
1131    .. versionchanged:: 1.6
1132        The `_required_parameters` is now removed and is unnecessary since tests are
1133        refactored and don't use this anymore.
1134
1135    Examples
1136    --------
1137    >>> from sklearn.base import MetaEstimatorMixin
1138    >>> from sklearn.datasets import load_iris
1139    >>> from sklearn.linear_model import LogisticRegression
1140    >>> class MyEstimator(MetaEstimatorMixin):
1141    ...     def __init__(self, *, estimator=None):
1142    ...         self.estimator = estimator
1143    ...     def fit(self, X, y=None):
1144    ...         if self.estimator is None:
1145    ...             self.estimator_ = LogisticRegression()
1146    ...         else:
1147    ...             self.estimator_ = self.estimator
1148    ...         return self
1149    >>> X, y = load_iris(return_X_y=True)
1150    >>> estimator = MyEstimator().fit(X, y)
1151    >>> estimator.estimator_
1152    LogisticRegression()
1153    """
1154
1155
1156class MultiOutputMixin:
1157    """Mixin to mark estimators that support multioutput."""
1158
1159    def __sklearn_tags__(self):
1160        tags = super().__sklearn_tags__()
1161        tags.target_tags.multi_output = True
1162        return tags
1163
1164
1165class _UnstableArchMixin:
1166    """Mark estimators that are non-determinstic on 32bit or PowerPC"""
1167
1168    def __sklearn_tags__(self):
1169        tags = super().__sklearn_tags__()
1170        tags.non_deterministic = _IS_32BIT or platform.machine().startswith(
1171            ("ppc", "powerpc")
1172        )
1173        return tags
1174
1175
1176def is_classifier(estimator):
1177    """Return True if the given estimator is (probably) a classifier.
1178
1179    Parameters
1180    ----------
1181    estimator : object
1182        Estimator object to test.
1183
1184    Returns
1185    -------
1186    out : bool
1187        True if estimator is a classifier and False otherwise.
1188
1189    Examples
1190    --------
1191    >>> from sklearn.base import is_classifier
1192    >>> from sklearn.cluster import KMeans
1193    >>> from sklearn.svm import SVC, SVR
1194    >>> classifier = SVC()
1195    >>> regressor = SVR()
1196    >>> kmeans = KMeans()
1197    >>> is_classifier(classifier)
1198    True
1199    >>> is_classifier(regressor)
1200    False

Showing the first 1,200 of 1370 lines. Download the file for the rest.

Aluode/PerceptionLabPortable · CoolFace