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)。计算步骤分为三步:
- 分箱:将变量划分为
k个区间(通常为10等频分箱)。 - 计算占比:分别计算每个区间在期望样本和实际样本中的占比。
- 求和:对每个区间计算
(实际占比 - 期望占比) * ln(实际占比 / 期望占比),并累加。
PSI判定标准:
- PSI < 0.1:分布稳定,无需干预。
- 0.1 ≤ PSI < 0.25:轻度偏移,需关注并排查原因。
- PSI ≥ 0.25:严重偏移,必须立即采取行动(如模型重建或特征调整)。
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 避免除零 |
| 新值处理 | 自动落入最近边界箱 | 需显式处理新类别 |
| 适用场景 | 年龄、收入、信用分、额度 | 职业、地区、产品类型、逾期状态 |
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超过阈值时自动触发告警通知。