数据挖掘实践(金融风控-贷款违约预测)(二):数据分析

数据挖掘实践(金融风控-贷款违约预测)(二):数据分析

1.引言

  此部分为零基础入门金融风控的第二部分:数据分析,可先学习第一部分的内容:
数据挖掘实践(金融风控-贷款违约预测)(一):赛题理解:https://blog.csdn.net/weixin_42691585/article/details/108580621
  赛题:零基础入门数据挖掘 - 零基础入门金融风控之贷款违约
  目的:
   1.EDA价值主要在于熟悉了解整个数据集的基本情况(缺失值,异常值),对数据集进行验证是否可以进行接下来的机器学习或者深度学习建模
  2.了解变量间的相互关系、变量与预测值之间的存在关系
  3.为特征工程做准备

项目地址:https://github.com/datawhalechina/team-learning-data-mining/tree/master/FinancialRiskControl
比赛地址:https://tianchi.aliyun.com/competition/entrance/531830/introduction

2.基本知识点

2.1缺失值(Missing data)

  缺失值指粗糙数据中由于缺少信息而造成的数据的聚类、分组、失或截断。它指的是现有数据集中某个或某些属性的值是不完全的数据挖掘所面对的数据不是特地为某个挖掘目的收集的,所以可能与分析相关的属性并未收集(或某段时间以后才开始收集),这类属性的缺失不能用缺失值的处理方法进行处理,因为它们未提供任何不完全数据的信息,它和缺失某些属性的值有着本质的区别。

2.1.1缺失值的机制

  将数据集中不含缺失值的变量(属性)称为完全变量,数据集中含有缺失值的变量称为不完全变量,Little和Rubin定义了以下三种不同的数据缺失机制:

  1)完全随机缺失(Missing Completely at Random, MCAR)。数据的缺失与不完全变量以及完全变量都是无关的。
  2)随机缺失(Missing at Random, MAR)。数据的缺失仅仅依赖于完全变量。
  3)非随机缺失,不可忽略缺失(Not Missing at Random,NMAR, or nonignorable)。不完全变量中数据的缺失依赖于不完全变量本身,这种缺失是不可忽略的。

2.1.2缺失值处理的必要性

  数据缺失在许多研究领域都是一个复杂的问题,对数据挖掘来说,缺失值的存在,造成了以下影响:

  1.系统丢失了大量的有用信息
  2.系统中所表现出的不确定性更加显著,系统中蕴涵的确定性成分更难把握
  3.包含空值的数据会使挖掘过程陷入混乱,导致不可靠的输出

  数据挖掘算法本身更致力于避免数据过分拟合所建的模型,这一特性使得它难以通过自身的算法去很好地处理不完整数据。因此,缺失值需要通过专门的方法进行推导、填充等,以减少数据挖掘算法与实际应用之间的差距。

2.1.3缺失值的处理方法

  对于缺失值的处理,从总体上来说分为删除存在缺失值的个案和缺失值插补。对于主观数据,人将影响数据的真实性,存在缺失值的样本的其他属性的真实值不能保证,那么依赖于这些属性值的插补也是不可靠的,所以对于主观数据一般不推荐插补的方法。插补主要是针对客观数据,它的可靠性有保证。

  1)删除元组
  将存在遗漏信息属性值的对象(记录)删除,从而得到一个完备的信息表。这种方法在对象有多个属性缺失值、被删除的含缺失值的对象与信息表中的数据量相比非常小的情况下是非常有效的。然而这种方法丢弃了大量隐藏在这些对象中的信息。在信息表中对象很少的情况下会影响到结果的正确性,可能导致数据发生偏离,从而引出错误的结论。

  2)数据补齐

  ①人工填写
  这个方法产生数据偏离最小,是填充效果最好的一种。当数据规模很大、空值很多的时候,该方法是不可行的。

  ②特殊值填充
  将空值作为一种特殊的属性值来处理,它不同于其他的任何属性值。如所有的空值都用“unknown”填充。这样将形成另一个概念,可能导致严重的数据偏离,一般不使用。

  ③平均值填充
  如果空值是数值属性,就使用该属性在其他所有对象的取值的平均值来填充该缺失的属性值。
  如果空值是非数值属性,就根据统计学中的众数原理,用该属性在其他所有对象出现频率最高的值来补齐该缺失的属性值。

  ④热卡填充(就近补齐)
  对于一个包含空值的对象,热卡填充法在完整数据中找到一个与它最相似的对象,然后用这个相似对象的值来进行填充。不同的问题选用不同的标准来对相似进行判定。

  ⑤K最近邻法
  先根据欧式距离或相关分析来确定距离具有缺失数据样本最近的K个样本,将这K个值加权平均来估计该样本的缺失数据。

  ⑥使用所有可能的值填充
  这种方法是用空缺属性值的所有可能的属性取值来填充,能够得到较好的补齐效果。但是当数据量很大或者遗漏的属性值较多时,其计算的代价很大,可能的测试方案很多。

  ⑦回归
  基于完整的数据集,建立回归方程(模型)。对于包含空值的对象,将已知属性值代入方程来估计未知属性值,以此估计值来进行填充。

  ⑧期望值最大化方法(EM)
  在缺失类型为随机缺失的条件下,假设模型对于完整的样本是正确的,通过观测数据的边际分布可以对未知参数进行极大似然估计。它一个重要前提:适用于大样本。有效样本的数量足够以保证ML估计值是渐近无偏的并服从正态分布。但是这种方法可能会陷入局部极值,收敛速度也不是很快,并且计算很复杂。

  3)不处理
  直接在包含空值的数据上进行数据挖掘。这类方法包括贝叶斯网络和人工神经网络等。

2.2数据类型

  特征一般都是由类别型特征和数值型特征组成,而数值型特征又分为连续型和离散型

  类别型特征有时具有非数值关系,有时也具有数值关系。比如‘grade’中的等级A,B,C等,是否只是单纯的分类,还是A优于其他要结合业务判断。

  数值型特征本是可以直接入模的,但往往风控人员要对其做分箱,转化为WOE编码进而做标准评分卡等操作。从模型效果上来看,特征分箱主要是为了降低变量的复杂性,减少变量噪音对模型的影响,提高自变量和因变量的相关度,从而使模型更加稳定。

2.2.1统计学中的分类

  按照所采用的计量尺度的不同,可以将统计数据分为分类数据、顺序数据和数值型数据

  分类数据是只能归于某一类别的非数字型数据,它是对事物进行分类的结果,数据表现为类别,是用文字来表述的。
  顺序数据是只能归于某一有序类别的非数字型数据。顺序数据虽然也是类别,但这些类别是有序的。
  数值型数据是按数字尺度测量的观测值,其结果表现为具体的数值。现实中所处理的大多数数据都是数值型数据。

  分类数据和顺序数据说明的是事物的品质特征,通常是用文字来表述的,其结果均表现为类别,因而也可统称为定性数据或称品质数据;数值型数据说明的是现象的数量特征,通常是用数值来表现的,因此也可以称为定量数据或数量数据

2.2.2机器学习中的分类

  在监督学习(supervised learning) 的过程中,只需要给定输入样本集,机器就可以从中推演出指定目标变量的可能结果。监督学习相对比较简单,机器只需从输入数据中预测合适的模型,并从中计算出目标变量的结果。

  监督学习一般使用两种类型的目标变量:标称型和数值型

  标称型:标称型目标变量的结果只在有限目标集中取值,如真与假(标称型目标变量主要用于分类) 。
  数值型:数值型目标变量则可以从无限的数值集合中取值,如0.100,42.001等 (数值型目标变量主要用于回归分析)。

  在本次学习中,需要

  • 查看某一个数值型变量的分布,查看变量是否符合正态分布,如果不符合正太分布的变量可以log化后再观察下是否符合正态分布。
  • 如果想统一处理一批数据变标准化 必须把这些之前已经正态化的数据提出
  • 正态化的原因:一些情况下正态非正态可以让模型更快的收敛,一些模型要求数据正态(eg. GMM、KNN),保证数据不要过偏态即可,过于偏态可能会影响模型预测结果

3.读取文件的拓展知识

  pandas读取数据时相对路径载入报错时,尝试使用os.getcwd()查看当前工作目录

  TSV与CSV的区别

  • 从名称上即可知道,TSV是用制表符(Tab,’\t’)作为字段值的分隔符;CSV是用半角逗号(’,’)作为字段值的分隔符;
  • Python对TSV文件的支持: Python的csv模块准确的讲应该叫做dsv模块,因为它实际上是支持范式的分隔符分隔值文件(DSV,delimiter-separated values)的。 delimiter参数值默认为半角逗号,即默认将被处理文件视为CSV。当delimiter=’\t’时,被处理文件就是TSV。
    读取文件的部分(适用于文件特别大的场景)
  • 通过nrows参数,来设置读取文件的前多少行,nrows是一个大于等于0的整数。
  • 分块读取

  查看一下具体的列名,赛题理解部分已经给出具体的特征含义,这里方便阅读再给一下:

  train.csv

  • id 为贷款清单分配的唯一信用证标识
  • loanAmnt 贷款金额
  • term 贷款期限(year)
  • interestRate 贷款利率
  • installment 分期付款金额
  • grade 贷款等级
  • subGrade 贷款等级之子级
  • employmentTitle 就业职称
  • employmentLength 就业年限(年)
  • homeOwnership 借款人在登记时提供的房屋所有权状况
  • annualIncome 年收入
  • verificationStatus 验证状态
  • issueDate 贷款发放的月份
  • purpose 借款人在贷款申请时的贷款用途类别
  • postCode 借款人在贷款申请中提供的邮政编码的前3位数字
  • regionCode 地区编码
  • dti 债务收入比
  • delinquency_2years 借款人过去2年信用档案中逾期30天以上的违约事件数
  • ficoRangeLow 借款人在贷款发放时的fico所属的下限范围
  • ficoRangeHigh 借款人在贷款发放时的fico所属的上限范围
  • openAcc 借款人信用档案中未结信用额度的数量
  • pubRec 贬损公共记录的数量
  • pubRecBankruptcies 公开记录清除的数量
  • revolBal 信贷周转余额合计
  • revolUtil 循环额度利用率,或借款人使用的相对于所有可用循环信贷的信贷金额
  • totalAcc 借款人信用档案中当前的信用额度总数
  • initialListStatus 贷款的初始列表状态
  • applicationType 表明贷款是个人申请还是与两个共同借款人的联合申请
  • earliesCreditLine 借款人最早报告的信用额度开立的月份
  • title 借款人提供的贷款名称
  • policyCode 公开可用的策略_代码=1新产品不公开可用的策略_代码=2
  • n系列匿名特征 匿名特征n0-n14,为一些贷款人行为计数特征的处理

4.实战

# 导入数据分析及可视化过程需要的库
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import datetime
import warnings

import pandas_profiling

warnings.filterwarnings('ignore')

# 读取文件
data_train = pd.read_csv('./train.csv')
data_test_a = pd.read_csv('./testA.csv')

data_train_sample = pd.read_csv("./train.csv", nrows=5)

# # 设置chunksize参数,来控制每次迭代数据的大小
# chunker = pd.read_csv("./train.csv", chunksize=5)
# for item in chunker:
#     print(type(item))
#     # <class 'pandas.core.frame.DataFrame'>
#     print(len(item))
#     # 5

# 查看数据集的样本个数和原始特征维度
print(data_test_a.shape)
print(data_train.shape)
print(data_train.columns)

# 通过info()来熟悉数据类型
print(data_train.info())

# 总体粗略的查看数据集各个特征的一些基本统计量
print(data_train.describe())
print(data_train.head(3).append(data_train.tail(3)))

# 查看缺失值
print(f'There are {data_train.isnull().any().sum()} columns in train dataset with missing values.')
# 上面得到训练集有22列特征有缺失值,进一步查看缺失特征中缺失率大于50%的特征
have_null_fea_dict = (data_train.isnull().sum()/len(data_train)).to_dict()
fea_null_moreThanHalf = {
    
    }
for key, value in have_null_fea_dict.items():
    if value > 0.5:
        fea_null_moreThanHalf[key] = value
print(fea_null_moreThanHalf)

# 具体的查看缺失特征及缺失率
# nan可视化
missing = data_train.isnull().sum()/len(data_train)
missing = missing[missing > 0]
missing.sort_values(inplace=True)
missing.plot.bar()
plt.show()

# 查看训练集测试集中特征属性只有一值的特征
one_value_fea = [col for col in data_train.columns if data_train[col].nunique() <= 1]
one_value_fea_test = [col for col in data_test_a.columns if data_test_a[col].nunique() <= 1]
print(one_value_fea)
print(one_value_fea_test)

print(f'There are {len(one_value_fea)} columns in train dataset with one unique value.')
print(f'There are {len(one_value_fea_test)} columns in test dataset with one unique value.')

# 查看特征的数值类型有哪些,对象类型有哪些
numerical_fea = list(data_train.select_dtypes(exclude=['object']).columns)
category_fea = list(filter(lambda x: x not in numerical_fea, list(data_train.columns)))
print(numerical_fea)
print(category_fea)
print(data_train.grade)

# 划分数值型变量中的连续变量和离散型变量
#过滤数值型类别特征
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)
            continue
        numerical_serial_fea.append(fea)
    return numerical_serial_fea, numerical_noserial_fea
numerical_serial_fea,numerical_noserial_fea = get_numerical_serial_fea(data_train,numerical_fea)
print(numerical_serial_fea)
print(numerical_noserial_fea)

# 数值类别型变量分析
print(data_train['term'].value_counts())# 离散型变量
print(data_train['homeOwnership'].value_counts())# 离散型变量
print(data_train['verificationStatus'].value_counts())# 离散型变量
print(data_train['initialListStatus'].value_counts())# 离散型变量
print(data_train['applicationType'].value_counts())# 离散型变量
print(data_train['policyCode'].value_counts())# 离散型变量,无用,全部一个值
print(data_train['n11'].value_counts())# 离散型变量,相差悬殊,用不用再分析
print(data_train['n12'].value_counts())# 离散型变量,相差悬殊,用不用再分析

# 数值连续型变量分析
# 每个数字特征得分布可视化
f = pd.melt(data_train, value_vars=numerical_serial_fea)
g = sns.FacetGrid(f, col="variable",  col_wrap=2, sharex=False, sharey=False)
g = g.map(sns.distplot, "value")
plt.show()

# Ploting Transaction Amount Values Distribution
# 正态分布
plt.figure(figsize=(16,12))
plt.suptitle('Transaction Values Distribution', fontsize=22)
plt.subplot(221)
sub_plot_1 = sns.distplot(data_train['loanAmnt'])
sub_plot_1.set_title("loanAmnt Distribuition", fontsize=18)
sub_plot_1.set_xlabel("")
sub_plot_1.set_ylabel("Probability", fontsize=15)

plt.subplot(222)
sub_plot_2 = sns.distplot(np.log(data_train['loanAmnt']))
sub_plot_2.set_title("loanAmnt (Log) Distribuition", fontsize=18)
sub_plot_2.set_xlabel("")
sub_plot_2.set_ylabel("Probability", fontsize=15)
plt.show()

# 非数值类别型变量分析
print(category_fea)
print(data_train['grade'].value_counts())
print(data_train['subGrade'].value_counts())
print(data_train['employmentLength'].value_counts())
print(data_train['issueDate'].value_counts())
print(data_train['earliesCreditLine'].value_counts())
print(data_train['isDefault'].value_counts())

# 单一变量分布可视化
plt.figure(figsize=(8, 8))
sns.barplot(data_train["employmentLength"].value_counts(dropna=False)[:20],
            data_train["employmentLength"].value_counts(dropna=False).keys()[:20])
plt.show()

# 根绝y值不同可视化x某个特征的分布
# 首先查看类别型变量在不同y值上的分布
train_loan_fr = data_train.loc[data_train['isDefault'] == 1]
train_loan_nofr = data_train.loc[data_train['isDefault'] == 0]

fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(15, 8))
train_loan_fr.groupby('grade')['grade'].count().plot(kind='barh', ax=ax1, title='Count of grade fraud')
train_loan_nofr.groupby('grade')['grade'].count().plot(kind='barh', ax=ax2, title='Count of grade non-fraud')
train_loan_fr.groupby('employmentLength')['employmentLength'].count().plot(kind='barh', ax=ax3, title='Count of employmentLength fraud')
train_loan_nofr.groupby('employmentLength')['employmentLength'].count().plot(kind='barh', ax=ax4, title='Count of employmentLength non-fraud')
plt.show()

# 其次查看连续型变量在不同y值上的分布
fig, ((ax1, ax2)) = plt.subplots(1, 2, figsize=(15, 6))
data_train.loc[data_train['isDefault'] == 1] \
    ['loanAmnt'].apply(np.log) \
    .plot(kind='hist',
          bins=100,
          title='Log Loan Amt - Fraud',
          color='r',
          xlim=(-3, 10),
         ax= ax1)
data_train.loc[data_train['isDefault'] == 0] \
    ['loanAmnt'].apply(np.log) \
    .plot(kind='hist',
          bins=100,
          title='Log Loan Amt - Not Fraud',
          color='b',
          xlim=(-3, 10),
         ax=ax2)
plt.show()

total = len(data_train)
total_amt = data_train.groupby(['isDefault'])['loanAmnt'].sum().sum()
plt.figure(figsize=(12, 5))
plt.subplot(121)  ##1代表行,2代表列,所以一共有2个图,1代表此时绘制第一个图。
plot_tr = sns.countplot(x='isDefault', data=data_train)  # data_train‘isDefault’这个特征每种类别的数量**
plot_tr.set_title("Fraud Loan Distribution \n 0: good user | 1: bad user", fontsize=14)
plot_tr.set_xlabel("Is fraud by count", fontsize=16)
plot_tr.set_ylabel('Count', fontsize=16)
for p in plot_tr.patches:
    height = p.get_height()
    plot_tr.text(p.get_x() + p.get_width() / 2.,
                 height + 3,
                 '{:1.2f}%'.format(height / total * 100),
                 ha="center", fontsize=15)

percent_amt = (data_train.groupby(['isDefault'])['loanAmnt'].sum())
percent_amt = percent_amt.reset_index()
plt.subplot(122)
plot_tr_2 = sns.barplot(x='isDefault', y='loanAmnt', dodge=True, data=percent_amt)
plot_tr_2.set_title("Total Amount in loanAmnt  \n 0: good user | 1: bad user", fontsize=14)
plot_tr_2.set_xlabel("Is fraud by percent", fontsize=16)
plot_tr_2.set_ylabel('Total Loan Amount Scalar', fontsize=16)
for p in plot_tr_2.patches:
    height = p.get_height()
    plot_tr_2.text(p.get_x() + p.get_width() / 2.,
                   height + 3,
                   '{:1.2f}%'.format(height / total_amt * 100),
                   ha="center", fontsize=15)
plt.show()

# 时间格式数据处理及查看
# 转化成时间格式  issueDateDT特征表示数据日期离数据集中日期最早的日期(2007-06-01)的天数
data_train['issueDate'] = pd.to_datetime(data_train['issueDate'],format='%Y-%m-%d')
startdate = datetime.datetime.strptime('2007-06-01', '%Y-%m-%d')
data_train['issueDateDT'] = data_train['issueDate'].apply(lambda x: x-startdate).dt.days

# 转化成时间格式
data_test_a['issueDate'] = pd.to_datetime(data_train['issueDate'], format='%Y-%m-%d')
startdate = datetime.datetime.strptime('2007-06-01', '%Y-%m-%d')
data_test_a['issueDateDT'] = data_test_a['issueDate'].apply(lambda x: x-startdate).dt.days

plt.hist(data_train['issueDateDT'], label='train')
plt.hist(data_test_a['issueDateDT'], label='test')
plt.legend()
plt.title('Distribution of issueDateDT dates')
plt.show()
# train 和 test issueDateDT 日期有重叠 所以使用基于时间的分割进行验证是不明智的

# 掌握透视图可以让我们更好的了解数据
# 透视图 索引可以有多个,“columns(列)”是可选的,聚合函数aggfunc最后是被应用到了变量“values”中你所列举的项目上。
pivot = pd.pivot_table(data_train, index=['grade'], columns=['issueDateDT'], values=['loanAmnt'], aggfunc=np.sum)
print(pivot)

# 用pandas_profiling生成数据报告
pfr = pandas_profiling.ProfileReport(data_train)
pfr.to_file("./example.html")

  结果
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
  用pandas_profiling生成数据报告

在这里插入图片描述

5.总结

  数据探索性分析是我们初步了解数据,熟悉数据为特征工程做准备的阶段,甚至很多时候EDA阶段提取出来的特征可以直接当作规则来用。可见EDA的重要性,这个阶段的主要工作还是借助于各个简单的统计量来对数据整体的了解,分析各个类型变量相互之间的关系,以及用合适的图形可视化出来直观观察。希望本节内容能给初学者带来帮助,更期待各位学习者对其中的不足提出建议。

【参考资料】

  1. 数据挖掘实践(金融风控)
  2. 零基础入门数据挖掘 - 贷款违约预测
  3. https://zhuanlan.zhihu.com/p/88106020

猜你喜欢

转载自blog.csdn.net/weixin_42691585/article/details/108625323