金融风险预测赛 Task2 数据分析


金融风险预测赛 Task2 数据分析

比赛地址:https://tianchi.aliyun.com/competition/entrance/531830/introduction

目的

  • 1.EDA价值主要在于熟悉了解整个数据集的基本情况(缺失值,异常值),对数据集进行验证是否可以进行接下来的机器学习或者深度学习建模.
  • 2.了解变量间的相互关系、变量与预测值之间的存在关系。
  • 3.为特征工程做准备

1 主要内容

  • 数据总体了解:
    • 读取数据集并了解数据集大小,原始特征维度;
    • 通过info熟悉数据类型;
    • 粗略查看数据集中各特征基本统计量;
  • 缺失值和唯一值:
    • 查看数据缺失值情况
    • 查看唯一值特征情况
  • 深入数据-查看数据类型
    • 类别型数据
    • 数值型数据
      • 离散数值型数据
      • 连续数值型数据
  • 数据间相关关系
    • 特征和特征之间关系
    • 特征和目标变量之间关系
  • 用pandas_profiling生成数据报告

2 数据概览

读取数据并了解数据集大小,首先导入数据分析及可视化过程需要的库

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import datetime
import warnings
warnings.filterwarnings('ignore')

读取文件

train_data = pd.read_csv('./data/train.csv')
test_data_a = pd.read_csv('./data/testA.csv')
# 通过nrows参数读取文件的前多少行
train_data_sample = pd.read_csv('./data/train.csv',nrows=10)

查看数据集大小和维度

train_data.shape

(800000, 47)

test_data_a.shape

(200000, 46)

train_data.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’)

通过info()来熟悉数据类型

train_data.info()
<class 'pandas.core.frame.DataFrame'>

RangeIndex: 800000 entries, 0 to 799999

Data columns (total 47 columns):

 #   Column              Non-Null Count   Dtype  

---  ------              --------------   -----  

 0   id                  800000 non-null  int64  

 1   loanAmnt            800000 non-null  float64

 2   term                800000 non-null  int64  

 3   interestRate        800000 non-null  float64

 4   installment         800000 non-null  float64

 5   grade               800000 non-null  object 

 6   subGrade            800000 non-null  object 

 7   employmentTitle     799999 non-null  float64

 8   employmentLength    753201 non-null  object 

 9   homeOwnership       800000 non-null  int64  

 10  annualIncome        800000 non-null  float64

 11  verificationStatus  800000 non-null  int64  

 12  issueDate           800000 non-null  object 

 13  isDefault           800000 non-null  int64  

 14  purpose             800000 non-null  int64  

 15  postCode            799999 non-null  float64

 16  regionCode          800000 non-null  int64  

 17  dti                 799761 non-null  float64

 18  delinquency_2years  800000 non-null  float64

 19  ficoRangeLow        800000 non-null  float64

 20  ficoRangeHigh       800000 non-null  float64

 21  openAcc             800000 non-null  float64

 22  pubRec              800000 non-null  float64

 23  pubRecBankruptcies  799595 non-null  float64

 24  revolBal            800000 non-null  float64

 25  revolUtil           799469 non-null  float64

 26  totalAcc            800000 non-null  float64

 27  initialListStatus   800000 non-null  int64  

 28  applicationType     800000 non-null  int64  

 29  earliesCreditLine   800000 non-null  object 

 30  title               799999 non-null  float64

 31  policyCode          800000 non-null  float64

 32  n0                  759730 non-null  float64

 33  n1                  759730 non-null  float64

 34  n2                  759730 non-null  float64

 35  n3                  759730 non-null  float64

 36  n4                  766761 non-null  float64

 37  n5                  759730 non-null  float64

 38  n6                  759730 non-null  float64

 39  n7                  759730 non-null  float64

 40  n8                  759729 non-null  float64

 41  n9                  759730 non-null  float64

 42  n10                 766761 non-null  float64

 43  n11                 730248 non-null  float64

 44  n12                 759730 non-null  float64

 45  n13                 759730 non-null  float64

 46  n14                 759730 non-null  float64

dtypes: float64(33), int64(9), object(5)
memory usage: 286.9+ MB

总体粗略的查看数据集各个特征的一些基本统计量

train_data.describe()

image-20250226110009349

查看前三行和后三行

pd.concat([train_data.head(3),train_data.tail(3)])

3 查看缺失值和唯一值

print(f'There are {train_data.isnull().any().sum()} columns in train dataset with missing values.')

有22列特征有缺失值

进一步查看缺失特征中缺失率大于50%的特征

#计算train_data DataFrame中每一列的缺失值比例,并将结果转换为一个字典
have_null_fea_dict = ( train_data.isnull().sum()/len(train_data) ).to_dict()
# 缺失率大于50%的特征
fea_null_moreThanHalf= {}
for key,value in have_null_fea_dict.items():
    if value>0.5:
        fea_null_moreThanHalf[key] = value
fea_null_moreThanHalf

输出为:{}

通过直方图可视化缺失特征

missing = train_data.isnull().sum()/len(train_data)
missing = missing[missing>0]
missing.sort_values(inplace=True)
# 绘制条形图
missing.plot.bar()
plt.title('Missing Values Proportion by Column')
plt.xlabel('Columns')
plt.ylabel('Proportion of Missing Values')
plt.show()

image-20250226132805855

  • 纵向了解哪些列存在 “nan”, 并可以把nan的个数打印,主要的目的在于查看某一列nan存在的个数是否真的很大,如果nan存在的过多,说明这一列对label的影响几乎不起作用了,可以考虑删掉。如果缺失值很小一般可以选择填充。
  • 另外可以横向比较,如果在数据集中,某些样本数据的大部分列都是缺失的且样本足够的情况下可以考虑删除

查看唯一值的特征

one_value_features = [col for col in train_data.columns if train_data[col].nunique() <=1 ]
one_value_features_testA = [col for col in test_data_a if test_data_a[col].nunique() <= 1]
one_value_features
one_value_features_testA

输出都是:[‘policyCode’]

总结:

47列数据中有22列都缺少数据,这在现实世界中很正常。‘policyCode’具有一个唯一值(或全部缺失)。有很多连续变量和一些分类变量。

4 查看数据类型

查看特征的数值类型有哪些,对象类型有哪些

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

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

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

# numerical_fea 是一个列表,包含 data_train 中所有非 object 类型的列名(即数值型列)
numerical_fea  = list(train_data.select_dtypes(exclude=['object']).columns)
# 使用 filter 函数和 lambda 表达式来筛选出那些不在 numerical_fea 列表中的列名。
category_fea = list(filter(lambda x: x not in numerical_fea, list(train_data.columns)))
category_fea

output: [‘grade’, ‘subGrade’, ‘employmentLength’, ‘issueDate’, ‘earliesCreditLine’]

numerical_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']
data_train.grade
0         E
,1         D
,2         D
,3         A
,4         C
,         ..
,799995    C
,799996    A
,799997    C
,799998    A
,799999    B
,Name: grade, Length: 800000, dtype: object

分离离散型和连续型特征

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

离散型数值变量分析

train_data['term'].value_counts()
term
3    606902
5    193098
Name: count, dtype: int64

…..

train_data['policyCode'].value_counts() # 无用,全部一个值
train_data['n11'].value_counts() # 相差悬殊不用分析
train_data['n12'].value_counts() #,相差悬殊,用不用再分析

连续型数值变量分析

#每个数字特征分布可视化
# 这里画图估计需要比较长的时间
f = pd.melt(train_data, value_vars=numerical_serial_fea)
g = sns.FacetGrid(f, col="variable",  col_wrap=2, sharex=False, sharey=False)
g = g.map(sns.distplot, "value")

image-20250226155216128

  • 查看某一个数值型变量的分布,查看变量是否符合正态分布,如果不符合正太分布的变量可以log化后再观察下是否符合正态分布。
  • 如果想统一处理一批数据变标准化 必须把这些之前已经正态化的数据提出
  • 正态化的原因:一些情况下正态非正态可以让模型更快的收敛,一些模型要求数据正态(eg. GMM、KNN),保证数据不要过偏态即可,过于偏态可能会影响模型预测结果。
#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(train_data['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(train_data['loanAmnt']))
sub_plot_2.set_title("loanAmnt (Log) Distribuition", fontsize=18)
sub_plot_2.set_xlabel("")
sub_plot_2.set_ylabel("Probability", fontsize=15)

image-20250226161651766

非数值类别变量分析

category_fea

[‘grade’, ‘subGrade’, ‘employmentLength’, ‘issueDate’, ‘earliesCreditLine’]

train_data['grade'].value_counts()
grade
B    233690
C    227118
A    139661
D    119453
E     55661
F     19053
G      5364
Name: count, dtype: int64

。。。。

总结:

  • 上面我们用value_counts()等函数看了特征属性的分布,但是图表是概括原始信息最便捷的方式。
  • 数无形时少直觉。
  • 同一份数据集,在不同的尺度刻画上显示出来的图形反映的规律是不一样的。python将数据转化成图表,但结论是否正确需要由你保证。

5 单一变量分布可视化

# 假设 data_train 是你的 DataFrame,并且 "employmentLength" 是其中的一列
# 计算 employmentLength 列的值计数并排序
value_counts = train_data["employmentLength"].value_counts(dropna=False)

# 获取前20个值及其计数
top_20_values = value_counts[:20]
top_20_keys = value_counts.keys()[:20]

# 创建一个新的 DataFrame 以便于绘图
df_top_20 = pd.DataFrame({'employmentLength': top_20_keys, 'count': top_20_values}).reset_index(drop=True)

# 创建图形
plt.figure(figsize=(8, 8))

# 使用 Seaborn 绘制条形图
sns.barplot(x='count', y='employmentLength', data=df_top_20, orient='h')

# 显示图形
plt.show()

image-20250226172513234

使用Pandas和Matplotlib来可视化贷款数据集中不同类别(如信用等级grade和工作年限employmentLength)在欺诈(isDefault == 1)与非欺诈(isDefault == 0)样本中的分布情况

train_loan_fr = train_data.loc[train_data['isDefault'] == 1]
train_loan_nofr = train_data.loc[train_data['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))
train_data.loc[train_data['isDefault'] == 1] \
    ['loanAmnt'].apply(np.log) \
    .plot(kind='hist',
          bins=100,
          title='Log Loan Amt - Fraud',
          color='r',
          xlim=(-3, 10),
         ax= ax1)
train_data.loc[train_data['isDefault'] == 0] \
    ['loanAmnt'].apply(np.log) \
    .plot(kind='hist',
          bins=100,
          title='Log Loan Amt - Not Fraud',
          color='b',
          xlim=(-3, 10),
         ax=ax2)

image-20250226174501145

total = len(train_data)
total_amt = train_data.groupby(['isDefault'])['loanAmnt'].sum().sum()
plt.figure(figsize=(12,5))
plt.subplot(121)##1代表行,2代表列,所以一共有2个图,1代表此时绘制第一个图。
plot_tr = sns.countplot(x='isDefault',data=train_data)#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 = (train_data.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)

image-20250226175541264

这段代码的主要目的是对贷款数据集 train_data 进行可视化分析,展示欺诈(isDefault == 1)和非欺诈(isDefault == 0)样本的数量分布以及总贷款金额的分布。以下是详细的解释:

1. 初始化和全局设置

total = len(train_data)
total_amt = train_data.groupby(['isDefault'])['loanAmnt'].sum().sum()
plt.figure(figsize=(12,5))
  • total = len(train_data): 计算整个数据集中的样本总数。
  • total_amt = train_data.groupby(['isDefault'])['loanAmnt'].sum().sum():
    • groupby(['isDefault'])['loanAmnt'].sum(): 按照 isDefault 列进行分组,并计算每组的 loanAmnt 总和。
    • .sum(): 将所有组的贷款金额总和相加,得到整个数据集的贷款金额总和。
  • plt.figure(figsize=(12,5)): 创建一个大小为 12x5 英寸的图形窗口。

2. 绘制第一个子图:欺诈与非欺诈样本数量分布

plt.subplot(121)  # 1代表行,2代表列,所以一共有2个图,1代表此时绘制第一个图。
plot_tr = sns.countplot(x='isDefault', data=train_data)  # 绘制 '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)
  • plt.subplot(121): 创建一个 1 行 2 列的子图布局,并选择第一个子图进行绘制。
  • sns.countplot(x='isDefault', data=train_data): 使用 Seaborn 的 countplot 函数绘制 isDefault 列中不同类别的计数图。
  • 设置标题和标签
    • set_title: 设置图表标题。
    • set_xlabelset_ylabel: 设置 x 轴和 y 轴的标签。
  • 在每个柱状图上显示百分比
    • 遍历每个柱状图 (for p in plot_tr.patches)。
    • height = p.get_height(): 获取柱状图的高度(即该类别的计数)。
    • plot_tr.text(...): 在每个柱状图上方显示该类别的百分比。

3. 绘制第二个子图:欺诈与非欺诈样本的总贷款金额分布

percent_amt = (train_data.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)
  • percent_amt = train_data.groupby(['isDefault'])['loanAmnt'].sum(): 按照 isDefault 列进行分组,并计算每组的 loanAmnt 总和。
  • percent_amt = percent_amt.reset_index(): 将分组结果转换为 DataFrame 格式,以便后续绘图。
  • plt.subplot(122): 创建一个 1 行 2 列的子图布局,并选择第二个子图进行绘制。
  • sns.barplot(x='isDefault', y='loanAmnt', dodge=True, data=percent_amt): 使用 Seaborn 的 barplot 函数绘制 isDefault 列中不同类别的总贷款金额柱状图。
  • 设置标题和标签
    • set_title: 设置图表标题。
    • set_xlabelset_ylabel: 设置 x 轴和 y 轴的标签。
  • 在每个柱状图上显示百分比
    • 遍历每个柱状图 (for p in plot_tr_2.patches)。
    • height = p.get_height(): 获取柱状图的高度(即该类别的总贷款金额)。
    • plot_tr_2.text(...): 在每个柱状图上方显示该类别的百分比。

关键点总结

  1. 全局变量

    • total: 数据集中的总样本数。
    • total_amt: 数据集中所有贷款金额的总和。
  2. 子图布局

    • 使用 plt.subplot(121)plt.subplot(122) 分别创建两个子图,形成一个 1 行 2 列的布局。
  3. 绘图

    • 第一个子图使用 sns.countplot 显示欺诈和非欺诈样本的数量分布,并在每个柱状图上标注百分比。
    • 第二个子图使用 sns.barplot 显示欺诈和非欺诈样本的总贷款金额,并在每个柱状图上标注百分比。

通过这种方式,你可以直观地比较欺诈和非欺诈样本的数量和贷款金额分布情况,帮助你更好地理解数据特征及其在不同类别中的表现。

6 时间格式数据处理及查看

#转化成时间格式  issueDateDT特征表示数据日期离数据集中日期最早的日期(2007-06-01)的天数
train_data['issueDate'] = pd.to_datetime(train_data['issueDate'],format='%Y-%m-%d')
start_date = datetime.datetime.strptime('2007-06-01','%Y-%m-%d')  # 将字符串转化为datetime对象
train_data['issueDateDT'] = train_data['issueDate'].apply(lambda x: x-start_date).dt.days
#转化成时间格式
test_data_a['issueDate'] = pd.to_datetime(test_data_a['issueDate'],format='%Y-%m-%d')
startdate = datetime.datetime.strptime('2007-06-01', '%Y-%m-%d')
test_data_a['issueDateDT'] = test_data_a['issueDate'].apply(lambda x: x-startdate).dt.days
plt.hist(train_data['issueDateDT'], label='train');
plt.hist(test_data_a['issueDateDT'], label='test');
plt.legend();
plt.title('Distribution of issueDateDT dates');

image-20250226184452414

7 掌握透视图可以让我们更好的了解数据

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

代码解释:

pivot = pd.pivot_table(
    data_train,          # 输入的数据框
    index=['grade'],     # 设定行索引,这里是以 'grade' 列作为行索引
    columns=['issueDateDT'],  # 设定列索引,这里是用 'issueDateDT' 列作为列索引
    values=['loanAmnt'], # 需要聚合的值,这里是 'loanAmnt' 列
    aggfunc=np.sum       # 聚合函数,这里是使用 numpy 的 sum 函数求和
)

参数详解

  • data_train: 这是你从中提取数据的数据框(DataFrame)。
  • index=['grade']: 表示将 'grade' 列设置为行索引。这意味着结果透视表的每一行将对应于 'grade' 中的一个唯一值。
  • columns=['issueDateDT']: 表示将 'issueDateDT' 列设置为列索引。因此,结果透视表的每一列将代表 'issueDateDT' 中的一个唯一值。
  • values=['loanAmnt']: 指定了需要聚合的列,这里是 'loanAmnt',即贷款金额。
  • aggfunc=np.sum: 定义了聚合函数,这里是使用 NumPy 的 sum 函数来计算贷款金额的总和。

image-20250226191933005

我们举例解读一下这张表 issueDateDT = 30 , 在第30天,信用等级为A的贷款金额的总和为53650

8 用pandas_profiling生成数据报告

import pandas_profiling # `import pandas_profiling` is going to be deprecated by April 1st. Please use `import ydata_profiling` instead. 
profile = ProfileReport(train_data, title='Task2 Profiling Report')
profile.to_file("your_report")

Author: qwq小小舒
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint policy. If reproduced, please indicate source qwq小小舒 !
  TOC