deepbox/preprocess
Splitting & Cross-Validation
Train-test splitting and cross-validation splitters, including grouped, stratified, repeated and time-series strategies.
- Model validation
Overview
trainTestSplit(X, y, options)returns[XTrain, XTest, yTrain, yTest].testSizedefaults to 0.25 andshuffletotrue. WithoutrandomStatethe split changes on every call.stratifykeeps class proportions and needs at least 2 samples per class.- Splitters (
KFold,StratifiedKFold,GroupKFold,TimeSeriesSplitand others) havesplit(X, y?, groups?), which returns an array of{ trainIndex, testIndex }rather than a generator, andgetNSplits().KFolddefaults tonSplits: 5and no shuffling. - To cross-validate an estimator, use
crossValScore,crossValidateorGridSearchCVfromdeepbox/ml. They take a fold count, not a splitter object. - Rows are taken along the first axis, so
Xmay have more than two dimensions.
GroupKFold
Group K-Fold cross-validator.
Guarantees that no group appears in both the train and the test set of a split. Without shuffling, groups are assigned largest first to the fold that currently holds the fewest samples (the same greedy rule as scikit-learn), so fold sizes stay as balanced as the group sizes allow. With shuffle, the groups are permuted and cut into nSplits chunks of nearly equal group count. Train and test indices are returned in ascending order.
import { GroupKFold } from 'deepbox/preprocess';import { tensor } from 'deepbox/ndarray'; const gkf = new GroupKFold({ nSplits: 2 });const X = tensor([[1], [2], [3], [4]]);const splits = gkf.split(X, undefined, tensor([0, 0, 1, 1]));Members (3)
constructor(options?: { nSplits?: number; shuffle?: boolean; randomState?: number; })split(X: Tensor, _y: Tensor | undefined, groups: Tensor | readonly Label[]): SplitResult[]Generate train/test indices for every fold.
getNSplits(): numberNumber of folds.
GroupShuffleSplit
Shuffle-Group(s)-Out cross-validation iterator.
Provides randomized train/test indices to split data by groups. Ensures that the same group is not in both test and train sets. testSize and trainSize count groups, not samples: fractions are taken of the number of distinct groups, integers are absolute group counts. With neither given, 20% of the groups form the test set. Train and test indices are returned in ascending order. Iteration i shuffles with a seed derived from randomState and i (a hash, so that neighbouring seeds do not share iterations).
import { GroupShuffleSplit } from 'deepbox/preprocess';import { tensor } from 'deepbox/ndarray'; const gss = new GroupShuffleSplit({ nSplits: 5, testSize: 0.2, randomState: 42 });const X = tensor([[1], [2], [3], [4], [5], [6]]);const splits = gss.split(X, undefined, [0, 0, 1, 1, 2, 2]);Members (3)
constructor(options?: { nSplits?: number; testSize?: number; trainSize?: number; randomState?: number; })split(X: Tensor, _y?: Tensor, groups?: Tensor | readonly Label[]): SplitResult[]Generate train/test split indices based on groups.
getNSplits(): numberNumber of re-shuffling iterations.
KFold
K-Folds cross-validator.
Splits the samples into nSplits consecutive folds (the first n % nSplits folds hold one extra sample). Each fold is used once as the test set while the remaining folds form the train set. Train and test indices are always returned in ascending order, also when shuffle is enabled.
import { KFold } from 'deepbox/preprocess';import { tensor } from 'deepbox/ndarray'; const kf = new KFold({ nSplits: 2 });const splits = kf.split(tensor([[1], [2], [3], [4]]));// splits[0]: train=[2,3], test=[0,1]Members (3)
constructor(options?: { nSplits?: number; shuffle?: boolean; randomState?: number; })split(X: Tensor): SplitResult[]Generate train/test indices for every fold.
getNSplits(): numberNumber of folds.
LeaveOneGroupOut
Leave-One-Group-Out cross-validator.
Each distinct group is used once as the test set, in ascending order of the group labels, and the other groups form the train set. At least two groups are required. Train and test indices are returned in ascending order.
import { LeaveOneGroupOut } from 'deepbox/preprocess';import { tensor } from 'deepbox/ndarray'; const X = tensor([[1], [2], [3], [4]]);const splits = new LeaveOneGroupOut().split(X, undefined, tensor([0, 0, 1, 2]));// 3 splits: test=[0,1], test=[2], test=[3]Members (2)
split(X: Tensor, _y: Tensor | undefined, groups: Tensor | readonly Label[]): SplitResult[]Generate one split per group.
getNSplits(groups: Tensor | readonly Label[]): numberNumber of splits, which equals the number of distinct groups.
LeaveOneOut
Leave-One-Out cross-validator.
Each sample is used once as a test set of size one. This materialises n * (n - 1) train indices, so very large n is rejected with a MemoryError.
import { LeaveOneOut } from 'deepbox/preprocess';import { tensor } from 'deepbox/ndarray'; const splits = new LeaveOneOut().split(tensor([[1], [2], [3]]));// 3 splits, each with one test indexMembers (2)
split(X: Tensor): SplitResult[]Generate one split per sample.
getNSplits(X: Tensor): numberNumber of splits, which equals the number of samples.
LeavePGroupsOut
Leave-P-Groups-Out cross-validator.
Every combination of nGroups distinct groups is used once as the test set, in lexicographic order of the ascending group labels. The number of splits is C(g, nGroups) for g distinct groups, so the splits are only materialised when they number at most 100,000 and their indices fit a memory budget. Train and test indices are returned in ascending order.
import { LeavePGroupsOut } from 'deepbox/preprocess';import { tensor } from 'deepbox/ndarray'; const X = tensor([[1], [2], [3], [4], [5]]);const splits = new LeavePGroupsOut(2).split(X, undefined, tensor([0, 0, 1, 1, 2]));// C(3, 2) = 3 splitsMembers (3)
constructor(nGroups: number)split(X: Tensor, _y: Tensor | undefined, groups: Tensor | readonly Label[]): SplitResult[]Generate train/test indices for every combination of
nGroupstest groups.getNSplits(groups: Tensor | readonly Label[]): numberNumber of splits,
C(g, nGroups)forgdistinct groups.
LeavePOut
Leave-P-Out cross-validator.
Every combination of p samples is used once as the test set. The number of splits is C(n, p), so the splits are only materialised when they number at most 100,000 and their indices fit a memory budget.
import { LeavePOut } from 'deepbox/preprocess';import { tensor } from 'deepbox/ndarray'; const lpo = new LeavePOut(2);const splits = lpo.split(tensor([[1], [2], [3], [4]])); // C(4, 2) = 6 splitsMembers (3)
constructor(p: number)split(X: Tensor): SplitResult[]Generate train/test indices for every combination of
ptest samples, in lexicographic order of the test indices.getNSplits(X: Tensor): numberNumber of splits,
C(n, p).
PredefinedSplit
Predefined split cross-validator.
testFold[i] names the fold in which sample i is a test sample. Samples with testFold[i] === -1 are never part of a test set (they are always in the train set). One split is produced for each distinct fold value other than -1, in ascending order.
import { PredefinedSplit } from 'deepbox/preprocess'; const ps = new PredefinedSplit([0, 0, 1, 1, -1]);const splits = ps.split();// Split 0: test=[0,1], train=[2,3,4]// Split 1: test=[2,3], train=[0,1,4]Members (3)
constructor(testFold: Tensor | readonly number[])split(X?: Tensor): SplitResult[]Generate one split per distinct fold value other than -1.
getNSplits(): numberNumber of splits, the number of distinct fold values other than -1.
RepeatedKFold
Repeated K-Fold cross-validator.
Repeats K-Fold nRepeats times with different shuffling in each repetition. With a randomState, repetition r shuffles with a seed derived from randomState and r (a hash, so that neighbouring seeds do not share repetitions).
import { RepeatedKFold } from 'deepbox/preprocess';import { tensor } from 'deepbox/ndarray'; const rkf = new RepeatedKFold({ nSplits: 5, nRepeats: 3 });const X = tensor([[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]);const splits = rkf.split(X); // 15 splits (5 folds × 3 repeats)Members (3)
constructor(options?: { nSplits?: number; nRepeats?: number; randomState?: number; })split(X: Tensor): SplitResult[]Generate
nSplits * nRepeatssplits, repetition by repetition.getNSplits(): numberTotal number of splits,
nSplits * nRepeats.
RepeatedStratifiedKFold
Repeated Stratified K-Fold cross-validator.
Repeats Stratified K-Fold nRepeats times with different shuffling. With a randomState, repetition r shuffles with a seed derived from randomState and r (a hash, so that neighbouring seeds do not share repetitions).
import { RepeatedStratifiedKFold } from 'deepbox/preprocess';import { tensor } from 'deepbox/ndarray'; const rskf = new RepeatedStratifiedKFold({ nSplits: 2, nRepeats: 3, randomState: 0 });const splits = rskf.split(tensor([[1], [2], [3], [4]]), tensor([0, 0, 1, 1]));Members (3)
constructor(options?: { nSplits?: number; nRepeats?: number; randomState?: number; })split(X: Tensor, y: Tensor): SplitResult[]Generate
nSplits * nRepeatsstratified splits, repetition by repetition.getNSplits(): numberTotal number of splits,
nSplits * nRepeats.
ShuffleSplit
Random permutation cross-validator.
Yields indices to split data into train and test sets using random permutations. Unlike KFold, ShuffleSplit allows controlling the train/test size independently and produces overlapping test sets across iterations. With neither trainSize nor testSize given, the test set gets 10% of the samples (scikit-learn's default for this splitter). Iteration i shuffles with a seed derived from randomState and i (a hash, so that neighbouring seeds do not share iterations).
import { ShuffleSplit } from 'deepbox/preprocess';import { tensor } from 'deepbox/ndarray'; const ss = new ShuffleSplit({ nSplits: 5, testSize: 0.2, randomState: 42 });const X = tensor([[1,2],[3,4],[5,6],[7,8],[9,10]]);const splits = ss.split(X);Members (3)
constructor(options?: { nSplits?: number; testSize?: number; trainSize?: number; randomState?: number; })split(X: Tensor): SplitResult[]Generate
nSplitsrandom train/test splits.getNSplits(): numberNumber of re-shuffling iterations.
StratifiedGroupKFold
Stratified Group K-Fold cross-validator.
Keeps every group in a single fold (like GroupKFold) while trying to keep the class distribution of each fold close to the overall one (like StratifiedKFold). Without shuffling it follows scikit-learn's greedy algorithm: groups are visited from the most to the least unevenly distributed over classes and each goes to the fold whose class proportions end up most even, ties going to the fold with fewer samples. The result is deterministic and matches scikit-learn. With shuffle, the groups are visited in a random order before that sort, so equally uneven groups are placed in a seeded random order (scikit-learn's shuffled folds depend on NumPy's generator and are not reproduced). Train and test indices are returned in ascending order.
import { StratifiedGroupKFold } from 'deepbox/preprocess';import { tensor } from 'deepbox/ndarray'; const X = tensor([[1], [2], [3], [4], [5], [6]]);const y = tensor([0, 0, 1, 1, 0, 1]);const groups = tensor([0, 0, 1, 1, 2, 3]);const splits = new StratifiedGroupKFold({ nSplits: 2 }).split(X, y, groups);Members (3)
constructor(options?: { nSplits?: number; shuffle?: boolean; randomState?: number; })split(X: Tensor, y: Tensor, groups: Tensor | readonly Label[]): SplitResult[]Generate train/test indices for every fold.
getNSplits(): numberNumber of folds.
StratifiedKFold
Stratified K-Folds cross-validator.
Every fold keeps (as closely as possible) the class proportions of the full data set, and fold sizes differ by at most one sample. Without shuffling the assignment is identical to scikit-learn's StratifiedKFold. Each class must have at least nSplits samples. Train and test indices are returned in ascending order.
import { StratifiedKFold } from 'deepbox/preprocess';import { tensor } from 'deepbox/ndarray'; const skf = new StratifiedKFold({ nSplits: 2 });const splits = skf.split(tensor([[1], [2], [3], [4]]), tensor([0, 0, 1, 1]));Members (3)
constructor(options?: { nSplits?: number; shuffle?: boolean; randomState?: number; })split(X: Tensor, y: Tensor): SplitResult[]Generate stratified train/test indices for every fold.
getNSplits(): numberNumber of folds.
StratifiedShuffleSplit
Stratified ShuffleSplit cross-validator.
Provides train/test indices that preserve the percentage of samples for each class. Like ShuffleSplit but with stratification: every split contains exactly the requested number of train and test samples, divided among the classes by largest remainder rounding. Every class needs at least 2 samples, and both sets must be at least as large as the number of classes. With neither trainSize nor testSize given, the test set gets 10% of the samples. Iteration i shuffles with a seed derived from randomState and i (a hash, so that neighbouring seeds do not share iterations).
import { StratifiedShuffleSplit } from 'deepbox/preprocess';import { tensor } from 'deepbox/ndarray'; const sss = new StratifiedShuffleSplit({ nSplits: 5, testSize: 0.2, randomState: 42 });const X = tensor([[1],[2],[3],[4],[5],[6],[7],[8],[9],[10]]);const y = tensor([0,0,0,0,0,1,1,1,1,1]);const splits = sss.split(X, y);Members (3)
constructor(options?: { nSplits?: number; testSize?: number; trainSize?: number; randomState?: number; })split(X: Tensor, y: Tensor): SplitResult[]Generate
nSplitsstratified random train/test splits.getNSplits(): numberNumber of re-shuffling iterations.
TimeSeriesSplit
Time Series cross-validator.
Provides train/test indices for time series data. In each split, test indices must be higher than before, and thus shuffling in cross validator is inappropriate. The test sets are the last nSplits blocks of testSize samples; each train set is everything before its test block, minus gap samples, optionally capped to the most recent maxTrainSize samples.
import { TimeSeriesSplit } from 'deepbox/preprocess';import { tensor } from 'deepbox/ndarray'; const tscv = new TimeSeriesSplit({ nSplits: 3 });const X = tensor([[1], [2], [3], [4], [5], [6]]);const splits = tscv.split(X);// Split 0: train=[0,1,2], test=[3]// Split 1: train=[0,1,2,3], test=[4]// Split 2: train=[0,1,2,3,4], test=[5]Members (3)
constructor(options?: { nSplits?: number; maxTrainSize?: number; testSize?: number; gap?: number; })split(X: Tensor): SplitResult[]Generate the forward-chaining splits, oldest first.
getNSplits(): numberNumber of splits.
trainTestSplit
function trainTestSplit(X: Tensor, y?: Tensor, options?: { testSize?: number; trainSize?: number; randomState?: number; shuffle?: boolean; stratify?: Tensor; }): Tensor[];Split arrays into random train and test subsets.
Details
X and y may have any number of dimensions (at least one); rows are taken along the first axis. Inputs are never modified. Sizes follow scikit-learn: with neither trainSize nor testSize given, the test set gets 25% of the samples; fractions round the test size up and the train size down.
With stratify, each class contributes to the train and test sets in proportion to its size (largest remainder rounding, so the requested sizes are matched exactly). Every class needs at least 2 samples, and both the train and the test set must be at least as large as the number of classes.
Parameters
XTensor- Data tensor; rows are samples (shape
[nSamples, ...]) yoptionalTensor- Optional targets with the same number of rows as
X optionsoptional{ testSize?: number; trainSize?: number; randomState?: number; shuffle?: boolean; stratify?: Tensor; }- Split configuration options
Returns
Tensor[][XTrain, XTest], or [XTrain, XTest, yTrain, yTest] when y is given
Throws
InvalidParameterErrorIf sizes are invalid,Xis empty, or lengths disagreeShapeErrorIfXis 0-d orstratifyis not 1D
import { trainTestSplit } from 'deepbox/preprocess';import { tensor } from 'deepbox/ndarray'; const X = tensor([[1, 2], [3, 4], [5, 6], [7, 8]]);const y = tensor([0, 1, 0, 1]);const [XTrain, XTest, yTrain, yTest] = trainTestSplit(X, y, { testSize: 0.25 });import { KFold, StratifiedKFold, TimeSeriesSplit, trainTestSplit,} from "deepbox/preprocess";import { tensor } from "deepbox/ndarray"; const X = tensor([[1], [2], [3], [4], [5], [6]]);const y = tensor([0, 0, 1, 1, 0, 1]); const [XTrain, XTest, yTrain, yTest] = trainTestSplit(X, y, { testSize: 0.33, randomState: 42, stratify: y,});console.log(XTrain.shape, XTest.shape, yTrain.shape, yTest.shape); console.log(new KFold({ nSplits: 3 }).split(X));console.log(new StratifiedKFold({ nSplits: 3 }).split(X, y));console.log(new TimeSeriesSplit({ nSplits: 3 }).split(X));Found a mistake? Open an issue.