3. PSI 计算步骤详解:数据准备、分箱方法、计算每个箱子的占比、求和得到 PSI 值

PSI(Population Stability Index,群体稳定性指标)的计算并不依赖复杂的数学模型,其核心在于“分布对比”。只要掌握四个标准步骤——数据准备、分箱、占比计算、求和——即可准确量化模型评分在时间维度上的偏移程度。本节将逐一拆解每个环节的实操要点与数学逻辑。

3.1 数据准备:定义基准样本与测试样本

PSI 计算需要两组数据:基准样本(Expected) 和 测试样本(Actual)。基准样本通常来自模型开发时的训练集或验证集,代表“稳定状态”;测试样本来自模型上线后的某个监控周期(如月度、季度),代表“当前状态”。

样本类型 数据来源 用途 常见时间窗口
基准样本(E) 模型训练集 / 验证集 定义“稳定分布”参考线 模型开发期(静态)
测试样本(A) 线上评分结果 / 新批次数据 检测当前分布是否偏移 每月 / 每季度(滚动)
实操要点:确保两组样本的评分字段(Score)口径一致,包括评分范围、缺失值处理方式、异常值截断规则。若基准样本使用“标准化后评分”,测试样本也必须执行相同的标准化流程。

3.2 分箱方法:等距、等频与分位数

分箱是将连续评分变量离散化为若干个区间(Bins),这是 PSI 计算的前提。不同的分箱策略会直接影响 PSI 值的敏感度与稳定性。三种主流方法对比如下:

分箱方法 原理 优点 缺点 适用场景
等距分箱 将评分范围等分为 k 个宽度相同的区间 计算简单,区间边界固定 样本分布不均时,部分箱子可能为空或样本极少 评分范围已知且分布较均匀
等频分箱 使每个箱子包含相同数量的样本 每个箱子样本量均衡,避免空箱 区间边界随样本分布变化,不固定 样本量充足,分布偏态明显
分位数分箱 基于基准样本的分位数(如 10%、20%…)确定边界 边界由基准分布决定,可复用到测试样本 对基准样本的异常值敏感 需要跨时间周期保持边界一致
行业推荐:在模型监控实践中,分位数分箱是最常用的方法。通常将基准样本按 10 分位(Decile)划分为 10 个箱子,然后将相同的分位边界应用到测试样本上。这样既能保证每个箱子有足够的样本量,又能保持跨周期的边界一致性。

3.3 计算每个箱子的占比

分箱完成后,分别计算基准样本和测试样本在每个箱子中的样本占比。公式如下:

# 伪代码:计算每个箱子的占比
# bins: 分箱边界列表,如 [0, 100, 200, 300, ...]
# expected_scores: 基准样本评分数组
# actual_scores: 测试样本评分数组

def calculate_proportions(scores, bins):
    total = len(scores)
    proportions = []
    for i in range(len(bins) - 1):
        lower = bins[i]
        upper = bins[i + 1]
        count = sum(1 for s in scores if lower <= s < upper)
        proportions.append(count / total)
    return proportions

# 计算基准占比 E_i 和测试占比 A_i
E_props = calculate_proportions(expected_scores, bins)
A_props = calculate_proportions(actual_scores, bins)

假设分箱数为 10,则得到两组占比向量:E = [E₁, E₂, ..., E₁₀] 和 A = [A₁, A₂, ..., A₁₀],且每组占比之和均为 1(或 100%)。

避坑提示:若测试样本中某个箱子的占比为 0,则后续计算 ln(Aᵢ / Eᵢ) 时会出现除零错误。建议对占比进行“平滑处理”,例如将 0 替换为一个极小值(如 0.0001),或采用拉普拉斯平滑(Laplace Smoothing)。

3.4 求和得到 PSI 值

PSI 的最终计算公式为:

PSI = Σᵢ (Aᵢ - Eᵢ) × ln(Aᵢ / Eᵢ)

其中:

  • Aᵢ:测试样本在第 i 个箱子的占比
  • Eᵢ:基准样本在第 i 个箱子的占比
  • ln:自然对数

计算过程可拆解为以下步骤:

  1. 对每个箱子,计算 diff = Aᵢ - Eᵢ(占比差值)
  2. 对每个箱子,计算 ratio = Aᵢ / Eᵢ(占比比值)
  3. 对每个箱子,计算 term = diff × ln(ratio)(单项贡献)
  4. 将所有箱子的 term 求和,得到最终 PSI 值
# Python 实现:完整 PSI 计算
import numpy as np

def calculate_psi(expected, actual, bins):
    """
    计算群体稳定性指标 (PSI)
    :param expected: 基准样本评分数组
    :param actual: 测试样本评分数组
    :param bins: 分箱边界列表
    :return: PSI 值
    """
    def get_proportions(scores):
        total = len(scores)
        counts, _ = np.histogram(scores, bins=bins)
        return counts / total

    E = get_proportions(expected)
    A = get_proportions(actual)

    # 平滑处理:避免除零或 ln(0)
    epsilon = 1e-6
    E = np.clip(E, epsilon, None)
    A = np.clip(A, epsilon, None)

    psi = np.sum((A - E) * np.log(A / E))
    return psi

# 示例用法
bins = [0, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000]
psi_value = calculate_psi(expected_scores, actual_scores, bins)
print(f"PSI = {psi_value:.4f}")

3.5 PSI 计算全流程可视化

下图完整展示了从数据准备到 PSI 输出的四个核心阶段,帮助理解每一步的输入与输出关系:

PSI 计算四步流程图 PSI 计算四步流程 步骤 1:数据准备 基准样本 + 测试样本 步骤 2:分箱 等距 / 等频 / 分位数 步骤 3:占比计算 Eᵢ 与 Aᵢ 逐箱计算 步骤 4:求和 Σ (Aᵢ - Eᵢ) × ln(Aᵢ/Eᵢ) 输出:PSI 值 PSI < 0.1 → 稳定 0.1 ≤ PSI < 0.25 → 需关注 PSI ≥ 0.25 → 显著偏移 分箱方法选择建议: • 等距分箱:适用于评分范围固定、分布均匀的场景(如信用评分 300-850) • 等频分箱:适用于样本量充足、分布偏态明显的场景(如收入评分) • 分位数分箱:推荐用于模型监控,边界由基准样本决定,跨周期可复用 • 通用建议:分箱数通常设为 10~20 个,过少会丢失细节,过多会引入噪声

3.6 完整计算示例(手工演算)

假设将评分分为 5 个箱子,基准样本和测试样本的占比数据如下,逐步计算 PSI:

箱子编号 基准占比 Eᵢ 测试占比 Aᵢ 差值 (Aᵢ - Eᵢ) 比值 (Aᵢ / Eᵢ) ln(比值) 单项贡献
1 0.20 0.15 -0.05 0.75 -0.2877 0.0144
2 0.20 0.25 0.05 1.25 0.2231 0.0112
3 0.20 0.20 0.00 1.00 0.0000 0.0000
4 0.20 0.30 0.10 1.50 0.4055 0.0405
5 0.20 0.10 -0.10 0.50 -0.6931 0.0693
PSI = Σ 单项贡献 = 0.1354
结果解读:本例中 PSI = 0.1354,落在 0.1 ~ 0.25 区间,属于“需关注”级别。虽然整体分布未发生剧烈偏移,但箱子 4 和箱子 5 的占比变化较大,建议进一步分析这两个区间的样本特征是否发生了结构性变化。

3.7 常见陷阱与最佳实践

  • 分箱数选择:分箱数过少(如 < 5)会掩盖局部偏移;分箱数过多(如 > 20)会导致每个箱子样本量不足,PSI 对随机波动过于敏感。推荐 10 箱作为默认值。
  • 边界一致性:必须使用基准样本的分箱边界来划分测试样本,不能各自独立分箱。否则两个分布的“箱子”不对齐,PSI 失去可比性。
  • 零占比处理:测试样本中某个箱子占比为 0 时,ln(0) 无定义。建议在计算前对所有占比加上一个极小平滑项(如 1e-6),或使用拉普拉斯平滑。
  • 缺失值处理:评分缺失的样本应单独作为一个箱子处理,或直接剔除并在报告中注明缺失率。不要将缺失值混入正常评分区间。