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.
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
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.
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
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.
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")
)
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.
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.
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
Production insights
PSI scope
Click to revealRun PSI on channels and amount; do not rely on a single feature pick from training importances.
Reference window
Click to revealFreeze “reference month” in config; changing it weekly makes trends incomparable.
Acquisition campaigns
Click to revealTag campaigns in the feature log so mix shifts are explainable, not mysterious.
Before/after retrain
Click to revealHold 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.