3. PSI 计算原理:数学公式、分箱方法与实际步骤
群体稳定性指标(Population Stability Index, PSI)是衡量模型评分或催收触达策略在时间维度上分布偏移的核心量化工具。在催收场景中,PSI 用于监控每日/每周的客户风险评分分布是否与建模样本(基准分布)发生显著变化,从而触发策略阈值调整。本节从数学公式、分箱方法到计算步骤,完整拆解 PSI 的计算原理。
3.1 PSI 数学公式
PSI 基于信息论中的 KL 散度(Kullback-Leibler divergence)对称化变形,衡量两个概率分布之间的差异。其标准公式为:
PSI = Σ (P_actual_i - P_expected_i) × ln(P_actual_i / P_expected_i)
其中:
- i = 第 i 个分箱(共 n 个箱)
- P_expected_i = 基准分布(如建模样本)在第 i 箱中的占比
- P_actual_i = 实际分布(如当前催收触达批次)在第 i 箱中的占比
- ln = 自然对数
公式解读:
- 差值项 (P_actual_i - P_expected_i):反映两个分布在该箱上的绝对偏移方向与幅度。
- 对数项 ln(P_actual_i / P_expected_i):反映相对偏移的敏感度,当实际占比远大于或远小于预期占比时,该项会放大贡献。
- 求和:将所有箱的偏移贡献累加,得到全局稳定性指标。
催收场景阈值参考:
- PSI < 0.1:分布稳定,无需调整策略。
- 0.1 ≤ PSI < 0.25:轻度偏移,需关注并分析原因。
- PSI ≥ 0.25:显著偏移,必须触发策略阈值调整或模型重建。
3.2 分箱方法详解
PSI 的计算依赖将连续型评分或概率值离散化为有限个箱。分箱方式直接影响 PSI 的敏感度和稳定性。催收场景中常用的分箱方法有三种:
| 分箱方法 | 原理 | 优点 | 缺点 | 催收适用场景 |
|---|---|---|---|---|
| 等距分箱 | 将评分范围均匀划分为 k 个宽度相等的区间 | 计算简单,箱边界固定,便于跨时间对比 | 数据分布不均时,某些箱可能样本极少或为空 | 评分分布较均匀的成熟模型;监管要求固定分段 |
| 等频分箱 | 按样本量将数据划分为 k 个样本数相等的区间 | 每个箱样本量均衡,避免空箱,统计稳定性高 | 箱边界随分布变化而移动,跨期对比需重新对齐 | 分布偏态严重、长尾数据;催收早期样本量波动大 |
| 自定义分箱 | 基于业务经验或风险阈值手动设定断点 | 贴合业务逻辑,箱边界有业务解释性 | 主观性强,需定期验证断点合理性 | 催收策略中已定义的风险等级(如 A/B/C/D 级) |
实践建议: 催收触达 PSI 监控推荐优先使用 等频分箱(通常分 10 箱),因为催收评分分布往往左偏(高风险客户占比较小),等频分箱能保证每个箱都有足够的样本计算占比,避免因空箱导致 PSI 计算异常。
3.3 实际计算步骤详解
以下以等频分箱(10 箱)为例,展示从原始评分到 PSI 值的完整计算流程。
步骤 1:确定基准分布(Expected)
- 选取建模样本或历史稳定时段(如最近 3 个月)的客户评分数据。
- 按评分升序排序,等频划分为 10 箱,每箱包含 10% 的样本。
- 记录每箱的评分区间边界(如 [300, 450), [450, 520), ...)。
- 计算每箱的基准占比 P_expected_i = 该箱样本数 / 总样本数 = 0.10(等频分箱下每箱正好 10%)。
步骤 2:计算实际分布(Actual)
- 获取当前监控周期(如本周)的催收触达客户评分数据。
- 使用步骤 1 确定的评分区间边界,将当前数据映射到对应箱中。
- 计算每箱的实际占比 P_actual_i = 该箱当前样本数 / 当前总样本数。
步骤 3:逐箱计算偏移贡献
- 对每个箱 i,计算差值 (P_actual_i - P_expected_i)。
- 计算对数比 ln(P_actual_i / P_expected_i)。
- 将差值与对数比相乘,得到该箱的 PSI 贡献值。
步骤 4:求和得到 PSI
- 将所有箱的贡献值累加,即得最终 PSI。
# Python 伪代码示例:等频分箱 PSI 计算
import numpy as np
import pandas as pd
def calculate_psi_equalfreq(expected_scores, actual_scores, bins=10):
# 步骤1:基于基准分布确定等频分箱边界
percentiles = np.linspace(0, 100, bins + 1)
bin_edges = np.percentile(expected_scores, percentiles)
bin_edges[0] = -np.inf
bin_edges[-1] = np.inf
# 步骤2:将实际数据映射到箱中
expected_bins = np.digitize(expected_scores, bin_edges[1:-1])
actual_bins = np.digitize(actual_scores, bin_edges[1:-1])
# 步骤3:计算每箱占比
expected_counts = np.bincount(expected_bins, minlength=bins+1)[1:bins+1]
actual_counts = np.bincount(actual_bins, minlength=bins+1)[1:bins+1]
expected_pct = expected_counts / len(expected_scores)
actual_pct = actual_counts / len(actual_scores)
# 步骤4:计算 PSI(处理零占比情况)
psi = 0
for i in range(bins):
if expected_pct[i] == 0:
expected_pct[i] = 0.0001 # 极小值避免除零
if actual_pct[i] == 0:
actual_pct[i] = 0.0001
psi += (actual_pct[i] - expected_pct[i]) * np.log(actual_pct[i] / expected_pct[i])
return psi, bin_edges, expected_pct, actual_pct
避坑提示:
- 空箱处理: 当实际分布中某个箱样本数为 0 时,P_actual_i = 0 会导致对数项为负无穷。建议对空箱赋予极小值(如 0.0001)或使用平滑处理(如加 1 平滑)。
- 分箱数选择: 箱数过少(如 5 箱)会掩盖局部偏移;箱数过多(如 20 箱)会导致噪声放大。催收场景推荐 10 箱,兼顾敏感度与稳定性。
- 边界漂移: 等频分箱的边界随基准分布变化,每次重新建模后需重新定义箱边界,并回算历史 PSI 以保持一致性。
3.4 PSI 计算流程可视化
下图展示了从原始评分数据到 PSI 输出的完整计算链路,包含基准分布构建、实际分布映射、逐箱贡献计算与最终判定。
3.5 计算示例:从数据到 PSI
假设某催收模型评分范围为 300~900 分,基准分布(等频 10 箱)与本周实际分布如下:
| 箱号 | 评分区间 | 基准占比 P_expected | 实际占比 P_actual | 差值 | 对数比 ln(P_actual/P_expected) | 贡献值 |
|---|---|---|---|---|---|---|
| 1 | [300, 420) | 0.100 | 0.120 | +0.020 | 0.182 | 0.0036 |
| 2 | [420, 480) | 0.100 | 0.105 | +0.005 | 0.049 | 0.0002 |
| 3 | [480, 530) | 0.100 | 0.095 | -0.005 | -0.051 | 0.0003 |
| 4 | [530, 570) | 0.100 | 0.090 | -0.010 | -0.105 | 0.0011 |
| 5 | [570, 610) | 0.100 | 0.088 | -0.012 | -0.128 | 0.0015 |
| 6 | [610, 650) | 0.100 | 0.092 | -0.008 | -0.083 | 0.0007 |
| 7 | [650, 690) | 0.100 | 0.098 | -0.002 | -0.020 | 0.0000 |
| 8 | [690, 740) | 0.100 | 0.102 | +0.002 | 0.020 | 0.0000 |
| 9 | [740, 800) | 0.100 | 0.108 | +0.008 | 0.077 | 0.0006 |
| 10 | [800, 900] | 0.100 | 0.102 | +0.002 | 0.020 | 0.0000 |
| PSI = | 0.0080 | |||||
结果解读: 本例 PSI = 0.0080 < 0.1,表明本周催收触达客户的评分分布与建模基准分布高度一致,策略无需调整。若 PSI 超过 0.25,则需检查是否因客群结构变化、模型失效或外部环境突变导致分布偏移,并启动阈值调整流程。