4. PSI计算步骤详解(上):预期分布与实际分布

群体稳定性指标(PSI)的核心逻辑是量化两个概率分布之间的差异。在风控建模与监控场景中,这两个分布分别对应预期分布(基准集)和实际分布(验证集/监控集)。本章将拆解PSI计算的前半部分:如何从原始数据出发,完成预期分布的分箱统计,以及实际分布的对齐计算。

4.1 预期分布(训练集/基准集)的统计与分箱

预期分布通常来自模型开发时的训练集,或业务稳定期的样本集。分箱是PSI计算的基础,其质量直接影响指标灵敏度。

4.1.1 分箱方法选择

分箱方法决定了预期分布的结构。下表对比了三种主流方法:

分箱方法 原理 适用场景 注意事项
等距分箱 将数值范围等分为k个区间 评分卡分数、线性指标 易出现空箱或样本稀疏
等频分箱 使每个箱内样本数大致相等 非均匀分布、长尾数据 边界值可能跨箱
最优分箱 基于IV/卡方等统计量自动划分 特征筛选、高精度监控 计算成本高,需避免过拟合
实操建议:风控评分卡PSI计算通常采用等距分箱(如每20分一个箱),便于业务解释;若样本分布极度偏斜,可改用等频分箱保证每个箱都有足够样本。

4.1.2 分箱统计步骤

完成分箱后,需统计每个箱内的样本占比,形成预期分布的概率向量。具体流程如下:

  1. 确定分箱边界:基于训练集数据,计算各分箱的上下界。
  2. 统计频数:统计落在每个箱内的样本数量。
  3. 计算占比:将频数除以总样本数,得到每个箱的预期占比 \( P_i \)。
  4. 处理边界值:将小于最小边界的样本归入第一箱,大于最大边界的归入最后一箱。
  5. 记录分箱映射:保存分箱边界,用于后续实际分布的对齐。
关键公式:预期占比 \( P_i = \frac{n_i}{N} \),其中 \( n_i \) 为第i箱的样本数,\( N \) 为总样本数。所有箱的 \( P_i \) 之和必须等于1。

4.2 实际分布(验证集/监控集)的统计与对齐

实际分布来自模型上线后的验证集或定期监控样本。其统计必须与预期分布的分箱结构保持一致,才能进行后续的PSI计算。

4.2.1 对齐原则

对齐的核心是使用预期分布的分箱边界,将实际样本映射到相同的箱结构中。这保证了两个分布在同一概率空间下进行比较。

  • 边界复用:直接使用训练集分箱的上下界,不重新分箱。
  • 样本映射:根据实际样本的数值,判断其所属的箱。
  • 空箱处理:若某个箱在实际分布中无样本,该箱占比记为0。
  • 溢出处理:超出预期分布边界的样本,归入最近的有效箱。

4.2.2 统计步骤

实际分布的统计与预期分布类似,但分箱边界固定:

  1. 加载分箱映射:读取预期分布保存的分箱边界。
  2. 频数统计:将实际样本按边界映射到各箱,统计频数。
  3. 计算占比:用实际总样本数计算每个箱的实际占比 \( Q_i \)。
  4. 检查零箱:标记占比为0的箱,这些箱在后续PSI计算中需特殊处理(通常跳过或加极小值)。
常见陷阱:实际分布样本量远小于训练集时,容易出现大量空箱,导致PSI值虚高。建议监控集样本量不低于训练集的10%,且每个箱至少保留5个样本。

4.3 预期分布与实际分布的对齐流程

下图展示了从原始数据到两个分布对齐的完整流程:

预期分布与实际分布对齐流程 训练集原始数据 选择分箱方法(等距/等频/最优) 计算分箱边界 & 统计频数 计算预期占比 P_i 保存分箱边界 & P_i 向量 验证集/监控集原始数据 加载预期分布的分箱边界 按边界映射样本 & 统计频数 计算实际占比 Q_i 输出 Q_i 向量(与 P_i 对齐) 分箱边界传递

4.4 代码实现示例

以下Python代码展示了预期分布分箱与实际分布对齐的核心逻辑:

import pandas as pd
import numpy as np

def compute_expected_distribution(train_scores, bins=10, method='equal_width'):
    """
    计算预期分布的分箱边界和占比
    """
    if method == 'equal_width':
        # 等距分箱
        bin_edges = np.linspace(train_scores.min(), train_scores.max(), bins + 1)
    elif method == 'equal_freq':
        # 等频分箱
        percentiles = np.linspace(0, 100, bins + 1)
        bin_edges = np.percentile(train_scores, percentiles)
    else:
        raise ValueError("Unsupported binning method")

    # 统计频数
    counts, _ = np.histogram(train_scores, bins=bin_edges)
    total = len(train_scores)

    # 计算预期占比
    expected_pct = counts / total

    return bin_edges, expected_pct

def align_actual_distribution(valid_scores, bin_edges):
    """
    将实际分布对齐到预期分布的分箱结构
    """
    # 使用预期分布的分箱边界统计实际样本
    counts, _ = np.histogram(valid_scores, bins=bin_edges)
    total = len(valid_scores)

    # 计算实际占比
    actual_pct = counts / total

    return actual_pct

# 示例用法
train_scores = np.random.normal(600, 100, 10000)
valid_scores = np.random.normal(620, 110, 2000)

bin_edges, expected_pct = compute_expected_distribution(train_scores, bins=10)
actual_pct = align_actual_distribution(valid_scores, bin_edges)

print("预期分布占比:", expected_pct)
print("实际分布占比:", actual_pct)
print("分箱边界:", bin_edges)

4.5 分箱对齐检查清单

在进入PSI计算前,建议使用以下清单进行质量检查:

检查项 预期标准 异常处理
分箱数量 5~20箱(推荐10箱) 箱数过少降低灵敏度,过多引入噪声
空箱比例 实际分布空箱数 ≤ 总箱数的20% 空箱过多需检查样本代表性或合并边界箱
边界溢出 溢出样本占比 ≤ 5% 溢出过多说明监控集分布漂移严重,需重新评估分箱
占比总和 预期占比和 = 1,实际占比和 = 1 若不为1,检查浮点精度或缺失值处理
进阶技巧:对于评分卡模型,建议将分箱边界与评分卡的分段阈值对齐(如A/B/C/D等级),这样PSI结果可以直接与业务评级稳定性挂钩,便于向非技术团队解释。