台湾地区信用卡客户贷款违约预测

数据集地址:
链接:https://pan.baidu.com/s/1vQgkQs1aTC-zm6zcDH4kKQ
提取码:6ois

1 数据说明与导入

请添加图片描述


导入必需的库和数据。

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
warnings.filterwarnings("ignore")
%matplotlib inline

import sklearn
from sklearn.model_selection import train_test_split # 分离数据集和测试集
from sklearn.neighbors import KNeighborsClassifier 
from sklearn import metrics # 一些评价指标
from sklearn.metrics import precision_recall_curve # 召回率
from sklearn.metrics import confusion_matrix # 混淆矩阵
from sklearn import preprocessing
df = pd.read_csv('./UCI_Credit_Card.csv') # 导入数据

2 数据初步探索

2.1 数据信息检查

df.head()

在这里插入图片描述

df.shape
df.info()
df.describe()

在这里插入图片描述

一共有30000名信用卡持卡人的信息,这些持卡人信用卡平均额度LIMIT_BAL为167484,但是标准差达到了129747,信用卡最大额度达到100万新台币,额度最小的仅有10000新台币,跨度非常大;我们已知2代表女性,1代表男性,性别SEX的平均值在1.6以上,说明女性在该样本中的占比明显高于男性。同时我们也发现与描述不符的异常数据,教育程度EDUCATION的最大值和最小值均在给定的字段描述之外,婚姻状况MARRIAGE有标记为3的点,PAY_1PAY_6的还款记录中出现了描述之外的-2,我们应逐一进行理解与清洗。

2.2 数据清洗

首先,查看教育程度。

df['EDUCATION'].value_counts()

2 14030
1 10585
3 4917
5 280
4 123
6 51
0 14
Name: EDUCATION, dtype: int64

教育程度标记为4,5,6的值都可以理解为“其他”,代表低于高中的学历。为了将学历按低到高进行排序,交换了标签1和标签3。

df['EDUCATION'].replace([1,3,4,5,6],[3,1,0,0,0],inplace=True)
df['EDUCATION'].value_counts()

2 14030
3 10585
1 4917
0 468
Name: EDUCATION, dtype: int64

将性别SEX数据中男性和女性标记为0和1,符合编程习惯。

df['SEX'].replace([1,2],[0,1],inplace=True)
df['SEX'].value_counts()

1 18112
0 11888
Name: SEX, dtype: int64

df['MARRIAGE'].value_counts()

2 15964
1 13659
3 323
0 54
Name: MARRIAGE, dtype: int64

df['MARRIAGE'].replace(3,0,inplace=True)
df['MARRIAGE'].value_counts()

2 15964
1 13659
0 377
Name: MARRIAGE, dtype: int64

最后,我们对标记还款状况的列 PAY_1 到PAY_6 也同样进行查看。首先取 PAY_1 一列为例子。

df['PAY_1'].value_counts()

0 14737
-1 5686
1 3688
-2 2759
2 2667
3 322
4 76
5 26
8 19
6 11
7 9
Name: PAY_1, dtype: int64

还款为-2,-1,0均应视为还清债务,我们将其统一标记为0。经过验证PAY_1PAY_6的数据具有同样特征,我们用iloc定位我们要替换的列,进行替换操作。

df.iloc[:,[6,7,8,9,10,11]] = df.iloc[:,[6,7,8,9,10,11]].replace([-1,-2],[0,0])
df.iloc[:,[6,7,8,9,10,11]].describe()
df['default.payment.next.month'].value_counts()[0]/df['default.payment.next.month'].value_counts()[1]

正负样本的比例大概是3.5:1,数据存在不平衡问题。

2.3 数据可视化与分析

2.3.1 数据分布情况

plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False #用来正常显示负号
sns.set(rc={'figure.figsize':(10,5),"font.size":15,'font.sans-serif':['SimHei'],"axes.titlesize":15,"axes.labelsize":15})
plt.title('信用额度分布图')
# 改变 matplotlib 颜色缩写词的解释方式
sns.set_color_codes("pastel")
# kde:是否绘制高斯核密度估计图 bins:直方图bins(柱)的数目
sns.distplot(df['LIMIT_BAL'],kde=True,bins=200, color="blue")
plt.xlabel("信用额度")

plt.rcParams['figure.dpi'] = 1200 #为了使图片更加清晰
plt.show()

请添加图片描述


可以看到信用额度的分布很不均匀,少数人群的信用额度非常高,大部分人的信用额度集中在较低的值。

sns.set(rc={'figure.figsize':(10,5),"font.size":15,'font.sans-serif':['SimHei'],"axes.titlesize":15,"axes.labelsize":15})
#
sns.set_color_codes("pastel")
sns.displot(df['AGE'],kde=True,bins=59, color="green")
plt.xlabel("年龄")
plt.title('客户年龄分布图')
plt.show()

请添加图片描述

从图中能够发现,客户的年龄主要集中在20-40岁之间。

对于离散型变量SEX、EDUCATION,MARRIAGE,使用饼图查看数据的占比情况。

# 绘制多个子图,1行3列
fig = plt.subplots(3,figsize=(16,12))
sns.set(rc={'figure.figsize':(10,5),"font.size":15,'font.sans-serif':['SimHei'],"axes.titlesize":15,"axes.labelsize":15})

# 绘制第一个子图
plt.subplot(1,3,1)
sex_count = df['SEX'].value_counts()
# 画饼图,设置扇形标签,设置百分比显示格式,一位小数百分比
sex_count.plot(kind='pie', labels=['女性','男性'], autopct='%1.1f%%')
plt.ylabel("")
plt.title('性别分布图')

plt.subplot(1,3,2)
edu_count = df['EDUCATION'].value_counts()
edu_count.plot(kind='pie', labels=['本科', '研究生或以上','高中','其他'], autopct='%1.1f%%')
plt.title('教育情况分布图')
plt.ylabel("")

plt.subplot(1,3,3)
mar_count = df['MARRIAGE'].value_counts()
mar_count.plot(kind='pie', labels=['未婚', '已婚','其他'], autopct='%1.1f%%')
plt.title('婚姻状况分布图')
plt.ylabel("")

plt.show()

在这里插入图片描述


从本图中可以看出性别的分布不均匀,女性占比显著高于男性。教育情况上,72%的客户教育程度都在本科或以上。婚姻状况则显示已婚和未婚客户的数目差距不多。

2.3.2 结合违约情况查看数据分布

考察对于不同的性别,年龄和教育情况,客户的违约情况是否有着比较显著的区别。

sns.set(rc={'figure.figsize':(13,5),"font.size":15,'font.sans-serif':['SimHei'],"axes.titlesize":15,"axes.labelsize":15})
plt.figure()
# fig代表绘图窗口(Figure);ax代表这个绘图窗口上的坐标系(axis)
fig, ax = plt.subplots(1,3,figsize=(12,5))

plt.subplot(1,3,1)
# 条形图,以x轴标签划分统计个数,再以hue标签统计违约与未违约的个数
ax=sns.countplot(x='SEX',hue='default.payment.next.month',data = df)
# 设置x轴不同标签名称
ax.set_xticklabels(['男', '女'],fontsize=12)
# 设置x轴名称
plt.xlabel("性别")
plt.ylabel("")
# 设置图的标题
plt.title('不同性别违约情况')

plt.subplot(1,3,2)
ax=sns.countplot(x='EDUCATION',hue='default.payment.next.month',data = df)
ax.set_xticklabels(['其他', '高中','本科','研究生及以上'],fontsize=12)
plt.xlabel("教育程度")
plt.ylabel("")
plt.title('不同教育程度违约情况')


plt.subplot(1,3,3)
ax=sns.countplot(x='MARRIAGE',hue='default.payment.next.month',data = df)
ax.set_xticklabels(['其他', '已婚','未婚'], fontsize=12)
plt.xlabel("婚姻状况")
plt.ylabel("")
plt.title('不同婚姻状况违约情况')

plt.show()

在这里插入图片描述


从图中可以看出,虽然男性的数量远低于女性,但违约人数并没有明显低于女性违约人数。对于婚姻状况也存在类似情况,未婚的人数比已婚人数多,但违约人数和已婚的违约人数相差无几,也说明了未婚人数的违约概率可能要大一些。

我们再考察对于年龄和信用卡额度的违约情况。

t0 = df[df['default.payment.next.month'] == 0]   # 未违约的样本数据
t1 = df[df['default.payment.next.month'] == 1]   # 违约的样本数据
plt.figure()
fig, ax = plt.subplots(1,2,figsize=(12,6))

# 画第一个子图
plt.subplot(1,2,1)
# 画直方图,设置高斯核密度估计图参数,以字典传入核密度图的属性信息
sns.distplot(t0['AGE'],kde_kws={"color":"blue","label":"未违约"})
sns.distplot(t1['AGE'],kde_kws={"color":"orange","label":"违约"})
# 设置x轴名称、字符大小
plt.xlabel('年龄', fontsize=12)
locs, labels = plt.xticks()
plt.title('不同年龄违约情况')
# 刻度线参数设置
plt.tick_params(axis='both', which='major', labelsize=12)

plt.subplot(1,2,2)
sns.distplot(t0["LIMIT_BAL"],kde_kws={"color":"blue","label":"未违约"})
sns.distplot(t1["LIMIT_BAL"],kde_kws={"color":"orange","label":"违约"})
plt.xlabel('信用卡额度', fontsize=12)
locs, labels = plt.xticks()
plt.title('不同信用卡额度违约情况')
plt.tick_params(axis='both', which='major', labelsize=12)

plt.show()

在这里插入图片描述


仔细观察上图,我们可以获取更多有趣的信息。例如30岁前,客户的违约概率更大;30-40岁之间是客户信用最高的区间,40岁之后再次下降;信用卡额度较低的客户违约概率更高。

3 分类建模

我们首先尝试使用一些基本的机器学习模型对数据直接进行分类建模,并给出分类的预测结果。我们选用比较经典的KNN和决策树来观察效果。

3.1 KNN模型

在KNN模型中,由于要计算欧氏距离,因此应该先对离散变量做哑变量编码处理,使其变为数值型特征
我们对无序离散变量SEX,MARRIAGE进行哑变量编码。

str_columns = ['SEX','MARRIAGE']
df_new = pd.get_dummies(df, columns=str_columns)
y = df_new['default.payment.next.month']  #因变量
X = df_new.drop(columns=['ID','default.payment.next.month']) #去除因变量与ID,剩余为特征

数值型的变量的各指标相差很大时,如果直接使用原始值计算,就会突出数值较大的指标在分析中的作用、削弱数值较小的指标在分析中的作用,所以要进行特征缩放。

对特征列进行标准化处理,使得处理过后的每一列数据都是均值为0,标准差为1的数据。

X.iloc[:,0:21] = preprocessing.scale(X.iloc[:,0:21])

划分数据集。

X_train, X_test,y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0)

下面,我们使用sklearn中的KNeighborsClassifier对训练集进行分类训练。当不输入参数时,默认KNN所选取的n_neighbors参数取值为5。

# 创建knn类
knn_model = KNeighborsClassifier()
# 对数据进行拟合学习
knn_model.fit(X_train, y_train)

得到模型knn_model后,我们在测试集上验证其分类效果
用predict方法获取模型对每一个测试集样本标签的预测结果knn_pred
用predict_proba方法获取模型把每一个样本划分为正类的概率knn_score

# 预测结果
knn_pred = knn_model.predict(X_test)
# 属于正类的概率
knn_score = knn_model.predict_proba(X_test)[:,1]

为了展示分类模型的效果,我们需要获取模型的准确率得分,分类报告以及混淆矩阵。
sklearn中的accuracy_score用于计算准确率
sklearn中的classification_report函数用于显示主要分类指标的文本报告.在报告中显示每个类的精确度,召回率,F1值等信息
sklearn中的confusion_matrix总结模型真实值与预测值的情形分析表,以矩阵形式显示

为了方便后续使用,我们定义一个函数Get_report来获取这些信息。

Get_report(y_test , knn_pred)

模型的准确率为:0.7915555555555556
模型的分类报告展示如下:
precision recall f1-score support

       0       0.84      0.91      0.87      7060
       1       0.53      0.35      0.42      1940

accuracy                           0.79      9000

macro avg 0.68 0.63 0.64 9000
weighted avg 0.77 0.79 0.77 9000

模型的混淆矩阵展示如下:

请添加图片描述


我们发现,虽然总体预测的精准度尚可,违约样本的实际召回率只有0.27,能够综合展示正类样本的准确率和召回率的f1-score只有0.36。我们再尝试其他模型进行对比。

3.2 决策树

引入决策树算法,直接进行模型训练,并展示模型训练结果,与KNN得到的结果进行对比。

from sklearn import tree
from sklearn.tree import DecisionTreeClassifier
tree_model = DecisionTreeClassifier(random_state=1)
tree_model.fit(X_train, y_train)
tree_score = tree_model.predict_proba(X_test)[:,1]
tree_pred = tree_model.predict(X_test)
Get_report(y_test,tree_pred)

模型的准确率为:0.7307777777777777
模型的分类报告展示如下:
precision recall f1-score support

       0       0.84      0.82      0.83      7060
       1       0.39      0.42      0.40      1940

accuracy                           0.73      9000

macro avg 0.61 0.62 0.61 9000
weighted avg 0.74 0.73 0.74 9000

模型的混淆矩阵展示如下:

在这里插入图片描述

4 数据过采样

在使用KNN算法和决策树算法后,我们得到的训练结果均较为一般,我们重新回顾之前得到的结论:这个样本是一个存在类别不平衡问题的样本。

from imblearn.over_sampling import SMOTE
# 划分数据集
X_train, X_test,\
y_train, y_test \
= train_test_split(X, y, test_size=0.3, random_state=0)
# 定义一个SMOTE模型
smo = SMOTE(random_state=42)
# fit_resample拟合数据
X_smo, y_smo = smo.fit_resample(X_train, y_train)
from sklearn import tree
from sklearn.tree import DecisionTreeClassifier
tree_model = DecisionTreeClassifier(random_state=1)
tree_model.fit(X_smo, y_smo)
tree_score = tree_model.predict_proba(X_test)[:,1]
tree_pred = tree_model.predict(X_test)
Get_report(y_test,tree_pred)

5 引入新评价指标

同时,针对不平衡样本的评价指标也需要进行相应的更新,我们可以引入Precision_Recall_curve、AP值、ROC_curve、G-mean

我们定一个Get_curve函数,展示这些曲线,并给出一系列对应的数值。

6 集成学习

下面我们引入一种强力的算法-XGBoost,作为Boosting的代表之一,它是对GBDT(Gradient Boosting Decision Tree)的一种改进,我们在对数据进行重采样后进行学习。

6.1 XGBoost

# 划分数据集
X_train, X_test,\
y_train, y_test \
= train_test_split(X, y, test_size=0.3, random_state=0)

# 过采样处理
smo = SMOTE(random_state=42)
X_smo, y_smo = smo.fit_resample(X_train, y_train)
from xgboost import XGBClassifier
model = XGBClassifier(booster='gbtree',learning_rate=0.1,gamma=1,scale_pos_weight=1,n_estimators=1000,max_depth=6,alpha=5,reg_lambda=1)
# 设置训练数据
eval_set = [(X_test, y_test)]
model.fit(X_smo, y_smo, early_stopping_rounds=10, eval_metric="auc", eval_set=eval_set, verbose=False)
y_pred = model.predict(X_test)
y_socre = model.predict_proba(X_test)[:,1]
# predictions = [round(value) for value in y_pred]
Get_curve(y_test,y_socre,y_pred)

请添加图片描述


AP值:0.5284780662232265
F1最大对应阈值 = 0.49564602971076965
Gmean最大对应阈值 = 0.4803217351436615
准确率得分= 0.7938888888888889
最大Gmean = 0.7028493826938129
最大F1 = 0.5300546448087432

6.2 CATBOOST

我们最后再使用一种在处理类别特征时通常效果更好的算法:CatBoost。它的基本原理类似于常规的Gradient Boosting算法,它一方面可以自动处理分类特征

由于可以自动处理分类特征,我们可以直接使用未进行哑变量编码的数据进行计算。

y = df['default.payment.next.month'] # 因变量
X = df.drop(columns=['ID','default.payment.next.month']) # 去除ID与因变量,剩余为特征
X_train, X_test,\
y_train, y_test \
= train_test_split(X, y, test_size=0.3, random_state=0)
from sklearn.model_selection import GridSearchCV
from catboost import CatBoostClassifier

categorical_features_indices = ['SEX','MARRIAGE']
model = CatBoostClassifier(iterations=100, depth=6, cat_features = categorical_features_indices, loss_function='Logloss', logging_level='Silent')
model.fit(X_train,y_train,eval_set=(X_test, y_test))
cb_pred = model.predict(X_test)
cb_socre = model.predict_proba(X_test)[:,1]
# predictions = [round(value) for value in cb_pred]
Get_curve(y_test, cb_socre, cb_pred)

在这里插入图片描述


AP值:0.5496398051016296
F1最大对应阈值 = 0.2518803714670295
Gmean最大对应阈值 = 0.21489477235998308
准确率得分= 0.8255555555555556
最大Gmean = 0.7112365138132554
最大F1 = 0.5477949940405245

综合来看,CatBoost取得了较好的效果,但我们发现G-mean的最大值和F1的最大值并不能同时取到,二者之间存在取舍关系,在处理实际问题时,需要根据对正类样本的分类需求来决定。是更加追求正类样本的召回率,还是整体判别的精确性?此处我们分别给出G-mean最大时的模型和F1最大时的模型,仅供参考。

THRESHOLD = 0.2518803714670295
# 大于阈值输出1,小于则输出0
y_pred = np.where(model.predict_proba(X_test)[:,1] >= THRESHOLD, 1, 0)
Get_report(y_test,y_pred)

模型的准确率为:0.7892222222222223
模型的分类报告展示如下:
precision recall f1-score support

       0       0.88      0.84      0.86      7060
       1       0.51      0.59      0.55      1940

accuracy                           0.79      9000

macro avg 0.70 0.72 0.71 9000
weighted avg 0.80 0.79 0.79 9000
这是追求F1最大时的效果,此时设置分类阈值为0.2518803714670295 , F1值达到了0.55。

THRESHOLD = 0.21489477235998308
# 大于阈值输出1,小于则输出0
y_pred = np.where(model.predict_proba(X_test)[:,1] >= THRESHOLD, 1, 0)
Get_report(y_test,y_pred)

模型的准确率为:0.7566666666666667
模型的分类报告展示如下:
precision recall f1-score support

       0       0.89      0.79      0.84      7060
       1       0.45      0.64      0.53      1940

accuracy                           0.76      9000

macro avg 0.67 0.71 0.68 9000
weighted avg 0.80 0.76 0.77 9000

这是追求Gmeans最大时的效果,此时设置分类阈值为0.21489477235998308,F1值有一定的下降,但是正类样本的召回率达到了0.64。根据实际问题的不同需求,两种结果具有不同的实际意义。例如,如果我们认为放过违约样本的代价较大,我们可以选择以Gmeans最大化作为算法的评价指标。

7 结论

对于不平衡样本的分类问题,直接使用机器学习模型并非能得到很好的效果,重采样是可能能够提升模型训练效果的办法。本案例一共使用了六种不同的分类模型算法,其中CatBoost实现了最好的分类效果。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习编程?其实不难,不过在学习编程之前你得先了解你的目的是什么?这个很重要,因为目的决定你的发展方向、决定你的发展速度。
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面设计类、前端与移动、开发与测试、营销推广类、数据运营类、运营维护类、游戏相关类等,根据不同的分类下面有细分了不同的岗位。
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生学习Java开发,但要结合自身的情况,先了解自己适不适合去学习Java,不要盲目的选择不适合自己的Java培训班进行学习。只要肯下功夫钻研,多看、多想、多练
Can’t connect to local MySQL server through socket \'/var/lib/mysql/mysql.sock问题 1.进入mysql路径
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 sqlplus / as sysdba 2.普通用户登录
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服务器有时候会断掉,所以写个shell脚本每五分钟去判断是否连接,于是就有下面的shell脚本。
BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。
假如你已经使用过苹果开发者中心上架app,你肯定知道在苹果开发者中心的web界面,无法直接提交ipa文件,而是需要使用第三方工具,将ipa文件上传到构建版本,开...
下面的 SQL 语句指定了两个别名,一个是 name 列的别名,一个是 country 列的别名。**提示:**如果列名称包含空格,要求使用双引号或方括号:
在使用H5混合开发的app打包后,需要将ipa文件上传到appstore进行发布,就需要去苹果开发者中心进行发布。​
+----+--------------+---------------------------+-------+---------+
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 nu...
第一步:到appuploader官网下载辅助工具和iCloud驱动,使用前面创建的AppID登录。
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
前不久在制作win11pe,制作了一版,1.26GB,太大了,不满意,想再裁剪下,发现这次dism mount正常,commit或discard巨慢,以前都很快...
赛门铁克各个版本概览:https://knowledge.broadcom.com/external/article?legacyId=tech163829
实测Python 3.6.6用pip 21.3.1,再高就报错了,Python 3.10.7用pip 22.3.1是可以的
Broadcom Corporation (博通公司,股票代号AVGO)是全球领先的有线和无线通信半导体公司。其产品实现向家庭、 办公室和移动环境以及在这些环境...
发现个问题,server2016上安装了c4d这些版本,低版本的正常显示窗格,但红色圈出的高版本c4d打开后不显示窗格,
TAT:https://cloud.tencent.com/document/product/1340