Data Splitting Mastery
Press Next → or use ← → arrow keys
Why Data Splitting Matters
Never use the test set for any development decision — not hyperparameters, not feature selection, not architecture. Peek even once and the test set becomes a second validation set and your reported score becomes fiction.
Studying the exact exam paper then sitting it scores 100% — but proves nothing. A fair exam uses new questions. Your test set is that new exam.
Train · Validation · Test
1M+ rows tolerate a 98/1/1 split; under 1,000 rows, prefer cross-validation over a static holdout.
Train-Test Split — the Holdout Method
A spam filter hit 99.2% training on 5,000 emails — then caught only 60% of real spam, because sender addresses it had memorised never reappeared. Shuffle, then split, and you'd see it instantly.
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.20, random_state=42, shuffle=True, stratify=y)
Stratified Split — Keep the Class Balance
With a 5% fraud class, a random split can — by pure chance — put zero frauds in the test set. stratify=y guarantees both sides mirror the original ratio exactly.
| Set | Class 0 | Fraud |
|---|---|---|
| Train | 760 | 40 (5%) |
| Test | 190 | 10 — or 0! |
| Worst | 800 | 0 (0%) |
| Set | Class 0 | Fraud |
|---|---|---|
| Train | 760 | 40 (5%) |
| Test | 190 | 10 (5%) |
| Both | exact same ratio ✓ | |
Any time the target is categorical — especially when it's imbalanced — pass stratify=y. A test set with no minority cases can't measure minority-class performance at all.
How Much to Hold Out?
More training data helps the model learn, but leaves fewer rows to evaluate on. On big data the test fraction can shrink because even 1% is thousands of examples; on small data every row is precious, so cross-validation reuses them all.
# key parameters
train_test_split(X, y, test_size=0.2, random_state=42, shuffle=True, stratify=y)
# test_size · random_state (reproducible!) · shuffle (False for time-series) · stratify
The Validation Set — Tune Without Cheating
A scientist tested 50 hyperparameter combos, each time scoring on the test set and keeping the best. Test accuracy looked superb; production was terrible. Fifty peeks = she effectively trained on the test set.
Validation selected the winner, so it's a touch optimistic; the test set is the honest estimate. If test beats validation, suspect a bug or a leak.
K-Fold Cross-Validation
kf = KFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(model, X, y, cv=kf) # [.88 .91 .89 .90 .88]
print(f"{scores.mean():.3f} ± {2*scores.std():.3f}") # mean ± 95% CI · high σ = unstable
Stratified K-Fold — Balanced Folds
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
cross_val_score(model, X, y, cv=skf, scoring='roc_auc') # every fold sees the right ratio
Almost always prefer StratifiedKFold over plain KFold for classification — plain K-Fold can hand a fold zero positive examples and quietly ruin the score. (For regression, stratifying a continuous target isn't meaningful — use plain KFold.)
Leave-One-Out CV — for Tiny Datasets
Dr. Singh has just 10 patient records. An 80/20 split leaves 2 to validate on — useless. LOOCV trains on 9, tests on the 10th, and repeats 10 times so every patient serves as the test case once.
LOOCV uses the maximum training data every time — ideal under ~100 rows. But it runs N times, so on large data it's prohibitive: prefer 5- or 10-fold there.
Time-Series Split — Never Peek Ahead
Vivek's stock model hit 94% with random K-Fold — then lost money. It had trained on future prices to predict the past. TimeSeriesSplit revealed the real accuracy: 52%, barely better than a coin flip.
tscv = TimeSeriesSplit(n_splits=5) # never shuffle time-series!
cross_val_score(Ridge(), X, y, cv=tscv, scoring='neg_root_mean_squared_error')
Which Technique Should You Use?
Plain (or stratified) k-fold is robust, widely understood, and the right call for roughly 90% of projects.
Nested CV — Tune & Estimate Honestly
When one CV loop both tunes hyperparameters and reports performance, the score is optimistically biased. Nested CV separates the two: an outer loop measures, an inner loop searches.
clf = GridSearchCV(SVC(), param_grid, cv=inner_cv) # inner: tune
cross_val_score(clf, X, y, cv=outer_cv) # outer: estimate → 0.89 ± 0.01
Data Leakage — the #1 Mistake in ML
claim_amount to predict will_claim. Most dangerous.A student who saw next week's paper aces it — then fails the real exam. Leakage lets your model "cheat" on information it won't have in production, so brilliant dev scores collapse in the real world.
Split First, Then Fit — Every Time
| scaler.fit(X_all) |
| X = scaler.transform(X_all) |
| split(X) → train / test |
| test stats already leaked |
| split(X_raw) → train / test |
| scaler.fit(X_train) |
| transform train & test |
| test never seen at fit |
# a Pipeline enforces it automatically —
# the scaler is re-fit on TRAIN inside each fold
pipe = Pipeline([
('scaler', StandardScaler()),
('model', GradientBoostingClassifier())])
cross_val_score(pipe, X, y,
cv=StratifiedKFold(5, shuffle=True)) # leak-free
Inside CV, a Pipeline re-fits every transformer on each fold's training data only — killing the most common leak by design.
The Golden Rules — 1 to 3
random_state everywhere. A split without a seed is a split you can't reproduce — and reproducibility isn't optional.stratify for classification. Categorical target → stratify=y in splits and StratifiedKFold in CV.The Golden Rules — 4 to 6 & Takeaway
The gold-standard workflow: split off the test set first → Stratified K-Fold + Pipeline on the training data → pick the best model → evaluate once on test → report. A model evaluated properly is a model you can actually trust.
✂️ End of tutorial · Press ← to review, or click Restart