Learn
Concept·

Data Leakage

A model doing better than you expected is not good news

Most analysis mistakes look like mistakes. Code stops, a number comes out impossible, a chart goes wrong. Eventually somebody notices.

Leakage is not like that. When a pipeline leaks, the model fits better. Validation accuracy rises, AUC rises, the slide deck improves. Nobody questions it. The problem surfaces after deployment.

When this goes wrongDoes the code stop?What happens to the validation scoreWhen you find out
An ordinary mistake — a typo, a bad joinit stopsit gets worseimmediately
Data leakageit does not stopit gets betterafter deployment

The danger in the bottom row lives in the third column. A mistake that raises your score never gives anyone a reason to go looking for it.

Leakage is what you have when information unavailable at prediction time gets into the model. In the training table that information sits there quite legitimately, so nothing complains. At the moment a prediction is actually needed, the cell is empty.

The rules that kept recurring in earlier lessons — fit on training rows only, do it inside the fold, split on time — were all defences against this one problem. Here they come together.

The key question

Do I actually have this information at the moment I have to predict?

1

Concept

Six routes in

Six routes in

Target leakage

the answer is a column
y

A field that only exists once the outcome has happened.

Train–test contamination

the split is porous

The same rows, or their statistics, appear on both sides.

Temporal leakage

training on the future
now

Rows from after the prediction point are in training.

KindWhere it entersHow to catch it
Target leakageA feature that encodes the outcomeAsk whether the value exists at prediction time
Train–test contaminationPreprocessing fitted on everythingPut every fitted step inside a pipeline
Temporal leakageA random split on ordered dataSplit on time and check the maximum dates
Group leakageOne entity on both sidesSplit by the entity key
Duplicate leakageThe same row in both setsDe-duplicate before splitting
Selection leakageFeatures or thresholds chosen on all dataDo the choosing inside each fold

The first three are the main families and the last three are variations. What they share is that the evaluation data contributed, somehow, to the model it is grading.

Target leakage is the commonest and the biggest

Once a feature carries the outcome, the model looks at nothing else. And it almost always arrives as a by-product of the outcome — something that looks like a cause and is in fact a consequence.

Table 1 Applying the timing question column by column
PredictingFeatureExists at prediction time?Fix
Churn next monthCancellation reasonDrop it entirely
Churn next monthSupport tickets last 30 daysRe-anchor to the prediction date
Loan defaultRecovery amountDrop it entirely
Loan defaultCredit score at applicationKeep — but freeze the as-of date
Order valueSales taxIt is the target times a constant
Machine failureMaintenance ticket openedThe ticket follows the failure
Machine failureVibration in the previous hourKeep

The second row is the subtle one. "Support tickets in the last 30 days" is a perfectly reasonable feature. The problem is that "last" is anchored to today. In the training table those 30 days are the 30 days before cancellation, and customers who have decided to leave file a burst of tickets on the way out.

Anchor every window to the prediction date

Time aggregates have to be defined as "the 30 days before the date the prediction is made". Anywhere a spec says "last 30 days", find out what it is anchored to — it is usually wrong.

2

Why It Matters

It shows up as a good result, not an error

Every signal points the wrong way

Normally something going wrong makes performance worse, which is why worse performance triggers investigation. Leakage exploits exactly that habit.

An ordinary bugData leakage
ExecutionCrashes or warnsCompletes cleanly
Validation scoreFallsRises
First reaction"Something is broken""Better than I expected"
FoundDuring developmentAfter deployment
Cost to fixA few hoursRebuild or abandon the model
CredibilityUntouchedDamaged — the number was already reported

Which leaves exactly one workable rule: treat "better than expected" as a warning. Before celebrating, go and find the feature that already knows the answer.

The gap is the diagnostic

A validation score on its own cannot tell you anything. 0.88 could be an honest 0.88 or a leaked one. The verdict comes from the distance between validation and production.

  • Under 0.02 — the pipeline is clean and the validation number can be trusted.
  • 0.02 to 0.06 — something is leaking. Start with the order of preprocessing.
  • Over 0.06 — clear leakage. Audit every feature against the timing question.
  • No production numbers yet — a held-out recent period, split on time, stands in.

When you cannot wait for production

Before deployment there is no production figure. Instead, hold out the most recent period by time and evaluate on that; the gap will expose much of the leakage. If a random split and a temporal split disagree substantially, that disagreement is the size of your temporal leak.

Fixing leakage lowers the score

This creates an organisational problem. Finding and removing a leak reduces the reported performance. If 0.92 has already been presented, revising it to 0.78 is politically hard.

So leakage audits have to happen before results are shared. A number that has been announced is difficult to withdraw, and while it stands, decisions get made on it.

3

How It Works

One question and one pipeline stop most of it

1. Apply the timing question to everything

Open the feature list and go down it one at a time. "At the moment I need a prediction, does this value exist?" That single question catches most target leakage.

  • Is it a by-product of the outcome — cancellation reasons, recovery amounts, maintenance tickets: all created after the fact.
  • Is it a transform of the target — sales tax, pre-discount totals: anything in a fixed relationship with the answer.
  • Does the window reach forward — what is "the last 30 days" anchored to?
  • Is the field updated later — a column overwritten nightly is not what it held at prediction time.
  • When does production populate it — check the live system, not the training table.

2. Let a pipeline enforce the order

Preprocessing leakage is not prevented by being careful; it is prevented structurally. Put every fitted step into a pipeline object and cross-validation refits it per fold without being asked.

StepIn the pipelineBecause
ImputationRequiredIt learns a mean or median
ScalingRequiredIt learns μ and σ
Target encodingRequiredIt learns per-category target means
Feature selectionRequiredIt learns which features survive
Outlier thresholdsRequiredIt learns quantiles
Dimension reduction (PCA)RequiredIt learns component directions
Type casting, de-duplicationNot neededNothing is learned

3. The audit

Table 2 Six things to run before sharing a result
CheckWhat a bad answer looks likeWhat to do
Validation minus productionMore than a few points apartAssume leakage until shown otherwise
Single-feature performanceOne feature alone scores near the full modelCheck its timing before anything else
Correlation with the targetAbove 0.95It probably contains the target
Score by foldOne fold far above the restLook for duplicates or a group straddling it
Maximum date per splitTraining extends past the test startRe-split on time
Perfect scoresAUC of 1.00, accuracy of 100%This is never a modelling success

Row two is the most efficient in practice. Train a model on each feature alone and record the score: an implausibly strong single feature stands out immediately. Anything approaching the full model on its own is nearly always a leak.

4. The routes people miss

  • Duplicate rows — an identical row landing on each side means the test question was already memorised.
  • Oversampling order — running SMOTE before the split puts synthetic copies of training rows into the test set.
  • Sort order — cutting sorted data front-to-back stratifies it by ID or date without anyone deciding to.
  • Joining external data — joining a later snapshot pulls the future in at that moment.
  • Tuning on the test set — hyperparameters chosen against it means it has been used for training.
  • Too many experiments — a hundred runs against one validation set overfits that set.

SMOTE goes after the split

On imbalanced data, oversampling must be applied after splitting and to the training rows only. Applied first, an original row and the synthetic points generated from it end up on opposite sides, and the validation score inflates sharply.

4

Example

Fixing the leaks made the score go down

In practice: removing the leaks one at a time

A churn model validated at 0.94 AUC and ran at 0.76 in production. Here is the record of tracking down that 0.18.

ChangeValidation AUCProduction AUCGap
Starting point0.9400.7600.180
Dropped cancel_reason0.8600.7610.099
Re-anchored the ticket window0.8120.7620.050
Re-split by customer0.7890.7620.027
Moved scaling into the pipeline0.7750.7620.013
Re-split on time0.7660.7630.003

Validation fell from 0.940 to 0.766 at every step. Production went from 0.760 to 0.763 — essentially unchanged. Over six changes, the model got no better.

What changed is what was known about the model. The team started out believing it had a 0.94 model and ended up knowing it had a 0.766 one. The second state is far more useful than the first.

The lesson here

Removing leakage is a measurement fix, not a performance fix. Unless that distinction is explained in advance, whoever lowers the number looks like the person who broke it.

In practice: when the score comes back perfect

An equipment failure model returned a validation AUC of 1.00 — perfect separation. It looks like a triumph, and there is no version of this that is legitimate.

Tracking it down

Single-feature scores put maintenance_ticket_opened at AUC 0.998 on its own

That ticket is raised by the maintenance team after the failure occurs.

So it was not a feature predicting failures but a feature recording them. With it removed the AUC came to 0.81, which is the real number.

An AUC of 1.00 or 100% accuracy is not a sign of a well-built model but a sign that the answer is in the inputs. There is effectively no exception.

Common misunderstandings

Misconception 1

❌ Leakage is a beginner's mistake.

It is a structural problem, not a carelessness problem. Without a pipeline, an experienced practitioner gets the ordering wrong too. It is found in winning competition solutions on a regular basis.

Misconception 2

❌ As long as the target itself is not a feature, it is fine.

Leakage usually arrives as a by-product of the target. Cancellation reasons, recovery amounts and maintenance tickets are none of them the target, and none of them exist without it.

Misconception 3

❌ A high validation score means a good model.

A validation score alone decides nothing. The gap between validation and production is the real metric, and where the gap is wide, the high score is itself the evidence of a problem.

Misconception 4

❌ Preprocessing does not change the data, so order does not matter.

Scaling, imputation and encoding all learn numbers from data. Fitted before the split, the test set has helped set the standard it is then judged against.

Misconception 5

❌ We audited it once.

Every new feature and every new join opens another route. A leakage audit is something you run every time, immediately before sharing a result.

5

Interactive

Switch leaks on and compare the two bars

Switch leaks on and compare the two bars

Same model, same data. Turn on each of the six leaks and watch which bar moves. The dashed line is the true performance.

Switch leaks on one at a time. Watch which bar moves.

Leaks active

0

Validation − production

0.000

Verdict

trustworthy

With no leaks the two bars sit together at 0.762. That is what a clean pipeline looks like: the validation number is a prediction of the production number.

Leakage never announces itself as an error. It announces itself as a good result.

What to look for

  • When you enable a leak, how far does the production bar move?
  • Which leak opens the largest gap?
  • Turn on only the preprocessing leak — how big is the gap? Would you notice it?
  • What happens to the gap with several enabled at once?
  • Can the validation score alone tell you whether anything is leaking?

Learning points

Leakage only raises the validation bar — production stays put or drifts down.

A small leak opens a gap of around 0.03, which is easy to miss.

Which is why the test is not the height of the bars but the distance between them.

Key takeaways

It looks like success

Leakage never raises an error

  • The score goes up, not down
  • Better than expected is a warning
  • It surfaces only after deployment

One question finds most of it

Does this value exist at prediction time?

  • Applies to every feature, one at a time
  • Consequences of the outcome fail it
  • Re-anchor windows to the prediction date

The gap is the test

Compare validation against production

  • A clean pipeline has almost no gap
  • A big gap means the measurement, not the model
  • Pipelines make the ordering unbreakable

The question is not "what did it score?"

It is "will that score survive deployment?" Leakage arrives disguised as a good result, so unless you go looking for it, it is never found.

How does this apply to real data?

A leakage audit is where every rule in this section converges. In SKARI you can check the following alongside.

Leakage checks

Leakage Check, Single-Feature AUC, Timing Audit

Split validation

Group Leakage Check, Temporal Order Check

Pipelines

Pipeline, CV Fold Isolation, Nested CV

Monitoring

Drift Detection, Production vs Validation

Once this clicks, you can answer questions like these.

  • Does this feature exist at prediction time?
  • Is every aggregation window anchored to the prediction date?
  • Is every fitted preprocessing step inside the pipeline?
  • Does any single feature approach the full model on its own?
  • How many points separate validation from production?
Now try it on real dataOpen in Lab

Go Deeper

Probability Basics