3. PSI 计算步骤:分箱策略、实际分布与期望分布的计算、PSI 值的聚合与解读
PSI(Population Stability Index,群体稳定性指标)是衡量特征分布随时间或样本集变化程度的量化工具。其核心思想是将连续分布离散化为若干区间(分箱),然后比较每个区间内实际样本占比与期望样本占比的差异。以下从分箱策略、分布计算、聚合解读三个环节展开。
3.1 分箱策略:离散化的关键选择
分箱是 PSI 计算的第一步,直接影响指标的敏感性和稳定性。常见的分箱策略包括等距分箱、等频分箱和基于业务规则的分箱。
| 分箱策略 | 方法描述 | 适用场景 | 注意事项 |
|---|---|---|---|
| 等距分箱 | 将特征值范围均匀划分为 k 个区间,每个区间宽度相同 | 特征分布均匀、无极端异常值 | 若数据分布偏斜,部分箱可能样本极少,导致 PSI 不稳定 |
| 等频分箱 | 按样本数量均分,使每个箱包含大致相同的样本数 | 分布偏斜、长尾特征 | 箱边界可能随分布变化而移动,需固定边界用于后续监控 |
| 业务规则分箱 | 基于业务含义或监管要求划分,如信用评分中的“好/中/差”分段 | 有明确业务分段标准 | 需定期验证业务分段是否仍合理 |
实践建议:在模型监控中,通常使用等频分箱(如 10 箱)作为默认策略,并在模型开发阶段固定分箱边界。后续监控时,即使实际分布发生变化,仍使用相同的边界计算占比,以保证 PSI 的可比性。
3.2 实际分布与期望分布的计算
在分箱完成后,需要分别计算每个箱中样本在期望分布(通常为训练集或基准期)和实际分布(当前监控期)中的占比。
计算步骤:
- 确定分箱边界:基于期望数据集(如训练集)确定每个箱的上下界。
- 统计期望分布:统计期望数据集中落入每个箱的样本数 \( n_{e,i} \),计算占比 \( p_{e,i} = n_{e,i} / N_e \)。
- 统计实际分布:使用相同的分箱边界,统计实际数据集中落入每个箱的样本数 \( n_{a,i} \),计算占比 \( p_{a,i} = n_{a,i} / N_a \)。
- 处理零值:若某箱在期望或实际分布中占比为 0,需进行平滑处理(如加极小值 0.0001),避免对数计算无定义。
公式:每个箱的 PSI 贡献 = \( (p_{a,i} - p_{e,i}) \times \ln(p_{a,i} / p_{e,i}) \)
以下 SVG 展示了从原始数据到分布占比的计算流程:
3.3 PSI 值的聚合与解读
PSI 的最终值是对所有分箱贡献的求和。聚合公式如下:
PSI = Σ[(p_a,i - p_e,i) * ln(p_a,i / p_e,i)] (i = 1, 2, ..., k)
其中:
- p_a,i:实际分布中第 i 箱的样本占比
- p_e,i:期望分布中第 i 箱的样本占比
- k:分箱总数
解读标准:
| PSI 值范围 | 稳定性判断 | 建议行动 |
|---|---|---|
| PSI < 0.1 | 稳定 | 无需干预,持续监控 |
| 0.1 ≤ PSI < 0.25 | 轻度漂移 | 检查漂移方向,评估是否需要模型更新 |
| PSI ≥ 0.25 | 显著漂移 | 立即排查原因,考虑模型重训或特征替换 |
注意:PSI 阈值并非绝对,需结合业务场景调整。例如,在信贷风控中,PSI > 0.2 通常触发预警;而在营销响应模型中,由于分布波动较大,阈值可放宽至 0.3。此外,PSI 对分箱数量敏感,建议在模型开发阶段通过交叉验证确定最优分箱数。
以下 SVG 展示了 PSI 聚合计算与解读的完整逻辑:
3.4 计算示例(Python 伪代码)
以下代码展示了 PSI 计算的核心逻辑,使用等频分箱策略:
import numpy as np
def calculate_psi(expected, actual, bins=10):
"""
计算 PSI 值
:param expected: 期望分布数据(一维数组)
:param actual: 实际分布数据(一维数组)
:param bins: 分箱数
:return: PSI 值
"""
# 1. 基于期望数据确定分箱边界(等频分箱)
percentiles = np.linspace(0, 100, bins + 1)
bin_edges = np.percentile(expected, percentiles)
# 2. 统计期望分布占比
expected_counts, _ = np.histogram(expected, bins=bin_edges)
expected_pct = expected_counts / len(expected)
# 3. 统计实际分布占比(使用相同边界)
actual_counts, _ = np.histogram(actual, bins=bin_edges)
actual_pct = actual_counts / len(actual)
# 4. 平滑处理(避免 log(0))
epsilon = 1e-4
expected_pct = np.clip(expected_pct, epsilon, 1 - epsilon)
actual_pct = np.clip(actual_pct, epsilon, 1 - epsilon)
# 5. 计算 PSI
psi = np.sum((actual_pct - expected_pct) * np.log(actual_pct / expected_pct))
return psi
# 示例调用
expected_data = np.random.normal(0, 1, 10000)
actual_data = np.random.normal(0.2, 1.1, 10000)
psi_value = calculate_psi(expected_data, actual_data, bins=10)
print(f"PSI = {psi_value:.4f}")
关键点总结:
- 分箱策略决定了 PSI 的粒度,等频分箱是默认推荐。
- 期望分布与实际分布使用相同的分箱边界,确保可比性。
- PSI 聚合是对数比率与差异的加权和,值越大表示分布偏移越严重。
- 解读时需结合业务阈值,避免机械套用 0.1/0.25 标准。