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值的完整计算链路:

PSI计算公式拆解流程图 PSI 计算公式逐项拆解流程 原始评分数据 等距/等频分箱 计算各箱实际占比与期望占比 占比差 (实际−期望) 对数比 ln(实际/期望) Σ (占比差 × 对数比) = 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衡量区分能力