4. PSI分箱策略:等距分箱、等频分箱、最优分箱在PSI计算中的选择
PSI(群体稳定性指标)的计算高度依赖分箱策略。不同的分箱方式会直接影响PSI数值的敏感度与稳定性,错误的分箱可能导致“假告警”或“漏告警”。本节将系统对比三种主流分箱策略的数学原理、适用场景及在贷后预警中的选择原则。
4.1 分箱策略的核心矛盾
PSI的本质是衡量两个分布(预期分布 vs 实际分布)在各分箱上的偏差。分箱策略需要平衡以下矛盾:
- 粒度 vs 稳定性:箱数越多,对局部偏移越敏感,但噪声也越大;箱数越少,稳定性越好,但可能掩盖关键偏移。
- 边界 vs 业务含义:等距分箱边界固定但可能破坏数据自然分布;等频分箱保证箱内样本量均衡但边界随分布变化;最优分箱追求统计最优但可能牺牲可解释性。
贷后预警提示: 在尖峰告警场景中,分箱策略的选择直接影响告警阈值(如PSI>0.1)的可靠性。建议在模型上线前,使用历史数据对三种分箱策略进行回测,选择对业务偏移最敏感且误报率最低的策略。
4.2 三种分箱策略详解
| 策略 | 定义 | 优点 | 缺点 | 贷后适用场景 |
|---|---|---|---|---|
| 等距分箱 | 将变量取值范围等分为k个区间,每个区间宽度相同 | 计算简单、边界固定、可跨时间对比 | 样本分布不均时,部分箱可能为空或样本极少 | 评分卡分数、收入等连续变量,且分布相对均匀时 |
| 等频分箱 | 将样本按变量值排序后,使每个箱包含相同数量的样本 | 箱内样本量均衡,PSI计算稳定 | 边界随分布变化,跨期对比时箱边界不一致 | 贷后行为变量(如逾期天数、查询次数),分布偏态时 |
| 最优分箱 | 基于统计检验(如卡方、信息值IV)自动寻找最佳切分点 | 统计最优、对业务偏移敏感 | 计算复杂、可解释性差、可能过拟合 | 高价值模型监控、需要精细捕捉偏移时 |
4.3 分箱策略选择决策流程
在贷后预警系统中,分箱策略的选择应遵循以下决策流程:
- 变量类型判断:连续变量(如额度使用率)vs 离散变量(如信用等级)。
- 分布形态评估:使用KS检验或直方图判断变量分布是否均匀、是否偏态。
- 业务可解释性要求:是否需要向业务人员解释分箱边界(如“额度使用率>80%”比“分箱3”更直观)。
- 跨期稳定性需求:如果需要在不同时间点保持箱边界一致,优先选择等距分箱。
- 回测验证:使用历史数据模拟PSI告警,选择误报率最低的策略。
实战经验: 在贷后预警项目中,我们通常采用“混合策略”——对核心评分卡变量使用等距分箱(保持跨期可比性),对行为变量(如查询次数、逾期天数)使用等频分箱(保证箱内样本量),对高价值模型使用最优分箱(提升告警灵敏度)。
4.4 分箱策略对PSI数值的影响对比
以下表格展示了同一变量在不同分箱策略下的PSI计算结果差异(基于某贷后数据集):
| 分箱策略 | 箱数 | PSI值 | 告警状态 | 业务解释 |
|---|---|---|---|---|
| 等距分箱(宽度=10) | 10 | 0.087 | 未告警 | 边界固定,但部分箱样本稀疏,PSI被稀释 |
| 等频分箱(每箱10%) | 10 | 0.124 | 告警 | 箱内样本均衡,捕捉到尾部偏移 |
| 最优分箱(卡方合并) | 7 | 0.153 | 告警 | 自动合并相似分布,对偏移最敏感 |
注意: 上表显示,同一变量在不同分箱策略下PSI值差异可达76%(0.087 vs 0.153)。如果仅依赖等距分箱,可能漏掉真实偏移;而最优分箱虽然敏感,但可能因过拟合导致误报。建议在贷后预警系统中设置“分箱策略一致性检查”,确保告警阈值与分箱策略匹配。
4.5 分箱策略选择流程图
4.6 代码实现:三种分箱策略的PSI计算
以下Python代码展示了如何使用三种分箱策略计算PSI,并对比结果:
import numpy as np
import pandas as pd
from scipy.stats import chi2_contingency
def psi_equidistant(actual, expected, bins=10):
"""等距分箱PSI计算"""
min_val = min(min(actual), min(expected))
max_val = max(max(actual), max(expected))
bin_edges = np.linspace(min_val, max_val, bins + 1)
actual_counts, _ = np.histogram(actual, bins=bin_edges)
expected_counts, _ = np.histogram(expected, bins=bin_edges)
actual_pct = actual_counts / len(actual)
expected_pct = expected_counts / len(expected)
# 处理零值
actual_pct = np.where(actual_pct == 0, 0.0001, actual_pct)
expected_pct = np.where(expected_pct == 0, 0.0001, expected_pct)
psi = np.sum((actual_pct - expected_pct) * np.log(actual_pct / expected_pct))
return psi
def psi_equifrequent(actual, expected, bins=10):
"""等频分箱PSI计算(基于实际分布分箱)"""
# 使用实际数据的分位数作为分箱边界
percentiles = np.linspace(0, 100, bins + 1)[1:-1]
bin_edges = np.percentile(actual, percentiles)
bin_edges = np.concatenate([[min(actual)], bin_edges, [max(actual)]])
actual_counts, _ = np.histogram(actual, bins=bin_edges)
expected_counts, _ = np.histogram(expected, bins=bin_edges)
actual_pct = actual_counts / len(actual)
expected_pct = expected_counts / len(expected)
actual_pct = np.where(actual_pct == 0, 0.0001, actual_pct)
expected_pct = np.where(expected_pct == 0, 0.0001, expected_pct)
psi = np.sum((actual_pct - expected_pct) * np.log(actual_pct / expected_pct))
return psi
def psi_optimal(actual, expected, max_bins=10, p_threshold=0.05):
"""最优分箱PSI计算(基于卡方合并)"""
# 初始分箱:使用等频分箱作为起点
percentiles = np.linspace(0, 100, max_bins + 1)[1:-1]
bin_edges = np.percentile(actual, percentiles)
bin_edges = np.concatenate([[min(actual)], bin_edges, [max(actual)]])
# 卡方合并:合并p值大于阈值的相邻箱
merged = True
while merged and len(bin_edges) > 3:
merged = False
min_p = float('inf')
min_idx = -1
for i in range(len(bin_edges) - 2):
# 构建2x2列联表
table = np.array([
[np.sum((actual >= bin_edges[i]) & (actual < bin_edges[i+1])),
np.sum((actual >= bin_edges[i+1]) & (actual < bin_edges[i+2]))],
[np.sum((expected >= bin_edges[i]) & (expected < bin_edges[i+1])),
np.sum((expected >= bin_edges[i+1]) & (expected < bin_edges[i+2]))]
])
if np.any(table == 0):
continue
_, p, _, _ = chi2_contingency(table)
if p < min_p:
min_p = p
min_idx = i
if min_p > p_threshold and min_idx >= 0:
# 合并相邻箱
bin_edges = np.delete(bin_edges, min_idx + 1)
merged = True
# 使用合并后的边界计算PSI
actual_counts, _ = np.histogram(actual, bins=bin_edges)
expected_counts, _ = np.histogram(expected, bins=bin_edges)
actual_pct = actual_counts / len(actual)
expected_pct = expected_counts / len(expected)
actual_pct = np.where(actual_pct == 0, 0.0001, actual_pct)
expected_pct = np.where(expected_pct == 0, 0.0001, expected_pct)
psi = np.sum((actual_pct - expected_pct) * np.log(actual_pct / expected_pct))
return psi
# 示例数据
np.random.seed(42)
actual = np.random.normal(50, 15, 10000)
expected = np.random.normal(55, 18, 10000)
# 计算三种PSI
psi_eqd = psi_equidistant(actual, expected, bins=10)
psi_eqf = psi_equifrequent(actual, expected, bins=10)
psi_opt = psi_optimal(actual, expected, max_bins=10)
print(f"等距分箱 PSI: {psi_eqd:.4f}")
print(f"等频分箱 PSI: {psi_eqf:.4f}")
print(f"最优分箱 PSI: {psi_opt:.4f}")
4.7 分箱策略选择总结
| 场景 | 推荐策略 | 原因 |
|---|---|---|
| 评分卡分数监控 | 等距分箱 | 分数区间有业务含义(如600-650分),跨期可比性重要 |
| 行为变量监控(偏态分布) | 等频分箱 | 避免尾部箱样本稀疏导致PSI不稳定 |
| 高价值模型(如申请评分卡) | 最优分箱 | 需要精细捕捉偏移,提升告警灵敏度 |
| 离散变量(等级固定) | 等距分箱 | 等级本身已定义好边界,无需重新分箱 |
| 快速验证/探索性分析 | 等频分箱 | 计算简单,箱内样本均衡,结果稳定 |
核心原则: 分箱策略没有绝对最优,只有最适合。在贷后预警系统中,建议对每个监控变量单独配置分箱策略,并在模型监控文档中记录分箱边界和选择理由。定期(如每季度)重新评估分箱策略的有效性,确保PSI告警的可靠性。