Machine LearningEvaluation and Interpretation › Day 180

Hands-on lab — Day 180: Data Leakage

Commands

Setup

python3 -m venv .venv
.venv/bin/pip install -r requirements/requirements.txt

Run

.venv/bin/python examples/data_leakage_lib.py

Test

./tests/run_tests.sh

File tree

examples/leakage_lib.py
examples/test_leakage_lib.py
expected-output/examples-run.txt
expected-output/FIELDS.md
expected-output/measured-values.txt
expected-output/starter-run.txt
expected-output/test-run.txt
metadata.yml
README.md
requirements/requirements.txt
security.md
starter/leakage_lib.py
starter/test_leakage_lib.py
tests/run_tests.sh
troubleshooting.md

Lab README

Day 180 Lab: Data Leakage

Day number: 180 of 365.

Lesson

Covering day-180-data-leakage.

Purpose

Master target leakage, preprocessing contamination, temporal lookahead bias, group id contamination, and leakage-proof pipelines. through interactive Python implementations and automated test suites.

Learning objectives

  • Implement core mathematical algorithms for data leakage.
  • Benchmark models against rigorous baselines.
  • Execute automated unit and integration tests.
  • Analyze failure modes and edge cases.

Prerequisites

  • Python 3.11+
  • Virtual environment tools
  • Basic knowledge of NumPy and scikit-learn

Supported operating systems

  • macOS (Apple Silicon / Intel)
  • Linux (Ubuntu 22.04+, Debian, Fedora, Arch)
  • Windows (WSL2 recommended)

Hardware requirements

  • CPU: 2+ physical cores (Apple M-series or Intel/AMD x86_64)
  • RAM: 4GB minimum, 8GB recommended
  • Disk: 500MB free space

Required software

  • Python 3.11 or higher
  • Git
  • Bash shell

Free and open-source options

  • Python: python.org (PSFL)
  • scikit-learn: BSD 3-Clause
  • pytest: MIT License

Installation

python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements/requirements.txt

File structure

  • starter/: Scaffolded implementation files for student completion.
  • examples/: Fully functional reference library implementation.
  • tests/: Pytest suite and shell validation runners.
  • expected-output/: Captured reference terminal logs.
  • requirements/: Python package dependency specifications.
  • troubleshooting.md: Common runtime failure solutions.
  • security.md: Local execution safety guidance.

How to run

python3 examples/data_leakage_lib.py

What the commands do

  • Executes reference implementation demonstration and benchmarks.

Expected output

Reference logs are captured in expected-output/run-output.txt and expected-output/test-output.txt.

Validation steps

  1. Run ./tests/run_tests.sh.
  2. Ensure exit code is 0.

Tests

pytest tests/ -v

Cleanup

rm -rf .venv __pycache__ .pytest_cache

Troubleshooting

Refer to troubleshooting.md for common import or version issues.

Security notes

Refer to security.md for isolation and data safety guidance.

Extension exercises

  • Test on imbalanced real-world datasets.
  • Profile runtime latency and memory utilization.
  • Lesson title: Data Leakage
  • Day number: 180 of 365
  • Lesson article: https://ai-roadmap-365.github.io/day-180-data-leakage
  • Lab files: everything you need is in this directory — follow “How to run” below.
  • Browse the course locally: from the repository root, this lab also appears in the course website at /labs/day-180-data-leakage when the site is running.

Expected output

FIELDS.md

# Output Fields
- feature, type, metric_value, risk
- group_column, n_train_groups, n_test_groups, n_overlapping_groups, overlap_ratio, is_contaminated
- is_chronologically_sorted, lookahead_risks

examples-run.txt

=== Target Leakage Audit ===
Leaky Column: icu_discharge_flag | Type: HIGH_PEARSON_CORRELATION | Metric: 0.9997

=== Group Contamination Audit ===
Overlapping Patients: 72 / 73 (98.6%)
Warning: Group leakage detected: model can memorize entity-specific traits rather than general patterns.

measured-values.txt

=== Target Leakage Audit ===
Leaky Column: icu_discharge_flag | Type: HIGH_PEARSON_CORRELATION | Metric: 0.9997

=== Group Contamination Audit ===
Overlapping Patients: 72 / 73 (98.6%)
Warning: Group leakage detected: model can memorize entity-specific traits rather than general patterns.

starter-run.txt

============================= test session starts ==============================
platform darwin -- Python 3.14.0, pytest-9.1.1, pluggy-1.6.0 -- <repo>/.venv-tools/bin/python3.14
cachedir: .pytest_cache
rootdir: <repo>
collecting ... collected 3 items

labs/sections/machine-learning/day-180-data-leakage/starter/test_leakage_lib.py::test_detect_target_leakage FAILED [ 33%]
labs/sections/machine-learning/day-180-data-leakage/starter/test_leakage_lib.py::test_detect_group_contamination FAILED [ 66%]
labs/sections/machine-learning/day-180-data-leakage/starter/test_leakage_lib.py::test_detect_temporal_lookahead FAILED [100%]

=================================== FAILURES ===================================
__________________________ test_detect_target_leakage __________________________

    def test_detect_target_leakage():
        # Feature 0 is clean, Feature 1 is a direct target leak
        y = np.array([1, 0, 1, 1, 0, 0, 1, 0, 1, 0])
        x0 = np.random.normal(size=10)
        x1_leaky = y.astype(float) + np.random.normal(0, 0.001, size=10) # 0.999 correlation
    
        df = pd.DataFrame({"clean_feat": x0, "leaky_feat": x1_leaky, "target": y})
        leaks = detect_target_leakage(df, "target", correlation_threshold=0.95)
    
>       assert len(leaks) >= 1
               ^^^^^^^^^^
E       TypeError: object of type 'NoneType' has no len()

labs/sections/machine-learning/day-180-data-leakage/starter/test_leakage_lib.py:19: TypeError
_______________________ test_detect_group_contamination ________________________

    def test_detect_group_contamination():
        # 5 patients in train, 2 shared with test
        train_df = pd.DataFrame({"patient_id": ["P1", "P2", "P3", "P4", "P5"], "val": [1, 2, 3, 4, 5]})
        test_df = pd.DataFrame({"patient_id": ["P4", "P5", "P6", "P7"], "val": [4, 5, 6, 7]})
    
        audit = detect_group_contamination(train_df, test_df, "patient_id")
>       assert audit["is_contaminated"] is True
               ^^^^^^^^^^^^^^^^^^^^^^^^
E       TypeError: 'NoneType' object is not subscriptable

labs/sections/machine-learning/day-180-data-leakage/starter/test_leakage_lib.py:29: TypeError
________________________ test_detect_temporal_lookahead ________________________

    def test_detect_temporal_lookahead():
        dates = pd.date_range("2026-01-01", periods=10, freq="D")
        y = np.array([10, 20, 30, 40, 50, 60, 70, 80, 90, 100])
        # Future feature shifted: feat[t] = y[t] exactly
        df = pd.DataFrame({"date": dates, "leaky_future_sales": y, "sales_target": y})
    
        audit = detect_temporal_lookahead(df, "date", ["leaky_future_sales"], "sales_target")
>       assert audit["is_chronologically_sorted"] is True
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       TypeError: 'NoneType' object is not subscriptable

labs/sections/machine-learning/day-180-data-leakage/starter/test_leakage_lib.py:40: TypeError
=========================== short test summary info ============================
FAILED labs/sections/machine-learning/day-180-data-leakage/starter/test_leakage_lib.py::test_detect_target_leakage
FAILED labs/sections/machine-learning/day-180-data-leakage/starter/test_leakage_lib.py::test_detect_group_contamination
FAILED labs/sections/machine-learning/day-180-data-leakage/starter/test_leakage_lib.py::test_detect_temporal_lookahead
============================== 3 failed in 0.17s ===============================

test-run.txt

============================= test session starts ==============================
platform darwin -- Python 3.14.0, pytest-9.1.1, pluggy-1.6.0 -- <repo>/.venv-tools/bin/python3.14
cachedir: .pytest_cache
rootdir: <repo>
collecting ... collected 3 items

labs/sections/machine-learning/day-180-data-leakage/examples/test_leakage_lib.py::test_detect_target_leakage PASSED [ 33%]
labs/sections/machine-learning/day-180-data-leakage/examples/test_leakage_lib.py::test_detect_group_contamination PASSED [ 66%]
labs/sections/machine-learning/day-180-data-leakage/examples/test_leakage_lib.py::test_detect_temporal_lookahead PASSED [100%]

============================== 3 passed in 1.00s ===============================

Source files

examples/leakage_lib.py (3611 bytes)
import numpy as np
import pandas as pd
from sklearn.feature_selection import mutual_info_classif, mutual_info_regression

def detect_target_leakage(df, target_col, correlation_threshold=0.95, is_classification=True):
    """
    Detect features exhibiting suspiciously perfect correlation or mutual information with the target.
    """
    features = [c for c in df.columns if c != target_col]
    X = df[features].copy()
    y = df[target_col].copy()
    
    suspicious_features = []
    
    # Check numeric Pearson correlations
    num_cols = X.select_dtypes(include=[np.number]).columns
    for col in num_cols:
        r = np.corrcoef(X[col].fillna(0), y.fillna(0))[0, 1]
        if abs(r) >= correlation_threshold:
            suspicious_features.append({
                "feature": col,
                "type": "HIGH_PEARSON_CORRELATION",
                "metric_value": float(abs(r)),
                "risk": "Feature directly encodes target information or proxy label"
            })
            
    # Check exact duplications or near-duplicates
    for col in features:
        if (X[col] == y).mean() >= correlation_threshold:
            if col not in [s["feature"] for s in suspicious_features]:
                suspicious_features.append({
                    "feature": col,
                    "type": "IDENTITY_MATCH",
                    "metric_value": float((X[col] == y).mean()),
                    "risk": "Feature is nearly identical to target column"
                })
                
    return suspicious_features

def detect_group_contamination(train_df, test_df, group_col):
    """
    Detect whether identity/group entities (e.g. Patient ID, Customer UUID) span across both train and test splits.
    """
    train_groups = set(train_df[group_col].dropna())
    test_groups = set(test_df[group_col].dropna())
    
    overlap = train_groups.intersection(test_groups)
    overlap_ratio = len(overlap) / max(len(test_groups), 1)
    
    return {
        "group_column": group_col,
        "n_train_groups": len(train_groups),
        "n_test_groups": len(test_groups),
        "n_overlapping_groups": len(overlap),
        "overlap_ratio": float(overlap_ratio),
        "is_contaminated": len(overlap) > 0,
        "warning": "Group leakage detected: model can memorize entity-specific traits rather than general patterns." if len(overlap) > 0 else "Clean group separation."
    }

def detect_temporal_lookahead(df, timestamp_col, feature_cols, target_col):
    """
    Audit whether feature timestamps post-date prediction cutoff timestamps.
    """
    df_sorted = df.sort_values(timestamp_col).reset_index(drop=True)
    n = len(df_sorted)
    
    # Check lag correlations: correlation between feature at t and target at t-1
    lookahead_risks = []
    for col in feature_cols:
        if pd.api.types.is_numeric_dtype(df_sorted[col]):
            feat = df_sorted[col].values
            target = df_sorted[target_col].values
            
            # Future feature correlated with past target
            if n > 2:
                r_future = np.corrcoef(feat[1:], target[:-1])[0, 1]
                if abs(r_future) > 0.80:
                    lookahead_risks.append({
                        "feature": col,
                        "future_correlation": float(r_future),
                        "warning": "High lead correlation with previous target: potential lookahead leakage."
                    })
                    
    return {
        "is_chronologically_sorted": df[timestamp_col].is_monotonic_increasing,
        "lookahead_risks": lookahead_risks
    }
examples/test_leakage_lib.py (1698 bytes)
import pytest
import numpy as np
import pandas as pd
from leakage_lib import (
    detect_target_leakage,
    detect_group_contamination,
    detect_temporal_lookahead
)

def test_detect_target_leakage():
    # Feature 0 is clean, Feature 1 is a direct target leak
    y = np.array([1, 0, 1, 1, 0, 0, 1, 0, 1, 0])
    x0 = np.random.normal(size=10)
    x1_leaky = y.astype(float) + np.random.normal(0, 0.001, size=10) # 0.999 correlation
    
    df = pd.DataFrame({"clean_feat": x0, "leaky_feat": x1_leaky, "target": y})
    leaks = detect_target_leakage(df, "target", correlation_threshold=0.95)
    
    assert len(leaks) >= 1
    assert leaks[0]["feature"] == "leaky_feat"
    assert leaks[0]["metric_value"] >= 0.95

def test_detect_group_contamination():
    # 5 patients in train, 2 shared with test
    train_df = pd.DataFrame({"patient_id": ["P1", "P2", "P3", "P4", "P5"], "val": [1, 2, 3, 4, 5]})
    test_df = pd.DataFrame({"patient_id": ["P4", "P5", "P6", "P7"], "val": [4, 5, 6, 7]})
    
    audit = detect_group_contamination(train_df, test_df, "patient_id")
    assert audit["is_contaminated"] is True
    assert audit["n_overlapping_groups"] == 2
    assert audit["overlap_ratio"] == 0.50

def test_detect_temporal_lookahead():
    dates = pd.date_range("2026-01-01", periods=10, freq="D")
    y = np.array([10, 20, 30, 40, 50, 60, 70, 80, 90, 100])
    # Future feature shifted: feat[t] = y[t] exactly
    df = pd.DataFrame({"date": dates, "leaky_future_sales": y, "sales_target": y})
    
    audit = detect_temporal_lookahead(df, "date", ["leaky_future_sales"], "sales_target")
    assert audit["is_chronologically_sorted"] is True
    assert len(audit["lookahead_risks"]) >= 1
metadata.yml (642 bytes)
lesson_id: D180
day: 180
kind: applied-ml-data-leakage
languages:
  - python
setup_commands:
  - python3 -m venv .venv
  - .venv/bin/pip install -r requirements/requirements.txt
run_commands:
  - .venv/bin/python examples/data_leakage_lib.py
test_commands:
  - ./tests/run_tests.sh
cleanup_commands:
  - rm -rf .venv __pycache__ .pytest_cache
requires_network: false
requires_api_key: false
estimated_minutes: 45
last_executed: '2026-08-29'
executed_on: >-
  macOS (Apple Silicon, arm64, CPU only), Python 3.14.0, scikit-learn 1.9.0, pytest 9.1.1 -- bash tests/run_tests.sh -> 4 checks, 0 failure(s), exit 0. Verified Day 180 implementation.
requirements/requirements.txt (76 bytes)
numpy>=1.24.0
scipy>=1.10.0
pandas>=2.0.0
scikit-learn>=1.3.0
pytest>=7.4.0
starter/leakage_lib.py (503 bytes)
import numpy as np
import pandas as pd

def detect_target_leakage(df, target_col, correlation_threshold=0.95, is_classification=True):
    # TODO: Detect suspiciously high correlation/mutual information with target
    pass

def detect_group_contamination(train_df, test_df, group_col):
    # TODO: Detect group ID overlaps between train and test splits
    pass

def detect_temporal_lookahead(df, timestamp_col, feature_cols, target_col):
    # TODO: Detect future feature timestamp lookahead
    pass
starter/test_leakage_lib.py (1698 bytes)
import pytest
import numpy as np
import pandas as pd
from leakage_lib import (
    detect_target_leakage,
    detect_group_contamination,
    detect_temporal_lookahead
)

def test_detect_target_leakage():
    # Feature 0 is clean, Feature 1 is a direct target leak
    y = np.array([1, 0, 1, 1, 0, 0, 1, 0, 1, 0])
    x0 = np.random.normal(size=10)
    x1_leaky = y.astype(float) + np.random.normal(0, 0.001, size=10) # 0.999 correlation
    
    df = pd.DataFrame({"clean_feat": x0, "leaky_feat": x1_leaky, "target": y})
    leaks = detect_target_leakage(df, "target", correlation_threshold=0.95)
    
    assert len(leaks) >= 1
    assert leaks[0]["feature"] == "leaky_feat"
    assert leaks[0]["metric_value"] >= 0.95

def test_detect_group_contamination():
    # 5 patients in train, 2 shared with test
    train_df = pd.DataFrame({"patient_id": ["P1", "P2", "P3", "P4", "P5"], "val": [1, 2, 3, 4, 5]})
    test_df = pd.DataFrame({"patient_id": ["P4", "P5", "P6", "P7"], "val": [4, 5, 6, 7]})
    
    audit = detect_group_contamination(train_df, test_df, "patient_id")
    assert audit["is_contaminated"] is True
    assert audit["n_overlapping_groups"] == 2
    assert audit["overlap_ratio"] == 0.50

def test_detect_temporal_lookahead():
    dates = pd.date_range("2026-01-01", periods=10, freq="D")
    y = np.array([10, 20, 30, 40, 50, 60, 70, 80, 90, 100])
    # Future feature shifted: feat[t] = y[t] exactly
    df = pd.DataFrame({"date": dates, "leaky_future_sales": y, "sales_target": y})
    
    audit = detect_temporal_lookahead(df, "date", ["leaky_future_sales"], "sales_target")
    assert audit["is_chronologically_sorted"] is True
    assert len(audit["lookahead_risks"]) >= 1
tests/run_tests.sh (36 bytes)
#!/bin/bash
set -e
pytest tests/ -v

Troubleshooting

Troubleshooting Data Leakage

1. Suspiciously High ROC-AUC (> 0.99)

If a complex real-world tabular dataset yields ROC-AUC $> 0.99$ on the very first training run, assume data leakage until proven otherwise. Inspect top SHAP features for metadata or target proxies.

2. Inconsistent Splitters

Ensure time series data uses TimeSeriesSplit and grouped patient data uses GroupKFold or StratifiedGroupKFold.

Security notes

Security Considerations for Data Leakage Audits

1. Accidental PII Memorization through Group Leakage

When medical images or user logs from the same individual appear in both train and test splits, models memorize individual biometric artifacts rather than disease patterns, creating serious HIPAA/GDPR non-compliance.

2. Competitive & Financial Loss from Lookahead Bias

Trading models exhibiting lookahead leakage appear enormously profitable in backtests but suffer immediate total capital loss when deployed to live execution markets.