3. PSI 计算实战:使用 Python 计算 PSI 的完整代码实现
本节将带领你从零开始,使用 Python 实现 PSI 的完整计算流程。我们将深入探讨两种主流分箱策略——等距分箱与等频分箱,并通过实际代码对比它们对 PSI 结果的直接影响。
3.1 PSI 计算核心步骤
PSI 的计算本质上是对比两个分布(预期分布与实际分布)在各分箱上的比例变化。其标准计算流程如下:
- 数据分箱:将连续变量或离散变量划分为 K 个区间(箱)。
- 计算预期占比:统计每个箱中样本数占总样本数的比例(基准分布)。
- 计算实际占比:统计新数据在每个箱中的样本占比(当前分布)。
- 计算各箱 PSI 分量:对每个箱,计算 (实际占比 - 预期占比) * ln(实际占比 / 预期占比)。
- 汇总 PSI:将所有箱的 PSI 分量求和,得到最终的 PSI 值。
⚠️ 关键注意点: 当某个箱的预期占比或实际占比为 0 时,ln(0) 无定义。通常做法是给占比加上一个极小值(如 1e-6)或将该箱与相邻箱合并。
3.2 完整 Python 代码实现
以下代码实现了 PSI 计算的核心函数,并支持自定义分箱策略:
import numpy as np
import pandas as pd
def calculate_psi(expected, actual, bins=10, strategy='equal_width'):
"""
计算 PSI(群体稳定性指标)
参数:
expected: array-like, 预期分布(基准数据)
actual: array-like, 实际分布(当前数据)
bins: int, 分箱数量
strategy: str, 分箱策略 ('equal_width' 或 'equal_freq')
返回:
psi: float, PSI 值
bin_df: DataFrame, 各箱明细
"""
# 合并数据确定分箱边界
all_data = np.concatenate([expected, actual])
if strategy == 'equal_width':
# 等距分箱:在数据范围内均匀划分
bin_edges = np.linspace(np.min(all_data), np.max(all_data), bins + 1)
elif strategy == 'equal_freq':
# 等频分箱:按分位数划分,使每箱样本数大致相等
percentiles = np.linspace(0, 100, bins + 1)
bin_edges = np.percentile(all_data, percentiles)
# 确保边界唯一
bin_edges = np.unique(bin_edges)
else:
raise ValueError("strategy 必须是 'equal_width' 或 '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_data = np.random.normal(0, 1, 10000)
actual_data = np.random.normal(0.2, 1.2, 10000)
psi_value, details = calculate_psi(expected_data, actual_data, bins=10, strategy='equal_width')
print(f"PSI (等距分箱): {psi_value:.4f}")
psi_value_eq, details_eq = calculate_psi(expected_data, actual_data, bins=10, strategy='equal_freq')
print(f"PSI (等频分箱): {psi_value_eq:.4f}")
3.3 分箱策略对比:等距 vs 等频
分箱策略直接影响 PSI 的敏感性和稳定性。下表总结了两种策略的核心差异:
| 对比维度 | 等距分箱 (Equal Width) | 等频分箱 (Equal Frequency) |
|---|---|---|
| 分箱方式 | 将数值范围均匀划分为等宽区间 | 按分位数划分,使每箱样本数大致相等 |
| 对异常值敏感度 | 高——异常值会拉伸边界,导致多数样本集中在少数箱 | 低——异常值被分散到两端箱中 |
| 分布稀疏区域表现 | 边缘箱样本极少,易出现零占比问题 | 各箱样本均匀,计算更稳定 |
| PSI 数值稳定性 | 较低——零占比需特殊处理,PSI 易波动 | 较高——各箱均有足够样本,PSI 更稳健 |
| 适用场景 | 数据分布均匀、无极端值;需要固定业务含义的区间 | 数据分布偏态、含异常值;追求计算稳定性 |
💡 实战建议: 在风控评分卡监控中,推荐优先使用等频分箱。因为评分卡分数通常呈偏态分布,等频分箱能确保每个箱都有足够的样本量,避免因个别箱样本过少导致 PSI 虚高或计算失败。
3.4 分箱策略对 PSI 的影响可视化
下图直观展示了两种分箱策略下,预期分布与实际分布的对比情况:
3.5 实战对比:不同分箱策略下的 PSI 差异
我们使用模拟数据来观察两种分箱策略的实际效果。假设基准数据服从标准正态分布,当前数据均值偏移 0.2、标准差扩大 1.2 倍:
# 模拟数据
np.random.seed(42)
expected = np.random.normal(0, 1, 50000)
actual = np.random.normal(0.2, 1.2, 50000)
# 等距分箱
psi_width, df_width = calculate_psi(expected, actual, bins=10, strategy='equal_width')
print(f"等距分箱 PSI: {psi_width:.4f}")
print(df_width[['bin_start', 'bin_end', 'expected_pct', 'actual_pct', 'psi_component']].to_string())
print("\n" + "="*60 + "\n")
# 等频分箱
psi_freq, df_freq = calculate_psi(expected, actual, bins=10, strategy='equal_freq')
print(f"等频分箱 PSI: {psi_freq:.4f}")
print(df_freq[['bin_start', 'bin_end', 'expected_pct', 'actual_pct', 'psi_component']].to_string())
运行上述代码,你会观察到以下典型结果:
- 等距分箱:PSI 值通常偏高(例如 0.15~0.25),因为边缘箱样本稀疏,占比微小变化被对数放大。
- 等频分箱:PSI 值更稳定(例如 0.08~0.12),各箱样本均匀,对分布偏移的度量更平滑。
⚠️ 常见陷阱: 不要盲目认为等频分箱一定更好。如果业务上需要固定区间含义(如信用评分中 600-650 分是一个固定区间),等距分箱更符合业务解释性。此时应通过增加分箱数量或合并稀疏箱来缓解稳定性问题。
3.6 分箱策略选择决策流程
下图总结了在实际项目中如何选择分箱策略:
3.7 最佳实践与避坑指南
- 分箱数量建议:通常 5~20 箱。箱数太少会丢失分布细节,箱数太多则每个箱样本过少,PSI 不稳定。
- 零占比处理:始终添加极小值(如 1e-6)或使用平滑技术,避免 ln(0) 导致计算失败。
- 业务对齐:如果模型输出有业务含义的阈值(如信用评分 600 分是拒绝线),优先使用等距分箱并确保边界与业务阈值对齐。
- 交叉验证:在正式监控上线前,使用历史数据模拟 PSI 计算,对比两种分箱策略的结果差异,选择最稳定的方案。
- 动态调整:监控过程中如果发现某箱长期样本过少,应动态合并相邻箱,保持计算稳定性。
📌 核心结论: 等频分箱在大多数场景下提供更稳定的 PSI 计算,适合自动化监控;等距分箱在需要业务解释性时不可替代,但需要额外处理稀疏箱问题。实际项目中建议两种策略都计算,取业务可解释且数值稳定的结果。