Cross-Validation and Data Splitting
Preprocessing that learns from data, such as scaling, must only see the training portion of each cross-validation fold.
Try it: SELECT, WHERE, ORDER BY, LIMIT
How a SELECT query tests every row against the WHERE condition (keeping only rows where it is TRUE, not FALSE or NULL), then picks columns, removes duplicates with DISTINCT, sorts with ORDER BY and cuts with LIMIT.
How it works
- FROM: start with every row of the table.
- WHERE: evaluate the condition on each row. Comparisons with NULL give NULL (unknown); NOT, AND and OR follow three-valued logic, with AND binding tighter than OR. Only rows where the whole condition is TRUE are kept.
- SELECT picks the listed columns; DISTINCT then drops result rows that repeat an earlier one.
- ORDER BY sorts by each key in turn, ASC or DESC; SQLite puts NULL first when ascending and last when descending.
- LIMIT n keeps only the first n rows of the sorted result.
Default run (13 steps): SELECT title, plays FROM track WHERE plays >= 95 AND rating >= 4 ORDER BY plays DESC LIMIT 2. FROM: start with all 8 rows of track. … Result: 2 rows.
Simplified: A toy relational engine in the browser, not SQLite itself: a small structured query model built from the controls (no SQL text is parsed or executed), typed INTEGER/TEXT/NULL values, and tables of at most a few rows. Results were checked against Python's sqlite3 (SQLite 3.49). Ties in ORDER BY are broken by id here (by the selected columns with DISTINCT), and DISTINCT without ORDER BY keeps first-occurrence order; SQLite itself promises no order for ties. LIKE is case-insensitive for ASCII letters, as in SQLite.
Loading the simulation…
Why a Tuning Score Can Mislead
Hyperparameter tuning with GridSearchCV is usually introduced as a simple loop. You give it an estimator, a grid of candidate settings, and a number of cross-validation folds, and it reports which combination scored best. The part that is easy to miss is what happens to the data before that loop starts. Many workflows scale or transform the features first and then hand the transformed data to the search. That ordering feels natural, and it is the source of one of the most common errors in tuning.
This article follows one workflow step by step. You will watch where the data goes, what each cross-validation fold is allowed to learn from, and where a score can stop meaning what it appears to mean. Keep one question in mind as you read: at which step does the learner's expectation about the data first stop matching what the code actually does?
What do you think happens?
Preprocessing is done once on all rows, then cross-validation runs on the prepared data. Does this workflow produce an honest estimate of how the model will perform on new data?
Reveal answer
No, because the validation rows have already influenced the preprocessing
The misconception is that the validation portion is only used for scoring, so it is harmless if preprocessing has already seen it. But a scaler that learns its statistics from all rows has learned something from the validation rows before scoring begins, so the reported score can be misleading.
Tracing Where the Data Goes
Preprocessing that learns from data, such as scaling, must only see the training portion of each cross-validation fold. A standard scaler works by computing statistics, such as the mean and standard deviation of each feature, and then using them to transform the values. The question is which rows those statistics come from. If the scaler is fitted on the whole dataset, the statistics include the rows that a fold will later treat as validation rows.
The diagram below traces the same raw data through two workflows. In the flawed workflow, the scaler learns from every row before the split exists. In the correct workflow, the split happens first and the scaler learns only from the training rows of each fold.
In the leaky branch, the validation rows have already shaped the numbers the model receives. The validation score is no longer a clean test of how the fitted workflow handles rows it has never learned from. In the correct branch, the validation rows are only transformed using statistics computed from the training rows, so they remain unseen by the learning step.
Why the Split Must Come First
The source gives a direct rule for this problem. The splitting of the dataset during cross-validation should happen before any preprocessing. Any process that extracts knowledge from the dataset should only ever be applied to the training portion of the dataset. That makes cross-validation the outermost loop in your processing.
Read as a nested structure, the outer loop decides which rows are for training and which are for validation, and everything inside the loop must respect that decision. Preprocessing that learns from data is one of those things. It sits inside the loop, not in front of it.
| Property | Preprocessing before the split | Preprocessing inside each fold |
|---|---|---|
| Where preprocessing sits | Before the cross-validation loop | Inside the loop, after the split |
| Which rows the scaler learns from | All rows, including future validation rows | Only the training rows of the current fold |
| What validation rows experience | Already shaped by statistics from themselves | Transformed using statistics from training rows only |
| Reliability of the reported score | Can be optimistic or unstable | Honest estimate of performance on unseen rows |
The two approaches differ in one place: where the preprocessing sits relative to the split.
Building the Pipeline Inside GridSearchCV
Writing the order by hand inside a loop is error-prone. A Pipeline solves this by bundling the preprocessing step and the estimator into one object. The source notes that GridSearchCV can be used with a pipeline, which means the search can treat the whole bundle as its estimator. When that happens, the preprocessing is refitted inside each fold automatically, so the scaler only ever learns from the training portion of that fold.
- The Pipeline bundles the scaler and the SVC into one estimator.
- The parameter grid names C with its step prefix svc__C, because C belongs to the svc step.
- GridSearchCV receives the whole pipeline, so the split happens before any scaling.
- The scaler is fitted on every row before GridSearchCV sees the data. This is the flawed ordering, shown only so you can recognize it.
- Because the scaling happened first, no fold can refit the scaler on its own training rows.
Read the sequence top to bottom for one fold and one candidate setting. The search asks the pipeline to fit on the training rows of that fold, the scaler learns its statistics from those rows, the SVC fits on the transformed rows, and only then is the held-out fold scored. The search then repeats this for the next fold and the next setting.
Tracking Which Rows Each Fold Uses
Each cross-validation fold is a different partition of the same rows. In one fold, a set of rows serves as validation; the remaining rows form the training portion. The index sets change from fold to fold, so a row that is validation in one fold is training in another. The scaler inside the pipeline must be refitted for every fold because the training portion changes each time.
What the scaler sees in fold 2
Six rows are split into three folds. In fold 2, which rows may the scaler learn from, and which rows may it only transform?
- 1Identify the validation rows
In fold 2, rows 2 and 3 are the validation rows for this fold.
- 2Identify the training rows
The training rows are the remaining rows, 0, 1, 4 and 5.
- 3Fit the scaler
Inside the pipeline, the scaler computes its statistics only from rows 0, 1, 4 and 5.
- 4Transform the validation rows
Rows 2 and 3 are passed through the already-fitted scaler and scored. They do not contribute to the statistics.
The scaler learns from rows 0, 1, 4 and 5 only. Rows 2 and 3 are used only for scoring, which keeps the fold's validation score honest.
Mistakes That Break the Fold Boundary
Common mistake Scaling the full dataset before calling GridSearchCV
What it looks likeX_scaled = StandardScaler().fit_transform(X) followed by a search fitted on X_scaledWhy it failsThe scaler's statistics include the rows that each fold later uses for validation, so the validation portion has already influenced training.
Do this insteadPut the scaler inside a Pipeline and pass the pipeline to GridSearchCV, so it is refitted on the training portion of each fold.
Common mistake Assuming the validation rows are harmless because they only get scored
Why it failsScoring is the final step. Any learning that happened earlier, such as computing a mean from those rows, already used them.
Common mistake Writing an unprefixed hyperparameter name for a pipeline
What it looks likeparam_grid = {"C": [0.1, 1, 10]} with a pipeline that has an svc stepWhy it failsA pipeline has several steps, and the bare name does not say which step owns the setting.
Do this insteadUse the step prefix, such as svc__C.
Common mistake Placing the split after preprocessing because the data already looks clean
Why it failsCleanliness is not the criterion. The rule is that the split must happen before any step that extracts knowledge from the data.
Do this insteadMake the split the outer container and keep learning steps inside it.
Finding Where a Result Drifts
When a tuning result disappoints or seems too good, the cause is often a specific step rather than the model itself. Work through the workflow in order. Where is the split? Which steps run inside each fold? Which data does each step see? Locating the point where a learned statistic came from validation data is usually the fastest route to the source of an unexpectedly high or unstable score.
A colleague reports a suspiciously high cross-validation score. Their workflow loads the data, fits a scaler on all rows, then runs GridSearchCV on the scaled data. Starting from the raw data, at which step does the reported score first stop matching what the learner expected, and what single change would fix it?
Hints
- Trace the data from the raw rows forward and note the first step that learns a statistic.
- Ask whether that step can see the validation rows of any fold.
- The fix moves the split ahead of the learning step, usually by wrapping the scaler and estimator in a Pipeline.
Key Takeaways in Practice
- 1Preprocessing that learns from data, such as scaling, must only see the training portion of each cross-validation fold.
- 2Cross-validation is the outermost loop: the split happens first, and anything that learns from data sits inside it.
- 3A Pipeline passed to GridSearchCV refits the preprocessing inside every fold, which enforces the correct order automatically.
- 4Name pipeline hyperparameters with their step prefix, such as svc__C, and when a result looks wrong, trace back to the first step that learned from validation rows.
At a glance
- Preprocessing that learns from data must only see the training portion of each cross-validation fold.
- Cross-validation should be the outermost loop, so the split happens before any preprocessing.
- A Pipeline passed to GridSearchCV refits the preprocessing inside every fold, enforcing the correct order automatically.
- Pipeline hyperparameters are named with their step prefix, such as svc__C.
- When a tuning result looks too good or unstable, locate the first step that learned from validation data.