2. 分箱类型:等距分箱、等频分箱、自定义分箱、最优分箱
分箱(Binning)是将连续变量离散化为有限个区间(箱)的过程,是评分卡建模中特征工程的核心步骤。不同的分箱策略直接影响变量的预测能力、业务可解释性以及后续的拒绝原因分析。本节系统梳理四种主流分箱类型:等距分箱、等频分箱、自定义分箱和最优分箱(含决策树分箱与卡方分箱)。
2.1 分箱类型总览
下表从分箱原理、适用场景、优缺点三个维度对比四种分箱方法,帮助建模人员快速选型。
| 分箱类型 | 核心原理 | 适用场景 | 主要优点 | 主要缺点 |
|---|---|---|---|---|
| 等距分箱 | 将变量取值范围划分为宽度相等的区间 | 数据分布均匀、无极端异常值 | 实现简单、箱宽一致 | 易受异常值影响、箱内样本可能极不均匀 |
| 等频分箱 | 将变量取值范围划分为包含相同样本数的区间 | 数据分布偏态、需要保证每箱样本量 | 每箱样本量均衡、稳定性好 | 可能将相近值分到不同箱、边界不自然 |
| 自定义分箱 | 根据业务知识或监管要求手动设定切分点 | 业务规则明确、监管合规要求 | 业务可解释性强、灵活可控 | 依赖专家经验、可能过拟合 |
| 最优分箱 | 基于统计检验或信息增益自动寻找最优切分点 | 追求预测性能、自动化建模流程 | 数据驱动、信息损失最小 | 计算成本高、可能过拟合 |
2.2 等距分箱(Equal-Width Binning)
等距分箱将变量的取值范围 [min, max] 划分为 k 个宽度相等的区间,每个区间的宽度为 (max - min) / k。该方法对数据分布无假设,实现成本最低。
计算步骤:
- 确定分箱数量
k(通常取 5~10)。 - 计算箱宽
w = (max - min) / k。 - 生成切分点:
min + w, min + 2w, ..., min + (k-1)w。 - 将每个观测值分配到对应的箱中。
# Python 示例:等距分箱
import pandas as pd
import numpy as np
def equal_width_binning(data, k=5):
min_val, max_val = data.min(), data.max()
bins = np.linspace(min_val, max_val, k+1)
return pd.cut(data, bins=bins, include_lowest=True)
# 示例:对年龄变量进行等距分箱
ages = [22, 25, 28, 32, 35, 38, 42, 45, 48, 52]
bins = equal_width_binning(pd.Series(ages), k=4)
print(bins)
2.3 等频分箱(Equal-Frequency Binning)
等频分箱将变量取值范围划分为 k 个区间,使得每个区间包含大致相同的样本数(约 n/k 个)。该方法能有效应对偏态分布,保证每箱都有足够的样本进行统计推断。
计算步骤:
- 确定分箱数量
k。 - 将数据按升序排序。
- 在分位数位置
100/k, 200/k, ..., (k-1)*100/k处设置切分点。 - 将每个观测值分配到对应的箱中。
# Python 示例:等频分箱
def equal_frequency_binning(data, k=5):
# 使用分位数作为切分点
quantiles = np.linspace(0, 1, k+1)
bins = data.quantile(quantiles).values
# 确保切分点唯一
bins = np.unique(bins)
return pd.cut(data, bins=bins, include_lowest=True, duplicates='drop')
# 示例:对收入变量进行等频分箱
incomes = [3000, 4500, 5200, 5800, 6200, 7500, 8800, 12000, 15000, 25000]
bins = equal_frequency_binning(pd.Series(incomes), k=4)
print(bins)
2.4 自定义分箱(Custom Binning)
自定义分箱完全由建模人员根据业务逻辑、监管要求或历史经验手动设定切分点。该方法在金融风控领域应用广泛,尤其是涉及合规审查的场景。
典型应用场景:
- 监管合规:如银保监会要求“年龄”按特定区间分段。
- 业务常识:如“授信额度”按产品层级(0-1万、1-5万、5-20万、20万+)分段。
- 拒绝原因分析:为每个箱赋予明确的业务含义,便于生成拒绝原因码。
# Python 示例:自定义分箱
def custom_binning(data, cut_points, labels=None):
# cut_points: 切分点列表,如 [0, 30, 50, 100]
# labels: 箱标签,如 ['低', '中', '高']
bins = [-np.inf] + cut_points + [np.inf]
return pd.cut(data, bins=bins, labels=labels, include_lowest=True)
# 示例:对年龄按业务规则分箱
ages = [18, 22, 28, 35, 42, 50, 58, 65]
cut_points = [25, 35, 45, 55]
labels = ['18-25', '26-35', '36-45', '46-55', '55+']
bins = custom_binning(pd.Series(ages), cut_points, labels)
print(bins)
2.5 最优分箱(Optimal Binning)
最优分箱通过统计检验或机器学习算法自动寻找使目标变量区分度最大的切分点。本节重点介绍两种最常用的最优分箱方法:决策树分箱和卡方分箱。
2.5.1 决策树分箱(Decision Tree Binning)
决策树分箱利用单变量决策树(CART算法)对连续变量进行分割。算法以目标变量(如好坏标签)作为监督信号,选择使基尼系数或信息增益最大化的切分点。
算法流程:
- 将连续变量作为唯一特征,目标变量作为标签。
- 训练一个深度受限的决策树(通常设置最大深度为3~5,或最小叶子节点样本数为总样本的5%)。
- 提取决策树的所有切分点作为分箱边界。
- 将原始变量映射到决策树叶子节点对应的箱中。
# Python 示例:决策树分箱
from sklearn.tree import DecisionTreeClassifier
import numpy as np
def decision_tree_binning(X, y, max_depth=3, min_samples_leaf=0.05):
# X: 连续变量(需reshape为二维)
# y: 二分类目标变量
clf = DecisionTreeClassifier(
max_depth=max_depth,
min_samples_leaf=min_samples_leaf,
criterion='gini'
)
clf.fit(X.reshape(-1, 1), y)
# 提取切分点
tree = clf.tree_
threshold = tree.threshold[tree.feature == 0] # 特征索引为0
threshold = threshold[threshold != -2] # 移除叶子节点
cut_points = np.sort(threshold)
# 生成分箱
bins = [-np.inf] + list(cut_points) + [np.inf]
return pd.cut(X, bins=bins, include_lowest=True), cut_points
# 示例
X = np.array([22, 25, 28, 32, 35, 38, 42, 45, 48, 52])
y = np.array([0, 0, 0, 1, 1, 1, 1, 0, 0, 1]) # 好坏标签
bins, cut_points = decision_tree_binning(X, y, max_depth=2)
print("切分点:", cut_points)
print("分箱结果:", bins)
2.5.2 卡方分箱(Chi-Merge Binning)
卡方分箱是一种自底向上的合并式分箱方法。它从每个观测值作为一个独立箱开始,迭代合并卡方统计量最小的相邻箱对,直到满足停止条件(如达到预设箱数或所有相邻箱的卡方值均大于阈值)。
算法流程:
- 初始化:将每个观测值视为一个独立箱。
- 计算卡方值:对每对相邻箱,计算卡方统计量:
χ² = Σᵢ Σⱼ (Oᵢⱼ - Eᵢⱼ)² / Eᵢⱼ
其中Oᵢⱼ为观测频数,Eᵢⱼ为期望频数。 - 合并:合并卡方值最小的一对相邻箱。
- 迭代:重复步骤2~3,直到箱数达到预设值或所有卡方值均大于显著性水平对应的临界值。
# Python 示例:卡方分箱(简化版)
import scipy.stats as stats
def chi_merge_binning(X, y, max_bins=5):
# 初始化:每个观测值作为一个箱
data = pd.DataFrame({'X': X, 'y': y})
data = data.sort_values('X').reset_index(drop=True)
bins = list(range(len(data))) # 箱编号
while len(set(bins)) > max_bins:
# 计算相邻箱的卡方值
chi_values = []
unique_bins = sorted(set(bins))
for i in range(len(unique_bins)-1):
b1 = unique_bins[i]
b2 = unique_bins[i+1]
# 构建2x2列联表
group1 = data[bins == b1]['y']
group2 = data[bins == b2]['y']
table = np.array([
[sum(group1==0), sum(group1==1)],
[sum(group2==0), sum(group2==1)]
])
# 计算卡方统计量
chi2, _, _, _ = stats.chi2_contingency(table, correction=False)
chi_values.append(chi2)
# 合并卡方值最小的相邻箱
min_idx = np.argmin(chi_values)
b1 = unique_bins[min_idx]
b2 = unique_bins[min_idx+1]
bins = [b1 if b == b2 else b for b in bins]
# 生成分箱边界
unique_bins = sorted(set(bins))
cut_points = []
for i in range(len(unique_bins)-1):
b1 = unique_bins[i]
b2 = unique_bins[i+1]
# 取两个箱之间的中点作为切分点
max_val_b1 = data[bins == b1]['X'].max()
min_val_b2 = data[bins == b2]['X'].min()
cut_points.append((max_val_b1 + min_val_b2) / 2)
bins_final = [-np.inf] + cut_points + [np.inf]
return pd.cut(X, bins=bins_final, include_lowest=True), cut_points
# 示例
X = np.array([22, 25, 28, 32, 35, 38, 42, 45, 48, 52])
y = np.array([0, 0, 0, 1, 1, 1, 1, 0, 0, 1])
bins, cut_points = chi_merge_binning(X, y, max_bins=4)
print("切分点:", cut_points)
print("分箱结果:", bins)
2.6 最优分箱流程对比图
下图直观对比了决策树分箱(自上而下分割)与卡方分箱(自下而上合并)的核心流程差异。
2.7 分箱效果评估指标
无论采用哪种分箱方法,都需要通过以下指标评估分箱质量:
| 指标 | 计算公式 | 评估标准 |
|---|---|---|
| IV(信息量) | Σ (好占比 - 坏占比) × ln(好占比/坏占比) | IV > 0.3 为强预测力;0.1~0.3 为中等;< 0.1 为弱 |
| WOE(证据权重) | ln(好占比 / 坏占比) | WOE应单调递增或递减,且各箱WOE差异明显 |
| 卡方检验 p 值 | 基于列联表的卡方检验 | p < 0.05 表示相邻箱有显著差异,不宜合并 |
| 每箱样本占比 | 箱内样本数 / 总样本数 | 每箱占比应 > 5%,避免过拟合 |
2.8 分箱类型选择决策树
下图帮助建模人员根据数据特征和业务需求快速选择合适的分箱方法。
2.9 分箱在拒绝原因分析中的应用
分箱不仅是特征工程步骤,更是拒绝原因分析的基础。每个箱对应一个明确的业务含义,当客户被拒绝时,系统可根据其所在箱直接映射出拒绝原因码。
示例:年龄变量分箱与拒绝原因映射
| 分箱区间 | 箱标签 | WOE | 拒绝原因码 | 拒绝原因描述 |
|---|---|---|---|---|
| [18, 25) | 青年低龄 | -0.45 | AGE_01 | 年龄过低,收入稳定性不足 |
| [25, 35) | 青年中龄 | 0.12 | — | 正常区间,不触发拒绝 |
| [35, 50) | 中年 | 0.28 | — | 正常区间,不触发拒绝 |
| [50, 65) | 中老年 | -0.08 | AGE_02 | 年龄偏高,还款能力下降风险 |
| [65, +∞) | 老年 | -0.62 | AGE_03 | 年龄过高,不符合授信政策 |