4. PSI计算公式详解:期望分布与实际分布、分箱方法、PSI分段计算与汇总

群体稳定性指标(Population Stability Index, PSI)是评分卡监控中最核心的量化工具,用于衡量模型评分在时间维度上的分布偏移程度。本节将从公式拆解、分箱策略、分段计算到汇总输出,完整解析PSI的数学原理与工程实现。

4.1 PSI标准公式拆解

PSI的核心思想是比较两个分布——期望分布(Expected Distribution)与实际分布(Actual Distribution)——在每个分箱上的比例差异,并通过对数加权求和得到单一指标。标准公式如下:

PSI = Σ (Actual_i - Expected_i) × ln(Actual_i / Expected_i)

其中:
- i = 第i个分箱(共N个分箱)
- Expected_i = 期望样本在第i箱中的占比(开发样本/基准样本)
- Actual_i = 实际样本在第i箱中的占比(监控样本/当前样本)
- ln = 自然对数

公式的直观含义:当实际分布与期望分布完全一致时,每个分箱的差值为0,PSI=0;差异越大,PSI值越高。该公式本质上是离散化后的KL散度对称化形式。

关键理解:PSI并非直接比较分数值,而是比较分数落入各分箱的比例。因此分箱方式直接影响PSI的敏感度和稳定性。

4.2 期望分布与实际分布的定义

分布类型 别名 数据来源 用途
期望分布 基准分布、开发分布 模型开发时的训练集/验证集 作为稳定性的参考基准
实际分布 监控分布、当前分布 模型上线后的月度/季度样本 与基准对比,检测漂移

注意事项:

  • 期望分布通常使用开发样本的评分分布,而非全量拒绝样本(避免样本偏差)。
  • 实际分布必须与期望分布使用相同的分箱边界,否则PSI计算无效。
  • 当实际分布中出现某分箱比例为0时,公式中的ln(0)无定义,需做平滑处理(如加极小值ε=1e-6)。

4.3 分箱方法详解

分箱是PSI计算的前置步骤,分箱策略直接影响PSI的数值大小和业务解释性。常用的分箱方法有三种:

分箱方法 原理 优点 缺点 适用场景
等距分箱 按分数区间等宽划分(如每50分一箱) 边界固定,易于跨时间对比 样本分布不均时可能出现空箱 评分卡分数范围已知且稳定
等频分箱 按期望分布的百分位数划分,每箱样本量相等 避免空箱,统计稳定性好 边界随基准分布变化,跨期对比不便 开发样本分布不均匀时首选
业务分箱 按评分卡等级边界划分(如A/B/C/D/E级) 业务可解释性强,直接对应风险等级 分箱数少,可能降低PSI敏感度 面向业务汇报的监控报告
行业实践建议:评分卡监控通常采用等频分箱(10箱)作为默认方案。10箱能在统计稳定性和敏感度之间取得较好平衡。若样本量较小(<1000),可减少至5箱;若样本量极大(>50000),可增加至20箱。
PSI分箱方法对比示意图 等距分箱 分数区间:300-350, 350-400, ... 每箱宽度固定(如50分) ⚠ 可能出现空箱 等频分箱 按百分位数划分边界 每箱样本量≈10% ✅ 统计稳定性好 业务分箱 按风险等级:A/B/C/D/E 分箱数少(通常5-7箱) ⚠ 敏感度较低 ⭐ 推荐:10箱等频 分箱选择决策建议 • 样本量 < 1,000:使用等频5箱,避免统计波动 • 样本量 1,000 ~ 50,000:使用等频10箱(行业标准) • 面向业务汇报:使用业务分箱,配合等频分箱做敏感性验证

4.4 PSI分段计算与汇总

PSI的计算过程可以分解为四个步骤,每一步都有明确的数学操作和工程注意事项。

4.4.1 计算步骤

  1. 确定分箱边界:基于期望分布,使用等频法(或其他方法)确定N个分箱的边界值。
  2. 统计各箱占比:分别统计期望样本和实际样本在各箱中的数量,并计算占比。
  3. 计算分段PSI:对每个分箱,计算 (Actual_i - Expected_i) × ln(Actual_i / Expected_i)。
  4. 汇总总PSI:将所有分箱的分段PSI求和,得到最终PSI值。

4.4.2 计算示例(10箱等频)

分箱 分数区间 期望占比(%) 实际占比(%) 差值 ln(实际/期望) 分段PSI
1 300-420 10.0 12.5 +2.5 0.2231 0.00558
2 420-480 10.0 11.0 +1.0 0.0953 0.00095
3 480-530 10.0 10.5 +0.5 0.0488 0.00024
4 530-570 10.0 10.0 0.0 0.0000 0.00000
5 570-610 10.0 9.5 -0.5 -0.0513 0.00026
6 610-650 10.0 9.0 -1.0 -0.1054 0.00105
7 650-690 10.0 8.5 -1.5 -0.1625 0.00244
8 690-730 10.0 8.0 -2.0 -0.2231 0.00446
9 730-780 10.0 7.5 -2.5 -0.2877 0.00719
10 780-900 10.0 13.5 +3.5 0.3001 0.01050
总PSI = 0.03267
⚠ 计算陷阱:当实际占比为0时,ln(0)无定义。解决方案:对每个分箱的占比做平滑处理,常用方法为加极小值(如1e-6)或使用贝叶斯平滑。另一种方案是合并空箱到相邻分箱,但需保持期望分布和实际分布的分箱结构一致。

4.4.3 PSI分段贡献分析

总PSI由各分箱的分段PSI累加得到,但不同分箱对总PSI的贡献度差异很大。通过分析分段贡献,可以定位漂移发生的具体分数区间。

PSI分段贡献分析流程 总PSI = 0.03267 分段PSI贡献排序(Top 3) 箱10:0.01050 贡献占比 32.1% 箱9:0.00719 贡献占比 22.0% 箱1:0.00558 贡献占比 17.1% 业务解读与行动建议 • 箱10(高分区间)漂移最大:检查高分客群是否发生变化,或评分卡是否存在天花板效应 • 箱1(低分区间)漂移明显:关注低分客群质量变化,可能涉及获客渠道或风控策略调整

4.5 PSI计算代码实现

以下为Python实现PSI计算的完整函数,包含平滑处理和分箱逻辑:

import numpy as np
import pandas as pd

def calculate_psi(expected, actual, bins=10, method='equal_freq', epsilon=1e-6):
    """
    计算群体稳定性指标(PSI)
    
    Parameters:
    -----------
    expected : array-like
        期望分布(开发样本)的评分数据
    actual : array-like
        实际分布(监控样本)的评分数据
    bins : int
        分箱数量
    method : str
        分箱方法:'equal_freq'(等频), 'equal_width'(等距)
    epsilon : float
        平滑因子,防止log(0)
    
    Returns:
    --------
    psi : float
        总PSI值
    psi_table : DataFrame
        各分箱的详细计算结果
    """
    # 1. 基于期望分布确定分箱边界
    if method == 'equal_freq':
        # 等频分箱:按百分位数
        percentiles = np.linspace(0, 100, bins + 1)[1:-1]
        bin_edges = np.percentile(expected, percentiles)
        bin_edges = np.concatenate(([-np.inf], bin_edges, [np.inf]))
    elif method == 'equal_width':
        # 等距分箱
        min_val, max_val = expected.min(), expected.max()
        bin_edges = np.linspace(min_val, max_val, bins + 1)
        bin_edges[0] = -np.inf
        bin_edges[-1] = np.inf
    else:
        raise ValueError("method must be 'equal_freq' or 'equal_width'")
    
    # 2. 统计各箱占比
    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)
    
    # 3. 平滑处理(防止log(0))
    expected_pct = np.clip(expected_pct, epsilon, 1 - epsilon)
    actual_pct = np.clip(actual_pct, epsilon, 1 - epsilon)
    
    # 4. 计算分段PSI
    psi_segment = (actual_pct - expected_pct) * np.log(actual_pct / expected_pct)
    
    # 5. 汇总总PSI
    psi_total = np.sum(psi_segment)
    
    # 6. 构建结果表格
    psi_table = pd.DataFrame({
        'bin': range(1, bins + 1),
        'expected_pct': expected_pct,
        'actual_pct': actual_pct,
        'diff': actual_pct - expected_pct,
        'log_ratio': np.log(actual_pct / expected_pct),
        'segment_psi': psi_segment
    })
    
    return psi_total, psi_table

# 使用示例
# psi_value, psi_df = calculate_psi(expected_scores, actual_scores, bins=10)
# print(f"总PSI: {psi_value:.5f}")
# print(psi_df)
工程要点:
  • 分箱边界必须基于期望分布确定,并在整个监控周期内固定不变。
  • 平滑因子epsilon不宜过大(建议1e-6 ~ 1e-4),过大会压低真实漂移信号。
  • 当样本量差异较大时(如期望样本10万,实际样本5000),建议对实际占比做置信区间校正,避免小样本波动导致PSI虚高。

4.6 PSI阈值与业务解读

PSI范围 漂移程度 业务含义 建议行动
< 0.10 无显著漂移 模型分布稳定,客群结构未发生明显变化 常规监控,无需干预
0.10 ~ 0.25 轻度漂移 分布出现一定偏移,需关注趋势 增加监控频率,分析分段贡献
> 0.25 显著漂移 分布发生结构性变化,模型可能失效 立即触发告警,启动模型复核与重开发
行业经验:上述阈值是通用参考值,实际应用中应根据业务场景调整。例如,在贷前审批场景中,PSI阈值可放宽至0.15/0.30;在贷中管理场景中,由于客群变化更敏感,建议收紧至0.05/0.15。关键原则是:阈值设定应使告警频率与业务承受能力匹配。