一、賽題數據
數據大家可以到官網去下載: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")
跑這個需要時間有點長,就不跑了