4. PSI分箱策略:等距分箱、等频分箱、最优分箱在PSI计算中的选择

PSI(群体稳定性指标)的计算高度依赖分箱策略。不同的分箱方式会直接影响PSI数值的敏感度与稳定性,错误的分箱可能导致“假告警”或“漏告警”。本节将系统对比三种主流分箱策略的数学原理、适用场景及在贷后预警中的选择原则。

4.1 分箱策略的核心矛盾

PSI的本质是衡量两个分布(预期分布 vs 实际分布)在各分箱上的偏差。分箱策略需要平衡以下矛盾:

  • 粒度 vs 稳定性:箱数越多,对局部偏移越敏感,但噪声也越大;箱数越少,稳定性越好,但可能掩盖关键偏移。
  • 边界 vs 业务含义:等距分箱边界固定但可能破坏数据自然分布;等频分箱保证箱内样本量均衡但边界随分布变化;最优分箱追求统计最优但可能牺牲可解释性。
贷后预警提示: 在尖峰告警场景中,分箱策略的选择直接影响告警阈值(如PSI>0.1)的可靠性。建议在模型上线前,使用历史数据对三种分箱策略进行回测,选择对业务偏移最敏感且误报率最低的策略。

4.2 三种分箱策略详解

策略 定义 优点 缺点 贷后适用场景
等距分箱 将变量取值范围等分为k个区间,每个区间宽度相同 计算简单、边界固定、可跨时间对比 样本分布不均时,部分箱可能为空或样本极少 评分卡分数、收入等连续变量,且分布相对均匀时
等频分箱 将样本按变量值排序后,使每个箱包含相同数量的样本 箱内样本量均衡,PSI计算稳定 边界随分布变化,跨期对比时箱边界不一致 贷后行为变量(如逾期天数、查询次数),分布偏态时
最优分箱 基于统计检验(如卡方、信息值IV)自动寻找最佳切分点 统计最优、对业务偏移敏感 计算复杂、可解释性差、可能过拟合 高价值模型监控、需要精细捕捉偏移时

4.3 分箱策略选择决策流程

在贷后预警系统中,分箱策略的选择应遵循以下决策流程:

  1. 变量类型判断:连续变量(如额度使用率)vs 离散变量(如信用等级)。
  2. 分布形态评估:使用KS检验或直方图判断变量分布是否均匀、是否偏态。
  3. 业务可解释性要求:是否需要向业务人员解释分箱边界(如“额度使用率>80%”比“分箱3”更直观)。
  4. 跨期稳定性需求:如果需要在不同时间点保持箱边界一致,优先选择等距分箱。
  5. 回测验证:使用历史数据模拟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 分箱策略选择流程图

PSI分箱策略选择决策流程 输入:待监控变量 变量类型:连续 vs 离散? 连续 离散 分布是否均匀? 等级数是否固定? 均匀 偏态 固定 不固定 推荐:等距分箱 推荐:等频分箱 推荐:等距分箱 是否高价值模型? 是 否 推荐:最优分箱 保持原推荐策略

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告警的可靠性。