4. PSI分箱策略(等距分箱):等距分箱的原理、适用场景与代码实现
在计算群体稳定性指标(PSI)时,分箱策略直接决定了PSI值的准确性与鲁棒性。等距分箱(Equal-Width Binning)是最基础、最直观的分箱方法,它将特征的值域划分为宽度相等的区间,不依赖数据分布形态。
4.1 等距分箱的原理
等距分箱的核心思想是:将连续变量的取值范围 [min, max] 均匀切割成 k 个区间,每个区间的宽度 w = (max - min) / k。第 i 个箱的边界为:
- 下边界:
min + (i-1) * w - 上边界:
min + i * w
分箱完成后,统计每个箱中样本的实际占比(Actual_i)与预期占比(Expected_i),代入PSI公式:
PSI = Σ (Actual_i - Expected_i) * ln(Actual_i / Expected_i)
等距分箱不要求每个箱的样本量均匀,因此当数据分布极度偏斜时,可能出现某些箱为空或占比极低的情况,此时需要做平滑处理(如加0.0001避免除零)。
关键参数:分箱数 k 的选取直接影响PSI的灵敏度。k 过小会掩盖局部漂移,k 过大则放大噪声。推荐初始值 k=10,后续根据特征分布调整。
4.2 适用场景
| 场景类型 | 适用性 | 说明 |
|---|---|---|
| 均匀分布特征 | ★★★★★ | 数据在值域内近似均匀分布时,等距分箱效果最佳 |
| 正态分布特征 | ★★★★☆ | 中间箱样本密集,两端箱稀疏,需注意空箱处理 |
| 长尾分布特征 | ★★☆☆☆ | 大量样本集中在少数箱,多数箱样本极少,PSI易失真 |
| 离散型数值特征 | ★★★☆☆ | 取值较少时可直接用枚举分箱,等距分箱可能切割到整数中间 |
| 模型分数/概率输出 | ★★★★☆ | 评分卡模型输出通常范围固定(如300-900),等距分箱直观可解释 |
实践建议:在模型监控初期,建议先用等距分箱快速建立PSI基线。当发现PSI异常波动时,再切换到等频分箱或最优分箱进行深入分析。
4.3 等距分箱的流程
- 确定分箱数 k:通常取 10 或 20,也可根据样本量 N 取
k = min(20, int(N/100)) - 计算箱宽度:
width = (max_value - min_value) / k - 生成箱边界:从 min 开始,每次增加 width,直到 max
- 分配样本到各箱:统计每个箱中的样本数量
- 计算占比:分别计算预期分布和实际分布的各箱占比
- 计算PSI:逐箱计算贡献值并求和
4.4 代码实现
以下Python代码实现了等距分箱的PSI计算,包含空箱平滑处理和边界处理:
import numpy as np
import pandas as pd
def psi_equal_width(expected, actual, bins=10, eps=1e-4):
"""
等距分箱PSI计算
Parameters:
-----------
expected : array-like
预期分布(如训练集)
actual : array-like
实际分布(如监控集)
bins : int
分箱数量,默认10
eps : float
平滑项,避免除零和取对数零
Returns:
--------
psi_value : float
PSI值
bin_info : DataFrame
各箱的边界、占比和贡献
"""
expected = np.asarray(expected)
actual = np.asarray(actual)
# 1. 确定全局最小最大值
min_val = min(expected.min(), actual.min())
max_val = max(expected.max(), actual.max())
# 2. 等距生成箱边界
width = (max_val - min_val) / bins
edges = [min_val + i * width for i in range(bins + 1)]
edges[0] = -np.inf # 左开区间
edges[-1] = np.inf # 右闭区间
# 3. 分配样本到各箱
exp_counts, _ = np.histogram(expected, bins=edges)
act_counts, _ = np.histogram(actual, bins=edges)
# 4. 计算占比(加平滑)
exp_ratio = (exp_counts + eps) / (expected.shape[0] + eps * bins)
act_ratio = (act_counts + eps) / (actual.shape[0] + eps * bins)
# 5. 计算PSI
psi_value = np.sum((act_ratio - exp_ratio) * np.log(act_ratio / exp_ratio))
# 6. 构建结果DataFrame
bin_info = pd.DataFrame({
'bin_start': edges[:-1],
'bin_end': edges[1:],
'expected_count': exp_counts,
'actual_count': act_counts,
'expected_ratio': exp_ratio,
'actual_ratio': act_ratio,
'contribution': (act_ratio - exp_ratio) * np.log(act_ratio / exp_ratio)
})
return psi_value, bin_info
# 示例用法
if __name__ == "__main__":
# 模拟训练集分数(预期分布)
np.random.seed(42)
train_scores = np.random.normal(600, 100, 10000)
# 模拟监控集分数(实际分布,均值偏移)
monitor_scores = np.random.normal(620, 110, 5000)
psi, info = psi_equal_width(train_scores, monitor_scores, bins=10)
print(f"PSI = {psi:.4f}")
print(info.head())
常见陷阱:当预期分布和实际分布的值域差异较大时(如训练集分数范围300-900,监控集出现100-950),等距分箱会因边界外推导致空箱增多。建议先对两个分布取并集的值域,或使用分位数分箱作为替代方案。
4.5 等距分箱 vs 等频分箱
| 对比维度 | 等距分箱 | 等频分箱 |
|---|---|---|
| 箱宽度 | 所有箱宽度相等 | 各箱宽度不等 |
| 箱内样本量 | 可能差异很大 | 各箱样本量近似相等 |
| 对偏态分布 | 两端箱易空 | 能自适应分布密度 |
| 可解释性 | 高(边界整齐) | 中(边界依赖分位数) |
| PSI稳定性 | 对分布偏移敏感 | 对分布偏移较稳健 |
| 计算复杂度 | O(n) | O(n log n)(需排序) |
4.6 等距分箱的边界处理技巧
- 左开右闭:通常采用
(left, right]的区间定义,避免样本重复归属 - 边界外推:当监控集出现训练集范围之外的值时,将其归入最近的有效箱
- 空箱平滑:对占比为0的箱添加
eps = 1e-4的平滑项,避免PSI计算中的除零和ln(0)错误 - 箱数自适应:当样本量 N < 1000 时,建议
k = max(5, int(N/100)),避免箱数过多导致稀疏
工程实践:在实时监控系统中,等距分箱的边界可以预先计算并固化,监控时只需将新数据映射到对应箱中,计算效率极高。推荐将箱边界存储为配置文件,每次模型版本更新时重新计算。