Single train/test holdout splitting.
Holdout evaluation carves the data into one train part and one test part instead of rotating over folds. It costs a single fit, so it is the right default for a quick sanity check, for a large dataset where one split is already a reliable estimate, or for carving off a final untouched test set before any cross-validation happens.
This module offers two shapes of the same idea:
train_test_split -- an eager helper that slices the arrays you hand it and returns the pieces directly. HoldoutSplit and StratifiedHoldoutSplit -- lazy splitters implementing the BaseSplitter protocol, so they can be dropped into cross_val_score or any other code that expects split()/get_n_splits(). Both yield exactly one pair.
Use the stratified variants when y is a class label; the unstratified ones can leave a rare class entirely out of the training half. When one split is too noisy to trust, move to KFold or ShuffleSplit.
Classes
Single, unstratified train/test holdout exposed as a splitter.
split() yields exactly one (train_index, test_index) pair and get_n_splits() always returns 1, so this class plugs a plain holdout into any code written against the cross-validator protocol.
The first int(n_samples * test_size) positions of the (optionally shuffled) index vector become the test set and the rest become the training set. Labels are ignored, so on class-sorted data an unshuffled holdout can put an entire class on one side; use StratifiedHoldoutSplit for classification.
__init__( self, test_size: float = 0.3, shuffle: bool = True, random_state: Optional[int] = None, )
Parameters
test_size
train_test_split, an absolute count is not accepted here.
shuffle
shuffle=False the test set is the leading block of rows.
random_state
shuffle=True.
Notes
Layout for test_size=0.3 over 10 samples, one column per position of the (possibly shuffled) index vector:
position: 0 1 2 3 4 5 6 7 8 9 split 0: T T T . . . . . . . T = test, . = train
See Also
>>> import numpy as np
>>> from tuiml.evaluation.splitting import HoldoutSplit
>>> X = np.arange(20).reshape(10, 2)
>>> cv = HoldoutSplit(test_size=0.3, random_state=0)
>>> print(cv.get_n_splits())
1
>>> for train_idx, test_idx in cv.split(X):
... print(len(train_idx), len(test_idx), sorted(test_idx.tolist()))
7 3 [2, 4, 8]
Methods
split
(self, X: np.ndarray, y: Optional[np.ndarray]=None, groups: Optional[np.ndarray]=None) -> Iterator[Tuple[np.ndarray, np.ndarray]]
split
(self, X: np.ndarray, y: Optional[np.ndarray]=None, groups: Optional[np.ndarray]=None) -> Iterator[Tuple[np.ndarray, np.ndarray]]
Yield the single train/test index pair of an unstratified holdout.
Parameters
X
y
groups
Yields
train_index
test_index
n_test == int(n_samples * test_size). Exactly one pair is produced.
Single, stratified train/test holdout exposed as a splitter.
HoldoutSplit, split() yields exactly one (train_index, test_index) pair and get_n_splits() returns 1. The difference is that y is required and the holdout is taken within each class: from every class, max(1, int(n_class * test_size)) samples go to the test part. Both parts therefore keep roughly the class distribution of y, and every class is guaranteed at least one test sample even when it is tiny.__init__( self, test_size: float = 0.3, shuffle: bool = True, random_state: Optional[int] = None, )
Parameters
test_size
shuffle
shuffle=False the test part of each class is its leading block.
random_state
shuffle=True.
Notes
10 samples, classes 0 0 0 0 0 1 1 1 1 1, test_size=0.4. Two samples are drawn from each class rather than four from one end:
class: 0 0 0 0 0 1 1 1 1 1 split 0: T T . . . T T . . . T = test, . = train
Because the per-class count is rounded up with max(1, ...), the total test size can slightly exceed test_size * n_samples when there are many small classes.
See Also
>>> import numpy as np
>>> from tuiml.evaluation.splitting import StratifiedHoldoutSplit
>>> X = np.arange(20).reshape(10, 2)
>>> y = np.array([0, 0, 0, 0, 0, 1, 1, 1, 1, 1])
>>> cv = StratifiedHoldoutSplit(test_size=0.4, random_state=0)
>>> print(cv.get_n_splits())
1
>>> for train_idx, test_idx in cv.split(X, y):
... print(len(train_idx), len(test_idx), np.bincount(y[test_idx]).tolist())
6 4 [2, 2]
Methods
split
(self, X: np.ndarray, y: Optional[np.ndarray]=None, groups: Optional[np.ndarray]=None) -> Iterator[Tuple[np.ndarray, np.ndarray]]
split
(self, X: np.ndarray, y: Optional[np.ndarray]=None, groups: Optional[np.ndarray]=None) -> Iterator[Tuple[np.ndarray, np.ndarray]]
Yield the single train/test index pair of a stratified holdout.
Parameters
X
y
groups
Yields
train_index
test_index
y and at least one sample per class. Exactly one pair is produced.
Raises
ValueError
y is None.
Functions
Split one or more equal-length arrays into a single train and test part.
X, y and any extra arrays (sample weights, ids). The output is flattened: for inputs (X, y) the return order is X_train, X_test, y_train, y_test.Parameters
*arrays
test_size
(0, 1), the proportion of rows held out for test. If int, the absolute number of test rows. Defaults to 0.25 when neither test_size nor train_size is given. The resulting count is clipped to [1, n_samples - 1] so neither part is ever empty.
train_size
test_size is None.
shuffle
shuffle=False and no stratify, the test part is the leading block of rows, which is only meaningful if the row order already carries meaning.
stratify
y). When given, the split is done per class so both parts keep roughly the class proportions of stratify, and at least one row of each class lands in the test part.
random_state
Returns
splits
2 * len(arrays) arrays, alternating train part then test part for each input array in the order given.
See Also
>>> import numpy as np
>>> from tuiml.evaluation.splitting import train_test_split
>>> X = np.arange(20).reshape(10, 2)
>>> y = np.array([0, 0, 0, 0, 0, 1, 1, 1, 1, 1])
>>> X_train, X_test, y_train, y_test = train_test_split(
... X, y, test_size=0.3, random_state=0
... )
>>> print(X_train.shape, X_test.shape)
(7, 2) (3, 2)
>>> print(sorted(y_test.tolist()))
[0, 0, 1]
Passing stratify=y forces both parts to mirror the class balance:
>>> X_train, X_test, y_train, y_test = train_test_split(
... X, y, test_size=0.4, stratify=y, random_state=0
... )
>>> print(np.bincount(y_test).tolist(), np.bincount(y_train).tolist())
[2, 2] [3, 3]