Visual walkthrough — Model monitoring and observability
We build one running example the whole way down: yesterday's users vs today's users, sorted into three age buckets.
Step 1 — Two piles of data become two bar charts
WHAT. We have two batches of a single number (say user age): the reference batch (the data the model trained on) and the new batch (data arriving live today). We chop the number-line into some number of side-by-side bins (buckets), and count how many points fall in each bin. We call that number of bins .
WHY. We cannot compare two clouds of raw numbers directly — there is no single "distance" between two heaps. But if we turn each heap into a bar chart (a histogram), we get heights we can compare bin-by-bin. Binning is the trick that converts "compare two distributions" into "compare pairs of numbers."
PICTURE. Two histograms over the same three age bins (so here ). The pale-yellow bars are yesterday (reference), the blue bars are today (new). Notice they disagree — the young bin shrank, the old bin grew.

Step 2 — Turn counts into fractions so size doesn't fool us
WHAT. Divide every bin's count by the total number of points in its batch. Now each bar is a fraction between and , and the bars of one batch sum to .
- — the expected fraction (reference), the height of bin in yesterday's chart.
- — the actual fraction (new), the height of bin in today's chart.
- the subscript — "which bin", running .
WHY. Yesterday we might have had users and today only . Raw counts would scream "everything shrank!" even if the shape is identical. Dividing by the total strips away batch size and leaves pure shape — the thing drift actually cares about. After this step both charts are on the same -to- scale.
PICTURE. The same two histograms, now rescaled: each colour's bars add up to a full block of height . Only the shape difference remains.

Step 3 — The first honest attempt: the raw gap
WHAT. For each bin, subtract yesterday's fraction from today's fraction:
- — the bin grew (today has a bigger share here).
- — the bin shrank.
- — the bin is unchanged.
WHY. This is the most obvious "how different?" measure — literally the height difference of the two bars. It is our starting sketch. We will see it has a flaw, and fixing that flaw builds the rest of PSI.
PICTURE. For each bin, an arrow from the yellow bar top to the blue bar top: up-arrows (grew, pink) and down-arrows (shrank). The lengths are the raw gaps .

Step 4 — A relative gap: why we reach for the logarithm
WHAT. Look not at the plain gap but at the ratio , then take its natural logarithm:
- — "how many times bigger is today's share than yesterday's?" A ratio of means doubled; means halved.
- — the natural logarithm, the tool that turns "times bigger" into a symmetric, additive scale.
WHY this tool and not another? A plain gap treats (a share that doubled) the same as (barely nudged) — both have gap . But doubling a tiny bin is a big relative event. We want a measure of "how many-fold did this move", and that is exactly what a ratio answers. We then wrap it in because:
- — no change () gives zero, as it should.
- of "doubled" () and of "halved" () are equal in size, opposite in sign — growth and shrinkage by the same factor are treated even-handedly. Plain ratios don't do this ( vs look lopsided).
This is the same log-of-a-ratio idea behind KL divergence and cross-entropy — it is the natural currency for "distance between two probability shapes."
PICTURE. The log-ratio curve plotted against the ratio : crossing zero at ratio , symmetric on a log x-axis, one bin marked as "doubled" (positive) and one as "halved" (negative).

Step 5 — Multiply gap × log-ratio: the sign trick that stops cancellation
WHAT. Combine the two pieces we built — the raw gap and the log-ratio — by multiplying them per bin:
- — how much the share moved (the size).
- — how many-fold it moved (the direction & relative scale).
WHY multiply, and why this kills cancellation. Watch the two factors move together:
| Case | product | ||
|---|---|---|---|
| bin grew () | positive | ||
| bin shrank () | positive | ||
| bin unchanged () |
Both factors always share the same sign (a bin that grew has a bigger share AND a ratio above ; a bin that shrank has both below). So their product is never negative. This is the elegant fix for Step 3's cancellation problem: instead of adding signed gaps that cancel, we add per-bin products that are all . Every deviation now contributes, none cancel.
PICTURE. For the grown bin and the shrunk bin, a little "sign multiplication" box: and , both arrows pointing up into a positive pile.

Step 6 — Sum over all bins: one number for the whole shift
WHAT. Add up every bin's non-negative contribution:
- — "walk across all bins and add", where is the number of bins we defined in Step 1.
- the boxed result is a single number , equal to only when every bin matches ( for all ).
WHY sum. Each bin told us its own drift. Drift of the whole feature is the total, because a distribution moved a lot if it moved a lot somewhere. Summing non-negative pieces means drift in one bin can never be hidden by "un-drift" elsewhere.
PICTURE. A stacked bar: each bin's positive contribution stacked on top of the last, the final height labelled PSI. Threshold lines at (green, stable) and (pink, act) drawn across.

Step 7 — The degenerate case: an empty bin blows the log up
WHAT. Suppose a bin has today but in the reference (a brand-new region never seen in training). Then and PSI explodes to infinity. The mirror case , gives .
WHY it matters and the fix. A zero in a denominator is a division-by-zero — mathematically undefined, numerically a inf or nan. But an empty bin is real: rare categories, first-day-of-a-launch data. The standard fix is to add a tiny floor (e.g. ) to every fraction, so no bin is ever exactly :
Because we nudged the shares, they no longer add up to exactly . So we re-normalise afterwards — divide every floored fraction by its (new) batch total — restoring and . The distributions are not corrupted; the floor only prevents an exact zero, and the re-normalisation keeps both bars honest probability shapes. This keeps PSI finite while still reporting a large value (a genuinely new bin should scream drift). This is exactly the "data validation" concern of Feature stores and data validation.
The both-empty sub-case. If a bin is empty in both batches (), after flooring both become , so Good: a bin that never had anyone, before or after, contributes nothing — exactly the "no drift here" answer we want.
PICTURE. Left panel: the log-ratio curve shooting to as . Right panel: the same curve with the floor, capped at a large-but-finite value.

The one-picture summary
Everything at once: raw counts → fractions → per-bin (gap × log-ratio) → summed to one PSI, with the threshold verdict.

Recall Feynman retelling — the whole walk in plain words
Imagine two photos of a party crowd, one from last week and one from today. First I split the room into a few zones and count heads (Step 1). Because the crowd size differs, I switch from head-counts to shares — "what fraction of people are in each zone" — so a busier night doesn't fool me (Step 2). For each zone I check the change in share (Step 3), but I notice the gains and losses cancel to zero if I just add them, so that alone is useless. So I also measure the fold-change — did a zone double or halve? — using a logarithm, because a log makes "doubled" and "halved" mirror each other neatly (Step 4). Then the magic: I multiply the share-change by the fold-change. Because a zone that grew is positive in both, and a zone that shrank is negative in both, every product comes out positive — nothing cancels anymore (Step 5). I add up all the zones into one number, PSI (Step 6): means the two photos match perfectly, big means the crowd rearranged a lot. Finally I guard against an empty zone that would make the fold-change infinite by adding a tiny floor and re-normalising before dividing; a zone empty in both photos just contributes zero (Step 7). One number, and I know whether today's crowd is still the crowd my model was built for.
Connections
- Data drift and concept drift
- KL divergence and cross-entropy
- Feature stores and data validation
- Model retraining and CI-CD pipelines
- A-B testing and shadow deployment
- Logging, metrics and tracing