3. PSI计算步骤:数据分箱方法(等距、等频、分位数)、计算各箱占比、计算PSI值、汇总结果

PSI(Population Stability Index,群体稳定性指标)的计算核心在于比较两个分布(通常为基准分布与当前分布)在各区间上的占比差异。整个计算流程可归纳为四个关键步骤:数据分箱、计算各箱占比、计算PSI值、汇总结果。其中,数据分箱方法的选择直接影响PSI的敏感度和稳定性。

3.1 数据分箱方法

分箱是将连续变量或离散变量划分为若干个区间(箱)的过程。在PSI计算中,分箱的目的是将复杂的分布简化为可比较的区间占比。常用的分箱方法有三种:等距分箱、等频分箱和分位数分箱。

分箱方法 核心原理 适用场景 注意事项
等距分箱 将数据范围划分为宽度相等的区间 数据分布均匀,无极端异常值 易受异常值影响,可能导致空箱或样本稀疏
等频分箱 每个箱包含大致相同数量的样本 数据分布偏态,需保证每箱样本量 区间宽度可能差异较大,边界值处理需谨慎
分位数分箱 基于数据的分位数(如十分位数、五分位数)划分区间 需要稳定且可复现的分箱边界 对样本量要求较高,小样本时分位数不稳定
实践建议:在模型监控场景中,推荐优先使用等频分箱或分位数分箱。这两种方法能确保每个箱都有足够的样本量,避免因空箱导致的PSI计算异常。等距分箱仅在变量取值范围有限且分布均匀时使用。

3.2 计算各箱占比

确定分箱边界后,分别计算基准分布和当前分布中落在每个箱内的样本占比。占比计算公式为:

箱占比 = 该箱内样本数 / 总样本数

假设我们将某个评分变量分为5个箱,基准分布和当前分布的占比计算示例如下:

箱编号 区间范围 基准样本数 基准占比 (P_i) 当前样本数 当前占比 (Q_i)
1 [0, 200) 1500 0.150 1200 0.120
2 [200, 400) 2500 0.250 2600 0.260
3 [400, 600) 3500 0.350 3400 0.340
4 [600, 800) 1800 0.180 2000 0.200
5 [800, 1000] 700 0.070 800 0.080
关键避坑:当某个箱的基准占比或当前占比为0时,PSI计算公式中的对数项将出现无穷大。实际处理中,通常对占比进行平滑处理(如加一个小常数ε=0.0001),或将该箱与相邻箱合并。

3.3 计算PSI值

PSI的计算公式基于每个箱的占比差异,通过以下公式逐箱计算并累加:

PSI = Σ [(Q_i - P_i) * ln(Q_i / P_i)]

其中:

  • P_i:基准分布在第i个箱的占比
  • Q_i:当前分布在第i个箱的占比
  • ln:自然对数

继续以上述表格数据为例,逐箱计算PSI贡献值:

箱编号 P_i Q_i Q_i - P_i ln(Q_i / P_i) 单箱PSI贡献
1 0.150 0.120 -0.030 -0.2231 0.0067
2 0.250 0.260 0.010 0.0392 0.0004
3 0.350 0.340 -0.010 -0.0290 0.0003
4 0.180 0.200 0.020 0.1054 0.0021
5 0.070 0.080 0.010 0.1335 0.0013

最终PSI值 = 0.0067 + 0.0004 + 0.0003 + 0.0021 + 0.0013 = 0.0108。

3.4 汇总结果

汇总结果阶段需要将计算出的PSI值与预设的告警阈值进行比较,并输出监控结论。常见的PSI阈值判断标准如下:

PSI值范围 稳定性等级 建议操作
PSI < 0.10 稳定 无需告警,持续监控
0.10 ≤ PSI < 0.25 轻度漂移 发出预警,排查特征分布变化原因
PSI ≥ 0.25 严重漂移 触发告警,立即启动模型重训或特征工程调整
汇总输出示例:“当前评分变量PSI值为0.0108,处于稳定区间(<0.10),无需告警。各箱占比差异较小,主要贡献来自第1箱(低分段样本减少)和第4箱(中高分段样本增加),建议持续观察下一周期数据。”

3.5 PSI计算全流程SVG图

下图展示了从原始数据输入到最终告警决策的完整PSI计算链路:

PSI计算全流程示意图 PSI计算全流程 原始数据输入 数据分箱方法选择 等距分箱 | 等频分箱 | 分位数分箱 计算各箱占比 (P_i, Q_i) 逐箱计算PSI并累加 汇总结果与告警判断 PSI < 0.10 稳定 | 0.10-0.25 预警 | ≥0.25 告警 输出监控结论

3.6 分箱方法对比SVG图

为了更直观地理解三种分箱方法的差异,下图展示了同一组数据在不同分箱策略下的区间划分效果:

三种分箱方法对比示意图 三种分箱方法对比 等距分箱 区间宽度固定 样本分布不均 易出现空箱 受异常值影响大 等频分箱 每箱样本量相等 区间宽度自适应 适合偏态分布 边界值需谨慎 分位数分箱 基于分位数划分 边界稳定可复现 需足够样本量 小样本不稳定 ★ 模型监控场景首选 分箱方法选择建议 • 数据分布均匀 → 等距分箱(简单高效) • 数据偏态严重 → 等频分箱(保证每箱样本量) • 需要稳定边界 → 分位数分箱(可复现性强)

3.7 代码实现示例

以下Python代码展示了使用等频分箱计算PSI的完整实现:

import numpy as np
import pandas as pd

def calculate_psi(expected, actual, bins=10, method='quantile'):
    """
    计算PSI值
    
    Parameters:
    expected: array-like, 基准分布数据
    actual: array-like, 当前分布数据
    bins: int, 分箱数量
    method: str, 分箱方法 ('quantile', 'equal_width', 'equal_freq')
    
    Returns:
    psi: float, PSI值
    bin_details: DataFrame, 各箱详细占比信息
    """
    # 1. 确定分箱边界
    if method == 'quantile':
        # 分位数分箱
        percentiles = np.linspace(0, 100, bins + 1)
        bin_edges = np.percentile(expected, percentiles)
    elif method == 'equal_width':
        # 等距分箱
        bin_edges = np.linspace(np.min(expected), np.max(expected), bins + 1)
    else:
        # 等频分箱(默认)
        bin_edges = np.percentile(expected, np.linspace(0, 100, bins + 1))
    
    # 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. 平滑处理(避免除零)
    epsilon = 1e-6
    expected_pct = np.clip(expected_pct, epsilon, 1 - epsilon)
    actual_pct = np.clip(actual_pct, epsilon, 1 - epsilon)
    
    # 4. 计算PSI
    psi = np.sum((actual_pct - expected_pct) * np.log(actual_pct / expected_pct))
    
    # 5. 汇总结果
    bin_details = pd.DataFrame({
        'bin_start': bin_edges[:-1],
        'bin_end': bin_edges[1:],
        'expected_pct': expected_pct,
        'actual_pct': actual_pct,
        'psi_contribution': (actual_pct - expected_pct) * np.log(actual_pct / expected_pct)
    })
    
    return psi, bin_details

# 使用示例
expected_data = np.random.normal(500, 100, 10000)
actual_data = np.random.normal(520, 110, 10000)

psi_value, details = calculate_psi(expected_data, actual_data, bins=10, method='quantile')
print(f"PSI值: {psi_value:.4f}")
print(f"稳定性等级: {'稳定' if psi_value < 0.10 else '轻度漂移' if psi_value < 0.25 else '严重漂移'}")
print("\n各箱详情:")
print(details.head())
代码要点说明:
  • 分箱边界复用:使用基准分布(expected)确定分箱边界,确保当前分布(actual)使用相同的区间划分。
  • 平滑处理:通过np.clip将占比限制在[ε, 1-ε]范围内,避免对数计算出现无穷大。
  • 结果可追溯:返回DataFrame包含每箱的边界、占比和PSI贡献值,便于定位漂移来源。