3. PSI 计算基础:分箱方法的选择与实现

群体稳定性指标(PSI)的核心在于比较两个分布之间的差异。在计算 PSI 之前,必须先将连续变量或离散变量划分为若干个区间(即“分箱”)。分箱方法的选择直接决定了 PSI 的敏感度和可靠性。本章将系统讲解三种主流分箱方法:等距分箱、等频分箱与分位数分箱,并给出 Python 实现与选型建议。

3.1 分箱在 PSI 计算中的角色

PSI 的数学公式为:

PSI = Σ (实际占比_i - 预期占比_i) * ln(实际占比_i / 预期占比_i)

其中 i 代表第 i 个分箱。分箱决定了每个箱子的边界,进而决定了每个箱子的样本占比。如果分箱不合理,即使分布没有实质漂移,PSI 也可能异常波动;反之,如果分箱过于粗糙,则可能掩盖真实的漂移信号。

核心原则: 分箱应使每个箱子内的样本在统计上具有同质性,同时保证箱子间的区分度足够捕捉分布变化。

3.2 三种分箱方法对比

分箱方法 核心逻辑 优点 缺点 适用场景
等距分箱 将变量取值范围等分为 k 个宽度相同的区间 实现简单,边界直观,易于解释 对数据分布敏感,长尾分布下中间箱子样本稀疏 变量分布均匀、无极端异常值
等频分箱 使每个箱子包含大致相同的样本数量 箱子样本量均衡,避免空箱或稀疏箱 边界可能落在密集区域,导致相同值被分到不同箱子 样本量充足、分布偏态明显
分位数分箱 基于指定分位点(如 10%、20%…)划分边界 边界具有统计意义,对异常值鲁棒 分位点选择依赖经验,箱子数量固定 需要标准化分箱边界、跨时间对比

3.3 等距分箱实现

等距分箱将数据从最小值到最大值等分为 k 段。在 Python 中,可以使用 numpy.linspace 或 pandas.cut 实现。

import numpy as np
import pandas as pd

def equal_width_binning(data, bins=10):
    """
    等距分箱
    :param data: 一维数组或 Series
    :param bins: 箱子数量
    :return: 分箱标签、边界列表
    """
    # 生成等距边界
    bin_edges = np.linspace(data.min(), data.max(), bins + 1)
    # 使用 cut 进行分箱
    labels = pd.cut(data, bins=bin_edges, include_lowest=True, right=True)
    return labels, bin_edges

# 示例
sample_data = np.random.randn(1000) * 50 + 200  # 模拟评分数据
labels, edges = equal_width_binning(sample_data, bins=10)
print("边界:", np.round(edges, 2))
注意: 等距分箱在数据存在极端异常值时,可能导致大部分样本集中在少数几个箱子中,其余箱子样本极少甚至为空,从而造成 PSI 计算不稳定。

3.4 等频分箱实现

等频分箱通过分位数保证每个箱子样本数大致相等。使用 pandas.qcut 可以快速实现。

def equal_freq_binning(data, bins=10):
    """
    等频分箱
    :param data: 一维数组或 Series
    :param bins: 箱子数量
    :return: 分箱标签、边界列表
    """
    # qcut 按分位数切分,duplicates='drop' 处理重复边界
    labels, bin_edges = pd.qcut(data, q=bins, retbins=True, duplicates='drop')
    return labels, bin_edges

# 示例
labels, edges = equal_freq_binning(sample_data, bins=10)
print("各箱样本数:", labels.value_counts().sort_index().tolist())
实战技巧: 当数据中存在大量重复值时,pd.qcut 可能因无法找到唯一分位点而报错。此时应设置 duplicates='drop',或先对数据添加微小噪声后再分箱。

3.5 分位数分箱实现

分位数分箱允许用户自定义分位点序列,适用于需要固定边界进行跨时间对比的场景。

def quantile_binning(data, quantiles=[0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]):
    """
    分位数分箱
    :param data: 一维数组或 Series
    :param quantiles: 分位点列表(0~1之间)
    :return: 分箱标签、边界列表
    """
    # 计算指定分位点的值
    bin_edges = data.quantile(quantiles).unique()
    bin_edges = np.sort(np.concatenate([[data.min()], bin_edges, [data.max()]]))
    labels = pd.cut(data, bins=bin_edges, include_lowest=True, right=True)
    return labels, bin_edges

# 示例:使用十分位点
quantiles = [i/10 for i in range(1, 10)]
labels, edges = quantile_binning(pd.Series(sample_data), quantiles)
print("分位边界:", np.round(edges, 2))

3.6 分箱方法选择决策流程

下图展示了在实际项目中如何根据数据特征选择合适的分箱方法:

分箱方法选择决策流程 分箱方法选择决策流程 原始数据分布 分布是否均匀且无极端异常值? 是 否 等距分箱 样本量是否充足? 需要固定边界跨时间对比? 是 否 分位数分箱 等频分箱 计算 PSI 并设置漂移阈值 提示:实际项目中建议同时尝试 2~3 种分箱方法,对比 PSI 稳定性后再确定最终方案

3.7 分箱数量与 PSI 敏感度

分箱数量(k)是另一个关键参数。经验法则如下:

  • k = 10:行业默认值,适用于大多数评分卡和风控模型监控。
  • k = 5~8:样本量较小(< 1000)或变量取值有限时使用,避免箱子过细导致统计噪声。
  • k = 15~20:样本量极大(> 100000)且需要捕捉细微漂移时使用,但需警惕过拟合。
推荐做法: 在开发阶段,使用 k=10 的分位数分箱作为基线;上线后根据实际 PSI 波动情况,通过敏感性分析调整分箱数量。

3.8 分箱边界对齐:跨时间 PSI 计算的关键

在计算 PSI 时,必须使用同一套分箱边界对预期分布和实际分布进行划分。如果每次重新计算边界,PSI 将失去可比性。

def psi_with_fixed_bins(expected, actual, bin_edges):
    """
    使用固定边界计算 PSI
    :param expected: 预期分布数据
    :param actual: 实际分布数据
    :param bin_edges: 固定的分箱边界
    :return: PSI 值
    """
    # 使用相同的边界对两个分布分箱
    exp_binned = pd.cut(expected, bins=bin_edges, include_lowest=True, right=True)
    act_binned = pd.cut(actual, bins=bin_edges, include_lowest=True, right=True)
    
    # 计算各箱占比
    exp_counts = exp_binned.value_counts().sort_index()
    act_counts = act_binned.value_counts().sort_index()
    
    exp_pct = exp_counts / len(expected)
    act_pct = act_counts / len(actual)
    
    # 处理空箱:加极小值避免除零
    exp_pct = exp_pct.replace(0, 1e-6)
    act_pct = act_pct.replace(0, 1e-6)
    
    # 计算 PSI
    psi = np.sum((act_pct - exp_pct) * np.log(act_pct / exp_pct))
    return psi

# 示例:使用训练集确定边界,在测试集上计算 PSI
train_data = np.random.randn(5000) * 50 + 200
test_data = np.random.randn(5000) * 60 + 210  # 模拟漂移

_, fixed_edges = equal_freq_binning(train_data, bins=10)
psi_value = psi_with_fixed_bins(train_data, test_data, fixed_edges)
print(f"PSI = {psi_value:.4f}")
常见错误: 在计算实际分布时重新分箱,导致边界不一致。这会使 PSI 值虚高或虚低,无法反映真实漂移。务必在模型开发阶段就锁定分箱边界。

3.9 分箱方法选型总结

场景 推荐分箱方法 推荐箱子数 注意事项
信用评分卡(分数范围固定) 等距分箱 10~20 边界需与业务规则对齐
风控模型特征监控 等频分箱 10 处理重复值,避免 qcut 报错
跨时间分布对比 分位数分箱 10 锁定分位点,确保边界一致
小样本(< 500) 等频分箱 5~8 合并稀疏箱,保证每箱至少 50 样本
高精度漂移检测 分位数分箱 15~20 结合业务含义调整分位点

选择合适的分箱方法是 PSI 计算的基础。在实际项目中,建议先使用等频分箱快速验证,再根据数据分布特征切换到分位数分箱或等距分箱,并始终锁定边界用于后续监控。