4. PSI 计算步骤详解:分箱、计算占比、计算对数比、加权求和
群体稳定性指标(PSI)的核心价值在于量化特征分布随时间或样本集的变化程度。其计算过程可拆解为四个标准步骤:分箱 → 计算占比 → 计算对数比 → 加权求和。下面将逐一拆解每个环节的数学逻辑与工程实现。
4.1 步骤一:分箱(Binning)
分箱是将连续特征或离散特征划分为若干区间(箱),以便后续计算每个区间内的样本占比。分箱方式直接影响 PSI 的敏感度与稳定性。
| 分箱方式 | 适用场景 | 注意事项 |
|---|---|---|
| 等距分箱 | 特征值分布均匀,无极端异常值 | 若数据稀疏,部分箱可能为空 |
| 等频分箱 | 特征分布偏态,需保证每箱样本量均衡 | 边界值可能落在同一箱内,需处理重复值 |
| 自定义分箱 | 业务有明确分段标准(如信用评分区间) | 需确保训练集与测试集使用相同分箱边界 |
分箱完成后,需记录每个箱的边界值(如 [0, 10), [10, 20), ...),并确保后续步骤中测试集与训练集使用完全相同的分箱边界。
4.2 步骤二:计算占比(Proportion)
分别计算训练集(预期分布)和测试集(实际分布)在每个箱中的样本占比。
- 训练集占比(\(P_i\)):第 \(i\) 个箱中的样本数 / 训练集总样本数
- 测试集占比(\(Q_i\)):第 \(i\) 个箱中的样本数 / 测试集总样本数
若某个箱在测试集中样本数为 0,则 \(Q_i = 0\),此时对数比计算会引发数学错误(log(0) 无定义)。常见处理方式:
4.3 步骤三:计算对数比(Log Ratio)
对每个箱,计算测试集占比与训练集占比的比值,并取自然对数:
LogRatio_i = ln(Q_i / P_i)
该值反映了第 \(i\) 个箱中样本占比的相对变化方向与幅度:
- LogRatio > 0:测试集在该箱的占比高于训练集,即该区间样本增多
- LogRatio < 0:测试集在该箱的占比低于训练集,即该区间样本减少
- LogRatio = 0:占比无变化
4.4 步骤四:加权求和(Weighted Sum)
将每个箱的对数比乘以该箱的测试集占比,然后对所有箱求和,得到最终的 PSI 值:
PSI = Σ [ (Q_i - P_i) * ln(Q_i / P_i) ]
等价形式:
PSI = Σ [ (Q_i) * ln(Q_i / P_i) ] - Σ [ (P_i) * ln(Q_i / P_i) ]
但实际计算中,通常直接使用第一个公式,因为 \(Q_i - P_i\) 可正可负,而 \( \ln(Q_i / P_i) \) 符号与之对应,确保每个箱的贡献始终为非负值。
4.5 完整计算流程 SVG 图
下图展示了从原始数据到最终 PSI 值的完整计算链路,包含四个核心步骤及关键决策点:
4.6 计算示例(手工演算)
假设某特征分箱为 3 箱,训练集与测试集各 1000 样本,数据如下:
| 箱编号 | 训练集样本数 | 测试集样本数 | \(P_i\) | \(Q_i\) | \(Q_i - P_i\) | \(\ln(Q_i / P_i)\) | 贡献值 |
|---|---|---|---|---|---|---|---|
| 1 | 300 | 250 | 0.30 | 0.25 | -0.05 | -0.1823 | 0.0091 |
| 2 | 400 | 450 | 0.40 | 0.45 | 0.05 | 0.1178 | 0.0059 |
| 3 | 300 | 300 | 0.30 | 0.30 | 0.00 | 0.0000 | 0.0000 |
最终 PSI = 0.0091 + 0.0059 + 0.0000 = 0.0150。该值小于 0.1,表明特征分布稳定性良好。
4.7 代码实现(Python 伪代码)
import numpy as np
import pandas as pd
def calculate_psi(train_series, test_series, bins=10):
# 步骤1:分箱(等频分箱)
_, bin_edges = pd.qcut(train_series, q=bins, retbins=True, duplicates='drop')
# 步骤2:计算占比
train_bins = pd.cut(train_series, bins=bin_edges, include_lowest=True)
test_bins = pd.cut(test_series, bins=bin_edges, include_lowest=True)
train_counts = train_bins.value_counts().sort_index()
test_counts = test_bins.value_counts().sort_index()
train_pct = train_counts / len(train_series)
test_pct = test_counts / len(test_series)
# 步骤3:计算对数比(加微小平滑避免除零)
epsilon = 1e-6
log_ratio = np.log((test_pct + epsilon) / (train_pct + epsilon))
# 步骤4:加权求和
psi = np.sum((test_pct - train_pct) * log_ratio)
return psi
pd.qcut 实现等频分箱,retbins=True 返回箱边界供测试集复用。平滑因子 epsilon 仅用于防止数值异常,不影响整体 PSI 趋势。
4.8 分箱与 PSI 敏感度关系 SVG 图
下图展示了不同分箱数量对 PSI 值敏感度的影响:箱数过少会掩盖局部偏移,箱数过多则可能放大噪声。
通过以上四个步骤的拆解与示例,PSI 的计算逻辑已完全透明。在实际特征巡检中,建议将 PSI 计算封装为自动化函数,并定期对每个特征执行全量计算,以便及时发现分布漂移。