CoolFace
Apppublic

Aluode/PerceptionLabPortable

sourceHugging Faceupdated 9mo agoView on Hugging Face
0likes
_split.py3056 linesDownload Raw Back to model_selection
1"""
2The :mod:`sklearn.model_selection._split` module includes classes and
3functions to split the data based on a preset strategy.
4"""
5
6# Authors: The scikit-learn developers
7# SPDX-License-Identifier: BSD-3-Clause
8
9import numbers
10import warnings
11from abc import ABCMeta, abstractmethod
12from collections import defaultdict
13from collections.abc import Iterable
14from inspect import signature
15from itertools import chain, combinations
16from math import ceil, floor
17
18import numpy as np
19from scipy.special import comb
20
21from ..utils import (
22    _safe_indexing,
23    check_random_state,
24    indexable,
25    metadata_routing,
26)
27from ..utils._array_api import (
28    _convert_to_numpy,
29    ensure_common_namespace_device,
30    get_namespace,
31)
32from ..utils._param_validation import Interval, RealNotInt, validate_params
33from ..utils.extmath import _approximate_mode
34from ..utils.metadata_routing import _MetadataRequester
35from ..utils.multiclass import type_of_target
36from ..utils.validation import _num_samples, check_array, column_or_1d
37
38__all__ = [
39    "BaseCrossValidator",
40    "GroupKFold",
41    "GroupShuffleSplit",
42    "KFold",
43    "LeaveOneGroupOut",
44    "LeaveOneOut",
45    "LeavePGroupsOut",
46    "LeavePOut",
47    "PredefinedSplit",
48    "RepeatedKFold",
49    "RepeatedStratifiedKFold",
50    "ShuffleSplit",
51    "StratifiedGroupKFold",
52    "StratifiedKFold",
53    "StratifiedShuffleSplit",
54    "check_cv",
55    "train_test_split",
56]
57
58
59class _UnsupportedGroupCVMixin:
60    """Mixin for splitters that do not support Groups."""
61
62    def split(self, X, y=None, groups=None):
63        """Generate indices to split data into training and test set.
64
65        Parameters
66        ----------
67        X : array-like of shape (n_samples, n_features)
68            Training data, where `n_samples` is the number of samples
69            and `n_features` is the number of features.
70
71        y : array-like of shape (n_samples,)
72            The target variable for supervised learning problems.
73
74        groups : object
75            Always ignored, exists for compatibility.
76
77        Yields
78        ------
79        train : ndarray
80            The training set indices for that split.
81
82        test : ndarray
83            The testing set indices for that split.
84        """
85        if groups is not None:
86            warnings.warn(
87                f"The groups parameter is ignored by {self.__class__.__name__}",
88                UserWarning,
89            )
90        return super().split(X, y, groups=groups)
91
92
93class GroupsConsumerMixin(_MetadataRequester):
94    """A Mixin to ``groups`` by default.
95
96    This Mixin makes the object to request ``groups`` by default as ``True``.
97
98    .. versionadded:: 1.3
99    """
100
101    __metadata_request__split = {"groups": True}
102
103
104class BaseCrossValidator(_MetadataRequester, metaclass=ABCMeta):
105    """Base class for all cross-validators.
106
107    Implementations must define `_iter_test_masks` or `_iter_test_indices`.
108    """
109
110    # This indicates that by default CV splitters don't have a "groups" kwarg,
111    # unless indicated by inheriting from ``GroupsConsumerMixin``.
112    # This also prevents ``set_split_request`` to be generated for splitters
113    # which don't support ``groups``.
114    __metadata_request__split = {"groups": metadata_routing.UNUSED}
115
116    def split(self, X, y=None, groups=None):
117        """Generate indices to split data into training and test set.
118
119        Parameters
120        ----------
121        X : array-like of shape (n_samples, n_features)
122            Training data, where `n_samples` is the number of samples
123            and `n_features` is the number of features.
124
125        y : array-like of shape (n_samples,)
126            The target variable for supervised learning problems.
127
128        groups : array-like of shape (n_samples,), default=None
129            Group labels for the samples used while splitting the dataset into
130            train/test set.
131
132        Yields
133        ------
134        train : ndarray
135            The training set indices for that split.
136
137        test : ndarray
138            The testing set indices for that split.
139        """
140        X, y, groups = indexable(X, y, groups)
141        indices = np.arange(_num_samples(X))
142        for test_index in self._iter_test_masks(X, y, groups):
143            train_index = indices[np.logical_not(test_index)]
144            test_index = indices[test_index]
145            yield train_index, test_index
146
147    # Since subclasses must implement either _iter_test_masks or
148    # _iter_test_indices, neither can be abstract.
149    def _iter_test_masks(self, X=None, y=None, groups=None):
150        """Generates boolean masks corresponding to test sets.
151
152        By default, delegates to _iter_test_indices(X, y, groups)
153        """
154        for test_index in self._iter_test_indices(X, y, groups):
155            test_mask = np.zeros(_num_samples(X), dtype=bool)
156            test_mask[test_index] = True
157            yield test_mask
158
159    def _iter_test_indices(self, X=None, y=None, groups=None):
160        """Generates integer indices corresponding to test sets."""
161        raise NotImplementedError
162
163    @abstractmethod
164    def get_n_splits(self, X=None, y=None, groups=None):
165        """Returns the number of splitting iterations in the cross-validator."""
166
167    def __repr__(self):
168        return _build_repr(self)
169
170
171class LeaveOneOut(_UnsupportedGroupCVMixin, BaseCrossValidator):
172    """Leave-One-Out cross-validator.
173
174    Provides train/test indices to split data in train/test sets. Each
175    sample is used once as a test set (singleton) while the remaining
176    samples form the training set.
177
178    Note: ``LeaveOneOut()`` is equivalent to ``KFold(n_splits=n)`` and
179    ``LeavePOut(p=1)`` where ``n`` is the number of samples.
180
181    Due to the high number of test sets (which is the same as the
182    number of samples) this cross-validation method can be very costly.
183    For large datasets one should favor :class:`KFold`, :class:`ShuffleSplit`
184    or :class:`StratifiedKFold`.
185
186    Read more in the :ref:`User Guide <leave_one_out>`.
187
188    Examples
189    --------
190    >>> import numpy as np
191    >>> from sklearn.model_selection import LeaveOneOut
192    >>> X = np.array([[1, 2], [3, 4]])
193    >>> y = np.array([1, 2])
194    >>> loo = LeaveOneOut()
195    >>> loo.get_n_splits(X)
196    2
197    >>> print(loo)
198    LeaveOneOut()
199    >>> for i, (train_index, test_index) in enumerate(loo.split(X)):
200    ...     print(f"Fold {i}:")
201    ...     print(f"  Train: index={train_index}")
202    ...     print(f"  Test:  index={test_index}")
203    Fold 0:
204      Train: index=[1]
205      Test:  index=[0]
206    Fold 1:
207      Train: index=[0]
208      Test:  index=[1]
209
210    See Also
211    --------
212    LeaveOneGroupOut : For splitting the data according to explicit,
213        domain-specific stratification of the dataset.
214    GroupKFold : K-fold iterator variant with non-overlapping groups.
215    """
216
217    def _iter_test_indices(self, X, y=None, groups=None):
218        n_samples = _num_samples(X)
219        if n_samples <= 1:
220            raise ValueError(
221                "Cannot perform LeaveOneOut with n_samples={}.".format(n_samples)
222            )
223        return range(n_samples)
224
225    def get_n_splits(self, X, y=None, groups=None):
226        """Returns the number of splitting iterations in the cross-validator.
227
228        Parameters
229        ----------
230        X : array-like of shape (n_samples, n_features)
231            Training data, where `n_samples` is the number of samples
232            and `n_features` is the number of features.
233
234        y : object
235            Always ignored, exists for compatibility.
236
237        groups : object
238            Always ignored, exists for compatibility.
239
240        Returns
241        -------
242        n_splits : int
243            Returns the number of splitting iterations in the cross-validator.
244        """
245        if X is None:
246            raise ValueError("The 'X' parameter should not be None.")
247        return _num_samples(X)
248
249
250class LeavePOut(_UnsupportedGroupCVMixin, BaseCrossValidator):
251    """Leave-P-Out cross-validator.
252
253    Provides train/test indices to split data in train/test sets. This results
254    in testing on all distinct samples of size p, while the remaining n - p
255    samples form the training set in each iteration.
256
257    Note: ``LeavePOut(p)`` is NOT equivalent to
258    ``KFold(n_splits=n_samples // p)`` which creates non-overlapping test sets.
259
260    Due to the high number of iterations which grows combinatorically with the
261    number of samples this cross-validation method can be very costly. For
262    large datasets one should favor :class:`KFold`, :class:`StratifiedKFold`
263    or :class:`ShuffleSplit`.
264
265    Read more in the :ref:`User Guide <leave_p_out>`.
266
267    Parameters
268    ----------
269    p : int
270        Size of the test sets. Must be strictly less than the number of
271        samples.
272
273    Examples
274    --------
275    >>> import numpy as np
276    >>> from sklearn.model_selection import LeavePOut
277    >>> X = np.array([[1, 2], [3, 4], [5, 6], [7, 8]])
278    >>> y = np.array([1, 2, 3, 4])
279    >>> lpo = LeavePOut(2)
280    >>> lpo.get_n_splits(X)
281    6
282    >>> print(lpo)
283    LeavePOut(p=2)
284    >>> for i, (train_index, test_index) in enumerate(lpo.split(X)):
285    ...     print(f"Fold {i}:")
286    ...     print(f"  Train: index={train_index}")
287    ...     print(f"  Test:  index={test_index}")
288    Fold 0:
289      Train: index=[2 3]
290      Test:  index=[0 1]
291    Fold 1:
292      Train: index=[1 3]
293      Test:  index=[0 2]
294    Fold 2:
295      Train: index=[1 2]
296      Test:  index=[0 3]
297    Fold 3:
298      Train: index=[0 3]
299      Test:  index=[1 2]
300    Fold 4:
301      Train: index=[0 2]
302      Test:  index=[1 3]
303    Fold 5:
304      Train: index=[0 1]
305      Test:  index=[2 3]
306    """
307
308    def __init__(self, p):
309        self.p = p
310
311    def _iter_test_indices(self, X, y=None, groups=None):
312        n_samples = _num_samples(X)
313        if n_samples <= self.p:
314            raise ValueError(
315                "p={} must be strictly less than the number of samples={}".format(
316                    self.p, n_samples
317                )
318            )
319        for combination in combinations(range(n_samples), self.p):
320            yield np.array(combination)
321
322    def get_n_splits(self, X, y=None, groups=None):
323        """Returns the number of splitting iterations in the cross-validator.
324
325        Parameters
326        ----------
327        X : array-like of shape (n_samples, n_features)
328            Training data, where `n_samples` is the number of samples
329            and `n_features` is the number of features.
330
331        y : object
332            Always ignored, exists for compatibility.
333
334        groups : object
335            Always ignored, exists for compatibility.
336        """
337        if X is None:
338            raise ValueError("The 'X' parameter should not be None.")
339        return int(comb(_num_samples(X), self.p, exact=True))
340
341
342class _BaseKFold(BaseCrossValidator, metaclass=ABCMeta):
343    """Base class for K-Fold cross-validators and TimeSeriesSplit."""
344
345    @abstractmethod
346    def __init__(self, n_splits, *, shuffle, random_state):
347        if not isinstance(n_splits, numbers.Integral):
348            raise ValueError(
349                "The number of folds must be of Integral type. "
350                "%s of type %s was passed." % (n_splits, type(n_splits))
351            )
352        n_splits = int(n_splits)
353
354        if n_splits <= 1:
355            raise ValueError(
356                "k-fold cross-validation requires at least one"
357                " train/test split by setting n_splits=2 or more,"
358                " got n_splits={0}.".format(n_splits)
359            )
360
361        if not isinstance(shuffle, bool):
362            raise TypeError("shuffle must be True or False; got {0}".format(shuffle))
363
364        if not shuffle and random_state is not None:  # None is the default
365            raise ValueError(
366                (
367                    "Setting a random_state has no effect since shuffle is "
368                    "False. You should leave "
369                    "random_state to its default (None), or set shuffle=True."
370                ),
371            )
372
373        self.n_splits = n_splits
374        self.shuffle = shuffle
375        self.random_state = random_state
376
377    def split(self, X, y=None, groups=None):
378        """Generate indices to split data into training and test set.
379
380        Parameters
381        ----------
382        X : array-like of shape (n_samples, n_features)
383            Training data, where `n_samples` is the number of samples
384            and `n_features` is the number of features.
385
386        y : array-like of shape (n_samples,), default=None
387            The target variable for supervised learning problems.
388
389        groups : array-like of shape (n_samples,), default=None
390            Group labels for the samples used while splitting the dataset into
391            train/test set.
392
393        Yields
394        ------
395        train : ndarray
396            The training set indices for that split.
397
398        test : ndarray
399            The testing set indices for that split.
400        """
401        X, y, groups = indexable(X, y, groups)
402        n_samples = _num_samples(X)
403        if self.n_splits > n_samples:
404            raise ValueError(
405                (
406                    "Cannot have number of splits n_splits={0} greater"
407                    " than the number of samples: n_samples={1}."
408                ).format(self.n_splits, n_samples)
409            )
410
411        for train, test in super().split(X, y, groups):
412            yield train, test
413
414    def get_n_splits(self, X=None, y=None, groups=None):
415        """Returns the number of splitting iterations in the cross-validator.
416
417        Parameters
418        ----------
419        X : object
420            Always ignored, exists for compatibility.
421
422        y : object
423            Always ignored, exists for compatibility.
424
425        groups : object
426            Always ignored, exists for compatibility.
427
428        Returns
429        -------
430        n_splits : int
431            Returns the number of splitting iterations in the cross-validator.
432        """
433        return self.n_splits
434
435
436class KFold(_UnsupportedGroupCVMixin, _BaseKFold):
437    """K-Fold cross-validator.
438
439    Provides train/test indices to split data in train/test sets. Split
440    dataset into k consecutive folds (without shuffling by default).
441
442    Each fold is then used once as a validation while the k - 1 remaining
443    folds form the training set.
444
445    Read more in the :ref:`User Guide <k_fold>`.
446
447    For visualisation of cross-validation behaviour and
448    comparison between common scikit-learn split methods
449    refer to :ref:`sphx_glr_auto_examples_model_selection_plot_cv_indices.py`
450
451    Parameters
452    ----------
453    n_splits : int, default=5
454        Number of folds. Must be at least 2.
455
456        .. versionchanged:: 0.22
457            ``n_splits`` default value changed from 3 to 5.
458
459    shuffle : bool, default=False
460        Whether to shuffle the data before splitting into batches.
461        Note that the samples within each split will not be shuffled.
462
463    random_state : int, RandomState instance or None, default=None
464        When `shuffle` is True, `random_state` affects the ordering of the
465        indices, which controls the randomness of each fold. Otherwise, this
466        parameter has no effect.
467        Pass an int for reproducible output across multiple function calls.
468        See :term:`Glossary <random_state>`.
469
470    Examples
471    --------
472    >>> import numpy as np
473    >>> from sklearn.model_selection import KFold
474    >>> X = np.array([[1, 2], [3, 4], [1, 2], [3, 4]])
475    >>> y = np.array([1, 2, 3, 4])
476    >>> kf = KFold(n_splits=2)
477    >>> kf.get_n_splits(X)
478    2
479    >>> print(kf)
480    KFold(n_splits=2, random_state=None, shuffle=False)
481    >>> for i, (train_index, test_index) in enumerate(kf.split(X)):
482    ...     print(f"Fold {i}:")
483    ...     print(f"  Train: index={train_index}")
484    ...     print(f"  Test:  index={test_index}")
485    Fold 0:
486      Train: index=[2 3]
487      Test:  index=[0 1]
488    Fold 1:
489      Train: index=[0 1]
490      Test:  index=[2 3]
491
492    Notes
493    -----
494    The first ``n_samples % n_splits`` folds have size
495    ``n_samples // n_splits + 1``, other folds have size
496    ``n_samples // n_splits``, where ``n_samples`` is the number of samples.
497
498    Randomized CV splitters may return different results for each call of
499    split. You can make the results identical by setting `random_state`
500    to an integer.
501
502    See Also
503    --------
504    StratifiedKFold : Takes class information into account to avoid building
505        folds with imbalanced class distributions (for binary or multiclass
506        classification tasks).
507
508    GroupKFold : K-fold iterator variant with non-overlapping groups.
509
510    RepeatedKFold : Repeats K-Fold n times.
511    """
512
513    def __init__(self, n_splits=5, *, shuffle=False, random_state=None):
514        super().__init__(n_splits=n_splits, shuffle=shuffle, random_state=random_state)
515
516    def _iter_test_indices(self, X, y=None, groups=None):
517        n_samples = _num_samples(X)
518        indices = np.arange(n_samples)
519        if self.shuffle:
520            check_random_state(self.random_state).shuffle(indices)
521
522        n_splits = self.n_splits
523        fold_sizes = np.full(n_splits, n_samples // n_splits, dtype=int)
524        fold_sizes[: n_samples % n_splits] += 1
525        current = 0
526        for fold_size in fold_sizes:
527            start, stop = current, current + fold_size
528            yield indices[start:stop]
529            current = stop
530
531
532class GroupKFold(GroupsConsumerMixin, _BaseKFold):
533    """K-fold iterator variant with non-overlapping groups.
534
535    Each group will appear exactly once in the test set across all folds (the
536    number of distinct groups has to be at least equal to the number of folds).
537
538    The folds are approximately balanced in the sense that the number of
539    samples is approximately the same in each test fold when `shuffle` is True.
540
541    Read more in the :ref:`User Guide <group_k_fold>`.
542
543    For visualisation of cross-validation behaviour and
544    comparison between common scikit-learn split methods
545    refer to :ref:`sphx_glr_auto_examples_model_selection_plot_cv_indices.py`
546
547    Parameters
548    ----------
549    n_splits : int, default=5
550        Number of folds. Must be at least 2.
551
552        .. versionchanged:: 0.22
553            ``n_splits`` default value changed from 3 to 5.
554
555    shuffle : bool, default=False
556        Whether to shuffle the groups before splitting into batches.
557        Note that the samples within each split will not be shuffled.
558
559        .. versionadded:: 1.6
560
561    random_state : int, RandomState instance or None, default=None
562        When `shuffle` is True, `random_state` affects the ordering of the
563        indices, which controls the randomness of each fold. Otherwise, this
564        parameter has no effect.
565        Pass an int for reproducible output across multiple function calls.
566        See :term:`Glossary <random_state>`.
567
568        .. versionadded:: 1.6
569
570    Notes
571    -----
572    Groups appear in an arbitrary order throughout the folds.
573
574    Examples
575    --------
576    >>> import numpy as np
577    >>> from sklearn.model_selection import GroupKFold
578    >>> X = np.array([[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11, 12]])
579    >>> y = np.array([1, 2, 3, 4, 5, 6])
580    >>> groups = np.array([0, 0, 2, 2, 3, 3])
581    >>> group_kfold = GroupKFold(n_splits=2)
582    >>> group_kfold.get_n_splits(X, y, groups)
583    2
584    >>> print(group_kfold)
585    GroupKFold(n_splits=2, random_state=None, shuffle=False)
586    >>> for i, (train_index, test_index) in enumerate(group_kfold.split(X, y, groups)):
587    ...     print(f"Fold {i}:")
588    ...     print(f"  Train: index={train_index}, group={groups[train_index]}")
589    ...     print(f"  Test:  index={test_index}, group={groups[test_index]}")
590    Fold 0:
591      Train: index=[2 3], group=[2 2]
592      Test:  index=[0 1 4 5], group=[0 0 3 3]
593    Fold 1:
594      Train: index=[0 1 4 5], group=[0 0 3 3]
595      Test:  index=[2 3], group=[2 2]
596
597    See Also
598    --------
599    LeaveOneGroupOut : For splitting the data according to explicit
600        domain-specific stratification of the dataset.
601
602    StratifiedKFold : Takes class information into account to avoid building
603        folds with imbalanced class proportions (for binary or multiclass
604        classification tasks).
605    """
606
607    def __init__(self, n_splits=5, *, shuffle=False, random_state=None):
608        super().__init__(n_splits, shuffle=shuffle, random_state=random_state)
609
610    def _iter_test_indices(self, X, y, groups):
611        if groups is None:
612            raise ValueError("The 'groups' parameter should not be None.")
613        groups = check_array(groups, input_name="groups", ensure_2d=False, dtype=None)
614
615        unique_groups, group_idx = np.unique(groups, return_inverse=True)
616        n_groups = len(unique_groups)
617
618        if self.n_splits > n_groups:
619            raise ValueError(
620                "Cannot have number of splits n_splits=%d greater"
621                " than the number of groups: %d." % (self.n_splits, n_groups)
622            )
623
624        if self.shuffle:
625            # Split and shuffle unique groups across n_splits
626            rng = check_random_state(self.random_state)
627            unique_groups = rng.permutation(unique_groups)
628            split_groups = np.array_split(unique_groups, self.n_splits)
629
630            for test_group_ids in split_groups:
631                test_mask = np.isin(groups, test_group_ids)
632                yield np.where(test_mask)[0]
633
634        else:
635            # Weight groups by their number of occurrences
636            n_samples_per_group = np.bincount(group_idx)
637
638            # Distribute the most frequent groups first
639            indices = np.argsort(n_samples_per_group)[::-1]
640            n_samples_per_group = n_samples_per_group[indices]
641
642            # Total weight of each fold
643            n_samples_per_fold = np.zeros(self.n_splits)
644
645            # Mapping from group index to fold index
646            group_to_fold = np.zeros(len(unique_groups))
647
648            # Distribute samples by adding the largest weight to the lightest fold
649            for group_index, weight in enumerate(n_samples_per_group):
650                lightest_fold = np.argmin(n_samples_per_fold)
651                n_samples_per_fold[lightest_fold] += weight
652                group_to_fold[indices[group_index]] = lightest_fold
653
654            indices = group_to_fold[group_idx]
655
656            for f in range(self.n_splits):
657                yield np.where(indices == f)[0]
658
659    def split(self, X, y=None, groups=None):
660        """Generate indices to split data into training and test set.
661
662        Parameters
663        ----------
664        X : array-like of shape (n_samples, n_features)
665            Training data, where `n_samples` is the number of samples
666            and `n_features` is the number of features.
667
668        y : array-like of shape (n_samples,), default=None
669            The target variable for supervised learning problems.
670
671        groups : array-like of shape (n_samples,)
672            Group labels for the samples used while splitting the dataset into
673            train/test set.
674
675        Yields
676        ------
677        train : ndarray
678            The training set indices for that split.
679
680        test : ndarray
681            The testing set indices for that split.
682        """
683        return super().split(X, y, groups)
684
685
686class StratifiedKFold(_BaseKFold):
687    """Class-wise stratified K-Fold cross-validator.
688
689    Provides train/test indices to split data in train/test sets.
690
691    This cross-validation object is a variation of KFold that returns
692    stratified folds. The folds are made by preserving the percentage of
693    samples for each class in `y` in a binary or multiclass classification
694    setting.
695
696    Read more in the :ref:`User Guide <stratified_k_fold>`.
697
698    For visualisation of cross-validation behaviour and
699    comparison between common scikit-learn split methods
700    refer to :ref:`sphx_glr_auto_examples_model_selection_plot_cv_indices.py`
701
702    .. note::
703
704        Stratification on the class label solves an engineering problem rather
705        than a statistical one. See :ref:`stratification` for more details.
706
707    Parameters
708    ----------
709    n_splits : int, default=5
710        Number of folds. Must be at least 2.
711
712        .. versionchanged:: 0.22
713            ``n_splits`` default value changed from 3 to 5.
714
715    shuffle : bool, default=False
716        Whether to shuffle each class's samples before splitting into batches.
717        Note that the samples within each split will not be shuffled.
718
719    random_state : int, RandomState instance or None, default=None
720        When `shuffle` is True, `random_state` affects the ordering of the
721        indices, which controls the randomness of each fold for each class.
722        Otherwise, leave `random_state` as `None`.
723        Pass an int for reproducible output across multiple function calls.
724        See :term:`Glossary <random_state>`.
725
726    Examples
727    --------
728    >>> import numpy as np
729    >>> from sklearn.model_selection import StratifiedKFold
730    >>> X = np.array([[1, 2], [3, 4], [1, 2], [3, 4]])
731    >>> y = np.array([0, 0, 1, 1])
732    >>> skf = StratifiedKFold(n_splits=2)
733    >>> skf.get_n_splits(X, y)
734    2
735    >>> print(skf)
736    StratifiedKFold(n_splits=2, random_state=None, shuffle=False)
737    >>> for i, (train_index, test_index) in enumerate(skf.split(X, y)):
738    ...     print(f"Fold {i}:")
739    ...     print(f"  Train: index={train_index}")
740    ...     print(f"  Test:  index={test_index}")
741    Fold 0:
742      Train: index=[1 3]
743      Test:  index=[0 2]
744    Fold 1:
745      Train: index=[0 2]
746      Test:  index=[1 3]
747
748    Notes
749    -----
750    The implementation is designed to:
751
752    * Generate test sets such that all contain the same distribution of
753      classes, or as close as possible.
754    * Be invariant to class label: relabelling ``y = ["Happy", "Sad"]`` to
755      ``y = [1, 0]`` should not change the indices generated.
756    * Preserve order dependencies in the dataset ordering, when
757      ``shuffle=False``: all samples from class k in some test set were
758      contiguous in y, or separated in y by samples from classes other than k.
759    * Generate test sets where the smallest and largest differ by at most one
760      sample.
761
762    .. versionchanged:: 0.22
763        The previous implementation did not follow the last constraint.
764
765    See Also
766    --------
767    RepeatedStratifiedKFold : Repeats Stratified K-Fold n times.
768    """
769
770    def __init__(self, n_splits=5, *, shuffle=False, random_state=None):
771        super().__init__(n_splits=n_splits, shuffle=shuffle, random_state=random_state)
772
773    def _make_test_folds(self, X, y=None):
774        rng = check_random_state(self.random_state)
775        # XXX: as of now, cross-validation splitters only operate in NumPy-land
776        # without attempting to leverage array API namespace features. However
777        # they might be fed by array API inputs, e.g. in CV-enabled estimators so
778        # we need the following explicit conversion:
779        xp, is_array_api = get_namespace(y)
780        if is_array_api:
781            y = _convert_to_numpy(y, xp)
782        else:
783            y = np.asarray(y)
784        type_of_target_y = type_of_target(y)
785        allowed_target_types = ("binary", "multiclass")
786        if type_of_target_y not in allowed_target_types:
787            raise ValueError(
788                "Supported target types are: {}. Got {!r} instead.".format(
789                    allowed_target_types, type_of_target_y
790                )
791            )
792
793        y = column_or_1d(y)
794
795        _, y_idx, y_inv = np.unique(y, return_index=True, return_inverse=True)
796        # y_inv encodes y according to lexicographic order. We invert y_idx to
797        # map the classes so that they are encoded by order of appearance:
798        # 0 represents the first label appearing in y, 1 the second, etc.
799        _, class_perm = np.unique(y_idx, return_inverse=True)
800        y_encoded = class_perm[y_inv]
801
802        n_classes = len(y_idx)
803        y_counts = np.bincount(y_encoded)
804        min_groups = np.min(y_counts)
805        if np.all(self.n_splits > y_counts):
806            raise ValueError(
807                "n_splits=%d cannot be greater than the"
808                " number of members in each class." % (self.n_splits)
809            )
810        if self.n_splits > min_groups:
811            warnings.warn(
812                "The least populated class in y has only %d"
813                " members, which is less than n_splits=%d."
814                % (min_groups, self.n_splits),
815                UserWarning,
816            )
817
818        # Determine the optimal number of samples from each class in each fold,
819        # using round robin over the sorted y. (This can be done direct from
820        # counts, but that code is unreadable.)
821        y_order = np.sort(y_encoded)
822        allocation = np.asarray(
823            [
824                np.bincount(y_order[i :: self.n_splits], minlength=n_classes)
825                for i in range(self.n_splits)
826            ]
827        )
828
829        # To maintain the data order dependencies as best as possible within
830        # the stratification constraint, we assign samples from each class in
831        # blocks (and then mess that up when shuffle=True).
832        test_folds = np.empty(len(y), dtype="i")
833        for k in range(n_classes):
834            # since the kth column of allocation stores the number of samples
835            # of class k in each test set, this generates blocks of fold
836            # indices corresponding to the allocation for class k.
837            folds_for_class = np.arange(self.n_splits).repeat(allocation[:, k])
838            if self.shuffle:
839                rng.shuffle(folds_for_class)
840            test_folds[y_encoded == k] = folds_for_class
841        return test_folds
842
843    def _iter_test_masks(self, X, y=None, groups=None):
844        test_folds = self._make_test_folds(X, y)
845        for i in range(self.n_splits):
846            yield test_folds == i
847
848    def split(self, X, y, groups=None):
849        """Generate indices to split data into training and test set.
850
851        Parameters
852        ----------
853        X : array-like of shape (n_samples, n_features)
854            Training data, where `n_samples` is the number of samples
855            and `n_features` is the number of features.
856
857            Note that providing ``y`` is sufficient to generate the splits and
858            hence ``np.zeros(n_samples)`` may be used as a placeholder for
859            ``X`` instead of actual training data.
860
861        y : array-like of shape (n_samples,)
862            The target variable for supervised learning problems.
863            Stratification is done based on the y labels.
864
865        groups : object
866            Always ignored, exists for compatibility.
867
868        Yields
869        ------
870        train : ndarray
871            The training set indices for that split.
872
873        test : ndarray
874            The testing set indices for that split.
875
876        Notes
877        -----
878        Randomized CV splitters may return different results for each call of
879        split. You can make the results identical by setting `random_state`
880        to an integer.
881        """
882        if groups is not None:
883            warnings.warn(
884                f"The groups parameter is ignored by {self.__class__.__name__}",
885                UserWarning,
886            )
887        y = check_array(y, input_name="y", ensure_2d=False, dtype=None)
888        return super().split(X, y, groups)
889
890
891class StratifiedGroupKFold(GroupsConsumerMixin, _BaseKFold):
892    """Class-wise stratified K-Fold iterator variant with non-overlapping groups.
893
894    This cross-validation object is a variation of StratifiedKFold attempts to
895    return stratified folds with non-overlapping groups. The folds are made by
896    preserving the percentage of samples for each class in `y` in a binary or
897    multiclass classification setting.
898
899    Each group will appear exactly once in the test set across all folds (the
900    number of distinct groups has to be at least equal to the number of folds).
901
902    The difference between :class:`GroupKFold`
903    and `StratifiedGroupKFold` is that
904    the former attempts to create balanced folds such that the number of
905    distinct groups is approximately the same in each fold, whereas
906    `StratifiedGroupKFold` attempts to create folds which preserve the
907    percentage of samples for each class as much as possible given the
908    constraint of non-overlapping groups between splits.
909
910    Read more in the :ref:`User Guide <stratified_group_k_fold>`.
911
912    For visualisation of cross-validation behaviour and
913    comparison between common scikit-learn split methods
914    refer to :ref:`sphx_glr_auto_examples_model_selection_plot_cv_indices.py`
915
916    .. note::
917
918        Stratification on the class label solves an engineering problem rather
919        than a statistical one. See :ref:`stratification` for more details.
920
921    Parameters
922    ----------
923    n_splits : int, default=5
924        Number of folds. Must be at least 2.
925
926    shuffle : bool, default=False
927        Whether to shuffle each class's samples before splitting into batches.
928        Note that the samples within each split will not be shuffled.
929        This implementation can only shuffle groups that have approximately the
930        same y distribution, no global shuffle will be performed.
931
932    random_state : int or RandomState instance, default=None
933        When `shuffle` is True, `random_state` affects the ordering of the
934        indices, which controls the randomness of each fold for each class.
935        Otherwise, leave `random_state` as `None`.
936        Pass an int for reproducible output across multiple function calls.
937        See :term:`Glossary <random_state>`.
938
939    Examples
940    --------
941    >>> import numpy as np
942    >>> from sklearn.model_selection import StratifiedGroupKFold
943    >>> X = np.ones((17, 2))
944    >>> y = np.array([0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0])
945    >>> groups = np.array([1, 1, 2, 2, 3, 3, 3, 4, 5, 5, 5, 5, 6, 6, 7, 8, 8])
946    >>> sgkf = StratifiedGroupKFold(n_splits=3)
947    >>> sgkf.get_n_splits(X, y)
948    3
949    >>> print(sgkf)
950    StratifiedGroupKFold(n_splits=3, random_state=None, shuffle=False)
951    >>> for i, (train_index, test_index) in enumerate(sgkf.split(X, y, groups)):
952    ...     print(f"Fold {i}:")
953    ...     print(f"  Train: index={train_index}")
954    ...     print(f"         group={groups[train_index]}")
955    ...     print(f"  Test:  index={test_index}")
956    ...     print(f"         group={groups[test_index]}")
957    Fold 0:
958      Train: index=[ 0  1  2  3  7  8  9 10 11 15 16]
959             group=[1 1 2 2 4 5 5 5 5 8 8]
960      Test:  index=[ 4  5  6 12 13 14]
961             group=[3 3 3 6 6 7]
962    Fold 1:
963      Train: index=[ 4  5  6  7  8  9 10 11 12 13 14]
964             group=[3 3 3 4 5 5 5 5 6 6 7]
965      Test:  index=[ 0  1  2  3 15 16]
966             group=[1 1 2 2 8 8]
967    Fold 2:
968      Train: index=[ 0  1  2  3  4  5  6 12 13 14 15 16]
969             group=[1 1 2 2 3 3 3 6 6 7 8 8]
970      Test:  index=[ 7  8  9 10 11]
971             group=[4 5 5 5 5]
972
973    Notes
974    -----
975    The implementation is designed to:
976
977    * Mimic the behavior of StratifiedKFold as much as possible for trivial
978      groups (e.g. when each group contains only one sample).
979    * Be invariant to class label: relabelling ``y = ["Happy", "Sad"]`` to
980      ``y = [1, 0]`` should not change the indices generated.
981    * Stratify based on samples as much as possible while keeping
982      non-overlapping groups constraint. That means that in some cases when
983      there is a small number of groups containing a large number of samples
984      the stratification will not be possible and the behavior will be close
985      to GroupKFold.
986
987    See also
988    --------
989    StratifiedKFold: Takes class information into account to build folds which
990        retain class distributions (for binary or multiclass classification
991        tasks).
992
993    GroupKFold: K-fold iterator variant with non-overlapping groups.
994    """
995
996    def __init__(self, n_splits=5, shuffle=False, random_state=None):
997        super().__init__(n_splits=n_splits, shuffle=shuffle, random_state=random_state)
998
999    def _iter_test_indices(self, X, y, groups):
1000        # Implementation is based on this kaggle kernel:
1001        # https://www.kaggle.com/jakubwasikowski/stratified-group-k-fold-cross-validation
1002        # and is a subject to Apache 2.0 License. You may obtain a copy of the
1003        # License at http://www.apache.org/licenses/LICENSE-2.0
1004        # Changelist:
1005        # - Refactored function to a class following scikit-learn KFold
1006        #   interface.
1007        # - Added heuristic for assigning group to the least populated fold in
1008        #   cases when all other criteria are equal
1009        # - Swtch from using python ``Counter`` to ``np.unique`` to get class
1010        #   distribution
1011        # - Added scikit-learn checks for input: checking that target is binary
1012        #   or multiclass, checking passed random state, checking that number
1013        #   of splits is less than number of members in each class, checking
1014        #   that least populated class has more members than there are splits.
1015        rng = check_random_state(self.random_state)
1016        y = np.asarray(y)
1017        type_of_target_y = type_of_target(y)
1018        allowed_target_types = ("binary", "multiclass")
1019        if type_of_target_y not in allowed_target_types:
1020            raise ValueError(
1021                "Supported target types are: {}. Got {!r} instead.".format(
1022                    allowed_target_types, type_of_target_y
1023                )
1024            )
1025
1026        y = column_or_1d(y)
1027        _, y_inv, y_cnt = np.unique(y, return_inverse=True, return_counts=True)
1028        if np.all(self.n_splits > y_cnt):
1029            raise ValueError(
1030                "n_splits=%d cannot be greater than the"
1031                " number of members in each class." % (self.n_splits)
1032            )
1033        n_smallest_class = np.min(y_cnt)
1034        if self.n_splits > n_smallest_class:
1035            warnings.warn(
1036                "The least populated class in y has only %d"
1037                " members, which is less than n_splits=%d."
1038                % (n_smallest_class, self.n_splits),
1039                UserWarning,
1040            )
1041        n_classes = len(y_cnt)
1042
1043        _, groups_inv, groups_cnt = np.unique(
1044            groups, return_inverse=True, return_counts=True
1045        )
1046        y_counts_per_group = np.zeros((len(groups_cnt), n_classes))
1047        for class_idx, group_idx in zip(y_inv, groups_inv):
1048            y_counts_per_group[group_idx, class_idx] += 1
1049
1050        y_counts_per_fold = np.zeros((self.n_splits, n_classes))
1051        groups_per_fold = defaultdict(set)
1052
1053        if self.shuffle:
1054            rng.shuffle(y_counts_per_group)
1055
1056        # Stable sort to keep shuffled order for groups with the same
1057        # class distribution variance
1058        sorted_groups_idx = np.argsort(
1059            -np.std(y_counts_per_group, axis=1), kind="mergesort"
1060        )
1061
1062        for group_idx in sorted_groups_idx:
1063            group_y_counts = y_counts_per_group[group_idx]
1064            best_fold = self._find_best_fold(
1065                y_counts_per_fold=y_counts_per_fold,
1066                y_cnt=y_cnt,
1067                group_y_counts=group_y_counts,
1068            )
1069            y_counts_per_fold[best_fold] += group_y_counts
1070            groups_per_fold[best_fold].add(group_idx)
1071
1072        for i in range(self.n_splits):
1073            test_indices = [
1074                idx
1075                for idx, group_idx in enumerate(groups_inv)
1076                if group_idx in groups_per_fold[i]
1077            ]
1078            yield test_indices
1079
1080    def _find_best_fold(self, y_counts_per_fold, y_cnt, group_y_counts):
1081        best_fold = None
1082        min_eval = np.inf
1083        min_samples_in_fold = np.inf
1084        for i in range(self.n_splits):
1085            y_counts_per_fold[i] += group_y_counts
1086            # Summarise the distribution over classes in each proposed fold
1087            std_per_class = np.std(y_counts_per_fold / y_cnt.reshape(1, -1), axis=0)
1088            y_counts_per_fold[i] -= group_y_counts
1089            fold_eval = np.mean(std_per_class)
1090            samples_in_fold = np.sum(y_counts_per_fold[i])
1091            is_current_fold_better = fold_eval < min_eval or (
1092                np.isclose(fold_eval, min_eval)
1093                and samples_in_fold < min_samples_in_fold
1094            )
1095            if is_current_fold_better:
1096                min_eval = fold_eval
1097                min_samples_in_fold = samples_in_fold
1098                best_fold = i
1099        return best_fold
1100
1101
1102class TimeSeriesSplit(_BaseKFold):
1103    """Time Series cross-validator.
1104
1105    Provides train/test indices to split time-ordered data, where other
1106    cross-validation methods are inappropriate, as they would lead to training
1107    on future data and evaluating on past data.
1108    To ensure comparable metrics across folds, samples must be equally spaced.
1109    Once this condition is met, each test set covers the same time duration,
1110    while the train set size accumulates data from previous splits.
1111
1112    This cross-validation object is a variation of :class:`KFold`.
1113    In the k-th split, it returns the first k folds as the train set and the
1114    (k+1)-th fold as the test set.
1115
1116    Note that, unlike standard cross-validation methods, successive
1117    training sets are supersets of those that come before them.
1118
1119    Read more in the :ref:`User Guide <time_series_split>`.
1120
1121    For visualisation of cross-validation behaviour and
1122    comparison between common scikit-learn split methods
1123    refer to :ref:`sphx_glr_auto_examples_model_selection_plot_cv_indices.py`
1124
1125    .. versionadded:: 0.18
1126
1127    Parameters
1128    ----------
1129    n_splits : int, default=5
1130        Number of splits. Must be at least 2.
1131
1132        .. versionchanged:: 0.22
1133            ``n_splits`` default value changed from 3 to 5.
1134
1135    max_train_size : int, default=None
1136        Maximum size for a single training set.
1137
1138    test_size : int, default=None
1139        Used to limit the size of the test set. Defaults to
1140        ``n_samples // (n_splits + 1)``, which is the maximum allowed value
1141        with ``gap=0``.
1142
1143        .. versionadded:: 0.24
1144
1145    gap : int, default=0
1146        Number of samples to exclude from the end of each train set before
1147        the test set.
1148
1149        .. versionadded:: 0.24
1150
1151    Examples
1152    --------
1153    >>> import numpy as np
1154    >>> from sklearn.model_selection import TimeSeriesSplit
1155    >>> X = np.array([[1, 2], [3, 4], [1, 2], [3, 4], [1, 2], [3, 4]])
1156    >>> y = np.array([1, 2, 3, 4, 5, 6])
1157    >>> tscv = TimeSeriesSplit()
1158    >>> print(tscv)
1159    TimeSeriesSplit(gap=0, max_train_size=None, n_splits=5, test_size=None)
1160    >>> for i, (train_index, test_index) in enumerate(tscv.split(X)):
1161    ...     print(f"Fold {i}:")
1162    ...     print(f"  Train: index={train_index}")
1163    ...     print(f"  Test:  index={test_index}")
1164    Fold 0:
1165      Train: index=[0]
1166      Test:  index=[1]
1167    Fold 1:
1168      Train: index=[0 1]
1169      Test:  index=[2]
1170    Fold 2:
1171      Train: index=[0 1 2]
1172      Test:  index=[3]
1173    Fold 3:
1174      Train: index=[0 1 2 3]
1175      Test:  index=[4]
1176    Fold 4:
1177      Train: index=[0 1 2 3 4]
1178      Test:  index=[5]
1179    >>> # Fix test_size to 2 with 12 samples
1180    >>> X = np.random.randn(12, 2)
1181    >>> y = np.random.randint(0, 2, 12)
1182    >>> tscv = TimeSeriesSplit(n_splits=3, test_size=2)
1183    >>> for i, (train_index, test_index) in enumerate(tscv.split(X)):
1184    ...     print(f"Fold {i}:")
1185    ...     print(f"  Train: index={train_index}")
1186    ...     print(f"  Test:  index={test_index}")
1187    Fold 0:
1188      Train: index=[0 1 2 3 4 5]
1189      Test:  index=[6 7]
1190    Fold 1:
1191      Train: index=[0 1 2 3 4 5 6 7]
1192      Test:  index=[8 9]
1193    Fold 2:
1194      Train: index=[0 1 2 3 4 5 6 7 8 9]
1195      Test:  index=[10 11]
1196    >>> # Add in a 2 period gap
1197    >>> tscv = TimeSeriesSplit(n_splits=3, test_size=2, gap=2)
1198    >>> for i, (train_index, test_index) in enumerate(tscv.split(X)):
1199    ...     print(f"Fold {i}:")
1200    ...     print(f"  Train: index={train_index}")

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