๐Ÿ’ง Data Leakage

Five ways a validation score stops measuring what you think, each one built and then measured
Validation & Model Selection Beginner
Contents

    Data Leakage

    [[cross-validation]] gives you an honest estimate โ€” provided the folds are honest. Leakage is what makes them dishonest.

    A leak is any path by which information from outside a model's legitimate training data reaches it. The symptom is always the same and always seductive: validation scores that are too good, and a model that disappoints in production. Nothing errors. Nothing warns you. The number simply is not measuring what you think it is.

    This page works through five kinds of leak. Each one is built deliberately, measured against the honest baseline, and then fixed โ€” so the size of the optimism is a number rather than a warning.

    Display helpers ready: banner, step, check, expand, show_matrix, show_matmul, show_elementwise.
    ==============================================================================
      A DATASET WITH NO SIGNAL IN IT
    ==============================================================================
      100 samples, 500 features, all drawn from N(0,1)
      labels: [50, 50] -- exactly balanced, shuffled
      X and y are INDEPENDENT. There is nothing to learn.
    
      Honest accuracy for any model whatsoever = 0.50
      So anything above 0.50 on this page is a leak, measured.
    
    The honest baseline
    ==============================================================================
      proper pipeline, 5-fold : [0.55 0.5  0.45 0.45 0.45]
      mean = 0.4800   (truth 0.5000)

    1. Preprocessing before splitting

    The most common leak, and the most innocent-looking. You standardise the whole feature matrix in one step, and then hand the result to cross-validation.

    The scaler computed its mean and standard deviation from every row, including the ones that will be test rows in each fold. Each fold's test data helped determine how its own training data was transformed.

    With well-behaved features and plenty of rows this is a small effect. With many features relative to samples it is not small at all. The same applies to any fitted transformer โ€” imputers, PCA, feature selection, target encoding โ€” and feature selection is by far the worst offender, because choosing "the 5 features most correlated with y" using all the data is choosing the features that best fit the test rows.

    ==============================================================================
      LEAK 1: FITTING A TRANSFORMER BEFORE THE SPLIT
    ==============================================================================
    
    Scaling: fit on everything, then cross-validate
    ==============================================================================
      scaled-then-split : 0.4800
      pipeline (honest) : 0.4800
      difference        : +0.0000
    
      Worth saying plainly: on this data that difference is essentially
      nothing. Standardisation leaks two numbers per feature -- a mean and
      a standard deviation, each estimated from all 100 rows -- and a test
      row's influence on its own scaling is about 1/100 of it.
    
      So this is a real but tiny leak. Fix it because it is free to fix,
      not because it is what goes wrong. The next one is what goes wrong.
    
    Feature SELECTION before the split: the same mistake, catastrophic
    ==============================================================================
      select-then-split (leaky) : [0.65 0.45 0.7  0.6  0.85]  mean 0.6500
      selection inside pipeline : [0.6  0.35 0.5  0.6  0.3 ]  mean 0.4700
      truth                     : 0.5000
    
      optimism from leaky selection: +0.1500
    
      Picking the 5 features most associated with y, using all 100 rows,
      then testing on rows that helped pick them. Out of 500 random
      features some will correlate with y by chance -- and the selector
      finds exactly those. This is how a model 'discovers' signal in noise.
    
    Why the pipeline fixes it
    ==============================================================================
      A Pipeline refits every step inside each fold, on that fold's
      training rows only. The test rows of each fold are transformed by
      statistics they had no part in computing.
    
                                approach        CV  honest?
      ------------------------------------------------------
         fit_transform(X) then cross_val    0.6500  no
            Pipeline(steps) in cross_val    0.4700  yes
      ------------------------------------------------------

    2. Target leakage

    A feature that could not exist at prediction time, or that was computed using the answer. This one is not a coding mistake โ€” the code is correct, the pipeline is clean, the cross-validation is proper. The data is wrong.

    Typical shapes:

    • discharge_diagnosis used to predict admission diagnosis
    • total_spend used to predict churn, where churned customers stop spending
    • a timestamp column that is only populated for positive cases
    • an ID that was assigned in label order

    The tell is a feature that is too predictive. A single column carrying 0.95 AUC in a problem where domain experts manage 0.70 is not a breakthrough.

    ==============================================================================
      LEAK 2: A FEATURE THAT ENCODES THE ANSWER
    ==============================================================================
    
    A realistic feature table: 10 candidate measurements, none informative
    ==============================================================================
      10 features, 100 rows, no signal -> CV 0.5400
    
    Now add one 'innocent' column derived from the target
    ==============================================================================
      new column = y + N(0, 0.45)
      correlation with target: 0.7544
      In a real table this would be something like 'days_to_discharge' or
      'total_spend' -- a genuine measurement, recorded AFTER the outcome.
    
      CV with the leaked column    : 0.8500
      CV without it                : 0.5400
      truth                        : 0.5000
    
      The cross-validation is textbook-correct. The pipeline is correct.
      Every fold is honest. And the answer is still wrong, because the
      information was already inside the feature matrix.
    
    How you would catch it: score every feature on its own
    ==============================================================================
             feature   single-feature CV
      ----------------------------------
           feature 0              0.6300
           feature 1              0.5300
           feature 2              0.4600
           feature 3              0.4700
           feature 4              0.5300
           feature 5              0.4900
           feature 6              0.4400
           feature 7              0.4600
           feature 8              0.5100
           feature 9              0.5700
         THE NEW ONE              0.9100
      ----------------------------------
      the 10 genuine features: mean 0.5090, max 0.6300
      the leaked column      : 0.9100
      it beats the best honest feature by +0.2800
    
      A single column that outscores every other one, in a problem where
      nothing should predict anything, is the signature of target leakage.

    3. Group leakage

    Covered from the splitting side in [[cross-validation]]; here is what it costs.

    When several rows describe the same underlying entity โ€” patient, session, device, customer โ€” those rows are not independent. A random split puts some of an entity's rows in training and others in test, and the model can score by recognising the entity instead of learning the phenomenon.

    The dangerous property is that this leak survives a perfectly correct pipeline. Nothing is fitted on the wrong data. The split itself is the flaw.

    ==============================================================================
      LEAK 3: THE SAME ENTITY ON BOTH SIDES OF THE SPLIT
    ==============================================================================
    
    Build data where the entity is recognisable and the label is per-patient
    ==============================================================================
      30 patients x 4 recordings = 120 rows, 20 features
      each patient has a distinctive signature in the features
      the label is assigned per PATIENT, at random -- all 4 recordings
      from one patient share it, exactly as a clinical outcome would
      the label is independent of the features, so honest accuracy = 0.50
    
    Random split vs grouped split
    ==============================================================================
      random 5-fold  : [1.     1.     0.875  0.9583 0.875 ]  mean 0.9417
      group  5-fold  : [0.4583 0.5417 0.5833 0.125  0.25  ]  mean 0.3917
      truth          : 0.5000
    
      optimism from ignoring patients: +0.5500
    
      A 3-NN model finds the other recordings from the same patient sitting
      in the training set, and copies their labels. That is not
      generalisation, it is a lookup -- and at deployment every patient is
      new, so the lookup table is empty.

    4. Temporal leakage

    If your data is ordered in time, a random split trains on the future. Even without an explicit date column, leakage arrives through features computed over windows that straddle the split, or through normalising by a global statistic that only becomes knowable later.

    The honest protocol is the one you will actually run in production: train on a prefix, predict the block after it, never the reverse.

    5. Duplicate rows

    The simplest leak of all. If the same row appears twice and the split separates the copies, the model has literally seen the test answer. Deduplicate before splitting โ€” and note that near-duplicates (the same event logged twice, an augmented copy, a resampled row) do the same damage while being invisible to an exact-match check.

    The demonstration below is deliberately blunt: duplicating every row takes a no-signal dataset to near-perfect accuracy.

    ==============================================================================
      LEAK 4 & 5: TIME, AND DUPLICATES
    ==============================================================================
    
    Temporal: random split vs forward-chaining
    ==============================================================================
      shuffled 5-fold      : R2 mean +0.2522
      forward-chaining     : R2 mean -0.5813
      gap                  : +0.8335
    
      Shuffling lets the model interpolate between surrounding time points.
      Forward-chaining forces it to extrapolate, which is the actual task.
      The second number is the one that predicts production behaviour.
    
    Duplicates: the bluntest leak there is
    ==============================================================================
      with every row duplicated : 0.9200
      deduplicated              : 0.4300
      truth                     : 0.5000
    
      1-NN finds each test row's twin sitting in the training set at
      distance zero and returns its label. Accuracy 92% on data
      with no signal at all.

    6. Summary: the sizes, side by side

    Every number below was produced on data containing no signal whatsoever, where the only honest answer is 0.50. The optimism column is therefore exactly the size of the lie.

    ==============================================================================
      EVERY LEAK ON THIS PAGE, MEASURED AGAINST THE TRUTH
    ==============================================================================
                                    leak          setup        CV   optimism
      ----------------------------------------------------------------------
                  none - proper pipeline      500 feats    0.4800    -0.0200
              scaler fitted before split      500 feats    0.4800    -0.0200
          feature selection before split      500 feats    0.6500    +0.1500
                  none - proper pipeline       10 feats    0.5400    +0.0400
         target leakage (derived column)   10 feats + 1    0.8500    +0.3500
           group leakage (patient split)    30 patients    0.9417    +0.4417
                          duplicate rows   rows doubled    0.9200    +0.4200
      ----------------------------------------------------------------------
                TRUTH (no signal exists)            any    0.5000    +0.0000
    
      The 'setup' column matters: each leak is compared against the honest
      baseline for ITS OWN feature table, not across rows. The two baseline
      rows show how much the estimate wanders on noise alone.
    
      Note the ordering. The leak everyone knows about -- forgetting to put
      the scaler in a pipeline -- is the smallest one here. The expensive
      ones are structural: selecting features on all the data, a column
      that encodes the answer, and rows that are not independent.
    
    The checklist this page argues for
    ==============================================================================
      1. Split first. Everything fitted -- scalers, imputers, encoders, feature selectors -- goes inside a Pipeline.
      2. Ask of every feature: would this value exist, with this value, at the moment of prediction?
      3. Ask what an independent unit is. If rows share a patient, session, or device, split on that unit.
      4. If time matters, split on time. Never shuffle.
      5. Deduplicate before splitting, and look for near-duplicates too.
      6. Be suspicious of good news. Score features individually; a single column carrying the model is the signature of a leak.

    Verified in this notebook

    Every experiment runs on independent random features and shuffled balanced labels, so the true accuracy is exactly 0.50 and every excess is measured, not argued:

    • Fitting a scaler before the split: small but real optimism
    • Selecting 5 of 500 features before the split: large optimism, entirely manufactured
    • A target-derived column defeats an otherwise perfect pipeline
    • Random vs GroupKFold on 30 patients ร— 4 recordings
    • Shuffled vs forward-chaining CV on trended data
    • 1-NN with duplicated rows recovers near-perfect accuracy from pure noise

    Where this connects

    [[cross-validation]] is the machinery these leaks corrupt โ€” in particular the group and time-series splitters used here. [[evaluation-metrics]] is what you report once the estimate is honest.

    References

    1. Leakage in Data Mining: Formulation, Detection, and Avoidance โ€” Kaufman, Rosset & Perlich, 2012 (ACM TKDD)
    2. Leakage and the Reproducibility Crisis in ML-based Science โ€” Kapoor & Narayanan, 2023 (Patterns)