阿里雲的金融風控-貸款違約預測_數據分析


一、賽題數據

數據大家可以到官網去下載:https://tianchi.aliyun.com/competition/entrance/531830/information需要報名后才可以下載數據

賽題以預測用戶貸款是否違約為任務,數據集報名后可見並可下載,該數據來自某信貸平台的貸款記錄,總數據量超過120w,包含47列變量信息,其中15列為匿名變量。為了保證比賽的公平性,將會從中抽取80萬條作為訓練集,20萬條作為測試集A,20萬條作為測試集B,同時會對employmentTitle、purpose、postCode和title等信息進行脫敏。

字段表

Field Description
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,為一些貸款人行為計數特征的處理

 

二、數據分析

2.1主要內容

  • 數據總體了解:
    • 讀取數據集並了解數據集大小,原始特征維度;
    • 通過info熟悉數據類型;
    • 粗略查看數據集中各特征基本統計量;
  • 缺失值和唯一值:
    • 查看數據缺失值情況
    • 查看唯一值特征情況
  • 深入數據-查看數據類型
    • 類別型數據
    • 數值型數據
      • 離散數值型數據
      • 連續數值型數據
  • 數據間相關關系
    • 特征和特征之間關系
    • 特征和目標變量之間關系
  • 用pandas_profiling生成數據報告

2.2 代碼示例

2.2.1 導入數據分析及可視化過程需要的庫

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

2.2.2 讀取文件

data_train = pd.read_csv('F:/python/阿里雲金融風控-貸款違約預測/train.csv')
data_test_a = pd.read_csv('F:/python/阿里雲金融風控-貸款違約預測/testA.csv')

2.2.3 總體了解

data_test_a.shape  #(200000, 48)
data_train.shape  #(800000, 47) 測試集是沒有label,也就是y值
data_train.columns
data_train.info()
data_train.describe()
data_train.head(3).append(data_train.tail(3))

2.2.4 查看數據集中特征缺失值,唯一值等

print(f'There are {data_train.isnull().any().sum()} columns in train dataset with missing values.')
#There are 22 columns in train dataset with missing values.
# nan可視化
missing = data_train.isnull().sum()/len(data_train)
missing = missing[missing > 0]
missing.sort_values(inplace=True)
missing.plot.bar()

 

 查看訓練集測試集中特征屬性只有一值的特征

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,one_value_fea_test )
#['policyCode'] ['policyCode']
data_train['policyCode'].value_counts() 
#1.0    800000
#Name: policyCode, dtype: int64
#可以刪除  
data_train=data_train.drop(['policyCode'],axis=1)
data_test_a=data_test_a.drop(['policyCode'],axis=1)
print(data_train.shape,data_test_a.shape)
data_train.columns,data_test_a.columns

2.2.5 查看特征的數值類型有哪些,對象類型有哪些

  • 特征一般都是由類別型特征和數值型特征組成
  • 類別型特征有時具有非數值關系,有時也具有數值關系。比如‘grade’中的等級A,B,C等,是否只是單純的分類,還是A優於其他要結合業務判斷。
  • 數值型特征本是可以直接入模的,但往往風控人員要對其做分箱,轉化為WOE編碼進而做標准評分卡等操作。從模型效果上來看,特征分箱主要是為了降低變量的復雜性,減少變量噪音對模型的影響,提高自變量和因變量的相關度。從而使模型更加穩定
data_train.info()
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)))
['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',
 'n0',
 'n1',
 'n2',
 'n2.1',
 'n4',
 'n5',
 'n6',
 'n7',
 'n8',
 'n9',
 'n10',
 'n11',
 'n12',
 'n13',
 'n14']
['grade', 'subGrade', 'employmentLength', 'issueDate', 'earliesCreditLine']

數值型變量分析,數值型肯定是包括連續型變量和離散型變量的,找出來

#過濾數值型類別特征
def get_numerical_serial_fea(data,feas):
    '''
    目的:划分數值型變量中的連續變量和分類變量
    data:需要划分的數據集
    feas:需要區分的特征的名稱
    返回:連續變量和分類變量 的list集合
    '''
    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)
numerical_serial_fea,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',
  'n2.1',
  'n4',
  'n5',
  'n6',
  'n7',
  'n8',
  'n9',
  'n10',
  'n13',
  'n14'],
 ['term',
  'homeOwnership',
  'verificationStatus',
  'isDefault',
  'initialListStatus',
  'applicationType',
  'n11',
  'n12'])

在仔細查看每個類別變量

category_col=category_fea+numerical_noserial_fea
for i in category_col:
    print(i,data_train[i].value_counts())
grade B    233690
C    227118
A    139661
D    119453
E     55661
F     19053
G      5364
Name: grade, dtype: int64
subGrade C1    50763
B4    49516
B5    48965
B3    48600
C2    47068
C3    44751
C4    44272
B2    44227
B1    42382
C5    40264
A5    38045
A4    30928
D1    30538
D2    26528
A1    25909
D3    23410
A3    22655
A2    22124
D4    21139
D5    17838
E1    14064
E2    12746
E3    10925
E4     9273
E5     8653
F1     5925
F2     4340
F3     3577
F4     2859
F5     2352
G1     1759
G2     1231
G3      978
G4      751
G5      645
Name: subGrade, dtype: int64
employmentLength 10+ years    262753
2 years       72358
< 1 year      64237
3 years       64152
1 year        52489
5 years       50102
4 years       47985
6 years       37254
8 years       36192
7 years       35407
9 years       30272
Name: employmentLength, dtype: int64
issueDate 2016-03-01    29066
2015-10-01    25525
2015-07-01    24496
2015-12-01    23245
2014-10-01    21461
              ...  
2007-08-01       23
2007-07-01       21
2008-09-01       19
2007-09-01        7
2007-06-01        1
Name: issueDate, Length: 139, dtype: int64
earliesCreditLine Aug-2001    5567
Sep-2003    5403
Aug-2002    5403
Oct-2001    5258
Aug-2000    5246
            ... 
Nov-1953       1
Sep-1957       1
Mar-1958       1
Mar-1957       1
Aug-1946       1
Name: earliesCreditLine, Length: 720, dtype: int64
term 3    606902
5    193098
Name: term, dtype: int64
homeOwnership 0    395732
1    317660
2     86309
3       185
5        81
4        33
Name: homeOwnership, dtype: int64
verificationStatus 1    309810
2    248968
0    241222
Name: verificationStatus, dtype: int64
isDefault 0    640390
1    159610
Name: isDefault, dtype: int64
initialListStatus 0    466438
1    333562
Name: initialListStatus, dtype: int64
applicationType 0    784586
1     15414
Name: applicationType, dtype: int64
n11 0.0    729682
1.0       540
2.0        24
4.0         1
3.0         1
Name: n11, dtype: int64
n12 0.0    757315
1.0      2281
2.0       115
3.0        16
4.0         3
Name: n12, dtype: int64

 數值連續型變量分析

#每個數字特征得分布可視化,這個如果數據量太大,不建議這樣處理
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")

 

 

  • 查看某一個數值型變量的分布,查看變量是否符合正態分布,如果不符合正太分布的變量可以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(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)

 

 

總結:

  • 上面我們用value_counts()等函數看了特征屬性的分布,但是圖表是概括原始信息最便捷的方式。
  • 數無形時少直覺。
  • 同一份數據集,在不同的尺度刻畫上顯示出來的圖形反映的規律是不一樣的。python將數據轉化成圖表,但結論是否正確需要由你保證

2.2.6 變量分布可視化

單一變量分布可視化

#拿就業年限出來舉例子,取前面20個,也可以這樣子表達,data_train["employmentLength"].value_counts(dropna=False).plot.bar()
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()

 其實我覺得這樣子表達反而更加清晰明了

#使用列聯表,然后再畫圖,對於類別型特征特別有效
pd.crosstab(data_train['grade'],data_train["isDefault"]).plot(kind="bar")

 

 

還可以直接畫逾期率

a=pd.crosstab(data_train['grade'],data_train["isDefault"])
a['壞用戶占比']=a[1]/(a[0]+a[1])
a['壞用戶占比'].plot()

 

  • 其次查看連續型變量在不同y值上的分布
#首先找到 label=1 的數據,在看這個數據的loanAmnt字段,使用apply將這個字段全部log化,然后在畫圖
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=10,
          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=10,
          title='Log Loan Amt - Not Fraud',
          color='b',
          xlim=(-3, 10),
         ax=ax2)

 

 

 

 當然也可以先cut,然后再做列聯表

import pandas as pd 
data=pd.DataFrame()
data['loanAmnt']=pd.qcut(data_train['loanAmnt'].apply(np.log),10)
data['isDefault']=data_train['isDefault']
pd.crosstab(data['loanAmnt'],data['isDefault']).plot(kind="bar")
plt.show()
a=pd.crosstab(data['loanAmnt'],data['isDefault'])
a['壞用戶占比']=a[1]/(a[0]+a[1])
a['壞用戶占比'].plot()
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) 
 
#反正上面就是計算label的種類的值和占比
  
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)     

 

 

 

 

2.2.7 時間格式數據處理及查看

#轉化成時間格式
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');
#train 和 test issueDateDT 日期有重疊 所以使用基於時間的分割進行驗證是不明智的

2.2.8 掌握透視圖可以讓我們更好的了解數據

#透視圖 索引可以有多個,“columns(列)”是可選的,聚合函數aggfunc最后是被應用到了變量“values”中你所列舉的項目上。
pivot = pd.pivot_table(data_train, index=['grade'], columns=['issueDateDT'], values=['loanAmnt'], aggfunc=np.sum)

2.3.9 用pandas_profiling生成數據報告

import pandas_profiling
pfr = pandas_profiling.ProfileReport(data_train)
pfr.to_file("./example.html")

 跑這個需要時間有點長,就不跑了


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM