(7-3-2)金融风险管理实战:制作信贷风控模型
请各位读者予以关注,在本篇文章的粉丝数量有限的情况下,请问您是否打算继续阅读?我会持续更新下去,并将完整的代码将在对应的QQ群中发布:323140750。让我们共同努力,在共同进步与交流中成长吧!
7.3.4 数据分析
(1)异常值
在探索性数据分析的过程中,在识别数据特征时需要特别注意潜在的异常值。
这些异常值可能源于输入数据中的错误数值、测设备故障或是真实但极端的数据记录。
通过调用describe方法来分析列的数据统计特征,则有助于量化评估这些异常值的影响。
DAYS_BIRTH列中的数值均为负数,这是因为这些数值是以当前贷款申请日期作为基准计算出来的。
为了将时间范围转换为年份单位,则需将该数值乘以-1并除以一年的实际天数。
具体实现代码如下所示.
(app_train['DAYS_BIRTH'] / -365).describe()
执行后会输出:
count 307511.000000
mean 43.936973
std 11.956133
min 20.517808
25% 34.008219
50% 43.150685
75% 53.923288
max 69.120548
Name: DAYS_BIRTH, dtype: float64
通过分析输出结果可以看到这些数据表现合理性,在数据分布的上下界(即最大值与最小值)范围内未出现异常情况。我们可以通过以下方式验证工作日数量是否符合预期的具体实现代码如下所示
app_train['DAYS_EMPLOYED'].describe()
执行后会输出:
count 307511.000000
mean 63815.045904
std 141275.766519
min -17912.000000
25% -2760.000000
50% -1213.000000
75% -289.000000
max 365243.000000
Name: DAYS_EMPLOYED, dtype: float64
该方法运行结果与预期预期存在明显差异。
该特征的最大观测值在约1000天时被记录下来。
通过以下代码段生成相应的直方图,并详细描述了'DAYS_EMPLOYED'特征的数据分布情况。
app_train['DAYS_EMPLOYED'].plot.hist(title = 'Days Employment Histogram');
plt.xlabel('Days Employment');
对上述代码的具体说明如下:
- app_train['DAYS_EMPLOYED']:该特征列的名字是'DAYS_EMPLOYED'。
- plot.hist():这是一个用于绘制直方图的调用命令。具体来说:
- plot.hist() 是一个绘图函数
- 通过设置参数title='Days Employment Histogram' 来指定图表标题
- plt.xlabel('Days Employment'):这是一个设定坐标轴标签的操作
- 使用plt.xlabel('Days Employment') 来为图形指定x轴标签
- 该操作将使图形中的x轴刻度标记被命名为了'Days Employment'
具体而言来说
具体而言来说

图7-5 特征直方图
(2)将异常客户划分为特定群体,并观察其违约贷款率与其他客户的对比情况是否存在显著差异。具体实现代码如下所示。
anom = app_train[app_train['DAYS_EMPLOYED'] == 365243]
non_anom = app_train[app_train['DAYS_EMPLOYED'] != 365243]
print('The non-anomalies default on %0.2f%% of loans' % (100 * non_anom['TARGET'].mean()))
print('The anomalies default on %0.2f%% of loans' % (100 * anom['TARGET'].mean()))
print('There are %d anomalous days of employment' % len(anom))
对上述代码的具体说明如下:
- anom = app_train[app_train['days_employed'] == 365243]:通过筛选生成了一个名为'anom'的数据子集,在该子集中包含所有'DAYS_EMPLOYED'值为365243的记录。
- non_anom = app_train[app_train['days_employed'] != 365243]:通过筛选生成了一个名为'non_anom'的数据子集,在该子集中包含所有'DAYS_EMPLOYED'值不为365243的记录。
- print('非异常客户违约率为贷款中%.2f%%' % (100 * non_anom['TARGET'].mean())):计算并输出了非异常客户在贷款中的违约概率。
- print('异常客户违约率为贷款中%.2f%%' % (100 * anom['TARGET'].mean())):计算并输出了异常客户在贷款中的违约概率。
- print('存在%d个异常雇佣天数记录' % len(anom)):计算并输出了异常雇佣天数记录的数量。
上述代码旨在对比分析异乎寻常雇佣日数客户的违约情况与其非异常客户的差异,并探究是否存在异乎寻常雇佣日数与违约风险之间的联系。
The non-anomalies default on 8.66% of loans
The anomalies default on 5.40% of loans
There are 55374 anomalous days of employment
由此可见,异常值的违约率较低。
(3)不同情况下的处理方式也各不相同,并无统一的标准可循。其中一种最为稳妥的方式可能是将这些异常值标记为缺失数据,并在随后的数据准备阶段进行填补(采用数据填补法)。当所有异常观测具有相同的特征时,在填补过程中应将其视为同一类别进行操作。值得注意的是,在某些情况下这些离群点可能对模型性能产生显著影响,则需要特别关注其处理方式。鉴于此建议在后续建模流程中引入相应的监控机制以便于模型评估。为此计划采用非数值型标记(np.nan)来替代离群点的数值特征,并建立一个布尔字段来指示该行是否包含缺失数据
app_train['DAYS_EMPLOYED_ANOM'] = app_train["DAYS_EMPLOYED"] == 365243
app_train['DAYS_EMPLOYED'].replace({365243: np.nan}, inplace=True)
app_train['DAYS_EMPLOYED'].plot.hist(title='Days Employment Histogram');
plt.xlabel('Days Employment');
对上述代码的具体说明如下:
- 建立一个异常标志字段'DAYS_EMPLOYED_ANOM',用于标识异常数据。
- 通过调用replace方法将'DAYS_Employed'列中的特定数值替换为NaN。
- 生成直方图来展示处理后的工作天数分布情况。
生成并命名为 "Days Employment Histogram" 的直方图可用于直观呈现处理后的每天工作时间数据分布。如图表7-6所示,在此图表中展示了不同工作时间段内雇员数量的变化情况。通过该图表分析可以清晰地识别出各工作时间段雇员数量的差异,并且能够识别出可能存在的异常值或异常时段。

图7-6 "Days Employment Histogram" 的直方图
(4)处理测试数据中的就业天数列("DAYS_EMPLOYED"),具体实现代码如下所示。
app_test['DAYS_EMPLOYED_ANOM'] = app_test["DAYS_EMPLOYED"] == 365243
app_test["DAYS_EMPLOYED"].replace({365243: np.nan}, inplace = True)
print('There are %d anomalies in the test data out of %d entries' % (app_test["DAYS_EMPLOYED_ANOM"].sum(), len(app_test)))
对上述代码的具体说明如下:
- 首先生成了一个新字段 "DAYS_EMPLOYED_ANOM"。
- 该字段用于检测并标记测试数据中的异常值。
- 接下来将所有出现的数值型字段复制到目标表中。
- 特别是那些在源表中具有特殊意义的字段。
- 最后统计并输出了目标表中各字段的数据分布情况。
- 包括各分类变量的具体分布情况和数值型字段的基本统计信息。
执行后会输出:
There are 9274 anomalies in the test data out of 48744 entries
7.3.5 相关性分析
该指标用于衡量两个变量之间的线性关系。其取值范围从-1到+1,并且能够反映两者间的关联程度及其方向。我们特别关注特征与目标变量之间的关联程度,在此过程中代码会计算每个特征与其目标变量的相关系数,并以绝对值的形式展示结果。基于计算出的相关系数绝对值大小进行评估,则可大致推断两者的关联程度具体分类如下:
- 0.00-0.19:非常弱
- 0.20-0.39:较弱
- 0.40-0.59:中等
- 0.60-0.79:较强
- 0.80-1.0:非常强
根据以下代码计算各个特征与目标变量之间的相关性,并展示结果以便分析哪些特征与其存在显著关联。观察结果显示,在这些特征中,' DAYS_BIRTH '特性的相关系数最大。需要注意的是,' DAYS_BIRTH '特性的取值为负数,表示客户在申请贷款时的年龄信息,因此其相关系数为正值,表明随着客户的年龄增长(即'DAYS_BIRTH'特性的绝对值增大),他们违约的可能性降低(即目标变量取0的概率减小)。为了更好地理解分析过程,代码将该特性的绝对值用于计算相关系数以获得负向关系的结果。
correlations = app_train.corr()['TARGET'].sort_values()
print('Most Positive Correlations:\n', correlations.tail(15))
print('\nMost Negative Correlations:\n', correlations.head(15))
执行后会输出:
Most Positive Correlations:
OCCUPATION_TYPE_Laborers 0.043019
FLAG_DOCUMENT_3 0.044346
REG_CITY_NOT_LIVE_CITY 0.044395
FLAG_EMP_PHONE 0.045982
NAME_EDUCATION_TYPE_Secondary / secondary special 0.049824
REG_CITY_NOT_WORK_CITY 0.050994
DAYS_ID_PUBLISH 0.051457
CODE_GENDER_M 0.054713
DAYS_LAST_PHONE_CHANGE 0.055218
NAME_INCOME_TYPE_Working 0.057481
REGION_RATING_CLIENT 0.058899
REGION_RATING_CLIENT_W_CITY 0.060893
DAYS_EMPLOYED 0.074958
DAYS_BIRTH 0.078239
TARGET 1.000000
Name: TARGET, dtype: float64
Most Negative Correlations:
EXT_SOURCE_3 -0.178919
EXT_SOURCE_2 -0.160472
EXT_SOURCE_1 -0.155317
NAME_EDUCATION_TYPE_Higher education -0.056593
CODE_GENDER_F -0.054704
NAME_INCOME_TYPE_Pensioner -0.046209
DAYS_EMPLOYED_ANOM -0.045987
ORGANIZATION_TYPE_XNA -0.045987
FLOORSMAX_AVG -0.044003
FLOORSMAX_MEDI -0.043768
FLOORSMAX_MODE -0.043226
EMERGENCYSTATE_MODE_No -0.042201
HOUSETYPE_MODE_block of flats -0.040594
AMT_GOODS_PRICE -0.039645
REGION_POPULATION_RELATIVE -0.037227
Name: TARGET, dtype: float64
通过分析这些数据指标, 能够初步识别哪些特性对目标变量具有显著影响及其相互影响程度 (正向或负向), 这有助于确定在建模过程中应采用的特征.
(2)探究客户的年龄与其还款能力之间的关系,在进行这一统计关联性研究时,我们可以初步把握客户群体的年龄段分布及其还款能力的变化规律。具体实现代码如下所示。
# 通过这一相关性分析,可以初步了解客户年龄与还款能力之间的趋势。
app_train['DAYS_BIRTH'] = abs(app_train['DAYS_BIRTH'])
app_train['DAYS_BIRTH'].corr(app_train['TARGET'])
对上述代码的具体说明如下:
- 第1行代码:为了消除之前提到的相关性正负问题的影响,在本步骤中采用了abs函数来获取客户年龄(即'DAYS_BIRTH'列)的绝对数值。
- 第2行代码:通过调用corr方法计算客户年龄特征与目标变量'TARGET'之间的相关性程度。
执行后会输出:
-0.07823930830982694
通过上述输出可知,在客户的年龄增长过程中与其相关的目标变量呈现出负相关趋势。这表明,在客户年龄增长的过程中他们趋向于变得更加及时地偿还贷款。
(3)随后将着手分析客户的年龄变量。首先制作一个基于年龄的数据分布图表,并且为了便于直观理解我们将x轴标记为年份值的具体实现步骤如下所述
plt.style.use('fivethirtyeight')
plt.hist(app_train['DAYS_BIRTH'] / 365, edgecolor = 'k', bins = 25)
plt.title('Age of Client'); plt.xlabel('Age (years)'); plt.ylabel('Count');
运行结果如图7-7所示展示。可见地显示,在单独考察的情况下,年龄的分布并未提供足够的信息量。然而,在综合分析中发现:除年龄外其余指标均处于合理范围内,并未发现任何异常数据点

图7-7 年龄分布直方图
为了直观展示年龄对目标变量的影响, 下文将利用核密度估计图(KDE)进行可视化. 该图表能够清晰呈现单一变量的概率密度分布情况, 常被视为一种平滑化的直方图. 具体而言, KDE是通过在每个数据点上计算一个核(通常采用高斯函数)并对其取平均值, 从而生成一条光滑的概率密度曲线. 在本节中, 我们将借助seaborn库中的kdeplot函数来绘制该图表, 具体代码实现如下所示.
plt.figure(figsize = (10, 8))
sns.kdeplot(app_train.loc[app_train['TARGET'] == 0, 'DAYS_BIRTH'] / 365, label = 'target == 0')
sns.kdeplot(app_train.loc[app_train['TARGET'] == 1, 'DAYS_BIRTH'] / 365, label = 'target == 1')
plt.xlabel('Age (years)'); plt.ylabel('Density'); plt.title('Distribution of Ages');
运行结果如图7-8所示。从图中可以看出,在当target值等于1时的曲线主要集中在年轻年龄区间。然而相关系数仅为-0.07,并未表现出显著关联性。这一变量在机器学习模型中的应用价值不容小觑,因为它对预测目标变量具有实际意义。

图7-8 年龄分布直方图
(5)另外一种视角分析这种关联:按年龄段划分计算违约贷款率。用于生成该图表时需将年龄划分为每隔5年的区间段,并在各区间段内计算目标变量的平均值以反映各年龄段未偿还贷款的比例情况。具体实现代码如下所示
age_data = app_train[['TARGET', 'DAYS_BIRTH']]
age_data['YEARS_BIRTH'] = age_data['DAYS_BIRTH'] / 365
age_data['YEARS_BINNED'] = pd.cut(age_data['YEARS_BIRTH'], bins = np.linspace(20, 70, num = 11))
age_data.head(10)
执行后输出:
TARGET DAYS_BIRTH YEARS_BIRTH YEARS_BINNED
0 1 9461 25.920548 (25.0, 30.0]
1 0 16765 45.931507 (45.0, 50.0]
2 0 19046 52.180822 (50.0, 55.0]
3 0 19005 52.068493 (50.0, 55.0]
4 0 19932 54.608219 (50.0, 55.0]
5 0 16941 46.413699 (45.0, 50.0]
6 0 13778 37.747945 (35.0, 40.0]
7 0 18850 51.643836 (50.0, 55.0]
8 0 20099 55.065753 (55.0, 60.0]
9 0 14469 39.641096 (35.0, 40.0]
(6)将数据按照年龄区间进行分组,并计算各组的平均值,在该Dataframe中能够观察到各个年龄段的平均违约率。具体的代码实现可参考下方代码段。
age_groups = age_data.groupby('YEARS_BINNED').mean()
age_groups
运行后会呈现出来,这有助于我们更加清楚地了解年龄与还款能力之间的关系
TARGET DAYS_BIRTH YEARS_BIRTH
YEARS_BINNED
(20.0, 25.0] 0.123036 8532.795625 23.377522
(25.0, 30.0] 0.111436 10155.219250 27.822518
(30.0, 35.0] 0.102814 11854.848377 32.479037
(35.0, 40.0] 0.089414 13707.908253 37.555913
(40.0, 45.0] 0.078491 15497.661233 42.459346
(45.0, 50.0] 0.074171 17323.900441 47.462741
(50.0, 55.0] 0.066968 19196.494791 52.593136
(55.0, 60.0] 0.055314 20984.262742 57.491131
(60.0, 65.0] 0.052737 22780.547460 62.412459
(65.0, 70.0] 0.037270 24292.614340 66.555108
(7)可视化展示不同年龄段的平均违约率,具体实现代码如下所示。
plt.figure(figsize = (8, 8))
plt.bar(age_groups.index.astype(str), 100 * age_groups['TARGET'])
plt.xticks(rotation = 75); plt.xlabel('Age Group (years)'); plt.ylabel('Failure to Repay (%)')
plt.title('Failure to Repay by Age Group');
从图中可以看出执行效果。该执行结果揭示了不同年龄段平均违约率的情况。从图表数据来看,在不同年龄段中,年轻申请人的还款延迟现象更为显著。数据显示,在最年轻三个年龄段中(即小于25岁、26至35岁以及36至45岁这三个区间),其违约率分别达到了11.8%、12.3%和10.7%之高;而最年长的一个年龄段(即46岁以上)其违约率却仅有4.8%,呈现出了明显的下降趋势。这种趋势提示银行应据此采取相应的措施:例如对年轻申请者提供必要的财务指导或制定个性化的还款计划建议等。需要注意的是,在做出这些决策时应当避免基于年龄进行歧视性判断;但就改善年轻客户的还款能力而言采取预防性措施无疑是非常明智的做法

图7-9 不同年龄段的平均违约率
