金融借贷探索性数据分析实战案例
©️数据STUDIO投稿|作者
:理智
作者简介
:理智,河北科技大学的大四学生,主攻深度学习。和大多数程序员一样,他是个乐观主义者,大量的时间都在调试代码,在调试中满怀希望,克服遇到的无数挫折。
通过信用卡客户及交易数据 → 剥离出用户画像,查看不同种类卡根据属性、资产等划分的持卡情况,分析群体特征,为业务场景搭建、活动策划及针对性营销提供帮助。
通过贷款信息及交易信息等数据 → 选取性别、年龄、经济状况、区域情况贷款行为等指标,从用户、状态、行为三各维度对贷款情况进行分折并查看相关性,进而构建贷款违约预测模型,为决策提供参考。 本文 数据获取:在公众号:数据STUDIO 后台回复 「 data」。
导入需要的库
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
声明使用 Seaborn 样式
sns.set()
有五种seaborn的绘图风格,它们分别是:darkgrid, whitegrid, dark,white, ticks。默认的主题是darkgrid。
sns.set_style("whitegrid")
有四个预置的环境,按大小从小到大排列分别为:paper, notebook, talk, poster。其中,notebook是默认的。
sns.set_context('talk')
# 中文字体设置-黑体
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
# 解决保存图像是负号'-'显示为方块的问题
plt.rcParams['axes.unicode_minus'] = False
数据处理
(1)使用pandas库将表进行合并连接,对数据进行处理;
(2)使用数字代替分类的方式对数据进行变换,便于分析;
(3)使用datetime库对时间进行处理;
(4)筛选出需要的数据;
(5)对数据质量进行评估找出缺失数据,根据情兄进行处理。
df_train = pd.read_csv('./train.csv')
df_testA = pd.read_csv('./testA.csv')
# 数据获取:在公众号:数据STUDIO 后台回复 datadf_train.shape, df_testA.shape
((800000, 47), (200000, 46))
df_train.columns
Index(['id', 'loanAmnt', 'term', 'interestRate', 'installment', 'grade',
'subGrade', 'employmentTitle', 'employmentLength', 'homeOwnership',
'annualIncome', 'verificationStatus', 'issueDate', 'isDefault',
'purpose', 'postCode', 'regionCode', 'dti', 'delinquency_2years',
'ficoRangeLow', 'ficoRangeHigh', 'openAcc', 'pubRec',
'pubRecBankruptcies', 'revolBal', 'revolUtil', 'totalAcc',
'initialListStatus', 'applicationType', 'earliesCreditLine', 'title',
'policyCode', 'n0', 'n1', 'n2', 'n3', 'n4', 'n5', 'n6', 'n7', 'n8',
'n9', 'n10', 'n11', 'n12', 'n13', 'n14'],
dtype='object')
df_testA.columns
Index(['id', 'loanAmnt', 'term', 'interestRate', 'installment', 'grade',
'subGrade', 'employmentTitle', 'employmentLength', 'homeOwnership',
'annualIncome', 'verificationStatus', 'issueDate', 'purpose',
'postCode', 'regionCode', 'dti', 'delinquency_2years', 'ficoRangeLow',
'ficoRangeHigh', 'openAcc', 'pubRec', 'pubRecBankruptcies', 'revolBal',
'revolUtil', 'totalAcc', 'initialListStatus', 'applicationType',
'earliesCreditLine', 'title', 'policyCode', 'n0', 'n1', 'n2', 'n3',
'n4', 'n5', 'n6', 'n7', 'n8', 'n9', 'n10', 'n11', 'n12', 'n13', 'n14'],
dtype='object')
df_train.head()
查看数据整体分布
df_train['isDefault'].value_counts()
0 640390
1 159610
Name: isDefault, dtype: int64
查看特征的缺失程度
df_train.info()
df_train.isnull().sum()
missing_series = df_train.isnull().sum()/df_train.shape[0]
missing_df = pd.DataFrame(missing_series).reset_index()
missing_df = missing_df.rename(columns={'index': 'col', 0: 'missing_pct'})
missing_df = missing_df.sort_values('missing_pct', ascending=False).reset_index(drop=True)
missing_df.head()
查看特征的缺失程度
# 缺失率定义为0.8
threshold_features = 0.8
missing_col_num = missing_df[missing_df.missing_pct>=threshold_features].shape[0]
print('缺失率超过{}的变量个数为{}'.format(threshold_features, missing_col_num))
画图显示
# 设置标题
plt.figure(figsize=(23, 5))
plt.title('缺失特征的分布图')
sns.barplot(data=missing_df[missing_df.missing_pct>0], x='col', y='missing_pct')
plt.ylabel('缺失率')
plt.xticks(rotation=45)
plt.show()
缺失率超过0.8的变量个数为0
查看样本的缺失程度
missing_series = df_train.isnull().sum(axis=1)
list_missing_num = sorted(list(missing_series.values))
画图显示
# 设置标题
plt.figure(figsize=(23, 5))
plt.title('缺失变量的分布图')
plt.plot(range(df_train.shape[0]), list_missing_num)
plt.xlabel('samples')
plt.ylabel('缺失变量个数')
plt.show()
特征工程
查看特征的数值特征有哪些,类别特征有哪些
# 数值类型特性
numerical_fea = list(df_train.select_dtypes(exclude=['object']).columns)
# 字符串类型特征
category_fea = list(filter(lambda x: x not in numerical_fea, list(df_train.columns)))
print(numerical_fea)
print(category_fea)
['id', 'loanAmnt', 'term', 'interestRate',
'installment', 'employmentTitle', 'homeOwnership',
'annualIncome', 'verificationStatus', 'isDefault',
'purpose', 'postCode', 'regionCode', 'dti',
'delinquency_2years', 'ficoRangeLow', 'ficoRangeHigh',
'openAcc', 'pubRec', 'pubRecBankruptcies', 'revolBal',
'revolUtil', 'totalAcc', 'initialListStatus',
'applicationType', 'title', 'policyCode', 'n0',
'n1', 'n2', 'n3', 'n4', 'n5', 'n6', 'n7', 'n8',
'n9', 'n10', 'n11', 'n12', 'n13', 'n14']
['grade', 'subGrade', 'employmentLength',
'issueDate', 'earliesCreditLine']
划分数值型变量中的连续变量和分类变量
# 过滤数值型类别特征
def get_numerical_serial_fea(data, feas):
numerical_serial_fea = []
numerical_noserial_fea = []
for fea in feas:
temp = data[fea].nunique()
if temp <= 10:
numerical_noserial_fea.append(fea)
else:
numerical_serial_fea.append(fea)
return numerical_serial_fea,numerical_noserial_fea
numerical_serial_fea, numerical_noserial_fea = get_numerical_serial_fea(df_train, numerical_fea)
print(numerical_serial_fea)
print(numerical_noserial_fea)
['id', 'loanAmnt', 'interestRate', 'installment', 'employmentTitle', 'annualIncome', 'purpose', 'postCode', 'regionCode', 'dti', 'delinquency_2years', 'ficoRangeLow', 'ficoRangeHigh', 'openAcc', 'pubRec', 'pubRecBankruptcies', 'revolBal', 'revolUtil', 'totalAcc', 'title', 'n0', 'n1', 'n2', 'n3', 'n4', 'n5', 'n6', 'n7', 'n8', 'n9', 'n10', 'n13', 'n14']
['term', 'homeOwnership', 'verificationStatus', 'isDefault', 'initialListStatus', 'applicationType', 'policyCode', 'n11', 'n12']
同值化数据举例
df_train['verificationStatus'].value_counts()
1 309810
2 248968
0 241222
Name: verificationStatus, dtype: int64
df_train['n11'].value_counts()
0.0 729682
1.0 540
2.0 24
4.0 1
3.0 1
Name: n11, dtype: int64
df_train.columns
Index(['id', 'loanAmnt', 'term', 'interestRate', 'installment', 'grade',
'subGrade', 'employmentTitle', 'employmentLength', 'homeOwnership',
'annualIncome', 'verificationStatus', 'issueDate', 'isDefault',
'purpose', 'postCode', 'regionCode', 'dti', 'delinquency_2years',
'ficoRangeLow', 'ficoRangeHigh', 'openAcc', 'pubRec',
'pubRecBankruptcies', 'revolBal', 'revolUtil', 'totalAcc',
'initialListStatus', 'applicationType', 'earliesCreditLine', 'title',
'policyCode', 'n0', 'n1', 'n2', 'n3', 'n4', 'n5', 'n6', 'n7', 'n8',
'n9', 'n10', 'n11', 'n12', 'n13', 'n14'],
dtype='object')
df_train['policyCode'].value_counts()
1.0 800000
Name: policyCode, dtype: int64
查看特征中特征的单方差(同值化)性质
threshold_const = 0.95
const_list = [x for x in df_train.columns if x!='isDefault']
const_col = []
const_val = []
for col in const_list:
# value_counts 的最多的一个样本类别的样本数
max_samples_count = df_train[col].value_counts().iloc[0]
# 总体非空样本数
sum_samples_count = df_train[df_train[col].notnull()].shape[0]
# 计算特征中类别最多的样本占比
const_val.append(max_samples_count/sum_samples_count)
# 过滤同值化特征
if max_samples_count/sum_samples_count >= threshold_const:
const_col.append(col)
const_val = sorted(const_val)
const_val
小于500个类别的特征进行整体分布的探索
for f in df_train.columns:
if df_train[f].nunique()<500:
print(f, '类型数:', df_train[f].nunique())
term 类型数:2
grade 类型数:7
subGrade 类型数:35
employmentLength 类型数:11
homeOwnership 类型数:6
verificationStatus 类型数:3
issueDate 类型数:139
isDefault 类型数:2
purpose 类型数:14
regionCode 类型数:51
delinquency_2years 类型数:30
ficoRangeLow 类型数:39
ficoRangeHigh 类型数:39
openAcc 类型数:75
pubRec 类型数:32
pubRecBankruptcies 类型数:11
totalAcc 类型数:134
initialListStatus 类型数:2
applicationType 类型数:2
policyCode 类型数:1
n0 类型数:39
n1 类型数:33
n2 类型数:50
n3 类型数:50
n4 类型数:46
n5 类型数:65
n6 类型数:107
n7 类型数:70
n8 类型数:102
n9 类型数:44
n10 类型数:76
n11 类型数:5
n12 类型数:5
n13 类型数:28
n14 类型数:31
画图显示
print('常变量/同值化比例大于{}的特征个数为{}'.format(threshold_const, len(const_col)))
# 设置标题
plt.figure(figsize=(13, 5))
plt.title('同值化特征的分布图')
plt.plot(range(len(df_train.columns)-1), const_val)
plt.xlabel('特征个数')
plt.ylabel('同值化比例')
plt.show()
常变量/同值化比例大于0.95的特征个数为4
不同类型的特征对应的用户风险情况(少类别特征)
fig, ax = plt.subplots(4, 3, figsize=(25,35))
sns.countplot(x='term', hue='isDefault', data=df_train, ax=ax[0, 0])
sns.countplot(x='grade', hue='isDefault', data=df_train, ax=ax[0, 1])
sns.countplot(x='employmentLength', hue='isDefault', data=df_train, ax=ax[0, 2])
sns.countplot(x='homeOwnership', hue='isDefault', data=df_train, ax=ax[1, 0])
sns.countplot(x='verificationStatus', hue='isDefault', data=df_train, ax=ax[1, 1])
sns.countplot(x='purpose', hue='isDefault', data=df_train, ax=ax[1, 2])
sns.countplot(x='pubRecBankruptcies', hue='isDefault', data=df_train, ax=ax[2, 0])
sns.countplot(x='initialListStatus', hue='isDefault', data=df_train, ax=ax[2, 1])
sns.countplot(x='applicationType', hue='isDefault', data=df_train, ax=ax[2, 2])
sns.countplot(x='policyCode', hue='isDefault', data=df_train, ax=ax[3, 0])
# 设置标题
ax[0, 0].set_title('term对应的违约率分布')
ax[0, 1].set_title('grade对应的违约率分布')
ax[0, 2].set_title('employmentLength对应的违约率分布')
ax[1, 0].set_title('homeOwnership对应的违约率分布')
ax[1, 1].set_title('verificationStatus对应的违约率分布')
ax[1, 2].set_title('purpose对应的违约率分布')
ax[2, 0].set_title('pubRecBankruptcies对应的违约率分布')
ax[2, 1].set_title('initialListStatus对应的违约率分布')
ax[2, 2].set_title('applicationType对应的违约率分布')
ax[3, 0].set_title('policyCode对应的违约率分布')
plt.show()
计算每个地区的违约率情况
df_bucket = df_train.groupby('regionCode')
bad_trend = pd.DataFrame()
bad_trend['total'] = df_bucket['isDefault'].count()
bad_trend['bad'] = df_bucket['isDefault'].sum()
bad_trend['bad_rate'] = round(bad_trend['bad']/bad_trend['total'], 4)*100
bad_trend = bad_trend.reset_index()
# 查看Top10的数据
bad_trend.sort_values(by='bad_rate', ascending=False).iloc[:10]
画图显示
fig, ax = plt.subplots(2, 1, figsize=(25,15))
plt.title('违约率的regionCode趋势图')
sns.countplot(x='regionCode', hue='isDefault', data=df_train.sort_values(['regionCode']), ax=ax[0])
sns.pointplot(data=bad_trend, x='regionCode', y='bad_rate', ax=ax[1])
plt.show()
建立模型
根据上一步得到的最终模型数据构建逻辑回归模型,将样本数据分为训练集和测试集,建模拟合,并对测试集数据进行预测,输出预测结果及各变量回归系数,对建模结果进行评价并绘制ROC曲线。
计算subGrade的违约率情况
df_bucket = df_train.groupby('subGrade')
bad_trend = pd.DataFrame()
bad_trend['total'] = df_bucket['isDefault'].count()
bad_trend['bad'] = df_bucket['isDefault'].sum()
bad_trend['bad_rate'] = round(bad_trend['bad']/bad_trend['total'], 4)*100
bad_trend = bad_trend.reset_index()
画图显示
fig, ax = plt.subplots(2, 1, figsize=(25,15))
plt.title('违约率的subGrade趋势图')
sns.countplot(x='subGrade', hue='isDefault', data=df_train.sort_values(['subGrade']), ax=ax[0])
sns.pointplot(data=bad_trend, x='subGrade', y='bad_rate', ax=ax[1])
plt.show()
计算grade的违约率情况
df_bucket = df_train.groupby('grade')
bad_trend = pd.DataFrame()
bad_trend['total'] = df_bucket['isDefault'].count()
bad_trend['bad'] = df_bucket['isDefault'].sum()
bad_trend['bad_rate'] = round(bad_trend['bad']/bad_trend['total'], 4)*100
bad_trend = bad_trend.reset_index()
画图显示
fig, ax = plt.subplots(2, 1, figsize=(25,15))
plt.title('违约率的grade趋势图')
sns.countplot(x='grade', hue='isDefault', data=df_train.sort_values(['grade']), ax=ax[0])
sns.pointplot(data=bad_trend, x='grade', y='bad_rate', ax=ax[1])
plt.show()
计算delinquency_2years的违约率情况
df_bucket = df_train.groupby('delinquency_2years')
bad_trend = pd.DataFrame()
bad_trend['total'] = df_bucket['isDefault'].count()
bad_trend['bad'] = df_bucket['isDefault'].sum()
bad_trend['bad_rate'] = round(bad_trend['bad']/bad_trend['total'], 4)*100
bad_trend = bad_trend.reset_index()
画图显示
fig, ax = plt.subplots(2, 1, figsize=(25,15))
plt.title('违约率的delinquency_2years趋势图')
sns.countplot(x='delinquency_2years', hue='isDefault', data=df_train.sort_values(['delinquency_2years']), ax=ax[0])
sns.pointplot(data=bad_trend.sort_values(by='bad_rate', ascending=False), x='delinquency_2years', y='bad_rate', ax=ax[1])
plt.show()
🏴☠️宝藏级🏴☠️ 原创公众号『 数据STUDIO 』内容超级硬核。公众号以Python为核心语言,垂直于数据科学领域,包括 可戳 👉 Python | MySQL | 数据分析 | 数据可视化 | 机器学习与数据挖掘 | 爬虫 等,从入门到进阶!
长按👇关注- 数据STUDIO -设为星标,干货速递