Two summaries of how far a new binned distribution has moved from a reference one.
Details
The population stability index is \(\sum_i (p_i - q_i)\log(p_i/q_i)\) – the symmetrised Kullback-Leibler divergence of the two discrete distributions. The conventional reading, from credit-risk monitoring where it originates, is that below 0.1 is stable, 0.1 to 0.25 warrants a look, and above 0.25 is a material shift.
The Jensen-Shannon divergence is \(\tfrac12 KL(p\|m) + \tfrac12 KL(q\|m)\) with \(m\) the mixture \((p+q)/2\). Unlike PSI it is bounded – by \(\log 2\) in nats – so it is comparable across columns with different numbers of bins, and it is finite even when a category is absent from one side.
Empty bins are floored at eps for the PSI only, since
\(\log(0)\) would otherwise send it to infinity on a single missing
category.
References
Wu D, Olson DL (2010). Enterprise risk management: coping with model risk in a large bank. Journal of the Operational Research Society 61(2), 179–190. doi:10.1057/jors.2008.144
Lin J (1991). Divergence measures based on the Shannon entropy. IEEE Transactions on Information Theory 37(1), 145–151. doi:10.1109/18.61115
Examples
set.seed(2)
ref <- stats::rnorm(500)
# Same distribution: both indices near zero.
drift_psi(ref, stats::rnorm(500))
#> psi js_divergence
#> 0.022072326 0.002752915
# A shift both indices register.
drift_psi(ref, stats::rnorm(500, mean = 1))
#> psi js_divergence
#> 0.9543324 0.1053174
# A sample compared with itself has moved nowhere at all.
drift_psi(ref, ref)
#> psi js_divergence
#> 0 0
# The Jensen-Shannon divergence is bounded by log(2), whatever the
# shift, which is what makes it comparable across columns.
drift_psi(c(1, 1, 1), c(9, 9, 9))[["js_divergence"]] <= log(2)
#> [1] TRUE