T
traeai
登录
返回首页
Towards Data Science

从原始数据到风险类别

8.5Score
从原始数据到风险类别

TL;DR · AI 摘要

分类是信用评分中不可或缺的步骤,有助于减少维度、捕捉非线性风险模式并提升模型稳定性。

核心要点

  • 分类可将连续变量转化为有序风险类,降低模型复杂度。
  • 对类别变量进行分类能减少模态数,提升模型稳定性。
  • 使用Weight of Evidence方法可有效准备变量用于可解释的信用评分模型。

结构提纲

按章节快速跳转。

  1. 讨论分类在信用评分中的重要性及应用场景。

  2. 分类能减少维度、提升模型稳定性与可解释性。

  3. 通过分析变量与违约率的关系,确定合适的分类方式。

  4. 介绍等距分箱、等频分箱、卡方分组和Weight of Evidence分组等方法。

  5. 展示如何利用Weight of Evidence方法进行离散化以支持可解释模型。

思维导图

用一张图看清主题之间的关系。

查看大纲文本(无障碍 / 无 JS 友好)
  • 信用评分中的分类
    • 分类的重要性
      • 减少维度
      • 捕捉非线性风险模式
    • 分类方法
      • 等距分箱
      • 等频分箱
      • 卡方分组
      • Weight of Evidence分组
    • 应用
      • 提升模型稳定性
      • 支持可解释模型

金句 / Highlights

值得收藏与分享的关键句。

#信用评分#数据预处理#机器学习
打开原文

标题:从原始数据到风险等级

URL 来源:https://towardsdatascience.com/from-raw-data-to-risk-classes/

发布时间:2026-05-15T16:30:00+00:00

Markdown 内容: 模型失败并非因为算法薄弱,而是因为变量未能以模型能够正确理解的方式进行处理?

在信用风险建模中,我们常常关注模型选择、性能指标、特征选择或验证。但在估计任何系数之前,另一个问题值得关注:每个变量应以何种形式进入模型?

原始变量并不总是风险的最佳表征。

连续变量可能与违约存在非线性关系。分类变量可能包含过多模态。某些变量可能包含异常值、缺失值、不稳定分布或观测数极少的类别。如果忽略这些问题,模型可能会变得不稳定、难以解释,并在生产环境中可靠性降低。

这正是分类化(分箱)的重要性所在。

分类化(也称为粗分类、分组、分级或分箱)是指将原始变量值转换为数量更少的意义明确的组别。在信用评分中,这些分组的创建不仅是为了便利,更是为了让变量与违约风险之间的关系更清晰、更稳定,并更易于在模型中使用。

当最终模型是逻辑回归时,这一步骤尤其有用。逻辑回归因其透明可解释、易于转换为评分卡的特点,在信用评分领域仍被广泛使用。

对于分类变量,分类化有助于减少模态数量。对于连续变量,它有助于捕捉非线性风险模式、减少异常值影响、处理缺失值、提升可解释性,并为证据权重转换做好准备。

本文将探讨为何分类化是信用评分中的关键步骤,以及如何利用它将原始变量转化为稳定的风险等级。

第1节中,我们将说明分类化对分类变量和连续变量的作用,特别是在逻辑回归的背景下。

第2节中,我们将展示如何通过图形单调性分析来研究连续变量与违约风险之间的关系。

第3节中,我们将介绍主要的分类化方法,包括等距分箱、等频分箱、基于卡方的分组和基于证据权重的分组。

最后,在第4节中,我们将重点讨论使用证据权重对连续变量进行离散化的方法,并展示这一方法如何帮助构建可解释的信用评分模型。

1. 为什么分类化在信用评分中至关重要

构建信用评分模型时,变量可分为分类变量和连续变量两类。

分类化对两种变量类型都有价值,但其动机各不相同:

  • 对分类变量而言,主要目标通常是减少模态数量,并将具有相似风险行为的类别进行合并
  • 对连续变量而言,目标通常是将原始数值尺度转换为数量更少的有序风险等级

两种情况的最终目标一致:创建具有统计意义、经济可解释性且随时间保持稳定的变量。

1.1 分类化实现维度压缩

首先以分类变量为例。

假设我们有一个包含50个不同取值的industry_sector(行业部门)变量。

若直接将该变量用于逻辑回归模型,则需要创建虚拟变量。

由于共线性问题,必须选择一个类别作为参照组。因此对于50个类别,我们需要:

50−1=49个虚拟变量

这意味着模型仅针对一个变量就需要估计49个参数。

这会迅速引发诸多问题:模态过多的分类变量可能导致系数不稳定、过拟合、稳健性差、解释困难以及监控复杂度增加。

通过将相似类别合并分组,我们可以减少需要估计的参数数量。例如,不再保留50个行业部门,而是将其归类为5-6个风险等级。这些分组可以基于观测到的违约率、业务专业知识、样本量限制或这些标准的组合。

最终得到的是一个更紧凑、更稳定且更易解释的模型。

因此,分类化的首要优势就是维度压缩

1.2 分类化助力捕捉非线性风险模式

对于连续变量,分类化同样具有重要价值。

但在决定是否对连续变量进行分类化之前,我们首先需要理解其与违约风险的关系。

最简便的方法是通过绘制违约率与变量的关系图进行观察。例如,对于person_income(个人收入)这样的连续变量,我们可以将其划分为若干区间并计算每个区间的违约率。

随后绘制:

  • x轴:变量的分箱值
  • y轴:违约率

这样就能通过可视化方式检验风险模式。

如果关系呈现单调性,说明变量已具备明确的风险方向。例如:

  • 收入增加,违约率下降
  • 贷款利率上升,违约率上升

此类情况下变量关系易于理解。

但当关系呈现非单调性时,情况就变得复杂。假设违约风险在低收入至中等收入水平区间逐渐下降,但在高收入水平区间又重新上升。简单的逻辑回归模型可能无法准确捕捉这种模式,因为它估计的是变量与违约对数几率之间的线性效应。

逻辑回归模型具有以下形式:

$$ log ⁡ \left(\right. \frac{P \left(\right. Y = 1 \left|\right. X \left.\right)}{1 - P \left(\right. Y = 1 \left|\right. X \left.\right)} \left.\right) = \beta_{0} + \beta_{1} X $$

其中 Y=1 表示违约,X 是解释变量。

该方程意味着模型假设 X 与违约对数几率之间存在线性关系。

如果 X 的影响不是线性的,模型可能会遗漏风险结构的重要部分。

非线性模型(如神经网络、决策树、梯度提升或支持向量机)能够自然地捕捉复杂关系。

但在信用评分中,逻辑回归仍被广泛使用,因为它简单、透明且易于解释。

通过将连续变量分类为风险组别,我们可以在线性模型中引入部分非线性特性。

这正是分箱在评分卡建模中如此普遍的最重要原因之一。

1.3. 分箱可降低异常值影响

分箱的另一个重要优势是异常值管理。

连续变量常常包含极端值。

例如:

  • 极高收入
  • 超大贷款金额
  • 异常工作年限
  • 异常信用利用率

如果将这些值直接用于逻辑回归,会对估计系数产生强烈影响。

当我们对变量进行分箱时,异常值会被归入特定箱体。

例如,所有超过某个阈值收入值可以归入同一类别。

这减少了极端观测值的影响,使模型更加稳健。

我们不再允许极端值强烈影响模型,而是仅使用其所在组别包含的风险信息。

1.4. 分箱有助于处理缺失值

缺失值在信用评分数据集中非常常见。

客户可能未提供收入信息。

工作年限可能缺失。

信用历史变量可能不可用。

处理缺失值的一种方法是为其创建独立类别。

这使得模型能够学习具有缺失值个体的特定行为模式。

这一点非常重要,因为缺失并非总是随机现象。

在信用评分中,缺失值本身可能包含风险信息。

例如,未报告收入的客户与提供收入的客户相比,可能具有不同的违约行为。

通过创建缺失类别,我们使模型能够捕捉这种行为模式。

1.5 分箱提升可解释性

可解释性是信用评分最重要的要求之一。

信用评分模型不仅仅是黑盒预测引擎。

它的使用者通常包括:

  • 风险分析师
  • 信贷专员
  • 模型验证团队
  • 监管机构
  • 业务决策者

当变量被分箱后,模型会变得更容易解释。

例如,与其说:

_贷款利率每增加一个单位,违约对数几率会增加特定数值_

我们可以说:

_利率高于15%的客户违约风险显著高于利率低于10%的客户_

这种解释更直观。

也更容易转化为评分卡分值。

1.6. 分箱提升模型稳定性

一个好的信用评分模型不仅应在开发阶段表现良好。

在生产环境中也应保持稳定。

分箱有助于降低变量对数据微小变化的敏感度。

例如,如果客户收入从2990略微变为3010,原始数值会发生变化。

但如果这两个值属于同一收入区间,分箱后的值将保持不变。

这使得模型随时间推移更加稳定。

分箱也非常有助于监控。

一旦变量被分组为类别,我们可以轻松跟踪其在生产环境中的分布,并通过群体稳定性指数等指标与开发样本进行比较。

总结第一部分,我们对变量进行分箱主要是为了降低维度、捕捉非线性风险模式、处理缺失值和异常值,同时提升可解释性和稳定性。

2. 分箱前的图形单调性分析

在对连续变量进行分类之前,我们需要理解其与违约率的关系。

这一步很重要,因为分类不应随意进行。

目标不仅是创建分箱,更是从风险角度创建有意义的分箱。

良好的分箱应回答以下问题:

  • 变量与违约风险是否存在明确关系?
  • 这种关系是递增还是递减?
  • 关系是单调还是非单调?

为回答这些问题,我们从图形单调性分析开始。

当变量增加时违约率单向变化,则该变量相对于违约风险是单调的。

例如,如果收入增加时违约风险降低,则为单调递减关系。

如果利率增加时违约风险增加,则为单调递增关系。

单调性在信用评分中很重要,因为它使模型更易于解释。

单调变量具有明确的风险含义。

例如:

  • 收入越高意味着风险越低
  • 贷款负担越重意味着风险越高
  • 利率越高意味着风险越高
  • 工作年限越长意味着风险越低

这些关系易于解释,且通常符合业务直觉。

然而,如果关系不单调,变量可能需要更谨慎的处理。

非单调模式可能表明:

  • 真实的非线性风险效应
  • 噪声数据
  • 稀疏区间
  • 异常值
  • 与其他变量的交互作用
  • 跨数据集的不稳定性

这就是为什么我们在决定如何分箱变量之前,应该先检查违约率曲线。

2.1 用于可视化诊断的等宽分箱

一种简单的初步方法是将变量划分为等宽的区间,这被称为等宽分箱

假设某变量取值如下:

1000, 1200, 1300, 1400, 1800, 2000 最小值为1000,最大值为2000。

若创建两个等宽箱,宽度为:

$\frac{2000 – 1000}{2} = 500$

由此得到:

code
箱1:1000至1500
箱2:1500至2000

接着计算每个分箱的违约率:

Image 1

生成如下表格:

Image 2

然后绘制分箱违约率曲线。

这张图能帮助我们初步理解变量与违约率的关系形态。

等宽分箱简单易懂,但可能产生样本量差异巨大的分箱,特别是在变量分布高度偏斜时。

因此在进行探索性单调性分析时,等频分箱通常是更优选择。

2.2 风险曲线的等频分箱

等频分箱将变量划分为包含近似相同观测数量的区间。

例如十分位数分箱将样本分为10组,每组约包含10%的观测值。

这种方法能确保每个分箱都有足够数据来计算更可靠的违约率。

在Python中可使用pd.qcut实现。

但需要注意以下区别:

  • pd.cut执行等宽分箱
  • pd.qcut执行等频分箱

这种区分很重要,因为两种分箱方式的解读完全不同。

在本例中,我们使用等频分箱来研究连续变量的风险模式。

2.3 数据集与选定变量

在之前的文章中,我们已对同一数据集完成了多个重要步骤:

  • 探索性数据分析
  • 变量预筛选
  • 稳定性分析
  • 时间维度单调性分析
  • 训练集、测试集和跨时间数据集对比

经过这些步骤,我们筛选出最相关的建模变量。

本文重点讨论连续变量的分箱策略。定性变量本身模态数量有限,且前期分析已证实其稳定性和单调性符合要求。

因此,我们的目标是通过可视化方式研究连续变量,理解其与违约风险的关系,并制定合适的离散化策略。

选定的连续变量包括:

  • 个人收入
  • 工作年限
  • 贷款利率
  • 贷款收入占比

2.4 违约率曲线的Python实现

pandasscikit-learn中没有现成函数能完全满足评分卡建模所需的单调性诊断要求。

因此我们需要自行编写流程或使用专业评分卡库。

这里我们使用pandasmatplotlib手动实现:

code
import pandas as pd
import matplotlib.pyplot as plt

def plot_default_rate_ax(data, variable, target, bins=10, ax=None):
    """
    在指定matplotlib坐标轴上绘制数值变量分箱后的违约率曲线
    """

    df = data[[variable, target]].copy()

    # 创建分箱
    df[f"{variable}_bin"] = pd.qcut(
        df[variable],
        q=bins,
        duplicates="drop"
    )

    # 计算分箱违约率
    summary = (
        df.groupby(f"{variable}_bin", observed=True)[target]
        .mean()
        .reset_index()
    )

    # 将区间转换为字符串用于绘图
    summary[f"{variable}_bin"] = summary[f"{variable}_bin"].astype(str)

    # 绘制图形
    ax.plot(
        summary[f"{variable}_bin"],
        summary[target],
        marker="o"
    )

    ax.set_title(f"Default rate by {variable}")
    ax.set_xlabel(variable)
    ax.set_ylabel("Default rate")
    ax.tick_params(axis="x", rotation=45)

    return ax

variables = [
    "person_income",
    "person_emp_length",
    "loan_int_rate",
    "loan_percent_income"
]

fig, axes = plt.subplots(2, 2, figsize=(16, 10))

axes = axes.flatten()

for ax, variable in zip(axes, variables):
    plot_default_rate_ax(
        train_imputed,
        variable=variable,
        target="def",
        bins=10,
        ax=ax
    )

plt.tight_layout()
plt.show()
Image 3

绘制违约率曲线后,我们可以分析每个变量的风险方向:

对于个人收入,通常预期收入增加时违约率下降。这符合逻辑,因为高收入客户通常具备更强还款能力。

对于工作年限,预期就业时间增长时违约率降低。更长的职业经历可能意味着更稳定的职业状况。

对于贷款利率,预期利率上升时违约率增加。这与风险逻辑一致,因为高利率通常对应高风险借款人。

对于贷款收入占比,预期贷款金额相对收入增大时违约率上升。该变量衡量贷款负担与收入的比值,数值越高通常意味着更大的还款压力。

若观测曲线符合这些预期,则从业务角度这些变量具有一致性。本例中的图形分析表明,所选变量均呈现有意义的单调模式。

person_incomeperson_emp_length 增加时,违约率会下降。另一方面,当 loan_int_rateloan_percent_income 增加时,违约率会上升。

这正是我们在信用风险建模中所预期的。

3. 主要的分箱方法

一旦我们理解了每个连续变量与违约率之间的关系,就可以定义分箱策略。

变量分箱的方法有很多。

有些方法简单且无监督,它们不使用目标变量:

  • 等距分箱
  • 等频分箱

其他方法是监督式的,它们利用违约变量来创建基于风险的组别:

  • 基于卡方的分组
  • 基于证据权重(Weight of Evidence)的分组

在信用评分中,监督式方法通常更受青睐,因为目标不仅是将变量划分为多个区间,更是要创建在违约风险方面有意义的区间。

在本节中,我们将更详细地介绍这两种监督式方法。

3.1 基于卡方的分组

这是一种监督式分箱方法。其思想很简单:首先创建许多初始分箱,然后比较相邻的分箱。如果两个相邻分箱具有相似的违约行为,就将它们合并。

对于两个相邻的分箱 i 和 j,我们构建一个列联表:

Image 4

然后进行卡方检验。

卡方统计量为:

$$ \chi^{2} = \sum \frac{\left(\right. O - E \left.\right)^{2}}{E} $$

其中:

  • O 是观测频数
  • E 是独立假设下的期望频数

零假设为:

H0:两个分箱具有相同的违约分布。

备择假设为:

H1:两个分箱具有不同的违约分布。

如果两个分箱的违约行为相似,就可以合并它们。

重复此过程,直到获得数量较少且稳定的类别。

这种方法的优点是直接使用了违约变量。

因此,最终分组与风险更加一致。

然而,使用此方法时必须谨慎。

对于非常大的样本,微小的差异可能在统计上变得显著;而对于非常小的样本,检验可能不可靠。

这就是为什么统计分箱必须始终与业务判断相结合。

3.2 基于证据权重(WoE)的分组

信用评分中另一种非常常见的方法是基于证据权重(Weight of Evidence,简称 WoE)。WoE 衡量了每个类别中事件(违约)和非事件(未违约)的相对分布。

在本文中,我们定义:

  • 坏客户 = 违约(def = 1)= 事件
  • 好客户 = 未违约(def = 0)= 非事件

对于给定类别 i,WoE 定义为:

$$ W o E = ln ⁡ \left(\right. \frac{\% E v e n t s}{\% N o n E v e n t s} \left.\right) $$

按照此约定:

  • 正的 WoE 表示事件/违约集中度较高;
  • 负的 WoE 表示非事件/好客户集中度较高;
  • WoE 接近零,表示该分箱的风险水平接近总体平均水平。

基于 WoE 的分组是将具有相似 WoE 值的相邻分箱进行合并。目标是创建具有明确风险顺序的稳定组别。

实践中,该过程通常首先将连续变量切割为初始的细粒度分箱,通常使用等频区间。然后,当相邻区间的 WoE 值接近,或者其中一个区间未能提供足够的风险区分度时,逐步合并它们。

其目的不仅是减少类别数量,更是要创建能提供有用风险信息的类别。

例如,如果一个分箱的 WoE 非常接近零,它可能无法提供强有力的区分。在这种情况下,只要合并后从业务和风险角度仍然连贯,有时可以将其与相邻分箱合并。

为了最大化最终类别之间的风险区分度,还需要检查违约率是否充分分离。一个实用的规则是保持相邻类别之间的风险相对差异至少为 30%,同时确保每个最终类别至少包含 1% 的总体样本。

这些阈值不应机械地应用,但它们提供了有用的保障:

  • 避免创建过小的类别;
  • 避免保留风险水平几乎相同的类别;
  • 避免对开发样本过拟合;
  • 保持最终分组的可解释性和稳定性。

当最终模型是逻辑回归时,此方法尤其有用,因为经过 WoE 转换的变量与模型的对数几率结构非常契合。

4. 基于 WoE 分箱的 Python 实现

现在我们转向 Python 实现。

目标是构建一个简单透明的框架,用于分析分箱后的变量,并支持最终的分箱决策。

我们需要三个主要工具。

第一个工具是在给定预定义分箱数量的情况下,计算变量的 WoE。

第二个工具汇总每个离散化类别的观测数量和违约率。

第三个工具分析各类别违约率随时间的变化。这将帮助我们评估单调性和稳定性。

这一点很重要,因为一个好的分箱方案不仅仅在训练样本上有效,还必须在不同建模数据集(如训练集、测试集和跨时间样本)上保持稳定。

换句话说,一个好的分箱方案必须满足三个条件:

  • 必须在统计上有意义;
  • 必须从信用风险的角度看是连贯的;
  • 必须随时间保持稳定。
code
def iv_woe(data, target, bins=5, show_woe=False, epsilon=1e-16):
    """
    Compute the Information Value (IV) and Weight of Evidence (WoE)
    for all explanatory variables in a dataset.

    Numerical variables with more than 10 unique values are first discretized
    into quantile-based bins. Categorical variables and numerical variables
    with few unique values are used as they are.

参数


data : pandas DataFrame 包含解释变量和目标变量的输入数据集。

target : str 二元目标变量的名称。 目标变量应编码为:1 表示事件/违约,0 表示非事件/非违约。

bins : int, 默认值=5 用于离散化连续变量的分位数分组数量。

show_woe : bool, 默认值=False 如果为 True,显示每个变量的详细 WoE 表。

epsilon : float, 默认值=1e-16 用于避免除零和 log(0) 的小数值。

返回值


newDF : pandas DataFrame 包含各变量信息量(IV)的汇总表。

woeDF : pandas DataFrame 所有变量和所有分组的详细 WoE 表。 """

初始化输出数据框

newDF = pd.DataFrame() woeDF = pd.DataFrame()

获取所有列名

cols = data.columns

对所有解释变量运行 WoE 和 IV 计算

for ivars in cols[~cols.isin([target])]:

若变量为数值型且具有较多唯一值,则按分位数离散化

if (data[ivars].dtype.kind in "bifc") and (len(np.unique(data[ivars].dropna())) > 10): binned_x = pd.qcut( data[ivars], bins, duplicates="drop" )

d0 = pd.DataFrame({ "x": binned_x, "y": data[target] })

否则直接使用原始变量

else: d0 = pd.DataFrame({ "x": data[ivars], "y": data[target] })

计算各分组的观测数和事件数

d = ( d0.groupby("x", as_index=False, observed=True) .agg({"y": ["count", "sum"]}) )

重命名列

d.columns = ["分箱切点", "总数", "事件数"]

计算各分组的事件占比

d["事件占比"] = ( np.maximum(d["事件数"], epsilon) / (d["事件数"].sum() + epsilon) )

计算各分组的非事件数

d["非事件数"] = d["总数"] - d["事件数"]

计算各分组的非事件占比

d["非事件占比"] = ( np.maximum(d["非事件数"], epsilon) / (d["非事件数"].sum() + epsilon) )

计算证据权重(WoE)

此处 WoE 定义为 log(事件占比 / 非事件占比)

按照此定义,正 WoE 表示更高的违约/事件风险

d["WoE"] = np.log( d["事件占比"] / d["非事件占比"] )

计算各分组的 IV 贡献值

d["IV"] = d["WoE"] * ( d["事件占比"] - d["非事件占比"] )

在详细表中添加变量名

d.insert( loc=0, column="变量名", value=ivars )

输出变量的全局信息量

print("=" * 30 + "\n") print( "变量 " + ivars + " 的信息量为 " + str(round(d["IV"].sum(), 6)) )

存储变量的全局 IV

temp = pd.DataFrame( { "变量名": [ivars], "IV": [d["IV"].sum()] }, columns=["变量名", "IV"] )

newDF = pd.concat([newDF, temp], axis=0) woeDF = pd.concat([woeDF, d], axis=0)

如需显示详细 WoE 表

if show_woe: print(d)

return newDF, woeDF

def tx_rsq_par_var(df, categ_vars, date, target, cols=2, sharey=False): """ 生成展示分类变量随时间变化的平均事件率折线图网格。

参数


df : pandas DataFrame 输入数据集。

categ_vars : str 列表 待分析的分类变量列表。

date : str 日期或时间周期列的名称。

target : str 二元目标变量的名称。 目标变量应编码为:1 表示事件/违约,0 表示其他。

cols : int, 默认值=2 子图网格的列数。

sharey : bool, 默认值=False 是否所有子图共享相同的 y 轴刻度。

返回值


None 该函数直接显示图形。 """

使用副本操作以避免修改原始数据框

df = df.copy()

检查数据框中是否存在所有必需列

missing_cols = [col for col in [date] + categ_vars if col not in df.columns] if missing_cols: raise KeyError( f"数据框中缺失以下列: {missing_cols}" )

删除日期列和分类变量中存在缺失值的行

df = df.dropna(subset=[date] + categ_vars)

确定变量数量和所需子图行数

num_vars = len(categ_vars) rows = math.ceil(num_vars / cols)

创建子图网格

fig, axes = plt.subplots( rows, cols, figsize=(cols * 6, rows * 4), sharex=False, sharey=sharey )

将轴数组展平以便迭代

axes = axes.flatten()

遍历每个分类变量并创建对应图形

for i, categ_var in enumerate(categ_vars):

按日期和分类计算目标变量均值

df_times_series = ( df.groupby([date, categ_var])[target] .mean() .reset_index() )

markdown
# 重塑数据,使每个类别在图中成为一条线
df_pivot = df_times_series.pivot(
    index=date,
    columns=categ_var,
    values=target
)

# 选择当前变量对应的坐标轴
ax = axes[i]

# 为每个类别绘制一条线
for category in df_pivot.columns:
    ax.plot(
        df_pivot.index,
        df_pivot[category],
        label=str(category).strip()
    )

# 设置图表标题和轴标签
ax.set_title(f"{categ_var.strip()}")
ax.set_xlabel("日期")
ax.set_ylabel("违约率 (%)")

# 根据类别数量调整图例
if len(df_pivot.columns) > 10:
    ax.legend(
        title="类别",
        fontsize="x-small",
        loc="upper left",
        ncol=2
    )
else:
    ax.legend(
        title="类别",
        fontsize="small",
        loc="upper left"
    )

# 当网格大小超过变量数量时,移除未使用的子图坐标轴
for j in range(i + 1, len(axes)):
    fig.delaxes(axes[j])

# 为图形添加全局标题
fig.suptitle(
    "分类变量的违约率分析",
    fontsize=10,
    x=0.5,
    y=1.02,
    ha="center"
)

# 调整布局以避免元素重叠
plt.tight_layout()

# 显示最终图形
plt.show()

def combined_barplot_lineplot(df, cat_vars, cible, cols=2):
    """
    为分类变量列表生成组合柱状图和折线图的网格。

    对于每个分类变量:
    - 柱状图显示每个类别的相对频率;
    - 折线图显示每个类别的平均目标率。

    参数
    ----------
    df : pandas DataFrame
        输入数据集。

    cat_vars : 字符串列表
        待分析的分类变量列表。

    cible : 字符串
        二元目标变量的名称。
        目标变量应编码为:1表示事件/违约,0表示其他情况。

    cols : 整数, 默认=2
        子图网格的列数。

    返回
    -------
    None
        该函数直接显示图形。
    """

    # 计算需要绘制的分类变量数量
    num_vars = len(cat_vars)

    # 计算子图网格所需行数
    rows = math.ceil(num_vars / cols)

    # 创建子图网格
    fig, axes = plt.subplots(
        rows,
        cols,
        figsize=(cols * 6, rows * 4)
    )

    # 展平坐标轴数组以便迭代
    axes = axes.flatten()

    # 遍历每个分类变量
    for i, cat_col in enumerate(cat_vars):

        # 选择柱状图的当前子图坐标轴
        ax1 = axes[i]

        # 如有需要,将分类数据类型变量转换为字符串
        # 这可避免分类区间或有序类别导致的绘图问题
        if pd.api.types.is_categorical_dtype(df[cat_col]):
            df[cat_col] = df[cat_col].astype(str)

        # 按类别计算平均目标率
        tx_rsq = (
            df.groupby([cat_col])[cible]
            .mean()
            .reset_index()
        )

        # 计算每个类别的相对频率
        effectifs = (
            df[cat_col]
            .value_counts(normalize=True)
            .reset_index()
        )

        # 为清晰起见重命名列
        effectifs.columns = [cat_col, "count"]

        # 将类别频率与目标率合并
        merged_data = (
            effectifs
            .merge(tx_rsq, on=cat_col)
            .sort_values(by=cible, ascending=True)
        )

        # 为折线图创建次要y轴
        ax2 = ax1.twinx()

        # 以柱状图形式绘制类别频率
        sns.barplot(
            data=merged_data,
            x=cat_col,
            y="count",
            color="grey",
            ax=ax1
        )

        # 以折线形式绘制平均目标率
        sns.lineplot(
            data=merged_data,
            x=cat_col,
            y=cible,
            color="red",
            marker="o",
            ax=ax2
        )

        # 设置子图标题和轴标签
        ax1.set_title(f"{cat_col}")
        ax1.set_xlabel("")
        ax1.set_ylabel("类别频率")
        ax2.set_ylabel("风险率 (%)")

        # 旋转x轴标签以提升可读性
        ax1.tick_params(axis="x", rotation=45)

    # 如果网格大小超过变量数量,移除未使用的子图坐标轴
    for j in range(i + 1, len(axes)):
        fig.delaxes(axes[j])

    # 为整个图形添加全局标题
    fig.suptitle(
        "分类变量的组合柱状图与折线图分析",
        fontsize=10,
        x=0.0,
        y=1.02,
        ha="left"
    )

    # 调整布局以减少元素重叠
    plt.tight_layout()

    # 显示最终图形
    plt.show()

4.1 `person_income`变量示例

让我们将此流程应用于person_income变量。

第一步是使用WoE进行初始离散化。我们决定将该变量分为三个类别,并计算每个类别的WoE。

Image 5

结果显示WoE呈单调性。

收入较低的借款人,特别是收入低于约45,000的借款人,具有正的WoE。按照我们的约定,这意味着他们有更高的违约集中度。

收入较高的借款人,特别是收入高于约71,000的借款人,具有最低的WoE值。这表明违约集中度较低。

这一结果与信用风险的直觉一致:收入越高通常意味着更强的还款能力,从而违约风险越低。

随后我们可以应用这种分段方法创建名为person_income_dis的离散化变量。

只有当分箱保持稳定时,它才具有实际意义。

某个变量可能在训练样本中呈现良好的风险模式,但随着时间的推移可能变得不稳定。

因此我们还需要分析各类别违约率随时间的变化趋势:

Image 6

同时可视化每个类别的以下信息也很有帮助:

  • 人口占比
  • 违约率

这可以通过组合柱状图和折线图来实现:

Image 7

该图表的优势在于能同步呈现两类信息:柱状图显示该类别是否包含足够多的样本量,折线图则反映该类别是否具有一致的风险模式。理想的分箱方案应同时满足充足的样本量和有意义的风险模式。

随后必须将相同的分箱切点应用于测试集和跨时间验证集——这一点至关重要。分箱方案必须在训练样本上定义,并原封不动地应用于验证样本,否则会导致数据泄露,降低验证结果的可靠性。

结论

本文深入探讨了为何分箱处理是信用评分模型开发中的关键环节。这项技术既适用于分类变量,也适用于连续变量:对分类变量而言,它能有效减少模态数量,使模型更易估计和解释;对连续变量而言,则能捕捉非线性风险模式、减弱异常值影响、处理缺失值、提升稳定性,并为证据权重转换做好准备。

我们讨论了多种分箱方法,包括等距分箱、等频分箱、卡方分组和证据权重分组。实践中,分箱不应被视为机械化的预处理步骤。优秀的分箱方案必须同时满足统计学要求、业务逻辑要求和稳定性要求,需要创建具有充足样本量、风险排序清晰、时序稳定且易于解释的变量类别。

当最终模型采用逻辑回归评分卡时,这一点尤为重要。基于证据权重的分箱方法能将原始变量转化为稳定的风险类别,自然契合模型的对数几率结构。

核心结论在于:信用评分模型的可靠性取决于输入变量的质量。如果变量存在噪声、不稳定、分组不当或难以解释,即使采用优秀算法也可能得到脆弱模型。而当变量经过精心分箱处理后,模型将更具鲁棒性、可解释性和生产环境可监控性。

您在实际工作中通常在何种场景下进行变量分箱?出于哪些考量?又倾向于采用何种方法呢?

AI 可能会生成不准确的信息,请核实重要内容

从原始数据到风险类别 | Towards Data Science | traeai