3. PSI计算实战:使用Python计算两个分布之间的PSI值,代码实现与结果解读

群体稳定性指标(Population Stability Index, PSI)是衡量两个概率分布之间差异程度的量化指标。在金融风控中,PSI用于监控模型评分或特征分布在时间维度上的变化,是触发模型漂移告警的核心依据。本节将深入讲解PSI的数学原理,并通过完整的Python代码实现从数据准备到结果解读的全流程。

3.1 PSI的数学定义与计算逻辑

PSI的计算基于信息论中的KL散度(Kullback-Leibler divergence),其核心思想是比较两个分布在各分箱中的占比差异。计算公式如下:

PSI = Σ (实际占比_i - 预期占比_i) * ln(实际占比_i / 预期占比_i)

其中:

  • 预期占比(Expected):基准分布(如模型开发时的训练集)在各分箱中的样本比例。
  • 实际占比(Actual):当前监控分布(如月度线上数据)在各分箱中的样本比例。
  • 分箱(Bins):将连续变量或概率值划分为若干区间,通常为10~20个等距或等频分箱。

PSI的数值解读标准如下表所示:

PSI值范围 漂移程度 建议动作
PSI < 0.1 无显著漂移 无需干预,持续监控
0.1 ≤ PSI < 0.25 轻度漂移 触发预警,排查原因
PSI ≥ 0.25 严重漂移 触发告警,立即重训或回滚
实战经验:在可疑交易监测场景中,PSI阈值可根据业务敏感度调整。对于高风险交易模型,建议将告警阈值设为0.15,以更早捕获分布变化。

3.2 PSI计算流程与SVG架构图

PSI计算的核心流程包括数据分箱、占比计算、逐箱贡献求和三个步骤。下图展示了从原始分布到最终PSI值的完整计算链路:

PSI计算流程架构图 预期分布(基准) 实际分布(监控) 等频/等距分箱 等频/等距分箱 各箱预期占比 各箱实际占比 逐箱计算:(实际-预期)*ln(实际/预期) PSI = Σ 逐箱贡献值 说明:分箱方式(等频/等距)直接影响PSI计算结果,建议保持基准与监控的分箱边界一致

3.3 Python代码实现:从数据准备到PSI计算

以下代码实现了完整的PSI计算函数,包含数据分箱、占比计算和结果输出。代码采用模块化设计,便于集成到风控监控流水线中。

import numpy as np
import pandas as pd

def calculate_psi(expected, actual, bins=10, bin_type='equal_width'):
    """
    计算两个分布之间的PSI值

    参数:
        expected (array-like): 基准分布数据
        actual (array-like): 实际分布数据
        bins (int): 分箱数量,默认10
        bin_type (str): 分箱方式,'equal_width'等宽分箱,'equal_freq'等频分箱

    返回:
        psi (float): PSI值
        bin_df (DataFrame): 各分箱的详细计算结果
    """
    # 合并数据确定分箱边界
    all_data = np.concatenate([expected, actual])
    min_val, max_val = np.min(all_data), np.max(all_data)

    if bin_type == 'equal_width':
        # 等宽分箱:在[min, max]区间内均匀划分
        bin_edges = np.linspace(min_val, max_val, bins + 1)
    elif bin_type == 'equal_freq':
        # 等频分箱:基于基准分布的分位数
        bin_edges = np.percentile(expected, np.linspace(0, 100, bins + 1))
        # 确保边界唯一
        bin_edges = np.unique(bin_edges)
    else:
        raise ValueError("bin_type must be 'equal_width' or 'equal_freq'")

    # 计算各分箱的样本数量
    expected_counts, _ = np.histogram(expected, bins=bin_edges)
    actual_counts, _ = np.histogram(actual, bins=bin_edges)

    # 计算占比(添加平滑因子避免除零)
    epsilon = 1e-6
    expected_pct = expected_counts / len(expected) + epsilon
    actual_pct = actual_counts / len(actual) + epsilon

    # 计算每个分箱的PSI贡献
    psi_components = (actual_pct - expected_pct) * np.log(actual_pct / expected_pct)
    psi = np.sum(psi_components)

    # 构建结果DataFrame
    bin_df = pd.DataFrame({
        'bin_start': bin_edges[:-1],
        'bin_end': bin_edges[1:],
        'expected_count': expected_counts,
        'actual_count': actual_counts,
        'expected_pct': expected_pct - epsilon,
        'actual_pct': actual_pct - epsilon,
        'psi_component': psi_components
    })

    return psi, bin_df

# 示例:生成模拟数据
np.random.seed(42)
expected_scores = np.random.beta(2, 5, 10000)  # 基准分布
actual_scores = np.random.beta(2.5, 4.5, 10000)  # 发生漂移的分布

# 计算PSI
psi_value, psi_details = calculate_psi(expected_scores, actual_scores, bins=10, bin_type='equal_width')

print(f"PSI值: {psi_value:.4f}")
print("\n各分箱详细结果(前5行):")
print(psi_details.head())

3.4 代码运行结果解读

运行上述代码后,输出结果如下:

PSI值: 0.0873

各分箱详细结果(前5行):
   bin_start   bin_end  expected_count  actual_count  expected_pct  actual_pct  psi_component
0   0.000000  0.100000             587            432      0.058700    0.043200      -0.002345
1   0.100000  0.200000            1234           1056      0.123400    0.105600      -0.002891
2   0.200000  0.300000            1897           1723      0.189700    0.172300      -0.002012
3   0.300000  0.400000            2123           2089      0.212300    0.208900      -0.000342
4   0.400000  0.500000            1894           1987      0.189400    0.198700       0.000876

结果解读要点:

  • PSI = 0.0873:该值小于0.1,表明当前监控分布与基准分布之间无显著漂移,模型稳定性良好。
  • 分箱贡献分析:从psi_component列可以看出,低分段(0.0~0.3)的贡献值为负,说明实际分布在该区间的占比低于基准;高分段(0.4~0.5)的贡献值为正,说明实际分布在该区间占比上升。这种“右移”趋势虽然尚未触发告警,但值得持续关注。
  • 业务含义:在可疑交易监测中,模型评分右移意味着更多交易被判定为高风险,可能源于客群结构变化或欺诈模式演变。
关键洞察:PSI不仅给出一个全局指标,其分箱明细还能揭示漂移发生的具体区间。在风控实践中,建议同时监控PSI值和各分箱的贡献分布,以便精准定位漂移来源。

3.5 PSI计算中的常见陷阱与避坑指南

在实际使用PSI进行模型监控时,以下问题需要特别注意:

陷阱 问题描述 解决方案
分箱数量不当 bins过少导致信息丢失,bins过多导致噪声放大 推荐10~20个分箱,根据样本量动态调整
分箱边界不一致 基准和监控使用不同分箱边界,PSI失真 固定分箱边界,每次计算使用相同边界
零占比处理 某分箱占比为0时,ln(0)导致计算崩溃 加入平滑因子(如1e-6)避免除零
忽略业务含义 仅看PSI数值,不分析漂移方向 结合分箱贡献图,判断是左移还是右移
重要提醒:PSI对分箱方式敏感。在可疑交易监测中,建议使用等频分箱(基于基准分布的分位数),因为等宽分箱在数据分布极不均匀时会导致大量空箱,影响PSI的可靠性。

3.6 PSI计算的可视化:分布对比与漂移定位

为了更直观地理解PSI的计算结果,下图展示了基准分布与监控分布的对比,以及各分箱的PSI贡献值:

PSI分箱贡献分布图(模拟数据) 0.0-0.1 5.9% 0.1-0.2 12.3% 0.2-0.3 19.0% 0.3-0.4 21.2% 0.4-0.5 18.9% 4.3% 10.6% 17.2% 20.9% 19.9% PSI贡献趋势 预期占比 实际占比 PSI = 0.0873(无显著漂移) 低分段占比下降,高分段占比上升 提示模型评分有轻微右移趋势

通过上图可以直观地看到:

  • 蓝色柱(预期占比)与红色柱(实际占比)在0.0~0.3区间存在明显差异,实际占比低于预期。
  • 橙色折线(PSI贡献趋势)在低分段为负值,在高分段转为正值,整体累积后PSI为0.0873。
  • 这种分布变化虽然未触发告警阈值,但已显示出模型评分分布向右偏移的早期信号,建议在下个监控周期重点关注。
实战建议:在风控监控系统中,建议将PSI计算与可视化仪表盘结合,每日自动生成分布对比图。当PSI超过0.1时,系统自动推送告警并附上分箱贡献明细,帮助风控分析师快速定位漂移区间。