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 等距分箱的流程

  1. 确定分箱数 k:通常取 10 或 20,也可根据样本量 N 取 k = min(20, int(N/100))
  2. 计算箱宽度:width = (max_value - min_value) / k
  3. 生成箱边界:从 min 开始,每次增加 width,直到 max
  4. 分配样本到各箱:统计每个箱中的样本数量
  5. 计算占比:分别计算预期分布和实际分布的各箱占比
  6. 计算PSI:逐箱计算贡献值并求和
等距分箱PSI计算流程 等距分箱PSI计算流程 输入:特征数据 确定分箱数 k 计算箱宽度 w 生成箱边界 分配样本到各箱 计算各箱占比 计算 PSI 值 Σ (Actual - Expected) * ln(Actual/Expected) 输出:PSI 漂移指标 注意事项 • 空箱加平滑项 0.0001 • 箱数 k 默认取 10 • 边界包含 min/max

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)(需排序)
等距分箱与等频分箱对比 等距分箱 vs 等频分箱对比 等距分箱(宽度相等) 箱1 (n=500) 箱2 (n=3200) 箱3 (n=5800) 箱4 (n=450) 箱5 (n=50) 箱宽度相同,样本量分布不均 适合均匀分布特征 等频分箱(样本量相等) 箱1 箱2 箱3 箱4 箱5 各箱样本量相等,宽度自适应 适合偏态分布特征 箱内样本量差异大 箱宽度差异大 选择策略:均匀分布用等距,偏态分布用等频,监控初期用等距快速建基线

4.6 等距分箱的边界处理技巧

  • 左开右闭:通常采用 (left, right] 的区间定义,避免样本重复归属
  • 边界外推:当监控集出现训练集范围之外的值时,将其归入最近的有效箱
  • 空箱平滑:对占比为0的箱添加 eps = 1e-4 的平滑项,避免PSI计算中的除零和 ln(0) 错误
  • 箱数自适应:当样本量 N < 1000 时,建议 k = max(5, int(N/100)),避免箱数过多导致稀疏
工程实践:在实时监控系统中,等距分箱的边界可以预先计算并固化,监控时只需将新数据映射到对应箱中,计算效率极高。推荐将箱边界存储为配置文件,每次模型版本更新时重新计算。