1
Problem
2
Challenge
3
Solution
4
Implementation
5
Summary

When inputs move

Channel mix drift

The Problem

LedgerRoute was trained when most spend flowed through offline procurement. Marketing later pushed card programs and online checkout. Merchants, ticket sizes, and channel flags changed; the routing policy for human review did not.

That is covariate shift: P(X) moves while P(Y|X) stays stable in our synthetic generator. The model is still “correct” on regions it saw often; it is under-exposed on the growing online tail where decisions matter most.

Finance still sees familiar headline accuracy because offline rows dominate early months. The risk sits in a slice that is small in row count but large in euros.

↑ online share
Gradual mix shift over 120 days
0.029
Max windowed PSI on log_amount (this seed)
p ≈ 0
KS on channel_online (ref vs late)

Where global metrics lie

Segment loss before headline accuracy

The Challenge

Global metrics hide local failure

Headline accuracy on the full stream can move slowly while conditional accuracy on channel_online=1 degrades faster. PSI on a single continuous feature may stay below 0.1 even when the categorical mix plot clearly shows drift—binary and low-cardinality features need dedicated tests.

Automatic retrain on every PSI alarm is expensive and often wrong. At 0.1 many teams start a review; above 0.25 often warrants escalation—but the thresholds depend on traffic volume, cost of false positives, and whether you can importance-weight new rows instead of full redeploy.

Failure modes we see in reviews

Mix shift without accuracy drop (yet): The model still looks fine on average while the tail rots.
PSI on the wrong feature: Amount can look stable while channel proportions move.
Missing channel at scoring: Training saw channel; serving drops it → silent covariate gap.
Retrain without segment eval: New model repeats the mistake on the next acquisition push.

Covariate drift is not always an emergency. It is a demand to inspect segments before you schedule a full redeploy or freeze releases.

Our approach

Measure the channel, then the error

The Solution

Measure the channel, then the error

Run a fixed cadence: daily online share, weekly KS between reference and production windows, accuracy and calibration by channel and amount decile. Choose among retrain, importance weighting, or explicit channel features based on operational cost—not on whichever test fired first.

Covariate response playbook
Mix / PSI alert
Segment accuracy
Pick lever
Scheduled eval

Visual mix charts

Proportions beat single-number PSI for categorical shift.

Conditional metrics

Accuracy on channel_online=1 vs 0 side by side.

Importance weighting

Cheap when labels are stable and only P(X) moved.

Explicit channel feature

When the tail will stay and you need transparency.

What changes in operations

Triage
PSI & KS → human review
Confirm
Segment accuracy
Act
Reweight, feature, or retrain

Code for segment and PSI checks

Notebook-backed metrics

Implementation

Notebook 02 walks through mix plots, PSI/KS tables, and segment accuracy. Install data_drift with pip install -e ".[dev]", then run the cells top-to-bottom.

segment_accuracy.py
def accuracy_by_channel(df, y_true="label", y_pred="pred"):
    return (
        df.groupby("channel_online")
        .apply(lambda g: (g[y_pred] == g[y_true]).mean())
        .rename("accuracy")
    )
psi_window.py
def windowed_psi(ref, cur, feature, n_bins=10):
    # reference = days 0-29, current = trailing 7 days
    return population_stability_index(ref[feature], cur[feature], n_bins)

Key points

  • This seed: KS rejects equality for channel_online; PSI on log_amount stays modest.
  • Plot the mix: When PSI is flat, the channel mix chart still shows the story.
  • Store windows: Persist reference window definition with every metric row.

Notebook: 02_covariate_drift.ipynb

Summary

Figures and when to retrain

What this run shows

Channel mix drifts upward over the synthetic horizon; segment metrics diverge before the global average moves. That ordering is what you want alarms to respect.

Online channel share over time
Daily online channel share—visual evidence of covariate shift.
Windowed PSI on log amount
Windowed PSI on log_amount. Low PSI does not cancel a visible mix shift; test both.

Mix first, PSI second

Categorical proportions often alert before scalar PSI moves.

0.029 max PSI here

Below common 0.1 review line—would not auto-retrain on amount alone.

Segment before deploy

Ship only after online-tail accuracy is acceptable.

Questions answered

Retrain when PSI crosses 0.1 even if accuracy is flat? Not automatically. Start a timed review: if segment accuracy on the growing slice is within tolerance, log and reweight. Retrain when conditional error breaches your bar or after two consecutive review cycles.
Can you importance-weight instead of full retrain? Yes when P(Y|X) is stable and you have enough labeled rows from the new mix. Weight by inverse reference frequency or use a short fine-tune on recent data only.
Missing channel at scoring time? Block deploy: imputing “offline” silently recreates the worst covariate gap. Fail closed or route to rules.

Production insights

PSI scope

Click to reveal

Run PSI on channels and amount; do not rely on a single feature pick from training importances.

Reference window

Click to reveal

Freeze “reference month” in config; changing it weekly makes trends incomparable.

Acquisition campaigns

Click to reveal

Tag campaigns in the feature log so mix shifts are explainable, not mysterious.

Before/after retrain

Click to reveal

Hold out the last seven days of online-heavy traffic as a gate—never only global accuracy.

Continue this saga

Next chapter: When the label rule changes.

← Previous StoryBack to saga overviewNext Story →