2.1.13 · D4Data Preprocessing & Feature Engineering

Exercises — Data leakage identification and prevention

3,412 words16 min readBack to topic

Before we start, one shared vocabulary picture. Everything on this page rests on a single "arrow-of-time" diagram, described in words here so you can rebuild it from memory (and so it's readable without seeing the image).

What the diagram shows (alt text): two boxes sit side by side — a blue TRAIN box labelled "the past" on the left and a pink TEST box labelled "the future" on the right. A solid white arrow points from TRAIN to TEST, captioned "OK: learn stats on train, apply" — this is the only legal direction. A dashed yellow arrow points backward, from TEST to TRAIN, captioned "LEAK: test info flows backward" — this is forbidden. Below them, a third pair of boxes shows the label y on the left and a feature X' on the right, with a yellow arrow y → X' captioned "target leak" — meaning a feature that was computed from the answer. Read the whole picture as one sentence: information may only travel forward in time and from train to test; any arrow pointing the other way, or any arrow from the label into a feature, is leakage.

Figure — Data leakage identification and prevention

Level 1 — Recognition

Here "recognition" means: given a feature or a code snippet, decide leak or no leak, and name the type (target / temporal / train-test contamination).

L1.1 — Spot the leaky feature

You are predicting is_fraud for a credit-card transaction. Which of these features is target leakage?

  • (a) amount
  • (b) merchant_category
  • (c) chargeback_amount (money refunded after a fraud dispute)
  • (d) time_of_day
Recall Solution L1.1

Answer: (c) chargeback_amount — target leakage.

WHY: A chargeback only exists because a fraud dispute was filed, and disputes are caused by fraud. The causal chain is The feature is downstream of the label — it is computed only after you already know the answer. At prediction time (the moment the transaction happens) this number does not exist yet.

The other three (amount, merchant_category, time_of_day) are all known at transaction time, so they are legitimate.

L1.2 — Leak or not?

For each, say leak / no leak:

  1. Fitting StandardScaler on the full dataset before train_test_split.
  2. Imputing missing ages with the training-set median, then applying that same number to the test set.
  3. One-hot encoding a category using categories seen only in training.
  4. Using patient_id as a feature when IDs were assigned in the order patients were diagnosed.
  5. Running SelectKBest feature selection on the whole dataset, then splitting into train/test.
Recall Solution L1.2
  1. Leak (train-test contamination). The scaler's mean/std are learned from test rows too — test statistics leak into training features. Breaks Rule S.
  2. No leak. The statistic (median) is learned on train only, then applied to test. This is exactly the correct pattern. Obeys Rule S.
  3. No leakfor the encoding itself. (Test categories unseen in training must be handled, but that's a robustness issue, not leakage.)
  4. Leak (target leakage in disguise). If IDs were assigned in diagnosis order, the ID number secretly encodes when/whether a diagnosis happened — a proxy for the label. Breaks Rule T/target.
  5. Leak (train-test contamination via feature selection). SelectKBest scores each feature against the label using all rows, so the choice of which columns survive is influenced by test-set labels. The test set has already voted on your feature list. Breaks Rule S. The fix: select features inside the training fold only (or inside a Pipeline).

Level 2 — Application

Now you do the correct procedure, not just recognize it.

L2.1 — Scale it the right way

Training sqft values: . Test sqft values: . Standardize using with the population standard deviation (here is the number of points being averaged — 3 for the training set).

Compute for the test point the correct way (fit on train only).

Recall Solution L2.1

Step 1 — learn statistics on TRAIN only (Rule S). Here .

Step 2 — apply to the test point :

What it looks like: the test point sits ~4.9 standard-deviations above the training mean — a huge extrapolation. This is honest: it truthfully warns you that the test houses are far outside the training range. If you had (wrongly) fit on all six houses, and the same point would look like a tame — a comforting lie that hides the distribution shift.

L2.2 — Where does the leak flow?

A rolling 30-day mean is computed on the whole series, then split at day . For the test point at , the window covers days through . Which days in that window are leaked into the feature?

Recall Solution L2.2

The window is .

  • Days are training days — fine to use, they are in the past.
  • Days are test days.

But we're computing the feature for . Under Rule T we may only use data strictly before , i.e. up to . So the mean is contaminated by the future point at day itself, and any test-day values in are still legitimate history relative to .

The real leak: using day 's own value inside its own feature (a future point predicting itself). More broadly, the classic bug is computing the window on the whole frame so that, for a test-day feature, values are pooled together globally rather than causally. Leaked days: for this point (and, in the naive global recompute, all later days that shift earlier windows). See Time series forecasting fundamentals.


Level 3 — Analysis

Now you must quantify or reason about the size of the damage.

L3.1 — Quantify the optimism from a leaked scaler

Using the numbers from L2.1: a model uses the rule "predict expensive if ".

  • Under the leaky scaler (, over all points), compute for the test point .
  • Compare to the honest value from L2.1. Which one would falsely pass a "looks in-distribution" sanity filter that flags anything ?
Recall Solution L3.1

Leaky statistics over all six points (here ): Deviations squared:

Comparison: honest , leaky .

Sanity filter ( flags): the honest value 4.90 is correctly flagged as out-of-distribution; the leaky value 0.93 passes silently. The leak literally disables your own safety check — it dissolves the test point into a merged distribution so the extrapolation becomes invisible. That is the quiet danger: leakage doesn't just inflate accuracy, it hides the very evidence that would have warned you.

L3.2 — CV fold contamination

You run 5-fold cross-validation but call scaler.fit_transform(X) once, before the CV loop. Explain precisely how many times test information leaks, and why the reported CV score is biased upward.

Recall Solution L3.2

In 5-fold CV, each row serves as a validation row in exactly one fold and a training row in the other four. If you scale once globally, then for every fold the validation rows contributed their values to — so all 5 folds are contaminated (5 out of 5).

Why biased upward: each fold's validation features were normalized using knowledge of those very validation points, so the model never faces a truly unseen distribution. Averaged over folds, the reported score overstates real-world performance. The correct pattern refits the scaler inside each fold on that fold's training portion only — most cleanly done with a Pipeline passed to the CV routine. The identical warning applies to feature selection and hyperparameter tuning: if you pick features or tune once on all the data before CV, every fold is contaminated in exactly the same way. See Cross-validation techniques.


Level 4 — Synthesis

Combine multiple ideas into a correct end-to-end procedure.

L4.1 — Order the pipeline

You have: (i) impute missing values, (ii) train/test split, (iii) scale features, (iv) fit model, (v) one-hot encode, (vi) select the best features, (vii) tune hyperparameters. Put them in a leak-free order and justify.

Recall Solution L4.1

Correct order: (ii) split → then, fit on train only: (i) impute → (v) encode → (iii) scale → (vi) select features → (vii) tune hyperparameters (via CV inside the training data) → (iv) fit final model → finally apply every learned transform/selection to the test set → evaluate once.

Justification (Rule S): the split must come first so every subsequent statistic (imputation value, encoding table, scaler mean/std), plus every decision (which features survive, which hyperparameters win) is learned only from training rows. Feature selection and tuning are learning steps too — they consult the label — so they must never see test rows. In practice wrap (i,v,iii,vi,iv) in a single Pipeline and run the tuning search with cross-validation on the training set only — this makes leakage structurally impossible. See Pipeline and workflow automation and Feature scaling and normalization.

L4.2 — Time-aware CV design

For daily stock returns you must estimate performance without temporal leakage. Design the split strategy and state exactly what each fold may see.

Recall Solution L4.2

Use expanding-window (walk-forward) validation:

  • Fold 1: train on days , validate on .
  • Fold 2: train on , validate on .
  • Fold : train on all days up to a cutoff, validate on the next contiguous block — never a random shuffle.

Rule T enforcement: every validation day is predicted using only data with date . All rolling features (means, lags) for a validation day are computed from the training history only. No future row ever enters a past feature. Never use plain shuffled k-fold on ordered time data — it randomly mixes future into training folds. See Time series forecasting fundamentals and Cross-validation techniques.


Level 5 — Mastery

Open-ended design and subtle causal reasoning.

L5.1 — The proxy leak

A hospital model predicts will_be_readmitted. A data scientist adds number_of_followup_appointments_scheduled, which dramatically boosts accuracy. Is this leakage? Reason causally and propose a test.

Recall Solution L5.1

Yes — this is a subtle target/temporal leak (a proxy). Follow-up appointments are typically scheduled because a clinician already anticipates readmission risk, or are booked at/after discharge decisions that correlate with the outcome. The causal question (see Causal inference basics): is this feature a cause of readmission, or an effect of the same process that drives readmission? If it's downstream of the outcome decision, it leaks.

Proposed test procedure: for each patient, attach a timestamp to the appointment count and freeze the feature to a snapshot taken at the exact prediction moment (e.g., the instant of admission). Re-count using only appointments scheduled strictly before that cutoff. Then retrain and compare:

  • If the dramatic accuracy boost survives the point-in-time snapshot, the feature carries genuine, legitimately-available signal — keep it.
  • If the boost collapses once you restrict to pre-cutoff appointments, the signal came from information that only existed after the outcome was effectively decided — it is leakage, and the column must be dropped or replaced by its pre-cutoff version.

Conclusion: a "too good to be true" jump in accuracy from a single feature is itself the diagnostic symptom. Treat such a feature as guilty until the point-in-time reconstruction proves it innocent.

L5.2 — Target-mean encoding leak

A high-cardinality column zip_code is turned into a number by target-mean encoding: each zip is replaced by the average is_fraud label of all rows sharing that zip. Computed naively on the full training set, why does this leak, and how do you fix it?

Recall Solution L5.2

Why it leaks (target leakage into a feature): for a given row, its own label is included in the average that becomes its feature value. In the extreme (a zip that appears once), the "encoded" feature equals that row's own label — the model reads the answer directly off the feature. Even with several rows per zip, each row's feature is partly built from its own , so the causal arrow (the forbidden yellow arrow in the opening diagram) is present. Rare categories leak the most, because the mean is dominated by very few labels.

The fix — leave-one-out / out-of-fold encoding:

  • Leave-one-out: when encoding row , average the labels of all other rows with the same zip, excluding row itself. Now the feature can never contain .
  • Out-of-fold (K-fold target encoding): split training data into folds; encode each fold using target means computed only from the other folds. Then encode the test set using the means learned on the full training set.
  • Smoothing: blend the category mean toward the global mean, weighted by how many rows the category has, so rare zips don't over-fit to a handful of labels.

What it looks like: exactly the arrow-of-time picture — target-mean encoding secretly draws the arrow; leave-one-out and out-of-fold erase that arrow by making sure a row's own label never feeds its own feature. The same danger applies to target-frequency or count encodings computed with the label in scope. See Feature scaling and normalization.

L5.3 — Design a leak-proof deployment audit

You must ship a model to production (Production model deployment). Write a checklist that would have caught each leak type on this page.

Recall Solution L5.3
  1. Point-in-time reconstruction (catches temporal + target/proxy leak): for a random sample of training rows, reconstruct exactly what was knowable at prediction time. Any feature not reproducible from that snapshot is dropped.
  2. Fit-on-train-only enforcement (catches contamination, encoding, selection & tuning leaks): all transforms — scaling, imputation, target encoding, feature selection, hyperparameter search — live inside a single Pipeline; CI fails if any .fit() or selection/tuning step touches validation/test data. See Pipeline and workflow automation.
  3. Train-then-production metric gap (catches everything): monitor . A large gap after deploy is the empirical fingerprint of leakage; see Model evaluation metrics.
  4. Feature-importance red-flag review (catches proxies & target encodings): any single feature with implausibly high importance gets a causal audit — is it downstream of the label, or built using the label? See Causal inference basics.

Recall Quick self-check clozes

A feature computed after the label is known is target leakage. Learning on the full dataset before splitting is train-test contamination. The rule "a feature at time uses only data before " is Rule T (temporal). Replacing a category by the average label of its rows (including the row's own label) is target-mean encoding leakage. Choosing features or tuning hyperparameters on the full dataset before splitting is selection/tuning leakage (a form of train-test contamination). The honest test-point z-score in L2.1 was ::: about 4.90 The leaky test-point z-score in L3.1 was ::: about 0.93 The safe order is split ::: first, then fit-on-train-only for every transform, selection and tuning step