How your estimate can lie — and how not to fool yourself
Four classic patterns, each measured twice on the same data: once with the leak, once with honest validation.
Predict. 100 rows, 5,000 columns of pure noise, coin-flip labels. The selector keeps 20 columns looking at all 100 labels, then CV runs. What accuracy comes out? And with the selector inside the pipeline?
# WRONG: the selector sees all 100 labels, then CV runs on the surviving columns
X_sel = SelectKBest(f_classif, k=20).fit_transform(X, y)
cross_val_score(LogisticRegression(), X_sel, y, cv=cv).mean() # 0.89
# RIGHT: the selector is refit inside each fold
pipe = make_pipeline(SelectKBest(f_classif, k=20), LogisticRegression())
cross_val_score(pipe, X, y, cv=cv).mean() # 0.53
A StandardScaler making the same mistake changes almost nothing (97.9% either way). The size of a leak depends on what the step learns — and you cannot know without measuring. The pipeline makes the question disappear.
Predict. Nothing here is learnable: an honest pipeline must land at 50%. Raise the noise features from 20 to 300: what happens to the reported score, and to the honest one?
Predict. Each row gets a near-identical twin; the labels are coin flips. What accuracy will 1-NN (k-nearest neighbors, k-NN, with \(k = 1\)) report under shuffled KFold? Can you get it by hand?
Predicting readmission with number_of_followup_visits; predicting churn with account_closed_date is null.
| 1-NN, original data | 0.487 |
| with twins, KFold | 0.902 |
| with twins, GroupKFold | 0.515 |
The twin lands in training with probability 4/5; 1-NN finds it: \(0.8 \times 1 + 0.2 \times 0.5 = 0.9\).
Predict. Will random KFold score above or below 200 brand-new patients? And what R² (coefficient of determination) should an honest split give on a random walk?
60 patients, 10 scans each; each patient has a "fingerprint".
| random KFold | 0.938 |
| GroupKFold by patient | 0.780 |
| 200 new patients | 0.7545 |
The model recognizes the patient, not the disease. The grouped estimate was the honest one.
A random walk; the only feature is the day number:
| shuffled KFold | R² 0.99 |
| TimeSeriesSplit | R² −1.78 |
Every validation day is surrounded by training days: that is interpolation, which production never offers. The −1.78 is the correct answer: the best honest forecast, “tomorrow = last value seen”, scores −1.70.
y at the very start and rerun the whole pipeline, preprocessing included. The score must fall to the shuffled baseline (chance for that model and metric, not necessarily 50%).Passing proves less: duplicates, groups, time order and leaked columns lose their link to the shuffled labels too, so leaks 2 to 5 pass this test anyway.
Mammography: 11,183 regions of mammograms, 6 numbers each; only 2.3% are calcifications. Five candidates, the same 5 folds, mean over folds.
Predict. What accuracy does a dummy that never flags anything get?
| accuracy | ROC AUC | AP | |
|---|---|---|---|
| dummy (never flags) | 0.977 | 0.500 | 0.023 |
| logistic | 0.983 | 0.923 | 0.622 |
| logistic balanced | 0.901 | 0.921 | 0.585 |
| logistic degree 2 | 0.986 | 0.948 | 0.710 |
| k-NN | 0.986 | 0.904 | 0.623 |
ROC AUC: area under the ROC (receiver operating characteristic) curve. AP (average precision): area under the precision–recall curve; a random ranking gets the prevalence.
Predict. Logistic 0.622 ± 0.050 against balanced 0.585 ± 0.048: is that a tie? What changes if you subtract fold by fold?
| difference | ahead | |
|---|---|---|
| logistic − balanced | +0.041 ± 0.025 | 49/50 |
| k-NN − degree 2 | +0.011 ± 0.037 | 31/50 |
The one-standard-error rule (Breiman et al., 1984) on k-NN (\(k\) = number of neighbors):
| k | AP |
|---|---|
| 25 | 0.712 |
| 35 | 0.708 |
| 51 | 0.693 |
| 75 | 0.675 |
Std of the 5 fold scores at \(k = 25\): 0.054, so one standard error \(= 0.054/\sqrt{5} = 0.024\); band: AP \(\geq 0.712 - 0.024 = 0.688\). The largest \(k\) inside it is 51.
And between tied families, what breaks the tie is what the metric does not measure:
The winner, 0.712, is the best of 35 configurations measured on the folds that chose it. To know what the whole procedure is worth, grade it on rows that played no part in it.
Predict. Will the nested estimate land above, on or below 0.712? Will the same family win in every outer fold?
Predict. SMOTE (Synthetic Minority Over-sampling Technique) creates new positives between neighboring ones. Applied to the whole table before CV, does AP go up or down? And inside the pipeline?
| AP | recall | precision | |
|---|---|---|---|
| SMOTE before CV | 0.979 | 0.963 | 0.952 |
| SMOTE in the pipeline | 0.601 | 0.875 | 0.270 |
| no SMOTE | 0.712 | 0.448 | 0.846 |
cv object with a fixed seed and use it for everything, including the DummyClassifier.GridSearchCV on that cv.Breast cancer, a random forest, a grid of 9 configurations; 15% locked away as the test set before anything else.
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.15, random_state=42, stratify=y)
pipeline = Pipeline([('scaler', StandardScaler()),
('clf', RandomForestClassifier(random_state=42))])
grid = GridSearchCV(pipeline, param_grid, cv=cv, scoring='roc_auc')
grid.fit(X_train, y_train) # CV on the training part only
proba = grid.best_estimator_.predict_proba(X_test)[:, 1] # the test, once
Predict. The CV score is the best of 9 configurations. Will the test score land far below it?
CV ROC AUC of the winner (grid.best_score_) | 0.9923 |
| test ROC AUC, measured once | 0.9931 |
Pace: steps 7 and 8 in 10 min; step 9 and its simulation in 12 min; steps 10 and 11 in 8 min; steps 12 and 13 with the simulation in 10 min.