Aluode/PerceptionLabPortable
0
1"""Test the split module"""
2
3import re
4import warnings
5from itertools import combinations, combinations_with_replacement, permutations
6
7import numpy as np
8import pytest
9from scipy import stats
10from scipy.sparse import issparse
11from scipy.special import comb
12
13from sklearn import config_context
14from sklearn.datasets import load_digits, make_classification
15from sklearn.dummy import DummyClassifier
16from sklearn.model_selection import (
17 GridSearchCV,
18 GroupKFold,
19 GroupShuffleSplit,
20 KFold,
21 LeaveOneGroupOut,
22 LeaveOneOut,
23 LeavePGroupsOut,
24 LeavePOut,
25 PredefinedSplit,
26 RepeatedKFold,
27 RepeatedStratifiedKFold,
28 ShuffleSplit,
29 StratifiedGroupKFold,
30 StratifiedKFold,
31 StratifiedShuffleSplit,
32 TimeSeriesSplit,
33 check_cv,
34 cross_val_score,
35 train_test_split,
36)
37from sklearn.model_selection._split import (
38 _build_repr,
39 _validate_shuffle_split,
40 _yields_constant_splits,
41)
42from sklearn.svm import SVC
43from sklearn.tests.metadata_routing_common import assert_request_is_empty
44from sklearn.utils._array_api import (
45 _convert_to_numpy,
46 _get_namespace_device_dtype_ids,
47 get_namespace,
48 yield_namespace_device_dtype_combinations,
49)
50from sklearn.utils._array_api import (
51 device as array_api_device,
52)
53from sklearn.utils._mocking import MockDataFrame
54from sklearn.utils._testing import (
55 assert_allclose,
56 assert_array_almost_equal,
57 assert_array_equal,
58 ignore_warnings,
59)
60from sklearn.utils.estimator_checks import (
61 _array_api_for_tests,
62)
63from sklearn.utils.fixes import COO_CONTAINERS, CSC_CONTAINERS, CSR_CONTAINERS
64from sklearn.utils.validation import _num_samples
65
66NO_GROUP_SPLITTERS = [
67 KFold(),
68 StratifiedKFold(),
69 TimeSeriesSplit(),
70 LeaveOneOut(),
71 LeavePOut(p=2),
72 ShuffleSplit(),
73 StratifiedShuffleSplit(test_size=0.5),
74 PredefinedSplit([1, 1, 2, 2]),
75 RepeatedKFold(),
76 RepeatedStratifiedKFold(),
77]
78
79GROUP_SPLITTERS = [
80 GroupKFold(),
81 LeavePGroupsOut(n_groups=1),
82 StratifiedGroupKFold(),
83 LeaveOneGroupOut(),
84 GroupShuffleSplit(),
85]
86GROUP_SPLITTER_NAMES = set(splitter.__class__.__name__ for splitter in GROUP_SPLITTERS)
87
88ALL_SPLITTERS = NO_GROUP_SPLITTERS + GROUP_SPLITTERS # type: ignore[list-item]
89
90SPLITTERS_REQUIRING_TARGET = [
91 StratifiedKFold(),
92 StratifiedShuffleSplit(),
93 RepeatedStratifiedKFold(),
94]
95
96X = np.ones(10)
97y = np.arange(10) // 2
98test_groups = (
99 np.array([1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3, 3]),
100 np.array([0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3]),
101 np.array([0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2]),
102 np.array([1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4]),
103 [1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3, 3],
104 ["1", "1", "1", "1", "2", "2", "2", "3", "3", "3", "3", "3"],
105)
106digits = load_digits()
107
108pytestmark = pytest.mark.filterwarnings(
109 "error:The groups parameter:UserWarning:sklearn.*"
110)
111
112
113def _split(splitter, X, y, groups):
114 if splitter.__class__.__name__ in GROUP_SPLITTER_NAMES:
115 return splitter.split(X, y, groups=groups)
116 else:
117 return splitter.split(X, y)
118
119
120def test_cross_validator_with_default_params():
121 n_samples = 4
122 n_unique_groups = 4
123 n_splits = 2
124 p = 2
125 n_shuffle_splits = 10 # (the default value)
126
127 X = np.array([[1, 2], [3, 4], [5, 6], [7, 8]])
128 X_1d = np.array([1, 2, 3, 4])
129 y = np.array([1, 1, 2, 2])
130 groups = np.array([1, 2, 3, 4])
131 loo = LeaveOneOut()
132 lpo = LeavePOut(p)
133 kf = KFold(n_splits)
134 skf = StratifiedKFold(n_splits)
135 lolo = LeaveOneGroupOut()
136 lopo = LeavePGroupsOut(p)
137 ss = ShuffleSplit(random_state=0)
138 ps = PredefinedSplit([1, 1, 2, 2]) # n_splits = np of unique folds = 2
139 sgkf = StratifiedGroupKFold(n_splits)
140
141 loo_repr = "LeaveOneOut()"
142 lpo_repr = "LeavePOut(p=2)"
143 kf_repr = "KFold(n_splits=2, random_state=None, shuffle=False)"
144 skf_repr = "StratifiedKFold(n_splits=2, random_state=None, shuffle=False)"
145 lolo_repr = "LeaveOneGroupOut()"
146 lopo_repr = "LeavePGroupsOut(n_groups=2)"
147 ss_repr = (
148 "ShuffleSplit(n_splits=10, random_state=0, test_size=None, train_size=None)"
149 )
150 ps_repr = "PredefinedSplit(test_fold=array([1, 1, 2, 2]))"
151 sgkf_repr = "StratifiedGroupKFold(n_splits=2, random_state=None, shuffle=False)"
152
153 n_splits_expected = [
154 n_samples,
155 comb(n_samples, p),
156 n_splits,
157 n_splits,
158 n_unique_groups,
159 comb(n_unique_groups, p),
160 n_shuffle_splits,
161 2,
162 n_splits,
163 ]
164
165 for i, (cv, cv_repr) in enumerate(
166 zip(
167 [loo, lpo, kf, skf, lolo, lopo, ss, ps, sgkf],
168 [
169 loo_repr,
170 lpo_repr,
171 kf_repr,
172 skf_repr,
173 lolo_repr,
174 lopo_repr,
175 ss_repr,
176 ps_repr,
177 sgkf_repr,
178 ],
179 )
180 ):
181 # Test if get_n_splits works correctly
182 assert n_splits_expected[i] == cv.get_n_splits(X, y, groups)
183
184 # Test if the cross-validator works as expected even if
185 # the data is 1d
186 np.testing.assert_equal(
187 list(_split(cv, X, y, groups)), list(_split(cv, X_1d, y, groups))
188 )
189 # Test that train, test indices returned are integers
190 for train, test in _split(cv, X, y, groups):
191 assert np.asarray(train).dtype.kind == "i"
192 assert np.asarray(test).dtype.kind == "i"
193
194 # Test if the repr works without any errors
195 assert cv_repr == repr(cv)
196
197 # ValueError for get_n_splits methods
198 msg = "The 'X' parameter should not be None."
199 with pytest.raises(ValueError, match=msg):
200 loo.get_n_splits(None, y, groups)
201 with pytest.raises(ValueError, match=msg):
202 lpo.get_n_splits(None, y, groups)
203
204
205def test_2d_y():
206 # smoke test for 2d y and multi-label
207 n_samples = 30
208 rng = np.random.RandomState(1)
209 X = rng.randint(0, 3, size=(n_samples, 2))
210 y = rng.randint(0, 3, size=(n_samples,))
211 y_2d = y.reshape(-1, 1)
212 y_multilabel = rng.randint(0, 2, size=(n_samples, 3))
213 groups = rng.randint(0, 3, size=(n_samples,))
214 splitters = [
215 LeaveOneOut(),
216 LeavePOut(p=2),
217 KFold(),
218 StratifiedKFold(),
219 RepeatedKFold(),
220 RepeatedStratifiedKFold(),
221 StratifiedGroupKFold(),
222 ShuffleSplit(),
223 StratifiedShuffleSplit(test_size=0.5),
224 GroupShuffleSplit(),
225 LeaveOneGroupOut(),
226 LeavePGroupsOut(n_groups=2),
227 GroupKFold(n_splits=3),
228 TimeSeriesSplit(),
229 PredefinedSplit(test_fold=groups),
230 ]
231 for splitter in splitters:
232 list(_split(splitter, X, y, groups=groups))
233 list(_split(splitter, X, y_2d, groups=groups))
234 try:
235 list(_split(splitter, X, y_multilabel, groups=groups))
236 except ValueError as e:
237 allowed_target_types = ("binary", "multiclass")
238 msg = "Supported target types are: {}. Got 'multilabel".format(
239 allowed_target_types
240 )
241 assert msg in str(e)
242
243
244def check_valid_split(train, test, n_samples=None):
245 # Use python sets to get more informative assertion failure messages
246 train, test = set(train), set(test)
247
248 # Train and test split should not overlap
249 assert train.intersection(test) == set()
250
251 if n_samples is not None:
252 # Check that the union of train an test split cover all the indices
253 assert train.union(test) == set(range(n_samples))
254
255
256def check_cv_coverage(cv, X, y, groups, expected_n_splits):
257 n_samples = _num_samples(X)
258 # Check that a all the samples appear at least once in a test fold
259 assert cv.get_n_splits(X, y, groups) == expected_n_splits
260
261 collected_test_samples = set()
262 iterations = 0
263 for train, test in cv.split(X, y, groups):
264 check_valid_split(train, test, n_samples=n_samples)
265 iterations += 1
266 collected_test_samples.update(test)
267
268 # Check that the accumulated test samples cover the whole dataset
269 assert iterations == expected_n_splits
270 if n_samples is not None:
271 assert collected_test_samples == set(range(n_samples))
272
273
274def test_kfold_valueerrors():
275 X1 = np.array([[1, 2], [3, 4], [5, 6]])
276 X2 = np.array([[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]])
277 # Check that errors are raised if there is not enough samples
278 (ValueError, next, KFold(4).split(X1))
279
280 # Check that a warning is raised if the least populated class has too few
281 # members.
282 y = np.array([3, 3, -1, -1, 3])
283
284 skf_3 = StratifiedKFold(3)
285 with pytest.warns(Warning, match="The least populated class"):
286 next(skf_3.split(X2, y))
287
288 sgkf_3 = StratifiedGroupKFold(3)
289 naive_groups = np.arange(len(y))
290 with pytest.warns(Warning, match="The least populated class"):
291 next(sgkf_3.split(X2, y, naive_groups))
292
293 # Check that despite the warning the folds are still computed even
294 # though all the classes are not necessarily represented at on each
295 # side of the split at each split
296 with warnings.catch_warnings():
297 warnings.simplefilter("ignore")
298 check_cv_coverage(skf_3, X2, y, groups=None, expected_n_splits=3)
299
300 with warnings.catch_warnings():
301 warnings.simplefilter("ignore")
302 check_cv_coverage(sgkf_3, X2, y, groups=naive_groups, expected_n_splits=3)
303
304 # Check that errors are raised if all n_groups for individual
305 # classes are less than n_splits.
306 y = np.array([3, 3, -1, -1, 2])
307
308 with pytest.raises(ValueError):
309 next(skf_3.split(X2, y))
310 with pytest.raises(ValueError):
311 next(sgkf_3.split(X2, y))
312
313 # Error when number of folds is <= 1
314 with pytest.raises(ValueError):
315 KFold(0)
316 with pytest.raises(ValueError):
317 KFold(1)
318 error_string = "k-fold cross-validation requires at least one train/test split"
319 with pytest.raises(ValueError, match=error_string):
320 StratifiedKFold(0)
321 with pytest.raises(ValueError, match=error_string):
322 StratifiedKFold(1)
323 with pytest.raises(ValueError, match=error_string):
324 StratifiedGroupKFold(0)
325 with pytest.raises(ValueError, match=error_string):
326 StratifiedGroupKFold(1)
327
328 # When n_splits is not integer:
329 with pytest.raises(ValueError):
330 KFold(1.5)
331 with pytest.raises(ValueError):
332 KFold(2.0)
333 with pytest.raises(ValueError):
334 StratifiedKFold(1.5)
335 with pytest.raises(ValueError):
336 StratifiedKFold(2.0)
337 with pytest.raises(ValueError):
338 StratifiedGroupKFold(1.5)
339 with pytest.raises(ValueError):
340 StratifiedGroupKFold(2.0)
341
342 # When shuffle is not a bool:
343 with pytest.raises(TypeError):
344 KFold(n_splits=4, shuffle=None)
345
346
347def test_kfold_indices():
348 # Check all indices are returned in the test folds
349 X1 = np.ones(18)
350 kf = KFold(3)
351 check_cv_coverage(kf, X1, y=None, groups=None, expected_n_splits=3)
352
353 # Check all indices are returned in the test folds even when equal-sized
354 # folds are not possible
355 X2 = np.ones(17)
356 kf = KFold(3)
357 check_cv_coverage(kf, X2, y=None, groups=None, expected_n_splits=3)
358
359 # Check if get_n_splits returns the number of folds
360 assert 5 == KFold(5).get_n_splits(X2)
361
362
363def test_kfold_no_shuffle():
364 # Manually check that KFold preserves the data ordering on toy datasets
365 X2 = [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]
366
367 splits = KFold(2).split(X2[:-1])
368 train, test = next(splits)
369 assert_array_equal(test, [0, 1])
370 assert_array_equal(train, [2, 3])
371
372 train, test = next(splits)
373 assert_array_equal(test, [2, 3])
374 assert_array_equal(train, [0, 1])
375
376 splits = KFold(2).split(X2)
377 train, test = next(splits)
378 assert_array_equal(test, [0, 1, 2])
379 assert_array_equal(train, [3, 4])
380
381 train, test = next(splits)
382 assert_array_equal(test, [3, 4])
383 assert_array_equal(train, [0, 1, 2])
384
385
386def test_stratified_kfold_no_shuffle():
387 # Manually check that StratifiedKFold preserves the data ordering as much
388 # as possible on toy datasets in order to avoid hiding sample dependencies
389 # when possible
390 X, y = np.ones(4), [1, 1, 0, 0]
391 splits = StratifiedKFold(2).split(X, y)
392 train, test = next(splits)
393 assert_array_equal(test, [0, 2])
394 assert_array_equal(train, [1, 3])
395
396 train, test = next(splits)
397 assert_array_equal(test, [1, 3])
398 assert_array_equal(train, [0, 2])
399
400 X, y = np.ones(7), [1, 1, 1, 0, 0, 0, 0]
401 splits = StratifiedKFold(2).split(X, y)
402 train, test = next(splits)
403 assert_array_equal(test, [0, 1, 3, 4])
404 assert_array_equal(train, [2, 5, 6])
405
406 train, test = next(splits)
407 assert_array_equal(test, [2, 5, 6])
408 assert_array_equal(train, [0, 1, 3, 4])
409
410 # Check if get_n_splits returns the number of folds
411 assert 5 == StratifiedKFold(5).get_n_splits(X, y)
412
413 # Make sure string labels are also supported
414 X = np.ones(7)
415 y1 = ["1", "1", "1", "0", "0", "0", "0"]
416 y2 = [1, 1, 1, 0, 0, 0, 0]
417 np.testing.assert_equal(
418 list(StratifiedKFold(2).split(X, y1)), list(StratifiedKFold(2).split(X, y2))
419 )
420
421 # Check equivalence to KFold
422 y = [0, 1, 0, 1, 0, 1, 0, 1]
423 X = np.ones_like(y)
424 np.testing.assert_equal(
425 list(StratifiedKFold(3).split(X, y)), list(KFold(3).split(X, y))
426 )
427
428
429@pytest.mark.parametrize("shuffle", [False, True])
430@pytest.mark.parametrize("k", [4, 5, 6, 7, 8, 9, 10])
431@pytest.mark.parametrize("kfold", [StratifiedKFold, StratifiedGroupKFold])
432def test_stratified_kfold_ratios(k, shuffle, kfold):
433 # Check that stratified kfold preserves class ratios in individual splits
434 # Repeat with shuffling turned off and on
435 n_samples = 1000
436 X = np.ones(n_samples)
437 y = np.array(
438 [4] * int(0.10 * n_samples)
439 + [0] * int(0.89 * n_samples)
440 + [1] * int(0.01 * n_samples)
441 )
442 # ensure perfect stratification with StratifiedGroupKFold
443 groups = np.arange(len(y))
444 distr = np.bincount(y) / len(y)
445
446 test_sizes = []
447 random_state = None if not shuffle else 0
448 skf = kfold(k, random_state=random_state, shuffle=shuffle)
449 for train, test in _split(skf, X, y, groups=groups):
450 assert_allclose(np.bincount(y[train]) / len(train), distr, atol=0.02)
451 assert_allclose(np.bincount(y[test]) / len(test), distr, atol=0.02)
452 test_sizes.append(len(test))
453 assert np.ptp(test_sizes) <= 1
454
455
456@pytest.mark.parametrize("shuffle", [False, True])
457@pytest.mark.parametrize("k", [4, 6, 7])
458@pytest.mark.parametrize("kfold", [StratifiedKFold, StratifiedGroupKFold])
459def test_stratified_kfold_label_invariance(k, shuffle, kfold):
460 # Check that stratified kfold gives the same indices regardless of labels
461 n_samples = 100
462 y = np.array(
463 [2] * int(0.10 * n_samples)
464 + [0] * int(0.89 * n_samples)
465 + [1] * int(0.01 * n_samples)
466 )
467 X = np.ones(len(y))
468 # ensure perfect stratification with StratifiedGroupKFold
469 groups = np.arange(len(y))
470
471 def get_splits(y):
472 random_state = None if not shuffle else 0
473 return [
474 (list(train), list(test))
475 for train, test in _split(
476 kfold(k, random_state=random_state, shuffle=shuffle),
477 X,
478 y,
479 groups=groups,
480 )
481 ]
482
483 splits_base = get_splits(y)
484 for perm in permutations([0, 1, 2]):
485 y_perm = np.take(perm, y)
486 splits_perm = get_splits(y_perm)
487 assert splits_perm == splits_base
488
489
490def test_kfold_balance():
491 # Check that KFold returns folds with balanced sizes
492 for i in range(11, 17):
493 kf = KFold(5).split(X=np.ones(i))
494 sizes = [len(test) for _, test in kf]
495
496 assert (np.max(sizes) - np.min(sizes)) <= 1
497 assert np.sum(sizes) == i
498
499
500@pytest.mark.parametrize("kfold", [StratifiedKFold, StratifiedGroupKFold])
501def test_stratifiedkfold_balance(kfold):
502 # Check that KFold returns folds with balanced sizes (only when
503 # stratification is possible)
504 # Repeat with shuffling turned off and on
505 X = np.ones(17)
506 y = [0] * 3 + [1] * 14
507 # ensure perfect stratification with StratifiedGroupKFold
508 groups = np.arange(len(y))
509
510 for shuffle in (True, False):
511 cv = kfold(3, shuffle=shuffle)
512 for i in range(11, 17):
513 skf = _split(cv, X[:i], y[:i], groups[:i])
514 sizes = [len(test) for _, test in skf]
515
516 assert (np.max(sizes) - np.min(sizes)) <= 1
517 assert np.sum(sizes) == i
518
519
520def test_shuffle_kfold():
521 # Check the indices are shuffled properly
522 kf = KFold(3)
523 kf2 = KFold(3, shuffle=True, random_state=0)
524 kf3 = KFold(3, shuffle=True, random_state=1)
525
526 X = np.ones(300)
527
528 all_folds = np.zeros(300)
529 for (tr1, te1), (tr2, te2), (tr3, te3) in zip(
530 kf.split(X), kf2.split(X), kf3.split(X)
531 ):
532 for tr_a, tr_b in combinations((tr1, tr2, tr3), 2):
533 # Assert that there is no complete overlap
534 assert len(np.intersect1d(tr_a, tr_b)) != len(tr1)
535
536 # Set all test indices in successive iterations of kf2 to 1
537 all_folds[te2] = 1
538
539 # Check that all indices are returned in the different test folds
540 assert sum(all_folds) == 300
541
542
543@pytest.mark.parametrize("kfold", [KFold, StratifiedKFold, StratifiedGroupKFold])
544def test_shuffle_kfold_stratifiedkfold_reproducibility(kfold):
545 X = np.ones(15) # Divisible by 3
546 y = [0] * 7 + [1] * 8
547 groups_1 = np.arange(len(y))
548 X2 = np.ones(16) # Not divisible by 3
549 y2 = [0] * 8 + [1] * 8
550 groups_2 = np.arange(len(y2))
551
552 # Check that when the shuffle is True, multiple split calls produce the
553 # same split when random_state is int
554 kf = kfold(3, shuffle=True, random_state=0)
555
556 np.testing.assert_equal(
557 list(_split(kf, X, y, groups_1)), list(_split(kf, X, y, groups_1))
558 )
559
560 # Check that when the shuffle is True, multiple split calls often
561 # (not always) produce different splits when random_state is
562 # RandomState instance or None
563 kf = kfold(3, shuffle=True, random_state=np.random.RandomState(0))
564 for data in zip((X, X2), (y, y2), (groups_1, groups_2)):
565 # Test if the two splits are different cv
566 for (_, test_a), (_, test_b) in zip(_split(kf, *data), _split(kf, *data)):
567 # cv.split(...) returns an array of tuples, each tuple
568 # consisting of an array with train indices and test indices
569 # Ensure that the splits for data are not same
570 # when random state is not set
571 with pytest.raises(AssertionError):
572 np.testing.assert_array_equal(test_a, test_b)
573
574
575def test_shuffle_stratifiedkfold():
576 # Check that shuffling is happening when requested, and for proper
577 # sample coverage
578 X_40 = np.ones(40)
579 y = [0] * 20 + [1] * 20
580 kf0 = StratifiedKFold(5, shuffle=True, random_state=0)
581 kf1 = StratifiedKFold(5, shuffle=True, random_state=1)
582 for (_, test0), (_, test1) in zip(kf0.split(X_40, y), kf1.split(X_40, y)):
583 assert set(test0) != set(test1)
584 check_cv_coverage(kf0, X_40, y, groups=None, expected_n_splits=5)
585
586 # Ensure that we shuffle each class's samples with different
587 # random_state in StratifiedKFold
588 # See https://github.com/scikit-learn/scikit-learn/pull/13124
589 X = np.arange(10)
590 y = [0] * 5 + [1] * 5
591 kf1 = StratifiedKFold(5, shuffle=True, random_state=0)
592 kf2 = StratifiedKFold(5, shuffle=True, random_state=1)
593 test_set1 = sorted([tuple(s[1]) for s in kf1.split(X, y)])
594 test_set2 = sorted([tuple(s[1]) for s in kf2.split(X, y)])
595 assert test_set1 != test_set2
596
597
598def test_shuffle_groupkfold():
599 # Check that shuffling is happening when requested, and for proper
600 # sample coverage
601 X = np.ones(40)
602 y = [0] * 20 + [1] * 20
603 groups = np.arange(40) // 3
604 gkf0 = GroupKFold(4, shuffle=True, random_state=0)
605 gkf1 = GroupKFold(4, shuffle=True, random_state=1)
606
607 # Check that the groups are shuffled differently
608 test_groups0 = [
609 set(groups[test_idx]) for _, test_idx in gkf0.split(X, None, groups)
610 ]
611 test_groups1 = [
612 set(groups[test_idx]) for _, test_idx in gkf1.split(X, None, groups)
613 ]
614 for g0, g1 in zip(test_groups0, test_groups1):
615 assert g0 != g1, "Test groups should differ with different random states"
616
617 # Check coverage and splits
618 check_cv_coverage(gkf0, X, y, groups, expected_n_splits=4)
619 check_cv_coverage(gkf1, X, y, groups, expected_n_splits=4)
620
621
622def test_kfold_can_detect_dependent_samples_on_digits(): # see #2372
623 # The digits samples are dependent: they are apparently grouped by authors
624 # although we don't have any information on the groups segment locations
625 # for this data. We can highlight this fact by computing k-fold cross-
626 # validation with and without shuffling: we observe that the shuffling case
627 # wrongly makes the IID assumption and is therefore too optimistic: it
628 # estimates a much higher accuracy (around 0.93) than that the non
629 # shuffling variant (around 0.81).
630
631 X, y = digits.data[:600], digits.target[:600]
632 model = SVC(C=10, gamma=0.005)
633
634 n_splits = 3
635
636 cv = KFold(n_splits=n_splits, shuffle=False)
637 mean_score = cross_val_score(model, X, y, cv=cv).mean()
638 assert 0.92 > mean_score
639 assert mean_score > 0.80
640
641 # Shuffling the data artificially breaks the dependency and hides the
642 # overfitting of the model with regards to the writing style of the authors
643 # by yielding a seriously overestimated score:
644
645 cv = KFold(n_splits, shuffle=True, random_state=0)
646 mean_score = cross_val_score(model, X, y, cv=cv).mean()
647 assert mean_score > 0.92
648
649 cv = KFold(n_splits, shuffle=True, random_state=1)
650 mean_score = cross_val_score(model, X, y, cv=cv).mean()
651 assert mean_score > 0.92
652
653 # Similarly, StratifiedKFold should try to shuffle the data as little
654 # as possible (while respecting the balanced class constraints)
655 # and thus be able to detect the dependency by not overestimating
656 # the CV score either. As the digits dataset is approximately balanced
657 # the estimated mean score is close to the score measured with
658 # non-shuffled KFold
659
660 cv = StratifiedKFold(n_splits)
661 mean_score = cross_val_score(model, X, y, cv=cv).mean()
662 assert 0.94 > mean_score
663 assert mean_score > 0.80
664
665
666def test_stratified_group_kfold_trivial():
667 sgkf = StratifiedGroupKFold(n_splits=3)
668 # Trivial example - groups with the same distribution
669 y = np.array([1] * 6 + [0] * 12)
670 X = np.ones_like(y).reshape(-1, 1)
671 groups = np.asarray((1, 2, 3, 4, 5, 6, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6))
672 distr = np.bincount(y) / len(y)
673 test_sizes = []
674 for train, test in sgkf.split(X, y, groups):
675 # check group constraint
676 assert np.intersect1d(groups[train], groups[test]).size == 0
677 # check y distribution
678 assert_allclose(np.bincount(y[train]) / len(train), distr, atol=0.02)
679 assert_allclose(np.bincount(y[test]) / len(test), distr, atol=0.02)
680 test_sizes.append(len(test))
681 assert np.ptp(test_sizes) <= 1
682
683
684def test_stratified_group_kfold_approximate():
685 # Not perfect stratification (even though it is possible) because of
686 # iteration over groups
687 sgkf = StratifiedGroupKFold(n_splits=3)
688 y = np.array([1] * 6 + [0] * 12)
689 X = np.ones_like(y).reshape(-1, 1)
690 groups = np.array([1, 2, 3, 3, 4, 4, 1, 1, 2, 2, 3, 4, 5, 5, 5, 6, 6, 6])
691 expected = np.asarray([[0.833, 0.166], [0.666, 0.333], [0.5, 0.5]])
692 test_sizes = []
693 for (train, test), expect_dist in zip(sgkf.split(X, y, groups), expected):
694 # check group constraint
695 assert np.intersect1d(groups[train], groups[test]).size == 0
696 split_dist = np.bincount(y[test]) / len(test)
697 assert_allclose(split_dist, expect_dist, atol=0.001)
698 test_sizes.append(len(test))
699 assert np.ptp(test_sizes) <= 1
700
701
702@pytest.mark.parametrize(
703 "y, groups, expected",
704 [
705 (
706 np.array([0] * 6 + [1] * 6),
707 np.array([1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6]),
708 np.asarray([[0.5, 0.5], [0.5, 0.5], [0.5, 0.5]]),
709 ),
710 (
711 np.array([0] * 9 + [1] * 3),
712 np.array([1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 5, 6]),
713 np.asarray([[0.75, 0.25], [0.75, 0.25], [0.75, 0.25]]),
714 ),
715 ],
716)
717def test_stratified_group_kfold_homogeneous_groups(y, groups, expected):
718 sgkf = StratifiedGroupKFold(n_splits=3)
719 X = np.ones_like(y).reshape(-1, 1)
720 for (train, test), expect_dist in zip(sgkf.split(X, y, groups), expected):
721 # check group constraint
722 assert np.intersect1d(groups[train], groups[test]).size == 0
723 split_dist = np.bincount(y[test]) / len(test)
724 assert_allclose(split_dist, expect_dist, atol=0.001)
725
726
727@pytest.mark.parametrize("cls_distr", [(0.4, 0.6), (0.3, 0.7), (0.2, 0.8), (0.8, 0.2)])
728@pytest.mark.parametrize("n_groups", [5, 30, 70])
729def test_stratified_group_kfold_against_group_kfold(cls_distr, n_groups):
730 # Check that given sufficient amount of samples StratifiedGroupKFold
731 # produces better stratified folds than regular GroupKFold
732 n_splits = 5
733 sgkf = StratifiedGroupKFold(n_splits=n_splits)
734 gkf = GroupKFold(n_splits=n_splits)
735 rng = np.random.RandomState(0)
736 n_points = 1000
737 y = rng.choice(2, size=n_points, p=cls_distr)
738 X = np.ones_like(y).reshape(-1, 1)
739 g = rng.choice(n_groups, n_points)
740 sgkf_folds = sgkf.split(X, y, groups=g)
741 gkf_folds = gkf.split(X, y, groups=g)
742 sgkf_entr = 0
743 gkf_entr = 0
744 for (sgkf_train, sgkf_test), (_, gkf_test) in zip(sgkf_folds, gkf_folds):
745 # check group constraint
746 assert np.intersect1d(g[sgkf_train], g[sgkf_test]).size == 0
747 sgkf_distr = np.bincount(y[sgkf_test]) / len(sgkf_test)
748 gkf_distr = np.bincount(y[gkf_test]) / len(gkf_test)
749 sgkf_entr += stats.entropy(sgkf_distr, qk=cls_distr)
750 gkf_entr += stats.entropy(gkf_distr, qk=cls_distr)
751 sgkf_entr /= n_splits
752 gkf_entr /= n_splits
753 assert sgkf_entr <= gkf_entr
754
755
756def test_shuffle_split():
757 ss1 = ShuffleSplit(test_size=0.2, random_state=0).split(X)
758 ss2 = ShuffleSplit(test_size=2, random_state=0).split(X)
759 ss3 = ShuffleSplit(test_size=np.int32(2), random_state=0).split(X)
760 ss4 = ShuffleSplit(test_size=2, random_state=0).split(X)
761 for t1, t2, t3, t4 in zip(ss1, ss2, ss3, ss4):
762 assert_array_equal(t1[0], t2[0])
763 assert_array_equal(t2[0], t3[0])
764 assert_array_equal(t3[0], t4[0])
765 assert_array_equal(t1[1], t2[1])
766 assert_array_equal(t2[1], t3[1])
767 assert_array_equal(t3[1], t4[1])
768
769
770@pytest.mark.parametrize("split_class", [ShuffleSplit, StratifiedShuffleSplit])
771@pytest.mark.parametrize(
772 "train_size, exp_train, exp_test", [(None, 9, 1), (8, 8, 2), (0.8, 8, 2)]
773)
774def test_shuffle_split_default_test_size(split_class, train_size, exp_train, exp_test):
775 # Check that the default value has the expected behavior, i.e. 0.1 if both
776 # unspecified or complement train_size unless both are specified.
777 X = np.ones(10)
778 y = np.ones(10)
779
780 X_train, X_test = next(split_class(train_size=train_size).split(X, y))
781
782 assert len(X_train) == exp_train
783 assert len(X_test) == exp_test
784
785
786@pytest.mark.parametrize(
787 "train_size, exp_train, exp_test", [(None, 8, 2), (7, 7, 3), (0.7, 7, 3)]
788)
789def test_group_shuffle_split_default_test_size(train_size, exp_train, exp_test):
790 # Check that the default value has the expected behavior, i.e. 0.2 if both
791 # unspecified or complement train_size unless both are specified.
792 X = np.ones(10)
793 y = np.ones(10)
794 groups = range(10)
795
796 X_train, X_test = next(GroupShuffleSplit(train_size=train_size).split(X, y, groups))
797
798 assert len(X_train) == exp_train
799 assert len(X_test) == exp_test
800
801
802def test_stratified_shuffle_split_init():
803 X = np.arange(7)
804 y = np.asarray([0, 1, 1, 1, 2, 2, 2])
805 # Check that error is raised if there is a class with only one sample
806 with pytest.raises(ValueError):
807 next(StratifiedShuffleSplit(3, test_size=0.2).split(X, y))
808
809 # Check that error is raised if the test set size is smaller than n_classes
810 with pytest.raises(ValueError):
811 next(StratifiedShuffleSplit(3, test_size=2).split(X, y))
812 # Check that error is raised if the train set size is smaller than
813 # n_classes
814 with pytest.raises(ValueError):
815 next(StratifiedShuffleSplit(3, test_size=3, train_size=2).split(X, y))
816
817 X = np.arange(9)
818 y = np.asarray([0, 0, 0, 1, 1, 1, 2, 2, 2])
819
820 # Train size or test size too small
821 with pytest.raises(ValueError):
822 next(StratifiedShuffleSplit(train_size=2).split(X, y))
823 with pytest.raises(ValueError):
824 next(StratifiedShuffleSplit(test_size=2).split(X, y))
825
826
827def test_stratified_shuffle_split_respects_test_size():
828 y = np.array([0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2])
829 test_size = 5
830 train_size = 10
831 sss = StratifiedShuffleSplit(
832 6, test_size=test_size, train_size=train_size, random_state=0
833 ).split(np.ones(len(y)), y)
834 for train, test in sss:
835 assert len(train) == train_size
836 assert len(test) == test_size
837
838
839def test_stratified_shuffle_split_iter():
840 ys = [
841 np.array([1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3, 3]),
842 np.array([0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3]),
843 np.array([0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2] * 2),
844 np.array([1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4]),
845 np.array([-1] * 800 + [1] * 50),
846 np.concatenate([[i] * (100 + i) for i in range(11)]),
847 [1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3, 3],
848 ["1", "1", "1", "1", "2", "2", "2", "3", "3", "3", "3", "3"],
849 ]
850
851 for y in ys:
852 sss = StratifiedShuffleSplit(6, test_size=0.33, random_state=0).split(
853 np.ones(len(y)), y
854 )
855 y = np.asanyarray(y) # To make it indexable for y[train]
856 # this is how test-size is computed internally
857 # in _validate_shuffle_split
858 test_size = np.ceil(0.33 * len(y))
859 train_size = len(y) - test_size
860 for train, test in sss:
861 assert_array_equal(np.unique(y[train]), np.unique(y[test]))
862 # Checks if folds keep classes proportions
863 p_train = np.bincount(np.unique(y[train], return_inverse=True)[1]) / float(
864 len(y[train])
865 )
866 p_test = np.bincount(np.unique(y[test], return_inverse=True)[1]) / float(
867 len(y[test])
868 )
869 assert_array_almost_equal(p_train, p_test, 1)
870 assert len(train) + len(test) == y.size
871 assert len(train) == train_size
872 assert len(test) == test_size
873 assert_array_equal(np.intersect1d(train, test), [])
874
875
876def test_stratified_shuffle_split_even():
877 # Test the StratifiedShuffleSplit, indices are drawn with a
878 # equal chance
879 n_folds = 5
880 n_splits = 1000
881
882 def assert_counts_are_ok(idx_counts, p):
883 # Here we test that the distribution of the counts
884 # per index is close enough to a binomial
885 threshold = 0.05 / n_splits
886 bf = stats.binom(n_splits, p)
887 for count in idx_counts:
888 prob = bf.pmf(count)
889 assert prob > threshold, (
890 "An index is not drawn with chance corresponding to even draws"
891 )
892
893 for n_samples in (6, 22):
894 groups = np.array((n_samples // 2) * [0, 1])
895 splits = StratifiedShuffleSplit(
896 n_splits=n_splits, test_size=1.0 / n_folds, random_state=0
897 )
898
899 train_counts = [0] * n_samples
900 test_counts = [0] * n_samples
901 n_splits_actual = 0
902 for train, test in splits.split(X=np.ones(n_samples), y=groups):
903 n_splits_actual += 1
904 for counter, ids in [(train_counts, train), (test_counts, test)]:
905 for id in ids:
906 counter[id] += 1
907 assert n_splits_actual == n_splits
908
909 n_train, n_test = _validate_shuffle_split(
910 n_samples, test_size=1.0 / n_folds, train_size=1.0 - (1.0 / n_folds)
911 )
912
913 assert len(train) == n_train
914 assert len(test) == n_test
915 assert len(set(train).intersection(test)) == 0
916
917 group_counts = np.unique(groups)
918 assert splits.test_size == 1.0 / n_folds
919 assert n_train + n_test == len(groups)
920 assert len(group_counts) == 2
921 ex_test_p = float(n_test) / n_samples
922 ex_train_p = float(n_train) / n_samples
923
924 assert_counts_are_ok(train_counts, ex_train_p)
925 assert_counts_are_ok(test_counts, ex_test_p)
926
927
928def test_stratified_shuffle_split_overlap_train_test_bug():
929 # See https://github.com/scikit-learn/scikit-learn/issues/6121 for
930 # the original bug report
931 y = [0, 1, 2, 3] * 3 + [4, 5] * 5
932 X = np.ones_like(y)
933
934 sss = StratifiedShuffleSplit(n_splits=1, test_size=0.5, random_state=0)
935
936 train, test = next(sss.split(X=X, y=y))
937
938 # no overlap
939 assert_array_equal(np.intersect1d(train, test), [])
940
941 # complete partition
942 assert_array_equal(np.union1d(train, test), np.arange(len(y)))
943
944
945def test_stratified_shuffle_split_multilabel():
946 # fix for issue 9037
947 for y in [
948 np.array([[0, 1], [1, 0], [1, 0], [0, 1]]),
949 np.array([[0, 1], [1, 1], [1, 1], [0, 1]]),
950 ]:
951 X = np.ones_like(y)
952 sss = StratifiedShuffleSplit(n_splits=1, test_size=0.5, random_state=0)
953 train, test = next(sss.split(X=X, y=y))
954 y_train = y[train]
955 y_test = y[test]
956
957 # no overlap
958 assert_array_equal(np.intersect1d(train, test), [])
959
960 # complete partition
961 assert_array_equal(np.union1d(train, test), np.arange(len(y)))
962
963 # correct stratification of entire rows
964 # (by design, here y[:, 0] uniquely determines the entire row of y)
965 expected_ratio = np.mean(y[:, 0])
966 assert expected_ratio == np.mean(y_train[:, 0])
967 assert expected_ratio == np.mean(y_test[:, 0])
968
969
970def test_stratified_shuffle_split_multilabel_many_labels():
971 # fix in PR #9922: for multilabel data with > 1000 labels, str(row)
972 # truncates with an ellipsis for elements in positions 4 through
973 # len(row) - 4, so labels were not being correctly split using the powerset
974 # method for transforming a multilabel problem to a multiclass one; this
975 # test checks that this problem is fixed.
976 row_with_many_zeros = [1, 0, 1] + [0] * 1000 + [1, 0, 1]
977 row_with_many_ones = [1, 0, 1] + [1] * 1000 + [1, 0, 1]
978 y = np.array([row_with_many_zeros] * 10 + [row_with_many_ones] * 100)
979 X = np.ones_like(y)
980
981 sss = StratifiedShuffleSplit(n_splits=1, test_size=0.5, random_state=0)
982 train, test = next(sss.split(X=X, y=y))
983 y_train = y[train]
984 y_test = y[test]
985
986 # correct stratification of entire rows
987 # (by design, here y[:, 4] uniquely determines the entire row of y)
988 expected_ratio = np.mean(y[:, 4])
989 assert expected_ratio == np.mean(y_train[:, 4])
990 assert expected_ratio == np.mean(y_test[:, 4])
991
992
993def test_predefinedsplit_with_kfold_split():
994 # Check that PredefinedSplit can reproduce a split generated by Kfold.
995 folds = np.full(10, -1.0)
996 kf_train = []
997 kf_test = []
998 for i, (train_ind, test_ind) in enumerate(KFold(5, shuffle=True).split(X)):
999 kf_train.append(train_ind)
1000 kf_test.append(test_ind)
1001 folds[test_ind] = i
1002 ps = PredefinedSplit(folds)
1003 # n_splits is simply the no of unique folds
1004 assert len(np.unique(folds)) == ps.get_n_splits()
1005 ps_train, ps_test = zip(*ps.split())
1006 assert_array_equal(ps_train, kf_train)
1007 assert_array_equal(ps_test, kf_test)
1008
1009
1010def test_group_shuffle_split():
1011 for groups_i in test_groups:
1012 X = y = np.ones(len(groups_i))
1013 n_splits = 6
1014 test_size = 1.0 / 3
1015 slo = GroupShuffleSplit(n_splits, test_size=test_size, random_state=0)
1016
1017 # Make sure the repr works
1018 repr(slo)
1019
1020 # Test that the length is correct
1021 assert slo.get_n_splits(X, y, groups=groups_i) == n_splits
1022
1023 l_unique = np.unique(groups_i)
1024 l = np.asarray(groups_i)
1025
1026 for train, test in slo.split(X, y, groups=groups_i):
1027 # First test: no train group is in the test set and vice versa
1028 l_train_unique = np.unique(l[train])
1029 l_test_unique = np.unique(l[test])
1030 assert not np.any(np.isin(l[train], l_test_unique))
1031 assert not np.any(np.isin(l[test], l_train_unique))
1032
1033 # Second test: train and test add up to all the data
1034 assert l[train].size + l[test].size == l.size
1035
1036 # Third test: train and test are disjoint
1037 assert_array_equal(np.intersect1d(train, test), [])
1038
1039 # Fourth test:
1040 # unique train and test groups are correct, +- 1 for rounding error
1041 assert abs(len(l_test_unique) - round(test_size * len(l_unique))) <= 1
1042 assert (
1043 abs(len(l_train_unique) - round((1.0 - test_size) * len(l_unique))) <= 1
1044 )
1045
1046
1047def test_leave_one_p_group_out():
1048 logo = LeaveOneGroupOut()
1049 lpgo_1 = LeavePGroupsOut(n_groups=1)
1050 lpgo_2 = LeavePGroupsOut(n_groups=2)
1051
1052 # Make sure the repr works
1053 assert repr(logo) == "LeaveOneGroupOut()"
1054 assert repr(lpgo_1) == "LeavePGroupsOut(n_groups=1)"
1055 assert repr(lpgo_2) == "LeavePGroupsOut(n_groups=2)"
1056 assert repr(LeavePGroupsOut(n_groups=3)) == "LeavePGroupsOut(n_groups=3)"
1057
1058 for j, (cv, p_groups_out) in enumerate(((logo, 1), (lpgo_1, 1), (lpgo_2, 2))):
1059 for i, groups_i in enumerate(test_groups):
1060 n_groups = len(np.unique(groups_i))
1061 n_splits = n_groups if p_groups_out == 1 else n_groups * (n_groups - 1) / 2
1062 X = y = np.ones(len(groups_i))
1063
1064 # Test that the length is correct
1065 assert cv.get_n_splits(X, y, groups=groups_i) == n_splits
1066
1067 groups_arr = np.asarray(groups_i)
1068
1069 # Split using the original list / array / list of string groups_i
1070 for train, test in cv.split(X, y, groups=groups_i):
1071 # First test: no train group is in the test set and vice versa
1072 assert_array_equal(
1073 np.intersect1d(groups_arr[train], groups_arr[test]).tolist(), []
1074 )
1075
1076 # Second test: train and test add up to all the data
1077 assert len(train) + len(test) == len(groups_i)
1078
1079 # Third test:
1080 # The number of groups in test must be equal to p_groups_out
1081 assert np.unique(groups_arr[test]).shape[0], p_groups_out
1082
1083 # check get_n_splits() with dummy parameters
1084 assert logo.get_n_splits(None, None, ["a", "b", "c", "b", "c"]) == 3
1085 assert logo.get_n_splits(groups=[1.0, 1.1, 1.0, 1.2]) == 3
1086 assert lpgo_2.get_n_splits(None, None, np.arange(4)) == 6
1087 assert lpgo_1.get_n_splits(groups=np.arange(4)) == 4
1088
1089 # raise ValueError if a `groups` parameter is illegal
1090 with pytest.raises(ValueError):
1091 logo.get_n_splits(None, None, [0.0, np.nan, 0.0])
1092 with pytest.raises(ValueError):
1093 lpgo_2.get_n_splits(None, None, [0.0, np.inf, 0.0])
1094
1095 msg = "The 'groups' parameter should not be None."
1096 with pytest.raises(ValueError, match=msg):
1097 logo.get_n_splits(None, None, None)
1098 with pytest.raises(ValueError, match=msg):
1099 lpgo_1.get_n_splits(None, None, None)
1100
1101
1102def test_leave_group_out_changing_groups():
1103 # Check that LeaveOneGroupOut and LeavePGroupsOut work normally if
1104 # the groups variable is changed before calling split
1105 groups = np.array([0, 1, 2, 1, 1, 2, 0, 0])
1106 X = np.ones(len(groups))
1107 groups_changing = np.array(groups, copy=True)
1108 lolo = LeaveOneGroupOut().split(X, groups=groups)
1109 lolo_changing = LeaveOneGroupOut().split(X, groups=groups)
1110 lplo = LeavePGroupsOut(n_groups=2).split(X, groups=groups)
1111 lplo_changing = LeavePGroupsOut(n_groups=2).split(X, groups=groups)
1112 groups_changing[:] = 0
1113 for llo, llo_changing in [(lolo, lolo_changing), (lplo, lplo_changing)]:
1114 for (train, test), (train_chan, test_chan) in zip(llo, llo_changing):
1115 assert_array_equal(train, train_chan)
1116 assert_array_equal(test, test_chan)
1117
1118 # n_splits = no of 2 (p) group combinations of the unique groups = 3C2 = 3
1119 assert 3 == LeavePGroupsOut(n_groups=2).get_n_splits(X, y=X, groups=groups)
1120 # n_splits = no of unique groups (C(uniq_lbls, 1) = n_unique_groups)
1121 assert 3 == LeaveOneGroupOut().get_n_splits(X, y=X, groups=groups)
1122
1123
1124def test_leave_group_out_order_dependence():
1125 # Check that LeaveOneGroupOut orders the splits according to the index
1126 # of the group left out.
1127 groups = np.array([2, 2, 0, 0, 1, 1])
1128 X = np.ones(len(groups))
1129
1130 splits = iter(LeaveOneGroupOut().split(X, groups=groups))
1131
1132 expected_indices = [
1133 ([0, 1, 4, 5], [2, 3]),
1134 ([0, 1, 2, 3], [4, 5]),
1135 ([2, 3, 4, 5], [0, 1]),
1136 ]
1137
1138 for expected_train, expected_test in expected_indices:
1139 train, test = next(splits)
1140 assert_array_equal(train, expected_train)
1141 assert_array_equal(test, expected_test)
1142
1143
1144def test_leave_one_p_group_out_error_on_fewer_number_of_groups():
1145 X = y = groups = np.ones(0)
1146 msg = re.escape("Found array with 0 sample(s)")
1147 with pytest.raises(ValueError, match=msg):
1148 next(LeaveOneGroupOut().split(X, y, groups))
1149
1150 X = y = groups = np.ones(1)
1151 msg = re.escape(
1152 f"The groups parameter contains fewer than 2 unique groups ({groups})."
1153 " LeaveOneGroupOut expects at least 2."
1154 )
1155 with pytest.raises(ValueError, match=msg):
1156 next(LeaveOneGroupOut().split(X, y, groups))
1157
1158 X = y = groups = np.ones(1)
1159 msg = re.escape(
1160 "The groups parameter contains fewer than (or equal to) n_groups "
1161 f"(3) numbers of unique groups ({groups}). LeavePGroupsOut expects "
1162 "that at least n_groups + 1 (4) unique groups "
1163 "be present"
1164 )
1165 with pytest.raises(ValueError, match=msg):
1166 next(LeavePGroupsOut(n_groups=3).split(X, y, groups))
1167
1168 X = y = groups = np.arange(3)
1169 msg = re.escape(
1170 "The groups parameter contains fewer than (or equal to) n_groups "
1171 f"(3) numbers of unique groups ({groups}). LeavePGroupsOut expects "
1172 "that at least n_groups + 1 (4) unique groups "
1173 "be present"
1174 )
1175 with pytest.raises(ValueError, match=msg):
1176 next(LeavePGroupsOut(n_groups=3).split(X, y, groups))
1177
1178
1179def test_repeated_cv_value_errors():
1180 # n_repeats is not integer or <= 0
1181 for cv in (RepeatedKFold, RepeatedStratifiedKFold):
1182 with pytest.raises(ValueError):
1183 cv(n_repeats=0)
1184 with pytest.raises(ValueError):
1185 cv(n_repeats=1.5)
1186
1187
1188@pytest.mark.parametrize("RepeatedCV", [RepeatedKFold, RepeatedStratifiedKFold])
1189def test_repeated_cv_repr(RepeatedCV):
1190 n_splits, n_repeats = 2, 6
1191 repeated_cv = RepeatedCV(n_splits=n_splits, n_repeats=n_repeats)
1192 repeated_cv_repr = "{}(n_repeats=6, n_splits=2, random_state=None)".format(
1193 repeated_cv.__class__.__name__
1194 )
1195 assert repeated_cv_repr == repr(repeated_cv)
1196
1197
1198def test_repeated_kfold_determinstic_split():
1199 X = [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]
1200 random_state = 258173307
