Winning Kaggle the Reproducible Way

Interactive chapter

This page shows the chapter notebook with its saved results. Constructed examples do not measure competition gains.

Chapter 51: Time Series Cross-Validation

Try the decision first

How much does a backtest that ignores label availability flatter each recipe, and does it still rank the recipes correctly?

Write your expectation before opening the worked results. Use the chapter activity to compare supported inputs. To change the calculation itself, run the notebook on GitHub.

Your answers stay on this page until you download them. They are not sent to a server.

Open the chapter activity

Worked notebook and saved results

How much does a backtest that ignores label availability flatter each recipe, and does it still rank the recipes correctly?

The chapter's rule is to exclude training rows whose labels would arrive after the validation origin, and its six-row example excludes row D for exactly that reason. Measuring the flattery as the delay grows shows who benefits from the shortcut and whether a recipe choice made with it would survive.

Constructed data and models; the sizes of these effects depend on the drift speed chosen here, not on a competition result.

The experiment

Constructed daily data: ten rows a day, five features, and a target that is linear in them with coefficients that drift slowly (about 25 days of memory) plus unit noise. Two ridge regression recipes are backtested at five forecast origins, each scored on the next 30 days: one trains on only the last 30 usable days, one on all usable history. A row's label is available delay days after it is observed. The honest backtest trains on rows with observation day plus delay before the origin, as the chapter's splitter does; the naive one trains on every row observed before the origin. A control world with fixed coefficients checks the leak without drift. Everything is a mean over 160 series.

Calculation code
import numpy as np
from sklearn.linear_model import Ridge

from kaggle_companion.activities._common import clean

SEED = 51
DRAWS = 160                      # independent series; every estimate is a mean over draws (40 was too few to rank the recipes)
DAYS, ROWS, FEATURES = 420, 10, 5
BLOCK = 30                       # each assessment block covers the 30 days after its forecast origin
ORIGINS = [100, 130, 160, 190, 220]
DRIFT = 0.04                     # daily pull of the coefficients toward a fresh random draw (about 25 days of memory)
SHORT_WINDOW = 30                # the "recent history only" recipe trains on the last 30 usable days


def generate(rng, drift):
    """Ten rows a day. The target is linear in five features whose coefficients drift slowly (a stationary process), plus noise of sd 1."""
    beta = np.zeros((DAYS, FEATURES))
    beta[0] = rng.normal(size=FEATURES)
    for t in range(1, DAYS):
        beta[t] = (1 - drift) * beta[t - 1] + np.sqrt(1 - (1 - drift) ** 2) * rng.normal(size=FEATURES)
    X = rng.normal(size=(DAYS, ROWS, FEATURES))
    return X, (beta[:, None, :] * X).sum(2) + rng.normal(size=(DAYS, ROWS))


def backtest_mae(X, y, delay, window, respect_availability):
    """Mean absolute error over the assessment blocks. A row observed on day d has its label available on day d + delay.
    Honest rule: train on rows with d + delay < origin. Naive rule: train on every row observed before the origin,
    including rows whose labels would still be on the way. `window` keeps only the most recent usable days (None keeps all)."""
    errors = []
    for origin in ORIGINS:
        last = origin - delay if respect_availability else origin
        first = 0 if window is None else max(0, last - window)
        model = Ridge(alpha=1.0).fit(X[first:last].reshape(-1, FEATURES), y[first:last].reshape(-1))
        block = slice(origin, origin + BLOCK)
        errors.append(np.mean(np.abs(model.predict(X[block].reshape(-1, FEATURES)) - y[block].reshape(-1))))
    return float(np.mean(errors))


def run(delay):
    rng = np.random.default_rng(SEED)
    recipes = {"short": SHORT_WINDOW, "expanding": None}
    rows = {f"{rule}_{name}": [] for rule in ("naive", "honest") for name in recipes}
    stable = []
    for _ in range(DRAWS):
        X, y = generate(rng, DRIFT)
        for name, window in recipes.items():
            for rule in ("naive", "honest"):
                rows[f"{rule}_{name}"].append(backtest_mae(X, y, delay, window, rule == "honest"))
        X, y = generate(rng, 0.0)                              # control world: coefficients never change
        stable.append(backtest_mae(X, y, delay, None, True) - backtest_mae(X, y, delay, None, False))
    mean = {k: float(np.mean(v)) for k, v in rows.items()}
    # Optimism of the naive backtest: how much lower its error is than the honest one, per recipe (paired by series).
    optimism = {name: np.array(rows[f"honest_{name}"]) - np.array(rows[f"naive_{name}"]) for name in recipes}
    # Difference between recipes under each rule; negative means the short-window recipe has the lower error.
    naive_diff = np.array(rows["naive_short"]) - np.array(rows["naive_expanding"])
    honest_diff = np.array(rows["honest_short"]) - np.array(rows["honest_expanding"])
    se = lambda a: np.std(a, ddof=1) / np.sqrt(DRAWS)
    return clean({
        "delay": delay, **mean,
        **{f"optimism_{name}": optimism[name].mean() for name in recipes},
        **{f"optimism_{name}_se": se(optimism[name]) for name in recipes},
        "naive_diff": naive_diff.mean(), "naive_diff_se": se(naive_diff),
        "honest_diff": honest_diff.mean(), "honest_diff_se": se(honest_diff),
        "stable_gap": np.mean(stable), "stable_gap_se": se(stable),
        "draws": DRAWS, "origins": len(ORIGINS), "noise_floor": 0.7979,
    })

Measure it across the control

The control is label delay (days between observing a row and receiving its label). Each value below runs the whole experiment from the generator up.

Calculation code
import pandas as pd
from kaggle_companion.activities.ch51 import explain

VALUES = [0, 10, 30, 60]
results = {value: run(value) for value in VALUES}
table = pd.DataFrame({str(v): explain(results[v], v)['metrics'] for v in VALUES})
print(table.to_string())
                                0      10      30      60
Naive MAE, 30-day window    1.919   1.919   1.919   1.919
Honest MAE, 30-day window   1.919   2.081   2.241   2.316
Naive MAE, expanding        1.957   1.957   1.957   1.957
Honest MAE, expanding       1.957   2.008   2.080   2.164
Optimism, 30-day window    +0.000  +0.162  +0.322  +0.397
Optimism, expanding        +0.000  +0.051  +0.123  +0.207

Look at it

The figure shows the default setting, label delay (days between observing a row and receiving its label) = 30.

Calculation code
import matplotlib.pyplot as plt
from kaggle_companion.activities.ch51 import draw, NCOLS, HEIGHT

DEFAULT = 30
fig, axes = plt.subplots(1, NCOLS, figsize=(6.6 if NCOLS == 1 else 10.4, HEIGHT), layout='constrained')
draw(axes, results[DEFAULT], DEFAULT)
plt.show()
Saved figure from this chapter calculation

What the result says

With a 30-day delay the naive backtest scores the 30-day window 1.919 and the expanding recipe 1.957, a gap of -0.038 (standard error 0.013), so it prefers the 30-day window; the honest backtest reverses that, with the expanding recipe ahead by 0.161 (standard error 0.015). Respecting availability raises the short window's error to 2.241: 2.241 - 1.919 = 0.322 of optimism, against 0.123 for the expanding recipe. With fixed coefficients the two rules differ by 0.001 MAE, so the effect comes from a relationship that changes.

Apply this to your competition

Book location: Chapter 51, Define the Forecast Origin.

Technical corrections

Apply a lesson from this chapter

Select a lesson whose conditions match your project. State how you would test it and what would make you reject it.

Revisit random validation as dense physical training expands

Conditions

  • new training rows are near validation rows in space and time

Action

  • Proposed conditional application: Check spatial/temporal proximity leakage when scaling training data and use genuinely separated periods for proposed validation.

Check

  • Proposed check: Hold out independent year/spacetime blocks and quantify nearest-training distances versus random-split score.

Limits

  • Public tuning can overfit; local spatial blocking does not necessarily cover future-year shifts.

Find source links and value grades in the lesson atlas

Test recency-weighted trend fitting

Conditions

  • older observations may be less representative of future trend

Action

  • Proposed adaptation of source observation: Compare exponential recency weighting with an equal-weight linear trend under matched forecast origins.

Check

  • Proposed verification: Tune decay on held-out origins and inspect series where recency weighting fails.

Limits

  • The decay rate and numeric improvement are absent; author-reported causality is not a controlled ablation.

Find source links and value grades in the lesson atlas

Test how much recent training history matters

Conditions

  • a forecast must be made beyond a substantial data-delivery lag

Action

  • Proposed adaptation of source observation: Compare otherwise matched models with and without the most recent month of training data.

Check

  • Proposed verification: Repeat across several historical forecast origins and report horizon-specific loss.

Limits

  • No numeric comparison is given; the finding does not justify omitting recent data for other series.

Find source links and value grades in the lesson atlas

Test recent-only training when older sales data hurts

Conditions

  • retail histories change and the forecast window is recent

Action

  • Proposed conditional application: Compare short recent training windows with longer histories using a forward validation period.

Check

  • Proposed check: Sweep training-history lengths on multiple rolling future windows at equal horizon; inspect seasonal/rare-item errors.

Limits

  • Short windows can lose annual seasonality; one historical competition period is insufficient.

Find source links and value grades in the lesson atlas

Match the holdout split to the test-generation mechanism

Conditions

  • the evaluation split mechanism is known

Action

  • Author advice: Construct the holdout using the same time or geographic separation mechanism as the intended test.

Check

  • Proposed verification: Check prediction horizon and group separation; retain another untouched check after repeated tuning.

Limits

  • The public leaderboard is not an independent remedy for repeated holdout tuning, despite the author’s advice in this paragraph.

Find source links and value grades in the lesson atlas

Match validation hours to the submission window

Conditions

  • test traffic occupies a restricted time-of-day window

Action

  • Proposed adaptation of source observation: Evaluate on held-out rows matching the test-hour window and also retain a full-day diagnostic.

Check

  • Proposed verification: Check hour coverage, date separation and label prevalence in both evaluation views.

Limits

  • Matching hours does not address all date-level distribution shifts; no comparison is reported.

Find source links and value grades in the lesson atlas

Compare subgroup-specific training windows

Conditions

  • separate environments have different historical data coverage and drift

Action

  • Proposed: fit environment-specific models with explicitly selected historical windows.

Check

  • Proposed: compare pooled and per-city models on the same forward holdout and audit sample counts for every city/window.

Limits

  • The per-city windows and settings have no pooled-model comparison. Learning rate and depth can carry across loop iterations, and precomputed features require a separate temporal-availability audit.

Find source links and value grades in the lesson atlas

Fit transforms within expanding validation windows

Conditions

  • time-ordered regression tuned across expanding windows

Action

  • Proposed: fit the transform on each training window and transform its held-out window before early-stopped model fitting.

Check

  • Proposed: verify expanding-window chronology and the forecast gap from the actual splitter; ensure any target encoder fits only the training portion.

Limits

  • The splitter implementation is imported, so this packet establishes the expanding-window constructor and fold-local calls, not its exact index leakage guarantees.

Find source links and value grades in the lesson atlas

Use recent time blocks for forward validation

Conditions

  • engagement prediction occurs in a later contiguous time period

Action

  • Proposed: partition a contiguous latest holdout block after a bounded earlier training window.

Check

  • Proposed: assert training days precede holdout days and features are computed with only information available at each boundary.

Limits

  • Category frequencies are computed before assigning the holdout; imported LOO/BAYES features are not proven fold-safe. Specific day/window constants are examples, not optimal recipes.

Find source links and value grades in the lesson atlas

Audit prediction-time availability after discovering panel structure

Conditions

  • anonymized categories hide customer-contract time sequences

Action

  • Proposed conditional application: Recover panel grouping and ordering, but distinguish historical lag labels from future lead labels before claiming deployment utility.

Check

  • Proposed check: Timestamp every derived feature and replay future-period predictions using only historically available labels.

Limits

  • Competition transduction with future labels does not establish a deployable model; anonymized grouping can be mistaken.

Find source links and value grades in the lesson atlas

Simulate missing neighboring labels in validation

Conditions

  • fraud features depend on labels from adjacent card transactions

Action

  • Proposed conditional application: Restrict label-derived temporal features to prediction-time-available labels and reproduce test-set hidden-label gaps in validation.

Check

  • Proposed check: Rebuild features with only confirmed historical labels; mask validation labels to emulate real feedback delays.

Limits

  • Future labels and held-out neighbor labels leak; reported training F1 is not prospective evidence.

Find source links and value grades in the lesson atlas

Validate truncated histories with repeated user-group splits

Conditions

  • test predictions use unseen users with incomplete histories

Action

  • Proposed adaptation of source observation: Split by user and simulate test truncation; repeat doubtful comparisons across multiple random histories.

Check

  • Proposed verification: Report metric dispersion and prohibit post-cutoff events in every feature.

Limits

  • Their 50–100 nested simulations are expensive and truncation must match target use.

Find source links and value grades in the lesson atlas

Use forward-time validation when test data are in the future

Conditions

  • target data are from a future period with temporal effects
  • the prediction task involves a future time period

Action

  • Proposed adaptation of author advice: Train on past periods and validate on subsequent periods, carrying all feature transforms inside the time split.
  • Proposed conditional application: Train on past observations and validate on later observations rather than using random stratified folds.

Check

  • Proposed verification: Compare repeated forward backtests and inspect horizon/distribution match.
  • Proposed check: Assert each fold's training timestamps precede validation and feature availability obeys forecast origin.

Limits

  • Random stratification is not generally valid with groups, leakage or temporal dependence; author advice is conditional.
  • Choose embargo/horizon for delayed labels; future distribution may still differ.

Find source links and value grades in the lesson atlas

Use contiguous validation periods for weather forecasting

Conditions

  • test forecasts cover continuous time periods

Action

  • Proposed conditional application: Train with three-fold cross-validation using continuous periods to resemble the test dataset.

Check

  • Proposed check: Inspect contiguous fold dates and forecast horizon; compare with rolling future holdouts and ensure preprocessing fits training periods.

Limits

  • Contiguous folds are not necessarily forward-only; seasonal and geographic shifts remain.

Find source links and value grades in the lesson atlas