k-Fold Cross-Validation
Stratified k-fold cross-validation keeps the proportions between classes the same in each fold as in the whole dataset.
Try it: K-Fold Cross-Validation and Model Selection
How hold-out and k-fold cross-validation choose among candidate models: each fold takes a turn as the validation set, the fold errors are averaged, the lowest average picks the model, and only then is the untouched test set used once — while tuning on the test set would report an optimistic number.
How it works
- Set 10 test points aside; they take no part in any choice.
- Optionally shuffle the 20 development points (seeded), then cut them into k folds (hold-out: one chunk validates, the rest trains).
- For every round, train each candidate polynomial on the other folds and measure its validation MSE on the held-out fold.
- Average each candidate's fold errors (with repeated shuffling: average the p run scores; p × k fits per candidate).
- Select the candidate with the lowest average, retrain it on all development points, and evaluate it once on the test set.
- For contrast, show which candidate the test set itself would have picked — that figure is leaked and optimistic.
Default run (12 steps): 20 development points (sorted by x as they arrived) and 10 test points set aside. Candidates: polynomial degree 0, 1, 2, 3, 4, 5, 6. Plan: 5-fold cross-validation, shuffled with seed 1 → 5 fits per candidate (p × k = 1 × 5), 35 in total. … 5-fold cross-validation picks degree 3 (score 0.0768); its honest test MSE is 0.0889. Picking by test error would report degree 4 at 0.0868 — a leaked, optimistic figure.
Simplified: One fixed toy dataset (20 development + 10 test points from sin(2πx) with noise) and polynomial least squares of degree 0–6 as the candidate models. Shuffling uses a seeded mulberry32 Fisher–Yates; folds are contiguous chunks like scikit-learn's KFold. Hold-out uses the first of k chunks as the validation set.
Loading the simulation…
The Recipe That Hides a Problem
Most people first meet k-fold cross-validation as a recipe. You cut the dataset into k folds, let each fold take a turn as the test set, train on the remaining folds, and average the scores. The recipe itself says nothing about which samples land in which fold, so it is easy to assume that cutting the data into consecutive pieces is harmless. This assumption breaks down when the dataset is sorted by its labels.
The misconception to watch for is this: a standard k-fold split is good enough for evaluating a classifier no matter how the classes are distributed. For a classifier, the class mix inside each test fold matters, and a plain split does not guarantee it. In this article you will trace exactly where that guarantee fails, and then see how stratified k-fold cross-validation changes what the split is required to preserve.
Tracing an Ordered Three-Fold Split
Consider a small dataset whose labels come in three consecutive blocks, similar to the iris dataset in the source. The first third of the samples is class 0, the second third is class 1, and the last third is class 2. Here is a version of that label arrangement with 18 samples, six per class, split into three folds with no shuffling. Read the code first, then work out the answer before you check the output.
Fold 1: test classes = [0], train classes = [1 2]
Fold 2: test classes = [1], train classes = [0 2]
Fold 3: test classes = [2], train classes = [0 1]- The labels array is ordered by class: indices 0 to 5 are class 0, 6 to 11 are class 1, and 12 to 17 are class 2.
- KFold assigns rows by position, so each fold is one contiguous block of indices.
- The unique-class printout makes the mismatch visible in every round.
What do you think happens?
Before you look at the output, which classes do you expect in the test set of fold 2?
Reveal answer
Only class 1
Fold 2 is the second contiguous block of indices, 6 to 11, and every one of those samples is class 1. Because the split is positional and the data is sorted, the test fold holds a single class.
The index map shows the root of the problem. Fold 1 contains only class 0, fold 2 contains only class 1, and fold 3 contains only class 2. The output confirms the same thing from the other direction: whenever a fold is the test set, the training set contains the other two classes and never sees the class being tested.
Stratification as a Proportion Rule
Stratified k-fold cross-validation fixes the problem by changing what the split is required to preserve. The split is still into k folds, and each fold still takes a turn as the test set. The difference is the rule that governs how samples are assigned. Each fold must mirror the class composition of the full dataset, rather than whatever happens to sit in a given block of rows.
Stratified k-fold cross-validation is a form of k-fold cross-validation in which the proportions between classes are kept the same in each fold as they are in the whole dataset.
The source gives a simple ratio to make the rule concrete. If 90% of the samples belong to class A and 10% belong to class B, stratified cross-validation ensures that in each fold 90% of the samples are class A and 10% are class B. The composition of every fold is anchored to the composition of the whole dataset, which is the property a fair test set needs.
In this statement, $n_{c}$ is the number of samples of class c in the full dataset, $N$ is the total number of samples, $n_{c,\text{fold}}$ is the number of class c samples in one fold, and $n_{\text{fold}}$ is the number of samples in that fold. The approximately-equal sign is deliberate. With real datasets the counts cannot always divide evenly, so the goal is that each fold is representative, not that every fold has an identical count.
Applying the proportion rule to a 90/10 dataset
A dataset has 100 samples: 90 belong to class A and 10 belong to class B. You want five stratified folds. How many samples of each class should each fold contain?
- 1Find the fold size
Divide the total by the number of folds: 100 divided by 5 gives 20 samples per fold.
- 2Apply the class share to class A
Class A is 90 out of 100, which is 90%. Its share of each 20-sample fold should also be 90%, so 0.9 times 20 gives 18 samples of class A.
- 3Apply the class share to class B
Class B is 10 out of 100, which is 10%. Its share of each fold should be 10%, so 0.1 times 20 gives 2 samples of class B.
- 4Check the totals
18 plus 2 gives 20, which matches the fold size, so every fold is a representative miniature of the full dataset.
Each fold contains 18 samples of class A and 2 samples of class B.
Why does this matter for evaluation? Suppose only 10% of the samples belong to class B. With a standard k-fold split, one fold can easily contain only samples of class A. Using that fold as the test set tells you little about overall performance, because the classifier was never asked to handle class B on that split. A stratified split guarantees that every test fold contains a fair share of class B, so each round asks the classifier the same kind of question.
Tracing the Same Data with a Stratified Split
Now run the same 18-sample, ordered label arrangement through a stratified splitter. The only change in the code is the class name. The labels are still sorted by class, but the stratified splitter assigns samples so that each class is shared across the folds.
Fold 1: test counts per class = [2 2 2], train counts per class = [4 4 4]
Fold 2: test counts per class = [2 2 2], train counts per class = [4 4 4]
Fold 3: test counts per class = [2 2 2], train counts per class = [4 4 4]- Each test fold holds two samples of every class, matching the 1:1:1 mix of the full dataset.
np.bincountcounts how many samples of each class appear, which is easier to compare than the class list.- The training folds contain four samples of each class, so no class is missing from any training set.
Compare the two outputs. The plain split produced test folds that each contained one class and none of the others. The stratified split produced test folds whose class mix matches the full dataset, which here is an equal three-way mix. The divergence you traced earlier, where the plain split drifted away from the expected mix in its very first round, is gone. The row order of the data has not changed; what changed is the rule that assigns samples to folds.
Mistakes When Choosing a Splitting Strategy
Common mistake Assuming a standard k-fold split is fine for any classifier
What it looks likeSplitting sorted iris-style labels into three plain folds and trusting the resulting scores.
Why it failsFor a classifier, the class mix inside each test fold matters, and a plain split does not guarantee it. Ordered data can leave whole classes out of a test fold entirely.
Do this insteadCheck the class counts in each fold, and use stratified k-fold when the classes must be represented in each test fold.
Common mistake Believing stratification changes the order of the rows
What it looks likeExpecting the stratified folds to look like consecutive blocks of the original sorted array.
Why it failsStratification is defined by class proportions, not by position. The folds are built so each one mirrors the overall class composition.
Do this insteadInspect the class counts per fold rather than the row positions when you want to confirm the split is representative.
Common mistake Expecting identical counts in every fold
What it looks likeRequiring each fold of an uneven dataset to contain exactly the same number of class B samples.
Why it failsReal datasets often cannot divide evenly. The rule is that each fold is representative of the overall proportions, not that every fold is identical.
Do this insteadAccept counts that are as close as the division allows, and focus on whether each fold reflects the class shares of the whole dataset.
Common mistake Using stratification for every kind of prediction
Choosing Stratified or Plain k-Fold
The source recommends stratified k-fold cross-validation as the usual choice for evaluating a classifier, because it results in more reliable estimates of generalization performance. Stratification is about class proportions, so it applies to classification. Scikit-learn uses stratified k-fold for classification by default and standard k-fold for regression by default.
For regression, the target is a continuous value rather than a set of classes, so there are no class proportions to preserve in the same way. The source notes that it would be possible to try to make each fold representative of the different values the regression target takes, but that this is not a commonly used strategy and would surprise most users. The practical lesson is to match the splitting strategy to the kind of prediction being evaluated.
Check Your Understanding
A dataset has 60 samples: 45 belong to class X and 15 belong to class Y. You plan to use three stratified folds. How many samples of each class should each test fold contain? Then explain in one sentence why a plain ordered split of this dataset could leave a test fold with no class Y samples.
Hints
- Start by finding the fold size: 60 samples divided into 3 folds.
- Class X is 45 out of 60, which is 75 percent. Apply that share to the fold size.
- A plain split assigns rows by position, so if the class Y rows sit in one block, a whole fold can fall inside that block.
Key Takeaways
- A standard k-fold split assigns rows by position, so when data is ordered by class label, a fold can contain only one class, and a classifier can be scored on a test set that never shows it the other classes.
- Stratified k-fold cross-validation keeps the proportions between classes the same in each fold as in the whole dataset, so every test fold mirrors the overall class mix.
- A zero or very low score from an ordered three-fold split can reflect the split itself rather than the classifier, so checking class counts per fold is a useful diagnostic.
- Use stratified k-fold as the default for evaluating a classifier, especially with imbalanced classes; plain k-fold is the usual default for regression, where class proportions do not apply.