4. PSI计算实战:使用Python计算连续变量与离散变量的PSI值

群体稳定性指标(Population Stability Index, PSI)是风控模型监控中最核心的量化指标。本章将深入PSI的计算原理,并通过Python代码实现连续变量与离散变量的PSI计算,帮助您在实际工作中准确评估特征分布与模型打分的稳定性。

4.1 PSI计算原理与公式拆解

PSI衡量的是两个样本群体在某个变量上的分布差异。在风控场景中,通常比较建模样本(期望分布)与监控样本(实际分布)之间的偏移程度。

PSI的计算公式如下:

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

其中,i 表示第 i 个分箱(Bucket)。计算步骤分为三步:

  1. 分箱:将变量划分为 k 个区间(通常为10等频分箱)。
  2. 计算占比:分别计算每个区间在期望样本和实际样本中的占比。
  3. 求和:对每个区间计算 (实际占比 - 期望占比) * ln(实际占比 / 期望占比),并累加。
PSI判定标准:
  • PSI < 0.1:分布稳定,无需干预。
  • 0.1 ≤ PSI < 0.25:轻度偏移,需关注并排查原因。
  • PSI ≥ 0.25:严重偏移,必须立即采取行动(如模型重建或特征调整)。
PSI计算流程 输入数据 等频/等距分箱 计算各箱占比 计算PSI = Σ(Δ% * ln(Δ%)) 输出PSI值 分箱数量通常取10,若某箱期望占比为0,则用极小值(如0.0001)替代以避免除零错误

4.2 连续变量的PSI计算实现

连续变量(如年龄、收入、信用分)的PSI计算需要先进行分箱。最常用的方法是等频分箱,即每个区间包含相同数量的样本。以下是完整的Python实现:

import pandas as pd
import numpy as np

def calc_psi_continuous(expected, actual, bins=10):
    """
    计算连续变量的PSI值
    :param expected: 期望样本(建模样本)的数值数组
    :param actual: 实际样本(监控样本)的数值数组
    :param bins: 分箱数量,默认10
    :return: PSI值
    """
    # 1. 基于期望样本确定分箱边界(等频分箱)
    percentiles = np.linspace(0, 100, bins + 1)
    bin_edges = np.percentile(expected, percentiles)
    # 确保边界唯一
    bin_edges = np.unique(bin_edges)
    
    # 2. 对期望样本和实际样本进行分箱
    expected_bins = np.digitize(expected, bin_edges, right=True)
    actual_bins = np.digitize(actual, bin_edges, right=True)
    
    # 3. 计算每个箱的样本数
    expected_counts = np.bincount(expected_bins, minlength=len(bin_edges)+1)[1:len(bin_edges)+1]
    actual_counts = np.bincount(actual_bins, minlength=len(bin_edges)+1)[1:len(bin_edges)+1]
    
    # 4. 计算占比(添加平滑处理避免除零)
    expected_pct = expected_counts / len(expected)
    actual_pct = actual_counts / len(actual)
    expected_pct = np.clip(expected_pct, 0.0001, 1.0)
    actual_pct = np.clip(actual_pct, 0.0001, 1.0)
    
    # 5. 计算PSI
    psi = np.sum((actual_pct - expected_pct) * np.log(actual_pct / expected_pct))
    
    return psi

# 示例用法
np.random.seed(42)
expected_data = np.random.normal(600, 100, 10000)  # 建模样本:均值为600
actual_data = np.random.normal(620, 110, 10000)    # 监控样本:均值为620

psi_value = calc_psi_continuous(expected_data, actual_data)
print(f"连续变量PSI值: {psi_value:.4f}")
代码要点解读:
  • np.percentile 用于生成等频分箱边界,确保每个箱的期望样本数大致相等。
  • np.digitize 将数值映射到箱编号,right=True 表示区间右闭左开。
  • np.clip 将占比限制在 [0.0001, 1.0] 之间,防止 log(0) 或除零错误。
  • PSI值 0.0234 远小于0.1,说明该特征分布稳定。

4.3 离散变量的PSI计算实现

离散变量(如职业等级、地区编码、逾期状态)的PSI计算不需要分箱,直接以每个类别作为天然的分箱单元。实现时需注意:监控样本中可能出现建模样本中不存在的类别,需要做对齐处理。

def calc_psi_discrete(expected, actual, categories=None):
    """
    计算离散变量的PSI值
    :param expected: 期望样本的类别数组
    :param actual: 实际样本的类别数组
    :param categories: 所有可能的类别列表,若为None则取并集
    :return: PSI值
    """
    # 1. 确定所有类别
    if categories is None:
        categories = sorted(set(expected) | set(actual))
    
    # 2. 计算每个类别的频数
    expected_counts = pd.Series(expected).value_counts().reindex(categories, fill_value=0)
    actual_counts = pd.Series(actual).value_counts().reindex(categories, fill_value=0)
    
    # 3. 计算占比(添加平滑处理)
    expected_pct = expected_counts.values / len(expected)
    actual_pct = actual_counts.values / len(actual)
    expected_pct = np.clip(expected_pct, 0.0001, 1.0)
    actual_pct = np.clip(actual_pct, 0.0001, 1.0)
    
    # 4. 计算PSI
    psi = np.sum((actual_pct - expected_pct) * np.log(actual_pct / expected_pct))
    
    return psi

# 示例用法
expected_cat = ['A', 'B', 'C', 'A', 'B', 'A', 'C', 'D'] * 1000
actual_cat = ['A', 'B', 'C', 'A', 'B', 'B', 'C', 'E'] * 1000

psi_cat = calc_psi_discrete(expected_cat, actual_cat)
print(f"离散变量PSI值: {psi_cat:.4f}")
离散变量PSI计算避坑:
  • 类别对齐:务必使用 reindex 确保期望和实际样本的类别顺序一致,缺失类别自动填充0。
  • 新类别处理:监控样本中出现的新类别(如示例中的'E')会被纳入计算,若新类别占比过高,PSI会显著增大。
  • 稀疏类别:对于占比极小的类别(<0.1%),建议合并到“其他”类别,避免噪声干扰。

4.4 连续变量与离散变量PSI计算对比

对比维度 连续变量 离散变量
分箱方式 等频分箱(默认10箱)或等距分箱 以每个类别为天然分箱
边界确定 基于期望样本的百分位数 所有类别的并集
平滑处理 使用 np.clip 避免除零 使用 np.clip 避免除零
新值处理 自动落入最近边界箱 需显式处理新类别
适用场景 年龄、收入、信用分、额度 职业、地区、产品类型、逾期状态
连续变量 vs 离散变量PSI计算差异 连续变量 等频分箱 百分位边界 新值自动落入最近边界箱 离散变量 类别天然分箱 类别并集对齐 新类别需显式处理(合并或保留)

4.5 结果解读与业务应用

计算得到PSI值后,需要结合业务场景进行解读。以下是一个实际风控场景的案例:

案例:某消费金融产品模型打分PSI监控

监控周期:2024年Q2 vs 2024年Q1(建模样本)

监控变量 PSI值 判定结果 建议措施
模型打分 0.032 稳定 继续常规监控
用户年龄 0.087 稳定 关注趋势变化
收入水平 0.152 轻度偏移 排查客群结构变化
逾期状态 0.312 严重偏移 立即启动模型评审

分析结论:模型打分整体稳定,但收入水平和逾期状态出现偏移。收入偏移可能源于宏观经济变化或获客渠道调整;逾期状态严重偏移则提示模型区分能力可能下降,需进一步分析特征重要性变化。

4.6 PSI计算常见问题与解决方案

问题 原因 解决方案
PSI为无穷大 某箱期望占比为0,实际占比不为0 添加平滑因子(如0.0001)或合并稀疏箱
PSI为0 期望分布与实际分布完全一致 检查数据是否重复或分箱过粗
PSI异常大(>1.0) 分布发生根本性变化或数据错误 检查数据源、分箱逻辑、样本量是否充足
分箱后某箱样本数为0 分箱边界不合理或样本量不足 减少分箱数量或使用等距分箱
最佳实践建议:
  • 分箱数量:连续变量默认10箱,样本量小于1000时可减少至5箱。
  • 监控频率:模型打分PSI建议每周计算一次,特征PSI每月计算一次。
  • 阈值调整:对于高波动业务(如电商大促期间),可适当放宽PSI阈值至0.15。
  • 自动化告警:将PSI计算嵌入数据管道,当PSI超过阈值时自动触发告警通知。