3. PSI 计算步骤详解:特征分箱、计算实际占比与预期占比、计算每个箱的贡献值、求和得到 PSI

群体稳定性指标(PSI)的核心逻辑是将特征在两个时间窗口(预期 / 实际)的分布差异量化成一个可比较的数值。整个计算过程严格遵循“分箱 → 占比 → 贡献 → 求和”四步法。下面逐一拆解每个步骤的数学含义与工程实现细节。

3.1 第一步:特征分箱(Binning)

分箱的目的是将连续型特征或离散值过多的特征划分为有限个区间(箱),使分布比较在统一的粒度上进行。分箱方式直接影响 PSI 的敏感度与稳定性。

分箱方法 适用场景 优点 缺点
等距分箱 特征分布均匀,无极端值 实现简单,箱宽一致 易受异常值影响,箱内样本可能稀疏
等频分箱 特征分布偏态,需保证每箱样本量 每箱样本数相等,统计稳定 箱边界可能集中在密集区域
决策树分箱 需要与目标变量关联的分箱 分箱边界有业务解释性 计算成本高,可能过拟合
自定义分箱 业务规则明确(如年龄分段) 完全可控,符合业务逻辑 依赖专家经验,主观性强
实战建议:风控模型监控中,推荐使用等频分箱(通常 10 箱)作为默认方案。等频分箱能保证每个箱在预期样本中都有足够的观测数,避免因某箱样本过少导致 PSI 计算不稳定。若特征取值较少(如枚举型),可直接将每个取值作为一个箱。

分箱完成后,需要记录每个箱的边界值(或类别值),并确保预期样本和实际样本使用完全相同的分箱规则。这是 PSI 计算正确性的前提。

3.2 第二步:计算实际占比与预期占比

在分箱基础上,分别统计每个箱在预期样本集和实际样本集中的样本占比。

  • 预期占比(Pi):第 i 个箱中预期样本数 ÷ 预期样本总数
  • 实际占比(Ai):第 i 个箱中实际样本数 ÷ 实际样本总数

占比计算必须使用相对比例而非绝对计数,以消除两个时间窗口样本总量不同带来的偏差。若某箱在实际样本中样本数为 0,则占比为 0%,但后续计算贡献值时需特殊处理(见步骤三)。

关键检查点:所有箱的预期占比之和必须等于 1(或 100%),实际占比之和也必须等于 1。若因四舍五入导致总和偏离 1,需做归一化处理。

3.3 第三步:计算每个箱的贡献值

每个箱对 PSI 的贡献值由以下公式给出:

贡献值_i = (A_i - P_i) × ln(A_i / P_i)

其中:

  • Ai:实际占比
  • Pi:预期占比
  • ln(·):自然对数

该公式的数学含义是:当实际占比与预期占比完全相等时,贡献值为 0;差异越大,贡献值越大。由于对数函数在自变量趋近于 0 时发散,工程实现中需要处理两种边界情况:

边界情况 处理方式 原因
Ai = 0 且 Pi > 0 贡献值 = Pi × ln(Pi / 极小值) 避免 ln(0) 无定义,用极小正数(如 1e-10)替代
Pi = 0 且 Ai > 0 贡献值 = Ai × ln(Ai / 极小值) 同上,用极小正数替代分母
Ai = 0 且 Pi = 0 贡献值 = 0 该箱在两个窗口均无样本,不贡献差异
常见错误:直接跳过 Ai = 0 的箱会导致 PSI 被低估。正确的做法是保留该箱并赋予一个极小占比(如 1e-6),使对数运算可执行,同时不显著影响最终结果。

3.4 第四步:求和得到 PSI

将所有箱的贡献值累加,即得到该特征的群体稳定性指标:

PSI = Σ (A_i - P_i) × ln(A_i / P_i)   (i = 1, 2, ..., n)

其中 n 为总箱数。PSI 是一个非负实数,数值越大表示特征分布偏移越严重。业界通常使用以下阈值进行判断:

PSI 范围 稳定性判断 建议操作
PSI < 0.1 稳定 无需处理,可保留特征
0.1 ≤ PSI < 0.25 轻度偏移 需关注,建议监控趋势
PSI ≥ 0.25 严重偏移 建议剔除或重新开发特征

3.5 完整计算流程图示

下图将上述四个步骤串联为一条可执行的计算流水线,帮助理解从原始特征到最终 PSI 值的完整路径。

PSI 计算四步流程 PSI 计算四步流程 步骤 1:特征分箱 等频 / 等距 / 自定义 步骤 2:占比计算 预期占比 P_i / 实际占比 A_i 步骤 3:贡献值 (A_i - P_i) × ln(A_i / P_i) 步骤 4:求和得到 PSI PSI = Σ (A_i - P_i) × ln(A_i / P_i) 输出 PSI 值 PSI 阈值判断标准 PSI < 0.1 → 稳定 | 0.1 ≤ PSI < 0.25 → 轻度偏移 | PSI ≥ 0.25 → 严重偏移 严重偏移的特征建议剔除或重新开发

3.6 计算示例(手工演算)

假设某特征“收入等级”分为 3 箱,预期样本 1000 个,实际样本 1200 个,分布如下:

箱编号 预期样本数 预期占比 Pi 实际样本数 实际占比 Ai Ai - Pi ln(Ai / Pi) 贡献值
低 300 0.300 420 0.350 +0.050 ln(0.350/0.300) = 0.154 0.050 × 0.154 = 0.0077
中 500 0.500 540 0.450 -0.050 ln(0.450/0.500) = -0.105 -0.050 × -0.105 = 0.0053
高 200 0.200 240 0.200 0.000 ln(1.000) = 0.000 0.000
合计 1.000 0.000 — PSI = 0.0130

该特征 PSI = 0.0130 < 0.1,属于稳定特征,无需处理。

3.7 工程实现要点

在 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)
    bin_edges[0] = -np.inf
    bin_edges[-1] = np.inf

    # 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. 处理边界情况:占比为 0 时替换为极小值
    eps = 1e-10
    expected_pct = np.where(expected_pct == 0, eps, expected_pct)
    actual_pct = np.where(actual_pct == 0, eps, actual_pct)

    # 5. 计算每个箱的贡献值并求和
    psi = np.sum((actual_pct - expected_pct) * np.log(actual_pct / expected_pct))

    return psi
代码要点:
  • 使用 np.percentile 实现等频分箱,确保每箱样本数大致相等。
  • 将首尾边界设为 ±inf,确保所有样本都能落入某个箱。
  • 用极小正数 eps 替代零占比,避免对数运算报错。
  • 最终 PSI 为所有箱贡献值的标量和。

3.8 常见避坑指南

  • 分箱数选择:箱数过少会掩盖分布差异,箱数过多会导致每箱样本稀疏、PSI 波动大。推荐 10~20 箱,样本量较大时可适当增加。
  • 预期样本与实际样本时间窗口:预期样本通常取模型开发时的训练集或验证集;实际样本取监控期(如月度、季度)的最新数据。两个窗口的时间跨度应保持一致。
  • 特征缺失值处理:缺失值应单独作为一个箱参与计算,不能直接丢弃。缺失率的变化本身就是分布偏移的重要信号。
  • PSI 的对称性:PSI 不具备对称性,即 PSI(预期→实际) ≠ PSI(实际→预期)。计算时务必明确哪个是基准分布。