2.1.13Data Preprocessing & Feature Engineering

Data leakage identification and prevention

3,702 words17 min readdifficulty · medium3 backlinks
Figure — Data leakage identification and prevention

[!definition] What IS Data Leakage?

Data leakage is the use of information in the model training process that would not be available at prediction time in a real-world deployment scenario.

Three main types:

  1. Target leakage: Features that are influenced by or derived from the target variable itself
  2. Train-test contamination: Information from the validation/test set influencing training
  3. Temporal leakage: Future information bleeding into past predictions

WHY These Are Different

  • Target leakage = using variables that are causally downstream of what you're predicting
  • Train-test contamination = violating the independence assumption between training and validation
  • Temporal leakage = violating the temporal ordering of real-world data flow

[!formula] Mathematical Framework of Leakage

Let's derive why leakage breaks the learning process from first principles.

The ML assumption: Ptrain(X,y)=Ptest(X,y)=Pproduction(X,y)P_{\text{train}}(X, y) = P_{\text{test}}(X, y) = P_{\text{production}}(X, y)

We want to minimize expected risk: R(f)=E(X,y)Pproduction[L(f(X),y)]R(f) = \mathbb{E}_{(X,y) \sim P_{\text{production}}}[\mathcal{L}(f(X), y)]

But we only have access to training data, so we minimize empirical risk: R^(f)=1ni=1nL(f(Xi),yi)\hat{R}(f) = \frac{1}{n}\sum_{i=1}^{n} \mathcal{L}(f(X_i), y_i)

Derivation of why leakage breaks this:

With leakage, our features become X=g(X,y,future info)X' = g(X, y, \text{future info}) instead of just XX.

During training: R^(f)=1ni=1nL(f(g(Xi,yi,leaki)),yi)\hat{R}(f) = \frac{1}{n}\sum_{i=1}^{n} \mathcal{L}(f(g(X_i, y_i, \text{leak}_i)), y_i)

WHY this step? The model learns fgf \circ g where gg depends on yy or unavailable information.

But in production, we only have XX, not g(X,y,leak)g(X, y, \text{leak}): Rproduction(f)=E[L(f(X),y)]R_{\text{production}}(f) = \mathbb{E}[\mathcal{L}(f(X), y)]

The divergence: Δ=Rproduction(f)R^(f)>ϵ\Delta = |R_{\text{production}}(f) - \hat{R}(f)| > \epsilon

This divergence is unbounded when gg strongly depends on yy or future information.

Concrete derivation for target leakage:

Suppose feature Xj=h(y)+ϵX_j = h(y) + \epsilon where hh is deterministic and $\epsilon is small noise.

The model learns: y^=f(X)f(h(y))h1(h(y))=y\hat{y} = f(X) \approx f(h(y)) \approx h^{-1}(h(y)) = y

WHY? The feature is a near-perfect encoding of the target.

Training error: R^=1ni=1nh1(h(yi)+ϵi)yi20\hat{R} = \frac{1}{n}\sum_{i=1}^{n}|h^{-1}(h(y_i) + \epsilon_i) - y_i|^2 \approx 0

But in production, XjX_j is not available or is computed without knowing yy: Rprod=1ni=1nf(Xavailable)yi20R_{\text{prod}} = \frac{1}{n}\sum_{i=1}^{n}|f(X_{\text{available}}) - y_i|^2 \gg 0

[!example] Worked Example 1: Credit Card Fraud (Target Leakage)

Scenario: Predicting fraudulent transactions using transaction data.

The Dataset:

transaction_id, amount, merchant, is_fraud, account_balanceafter_dispute
1001, 500, Amazon0, 5000
1002, 2000, Unknown, 1, 3000  ← disputed, money returned
1003, 100, Walmart, 0, 4900

The Leakage: Including account_balance_after_dispute as a feature.

Why it feels right: "Balance changes might indicate fraud patterns!"

Why it's WRONG:

  1. account_balance_after_dispute is calculated AFTER fraud investigation
  2. The fraud label is_fraud causes disputes, which cause balance changes
  3. Causal direction: is_fraud → dispute → account_balance_after_dispute

Step-by-step impact:

Training phase:

# Model sees:
X = [500, 'Amazon', 5000]  → y = 0 (no dispute, balance unchanged)
X = [2000, 'Unknown', 3000] → y = 1 (dispute filed, money returned!)

WHY this step? The balance column is a near-perfect predictor because it encodes the dispute outcome.

The model learns: "If balance went down by transaction amount, it's fraud!" (99% accuracy)

Production phase:

# Model receives:
X = [1500, 'Shady Site', ???]  ← account_balance_after_dispute doesn't exist yet!

Accuracy colapses because the feature that gave99% training accuracy is unavailable.

The fix: Only use features available at transaction time:

  • amount, merchant, time_of_day, location, historical_fraud_rate
  • Never: dispute_filed, chargeback_amount, investigation_outcome

[!example] Worked Example 2: Time Series (Temporal Leakage)

Scenario: Predicting daily stock returns.

The Dataset (2024-01-01 to 2024-12-31):

date, open, close, volume, rolling_mean_30d, return_next_day
2024-01-15, 100, 102, 1M, 101.5, 0.02
2024-01-16, 102, 104, 1.2M, 102.0, 0.015

The Leakage: Computing rolling_mean_30d on the entire dataset before splitting.

Step-by-step derivation of the problem:

# WRONG: Global calculation
df['rolling_mean_30d'] = df['close'].rolling(30).mean()
train = df[df['date'] < '2024-07-01']  # First 6 months
test = df[df['date'] >= '2024-07-01']  # Last 6 months

WHY this is wrong: For a test sample on2024-07-15:

  • The rolling_mean_30d uses data from 2024-06-15 to 2024-07-15
  • But 2024-06-15 to 2024-06-30 is in the training set!
  • Information flows backward: test set → training features

Mathematical formalization:

For test point t=T+kt = T + k (where TT is train/test split):

Feature calculation: XˉT+k=130i=T+k29T+kXi\bar{X}_{T+k} = \frac{1}{30}\sum_{i=T+k-29}^{T+k} X_i

WHY this step? Rolling window looks back30 days from the test point.

This includes: XˉT+k=130(i=T+k29TXi+i=T+1T+kXi)\bar{X}_{T+k} = \frac{1}{30}\left(\sum_{i=T+k-29}^{T} X_i + \sum_{i=T+1}^{T+k} X_i\right) ↑ training data ↑ test data

The model learns spurious correlations between train and test sets.

The correct approach:

# RIGHT: Temporal split FIRST, calculate SECOND
train = df[df['date'] < '2024-07-01']
test = df[df['date'] >= '2024-07-01']
 
# Calculate rolling mean ONLY on training data up to that point
train['rolling_mean_30d'] = train['close'].rolling(30).mean()
 
# For test: use only training data for the calculation
for in test.index:
    relevant_history = df[(df['date'] < df.loc[i, 'date']) & 
                          (df['date'] >= df.loc[i, 'date'] - timedelta(days=30))]
    test.loc[i, 'rolling_mean_30d'] = relevant_history['close'].mean()

WHY this step? We simulate the real-world scenario: at prediction time tt, you only know history up to t1t-1.

[!example] Worked Example 3: Train-Test Contamination (Scaling Leakage)

Scenario: Predicting house prices with features at different scales.

The Dataset:

house_id, sqft, bedrooms, price
1, 2000, 3000
2, 1500, 2, 250000
.., .., ...
100, 3000, 4, 5000

The Leakage: Fitting the scaler on the entire dataset.

# WRONG approach
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_scaled = scaler.fit(X)  # X contains both train and test
X_train, X_test, y_train, y_test = train_test_split(X_scaled, y)

Why it feels right: "Standardization is just preprocessing, not model fitting!"

Derivation of why it's wrong:

StandardScaler computes: μ=1ni=1nXi,σ2=1ni=1n(Xiμ)2\mu = \frac{1}{n}\sum_{i=1}^{n} X_i, \quad \sigma^2 = \frac{1}{n}\sum_{i=1}^{n}(X_i - \mu)^2

WHY this step? We're computing population statistics across all data.

Then scales: Xi=XiμσX'_i = \frac{X_i - \mu}{\sigma}

If we fit on all data (nn = train + test samples): μall=1ntrain+ntest(itrainXi+jtestXj)\mu_{\text{all}} = \frac{1}{n_{\text{train}} + n_{\text{test}}}\left(\sum_{i \in \text{train}}X_i + \sum_{j \in \text{test}}X_j\right)

The contamination: Test set statistics influence training features!

Impact on model:

The model learns on training data where features are scaled using test set information: Xtrain=XtrainμallσallX'_{\text{train}} = \frac{X_{\text{train}} - \mu_{\text{all}}}{\sigma_{\text{all}}}

In production, new data has different distribution: μprodμall,σprodσall\mu_{\text{prod}} \neq \mu_{\text{all}}, \quad \sigma_{\text{prod}} \neq \sigma_{\text{all}}

Numerical example:

Training houses: sqft = [1000, 1500, 2000], mean = 1500
Test houses: sqft = [3000, 3500, 4000], mean = 3500

Fitting on all: μall=2500\mu_{\text{all}} = 2500

Training sample (1000 sqft): X=10002500σ=large negativeX' = \frac{1000 - 2500}{σ} = \text{large negative}

WHY this step? The test set's high values pull the mean up, making training samples appear more extreme.

The model learns that "large negative scaled values" predict low prices, but this relationship is artifact of the scaling, not true pattern.

The correct approach:

# RIGHT: Fit on train, transform both
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)  # Learn stats from train only
X_test_scaled = scaler.transform(X_test)  # Apply train stats to test

This simulates production: you'll compute μtrain\mu_{\text{train}} and σtrain\sigma_{\text{train}} from historical data, then apply to new samples.

[!mistake] Common Mistakes & Stelmanning

Mistake 1: "Data augmentation can't cause leakage"

Why it feels right: "I'm just creating more training samples, not touching test data!"

The steel-man argument: Data augmentation increases training set size without collecting new data. It seems like a pure preprocessing step.

Why it CAN cause leakage:

# Image classification example
X_train, X_test, y_train, y_test = train_test_split(X, y)
 
# WRONG: Augment after split, but normalize on augmented data
X_train_augmented = augment(X_train)  # Now 10x larger
scaler.fit(X_train_augmented)  # Stats influenced by augmentation
X_train_final = scaler.transform(X_train_augmented)
X_test_final = scaler.transform(X_test)  # Uses augmented stats!

The problem: Augmentation changes the distribution. If you compute statistics (mean, std) on augmented data, you're using a distribution that doesn't exist in production.

Mathematical view:

Augmentation creates XP(X)X' \sim P'(X) where PX)P(X)P'X) \neq P(X)

If you scale using μ,σ\mu', \sigma' from PP', but test/production data comes from PP: train distribution: P(X),test/prod distribution: P(X)\text{train distribution: } P'(X), \quad \text{test/prod distribution: } P(X)

WHY this step? You've created a distribution shift between training and evaluation.

The fix: Compute statistics on original training data, then augment:

scaler.fit(X_train)  # Original training distribution
X_train_scaled = scaler.transform(X_train)
X_train_augmented = augment(X_train_scaled)  # Augment AFTER scaling

Mistake 2: "Cross-validation prevents all leakage"

Why it feels right: "CV splits data multiple times, so information can't leak!"

The steel-man: Cross-validation does protect against overfitting to a single train/test split. It's a robust validation strategy.

Why it's INCOMPLETE:

CV protects against train-test contamination within each fold, but NOT against:

  1. Global preprocessing leakage:
# WRONG: Scale before CV
X_scaled = scaler.fit_transform(X)
cross_val_score(model, X_scaled, y, cv=5)  # Each fold uses global stats!

WHY wrong? Each fold's validation set influenced the scaling of its training set.

  1. Temporal leakage in time series:
# WRONG: Random CV on time series
cross_val_score(model, X, y, cv=5)  # Random splits violate temporal order!

The fix for time series: Use TimeSeriesSplit:

from sklearn.model_selection import TimeSeriesSplit
tscv = TimeSeriesSplit(n_splits=5)
# Ensures fold i only uses data before fold i+1

Derivation of why random CV fails for time series:

In random CV, fold kk might have:

  • Training {t1,t5,t10,t15}\{t_1, t_5, t_{10}, t_{15}\}
  • Validation: {t3,t7,t12}\{t_3, t_7, t_{12}\}

WHY this is wrong? Training on t15t_{15} while validating on t12t_{12} means using future to predict past!

The model learns: y^t3=f(Xt3,Xt5,Xt10,Xt15future information)\hat{y}_{t_3} = f(X_{t_3}, \underbrace{X_{t_5}, X_{t_{10}}, X_{t_{15}}}_{\text{future information}})

Mistake 3: "Feature selection can't cause leakage"

Why it feels right: "I'm just choosing relevant features, not using test labels!"

The steel-man: Feature selection improves model performance by removing noise. Seems safe.

Why it DOES cause leakage:

# WRONG: Select features on full dataset
from sklearn.feature_selection import SelectKBest
selector = SelectKBest(k=10)
X_selected = selector.fit(X, y)  # Uses ALL y values, including test!
X_train, X_test, y_train, y_test = train_test_split(X_selected, y)

The leakage mechanism:

Feature selection computes correlation/mutual information: I(Xj;y)=x,yP(x,y)logP(x,y)P(x)P(y)I(X_j; y) = \sum_{x,y} P(x,y) \log\frac{P(x,y)}{P(x)P(y)}

WHY this step? We're measuring how much XjX_j tells us about yy.

If computed on all data: Iall(Xj;y)=f(Xj,train,ytrain,Xj,test,ytest)I_{\text{all}}(X_j; y) = f(X_{j,\text{train}}, y_{\text{train}}, X_{j,\text{test}}, y_{\text{test}})

The test set labels influence which features are selected for the training set!

Concrete example:

Dataset: 100 samples, 1000 features

  • Feature X42X_{42} is randomly corelated with ytesty_{\text{test}} (spurious)
  • Feature X73X_{73} is truly predictive

If we select features using all data, X42X_{42} might be selected because it correlates with test labels. The model learns this spurious correlation, but it won't generalize.

The fix:

# RIGHT: Feature selection inside CV
pipe = Pipeline([
    ('selector', SelectKBest(k=10)),
    ('model', RandomForestClassifier())
])
cross_val_score(pipe, X, y, cv=5)  # Selector refit in each fold

Systematic Leakage Detection Framework

Step 1: Causal Graph Analysis

Draw the causal relationships:

[Transaction] → [Fraud Occurs] → [Investigation → [Dispute Filed] → [Balance Change]
     ↑ OK to use            ↑ Target       ↗ LEAKED features →

Rule: Only use features that are causally upstream of or independent of the target.

Step 2: Timeline Analysis

For each feature, ask: "When is this value known?"

Prediction time: t
Feature X_j available at: t_j

If t_j > t → TEMPORAL LEAKAGE

Step 3: Validation Distribution Test

Compare feature distributions:

# Check for leakage signal
from scipy.stats import ks_2samp
 
for col in X_train.columns:
    stat, p_value = ks_2samp(X_train[col], X_test[col])
    if p_value < 0.05:
        print(f"Warning: {col} distributions differ - possible leakage")

WHY this works? If preprocessing leaked information, train/test distributions will be artificially similar.

Step 4: Ablation Testing

Remove suspected leaky features and retrain: Δaccuracy=Acc(all features)Acc(without suspicious feature)\Delta_{\text{accuracy}} = \text{Acc}(\text{all features}) - \text{Acc}(\text{without suspicious feature})

If Δaccuracy>10%\Delta_{\text{accuracy}} > 10\%, investigate deeply—one feature shouldn't have that much power unless it's leaking.

[!recall]- The Feynman Explanation

Imagine you're studying for a test, and your friend hands you a study guide. Later you find out that study guide had the actual test questions in it! You did great, but you didn't actually learn the material.

Data leakage is when your AI model gets to "peek at the answers" during training. It happens in three main ways:

  1. The answer is in the question: Like if I ask "What's the capital of the country where Paris is?" - the answer (France) is basically in the question. That's target leakage—using information that only exists because of the answer.

  2. Studying with tomorrow's newspaper: Imagine studying for a Monday test but you have Tuesday's newspaper. You'd see what happens after Monday! That's temporal leakage—using future information to predict the past.

  3. Sharing answers before the test: If your practice tests and real test shared some answers, you'd do great on test day but not actually know the material. That's train-test contamination—mixing training and testing information.

Why it's bad: Your model looks like a genius (99% accuracy!) but fails in the real world because it learned the wrong thing. It's like memorizing test answers vs. understanding concepts.

How to prevent it: Always ask "Would I have this information at prediction time in real life?" If no, don't use it for training!

[!mnemonic] The T.T.T Leakage Check

Target dependency → Does this feature depend on the target? Temporal order → Is this information from the future? Training isolation → Did I fit/compute this on training data only?

Before using ANY feature, check all three T's. If any fails, investigate for leakage.

Memory aid: "Test The Timeline" - trace information flow from feature creation to prediction.

Key Prevention Strategies (80/20 Rule)

These 20% of practices prevent 80% of leakage:

  1. Pipeline everything - Use sklearn.pipeline.Pipeline to ensure all preprocessing refit on each CV fold
  2. Split first, preprocess second - ALWAYS split before any statistics/scaling/feature selection
  3. Temporal awareness - For time series, use TimeSeriesSplit and never shuffle
  4. Causal reasoning - Draw the causal graph; only use features upstream of the target
  5. Production simulation - Ask "Would this be available at prediction time?"

Active Recall Practice


Connections

  • Train-testvalidation split strategies - Proper splitting prevents contamination
  • Cross-validation techniques - CV must respect temporal order and preprocessing isolation
  • Feature scaling and normalization - Common source of subtle leakage
  • Time series forecasting fundamentals - Temporal leakage is critical here
  • Model evaluation metrics - Leakage inflates metrics deceptively
  • Pipeline and workflow automation - Pipelines prevent preprocessing leakage
  • Causal inference basics - Understanding causality prevents target leakage
  • Production model deployment - Train/prod distribution mismatch reveals leakage

#flashcards/ai-ml

What is data leakage? :: Information from outside the training dataset that wouldn't be available at prediction time leaking into the model during training, causing artificially high validation performance that doesn't generalize to production.

What are the three main types of data leakage?
1) Target leakage (features derived from or caused by the target), 2) Train-test contamination (test data influencing training), 3) Temporal leakage (future information bleeding into past predictions).

Why does scaling on the full dataset cause leakage? :: The scaler's mean and std are computed using test data, so training features are transformed using test set statistics. This creates an artificial relationship between train and test that won't exist in production where new data has different statistics.

What is target leakage and give an example :: Using features that are causally downstream of or directly influenced by the target variable. Example: using "account_balance_after_dispute" to predict fraud, when disputes only occur AFTER fraud is identified—the feature encodes the target outcome.

What is temporal leakage in time series?
Using future information to predict past events, such as computing a rolling average across the entire dataset before splitting, or using random cross-validation that trains on later data to predict earlier data.
Why is feature selection a potential source of leakage?
If features are selected based on correlation with the full dataset (including test labels), the set influences which features the training set uses, creating spurious correlations that won't generalize.
How do you prevent preprocessing leakage in cross-validation?
Use sklearn Pipeline so that preprocessing steps (scaling, feature selection) are refit on each fold's training data separately, rather than fitting once all data before CV.
What is the correct order: split then preprocess, or preprocess then split?
Split THEN preprocess. Always split into train/test first, then fit all preprocessing (scaling, encoding, feature engineering) on training data only and apply those fitted transforms to test data.
Why can't you use train_test_split for time series data?
It randomly shuffles data, violating temporal order. You might train on future data and test on past data, learning relationships that use information from the future.
What is the T.T.T. leakage check mnemonic?
Target dependency (does feature depend on target?), Temporal order (is this future information?), Training isolation (did I fit only on training data?). Check all three before using any feature.
How do you properly scale data to prevent leakage?
Fit scaler on training data only: scaler.fit(X_train), then transform both: X_train_scaled = scaler.transform(X_train) and X_test_scaled = scaler.transform(X_test). Never fit on combined data.
What question should you ask for every feature to detect leakage?
"Would I have this exact information available at prediction time in real production deployment?" If no, it's potential leakage.
Why does data augmentation potentially cause leakage?
If you compute normalization statistics on augmented training data, you're using a distribution that doesn't exist in production. The augmented distribution P'(X) differs from the real data distribution P(X), creating train-test mismatch.
What is TimeSeriesSplit and why use it?
A CV strategy that respects temporal order—each fold uses only past data for training and future data for validation, simulating how the model will be used in production where you predict forward in time.

Give the mathematical reason why target leakage breaks ML assumptions :: ML assumes we minimize E[(f(X) - y)²] where X is independent of y. With target leakage, features become X' = g(X, y), so we train on the distribution P(X', y) with dependence, but deploy on P(X, y), creating unbounded generalization error.

What is the 80/20 leakage prevention strategy?
Five practices that prevent most leakage: 1) Pipeline everything, 2) Split before preprocessing, 3) Use TimeSeriesSplit for temporal data, 4) Apply causal reasoning to features, 5) Simulate production conditions.

Concept Map

violates

core issue

causes

result

type 1

type 2

type 3

features are

breaks

breaks

Xj = h of y

Data leakage

Distribution assumption Ptrain=Ptest=Pprod

Model sees info unavailable at prediction

Divergence between R_prod and R_hat

High validation but production collapse

Target leakage

Train-test contamination

Temporal leakage

Causally downstream of target

Train-validation independence

Temporal ordering of data

Hinglish (regional understanding)

Intuition Hinglish mein samjho

Hinglish (regional understanding)

Intuition Hinglish mein samjho

Dekho beta, data leakage ka core idea ekdum simple hai - ye tab hota hai jab tumhare model ko training ke time aisi information mil jaati hai jo real world mein prediction ke waqt available hi nahi hogi. Socho jaise tumne exam se pehle hi answer key dekh li - obviously tum test mein 99% le aaoge, but jab actual zindagi mein wahi knowledge apply karna hoga tab tum fail ho jaoge. Same cheez model ke saath hoti hai - training aur validation pe deceptively high accuracy dikhti hai, par production mein deploy karte hi wo 99% se 60% pe gir jaati hai. Isliye ye topic itna important hai, kyunki bahut saare beginners yahi galti karte hain aur unhe samajh hi nahi aata ki model achha score kyun de raha hai par kaam kyun nahi kar raha.

Ab intuition thoda deep samjho. Machine learning ka fundamental assumption ye hai ki future bhi past jaisa hi dikhega - matlab training data, test data aur production data ka distribution same hona chahiye. Leakage isi assumption ko todta hai. Iske teen main types hain - target leakage jahan tum aisi features use karte ho jo actual mein target variable se hi derive hui hain (jaise fraud example mein account_balance_after_dispute, jo fraud confirm hone ke baad calculate hoti hai). Yahan causal direction ulta hai - fraud dispute cause karta hai, dispute balance change karta hai, toh us feature ko use karna matlab answer already jaan lena. Doosra hai train-test contamination, jahan test set ki info galti se training mein aa jaati hai. Aur teesra temporal leakage, jahan future ka data past ki predictions mein leak ho jaata hai.

Mathematically dekho toh baat clear ho jaati hai - jab tumhari feature X_j = h(y) + noise ban jaati hai, matlab feature target ka near-perfect encoding hai, toh model easily y ko reverse-engineer kar leta hai aur training error almost zero aa jaata hai. Par production mein wahi feature available nahi hoti ya bina y jaane compute hoti hai, isliye real error bahut zyada ho jaata hai. Yahi gap - training performance aur actual performance ke beech ka fasla - unbounded ho sakta hai. Toh yaad rakhna, hamesha khud se poochho: "Kya ye feature prediction ke exact moment pe genuinely available hogi?" Agar answer no hai, toh wo leakage hai aur usko hatana zaroori hai.

Go deeper — visual, from zero

Test yourself — Data Preprocessing & Feature Engineering

Connections