Aluode/PerceptionLabPortable
0
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
