4. PSI计算公式详解:逐项拆解
群体稳定性指标(Population Stability Index, PSI)是衡量模型评分分布随时间或样本变化的核心指标。其数学公式为:
PSI = Σ (实际占比 − 期望占比) × ln(实际占比 / 期望占比)
该公式由三个关键元素构成:分箱、占比差、对数比。下面逐项拆解其数学含义与业务意义。
4.1 公式元素定义
| 符号 | 名称 | 含义 | 取值范围 |
|---|---|---|---|
| i | 分箱序号 | 将评分或特征值划分为 K 个等距或等频区间 | 1, 2, …, K |
| 实际占比 | 当前样本占比 | 第 i 箱中当前样本数 / 当前样本总数 | (0, 1) |
| 期望占比 | 基准样本占比 | 第 i 箱中基准样本数 / 基准样本总数 | (0, 1) |
| ln(实际占比/期望占比) | 对数比 | 衡量两个占比的相对偏离程度 | (−∞, +∞) |
| Σ | 求和 | 对所有分箱的贡献值求和 | ≥ 0 |
4.2 分箱步骤详解
分箱是PSI计算的前提,通常采用以下两种方式:
- 等距分箱:将评分范围(如0~100)均匀划分为K个区间,适用于评分分布均匀的场景。
- 等频分箱:使每个区间包含大致相等的样本数,适用于长尾分布。
分箱数量K一般取10~20。K过小会丢失分布细节,K过大会导致部分区间占比为0,使对数项无定义。
避坑提示: 若某箱中期望占比为0,则 ln(实际占比/0) 无定义。此时需将该箱与相邻箱合并,或使用平滑处理(如加极小值ε)。
4.3 占比差与对数比的业务含义
公式中的两个因子分别代表不同的信息维度:
| 因子 | 数学作用 | 业务含义 |
|---|---|---|
| (实际占比 − 期望占比) | 线性偏差 | 当前样本在该区间的占比相对于基准的绝对变化量 |
| ln(实际占比 / 期望占比) | 对数偏差 | 占比变化的相对幅度,对微小变化敏感 |
两者相乘后求和,使得占比变化大且相对幅度大的区间对PSI贡献更高,从而突出分布漂移的关键区域。
4.4 计算示例(手算演示)
假设将评分分为3个箱,基准样本和当前样本的占比数据如下:
| 分箱 | 期望占比 | 实际占比 | 占比差 | 实际/期望 | ln(实际/期望) | 单项贡献 |
|---|---|---|---|---|---|---|
| 箱1 | 0.30 | 0.25 | −0.05 | 0.833 | −0.182 | 0.0091 |
| 箱2 | 0.40 | 0.45 | +0.05 | 1.125 | +0.118 | 0.0059 |
| 箱3 | 0.30 | 0.30 | 0.00 | 1.000 | 0.000 | 0.0000 |
| PSI = Σ | 0.0150 | |||||
本例中PSI = 0.015,远小于0.1,说明分布稳定。
4.5 PSI计算公式的SVG流程图
下图展示了从原始数据到PSI值的完整计算链路:
4.6 公式的数学性质
- 非负性:PSI ≥ 0,当且仅当每个箱的实际占比等于期望占比时,PSI = 0。
- 对称性:交换实际与期望,PSI值不变(因为 ln(a/b) = −ln(b/a),且占比差符号相反,乘积不变)。
- 对尾部敏感:当实际占比或期望占比接近0时,对数比绝对值会急剧增大,使PSI对尾部漂移非常敏感。
注意: 当实际占比或期望占比为0时,对数项无定义。实践中需对零占比箱进行合并或平滑处理,否则PSI计算会报错。
4.7 代码实现示例(Python)
import numpy as np
def calculate_psi(expected, actual, bins=10):
"""
计算PSI值
:param expected: 基准样本评分数组
:param actual: 当前样本评分数组
:param bins: 分箱数量
:return: PSI值
"""
# 确定分箱边界(基于基准样本)
_, bin_edges = np.histogram(expected, bins=bins)
# 计算各箱占比
expected_counts, _ = np.histogram(expected, bins=bin_edges)
actual_counts, _ = np.histogram(actual, bins=bin_edges)
expected_pct = expected_counts / len(expected)
actual_pct = actual_counts / len(actual)
# 避免除零:将占比为0的箱替换为极小值
eps = 1e-6
expected_pct = np.where(expected_pct == 0, eps, expected_pct)
actual_pct = np.where(actual_pct == 0, eps, actual_pct)
# 计算PSI
psi = np.sum((actual_pct - expected_pct) * np.log(actual_pct / expected_pct))
return psi
4.8 常见误区与避坑
| 误区 | 正确做法 |
|---|---|
| 直接使用原始评分值计算,未分箱 | 必须先分箱,再计算各箱占比 |
| 分箱数量过多或过少 | 推荐10~20箱,根据样本量调整 |
| 忽略零占比箱 | 合并零占比箱或加极小值平滑 |
| 将PSI与KS、AUC混淆 | PSI衡量分布稳定性,KS/AUC衡量区分能力 |