數(shù)據(jù)挖掘?qū)嵺`(金融風(fēng)控):金融風(fēng)控之貸款違約預(yù)測挑戰(zhàn)賽(上篇)[xgboots/lightgbm/Catboost等模型]--模型融合:stacking、blending
1.賽題簡介
賽題以金融風(fēng)控中的個(gè)人信貸為背景,要求選手根據(jù)貸款申請人的數(shù)據(jù)信息預(yù)測其是否有違約的可能,以此判斷是否通過此項(xiàng)貸款,這是一個(gè)典型的分類問題。通過這道賽題來引導(dǎo)大家了解金融風(fēng)控中的一些業(yè)務(wù)背景,解決實(shí)際問題,幫助競賽新人進(jìn)行自我練習(xí)、自我提高。
賽題以預(yù)測金融風(fēng)險(xiǎn)為任務(wù),數(shù)據(jù)集報(bào)名后可見并可下載,該數(shù)據(jù)來自某信貸平臺的貸款記錄,總數(shù)據(jù)量超過120w,包含47列變量信息,其中15列為匿名變量。為了保證比賽的公平性,將會從中抽取80萬條作為訓(xùn)練集,20萬條作為測試集A,20萬條作為測試集B,同時(shí)會對employmentTitle、purpose、postCode和title等信息進(jìn)行脫敏。
比賽地址:https://tianchi.aliyun.com/competition/entrance/531830/introduction
項(xiàng)目鏈接以及碼源見文末
1.1數(shù)據(jù)介紹
賽題以預(yù)測用戶貸款是否違約為任務(wù),數(shù)據(jù)集報(bào)名后可見并可下載,該數(shù)據(jù)來自某信貸平臺的貸款記錄,總數(shù)據(jù)量超過120w,包含47列變量信息,其中15列為匿名變量。為了保證比賽的公平性,將會從中抽取80萬條作為訓(xùn)練集,20萬條作為測試集A,20萬條作為測試集B,同時(shí)會對employmentTitle、purpose、postCode和title等信息進(jìn)行脫敏。
一般而言,對于數(shù)據(jù)在比賽界面都有對應(yīng)的數(shù)據(jù)概況介紹(匿名特征除外),說明列的性質(zhì)特征。了解列的性質(zhì)會有助于我們對于數(shù)據(jù)的理解和后續(xù)分析。 Tip:匿名特征,就是未告知數(shù)據(jù)列所屬的性質(zhì)的特征列。
train.csv
- id 為貸款清單分配的唯一信用證標(biāo)識
- loanAmnt 貸款金額
- term 貸款期限(year)
- interestRate 貸款利率
- installment 分期付款金額
- grade 貸款等級
- subGrade 貸款等級之子級
- employmentTitle 就業(yè)職稱
- employmentLength 就業(yè)年限(年)
- homeOwnership 借款人在登記時(shí)提供的房屋所有權(quán)狀況
- annualIncome 年收入
- verificationStatus 驗(yàn)證狀態(tài)
- issueDate 貸款發(fā)放的月份
- purpose 借款人在貸款申請時(shí)的貸款用途類別
- postCode 借款人在貸款申請中提供的郵政編碼的前3位數(shù)字
- regionCode 地區(qū)編碼
- dti 債務(wù)收入比
- delinquency_2years 借款人過去2年信用檔案中逾期30天以上的違約事件數(shù)
- ficoRangeLow 借款人在貸款發(fā)放時(shí)的fico所屬的下限范圍
- ficoRangeHigh 借款人在貸款發(fā)放時(shí)的fico所屬的上限范圍
- openAcc 借款人信用檔案中未結(jié)信用額度的數(shù)量
- pubRec 貶損公共記錄的數(shù)量
- pubRecBankruptcies 公開記錄清除的數(shù)量
- revolBal 信貸周轉(zhuǎn)余額合計(jì)
- revolUtil 循環(huán)額度利用率,或借款人使用的相對于所有可用循環(huán)信貸的信貸金額
- totalAcc 借款人信用檔案中當(dāng)前的信用額度總數(shù)
- initialListStatus 貸款的初始列表狀態(tài)
- applicationType 表明貸款是個(gè)人申請還是與兩個(gè)共同借款人的聯(lián)合申請
- earliesCreditLine 借款人最早報(bào)告的信用額度開立的月份
- title 借款人提供的貸款名稱
- policyCode 公開可用的策略_代碼=1新產(chǎn)品不公開可用的策略_代碼=2
- n系列匿名特征 匿名特征n0-n14,為一些貸款人行為計(jì)數(shù)特征的處理
1.2預(yù)測指標(biāo)
競賽采用AUC作為評價(jià)指標(biāo)。AUC(Area Under Curve)被定義為 ROC曲線 下與坐標(biāo)軸圍成的面積。
1.2.1 分類算法常見的評估指標(biāo)如下:
1、混淆矩陣(Confuse Matrix)
- (1)若一個(gè)實(shí)例是正類,并且被預(yù)測為正類,即為真正類TP(True Positive )
- (2)若一個(gè)實(shí)例是正類,但是被預(yù)測為負(fù)類,即為假負(fù)類FN(False Negative )
- (3)若一個(gè)實(shí)例是負(fù)類,但是被預(yù)測為正類,即為假正類FP(False Positive )
- (4)若一個(gè)實(shí)例是負(fù)類,并且被預(yù)測為負(fù)類,即為真負(fù)類TN(True Negative )
2、準(zhǔn)確率(Accuracy)
準(zhǔn)確率是常用的一個(gè)評價(jià)指標(biāo),但是不適合樣本不均衡的情況。
$Accuracy = \frac{TP + TN}{TP + TN + FP + FN}$
3、精確率(Precision)
又稱查準(zhǔn)率,正確預(yù)測為正樣本(TP)占預(yù)測為正樣本(TP+FP)的百分比。
$Precision = \frac{TP}{TP + FP}$
4、召回率(Recall)
又稱為查全率,正確預(yù)測為正樣本(TP)占正樣本(TP+FN)的百分比。
$Recall = \frac{TP}{TP + FN}$
5、F1 Score
精確率和召回率是相互影響的,精確率升高則召回率下降,召回率升高則精確率下降,如果需要兼顧二者,就需要精確率、召回率的結(jié)合F1 Score。
$F1-Score = \frac{2}{\frac{1}{Precision} + \frac{1}{Recall}}$
6、P-R曲線(Precision-Recall Curve)
P-R曲線是描述精確率和召回率變化的曲線
7、ROC(Receiver Operating Characteristic)
- ROC空間將假正例率(FPR)定義為 X 軸,真正例率(TPR)定義為 Y 軸。
TPR:在所有實(shí)際為正例的樣本中,被正確地判斷為正例之比率。
$TPR = \frac{TP}{TP + FN}$
FPR:在所有實(shí)際為負(fù)例的樣本中,被錯(cuò)誤地判斷為正例之比率。
$FPR = \frac{FP}{FP + TN}$
8、AUC(Area Under Curve)
AUC(Area Under Curve)被定義為 ROC曲線 下與坐標(biāo)軸圍成的面積,顯然這個(gè)面積的數(shù)值不會大于1。又由于ROC曲線一般都處于y=x這條直線的上方,所以AUC的取值范圍在0.5和1之間。AUC越接近1.0,檢測方法真實(shí)性越高;等于0.5時(shí),則真實(shí)性最低,無應(yīng)用價(jià)值。
1.2.2 對于金融風(fēng)控預(yù)測類常見的評估指標(biāo)如下:
1、KS(Kolmogorov-Smirnov)
KS統(tǒng)計(jì)量由兩位蘇聯(lián)數(shù)學(xué)家A.N. Kolmogorov和N.V. Smirnov提出。在風(fēng)控中,KS常用于評估模型區(qū)分度。區(qū)分度越大,說明模型的風(fēng)險(xiǎn)排序能力(ranking ability)越強(qiáng)。
K-S曲線與ROC曲線類似,不同在于
- ROC曲線將真正例率和假正例率作為橫縱軸
- K-S曲線將真正例率和假正例率都作為縱軸,橫軸則由選定的閾值來充當(dāng)。
公式如下:
$KS=max(TPR-FPR)$
KS不同代表的不同情況,一般情況KS值越大,模型的區(qū)分能力越強(qiáng),但是也不是越大模型效果就越好,如果KS過大,模型可能存在異常,所以當(dāng)KS值過高可能需要檢查模型是否過擬合。以下為KS值對應(yīng)的模型情況,但此對應(yīng)不是唯一的,只代表大致趨勢。
KS(%) | 好壞區(qū)分能力 |
---|---|
20以下 | 不建議采用 |
20-40 | 較好 |
41-50 | 良好 |
51-60 | 很強(qiáng) |
61-75 | 非常強(qiáng) |
75以上 | 過于高,疑似存在問題 |
2、ROC
3、AUC
1.3 項(xiàng)目流程介紹
1.4 代碼示例
本部分為對于數(shù)據(jù)讀取和指標(biāo)評價(jià)的示例。
1.4.1 數(shù)據(jù)讀取pandas
import pandas as pd
train = pd.read_csv('train.csv')
testA = pd.read_csv('testA.csv')
print('Train data shape:',train.shape)
print('TestA data shape:',testA.shape)
Train data shape: (800000, 47)
TestA data shape: (200000, 48)
train.head()
1.4.2 分類指標(biāo)評價(jià)計(jì)算示例
## 混淆矩陣
import numpy as np
from sklearn.metrics import confusion_matrix
y_pred = [0, 1, 0, 1]
y_true = [0, 1, 1, 0]
print('混淆矩陣:\n',confusion_matrix(y_true, y_pred))
混淆矩陣:
[[1 1]
[1 1]]
## accuracy
from sklearn.metrics import accuracy_score
y_pred = [0, 1, 0, 1]
y_true = [0, 1, 1, 0]
print('ACC:',accuracy_score(y_true, y_pred))
ACC: 0.5
## Precision,Recall,F1-score
from sklearn import metrics
y_pred = [0, 1, 0, 1]
y_true = [0, 1, 1, 0]
print('Precision',metrics.precision_score(y_true, y_pred))
print('Recall',metrics.recall_score(y_true, y_pred))
print('F1-score:',metrics.f1_score(y_true, y_pred))
Precision 0.5
Recall 0.5
F1-score: 0.5
## P-R曲線
import matplotlib.pyplot as plt
from sklearn.metrics import precision_recall_curve
y_pred = [0, 1, 1, 0, 1, 1, 0, 1, 1, 1]
y_true = [0, 1, 1, 0, 1, 0, 1, 1, 0, 1]
precision, recall, thresholds = precision_recall_curve(y_true, y_pred)
plt.plot(precision, recall)
[<matplotlib.lines.Line2D at 0x2170d0d6108>]
## ROC曲線
from sklearn.metrics import roc_curve
y_pred = [0, 1, 1, 0, 1, 1, 0, 1, 1, 1]
y_true = [0, 1, 1, 0, 1, 0, 1, 1, 0, 1]
FPR,TPR,thresholds=roc_curve(y_true, y_pred)
plt.title('ROC')
plt.plot(FPR, TPR,'b')
plt.plot([0,1],[0,1],'r--')
plt.ylabel('TPR')
plt.xlabel('FPR')
Text(0.5, 0, 'FPR')
## AUC
import numpy as np
from sklearn.metrics import roc_auc_score
y_true = np.array([0, 0, 1, 1])
y_scores = np.array([0.1, 0.4, 0.35, 0.8])
print('AUC socre:',roc_auc_score(y_true, y_scores))
AUC socre: 0.75
## KS值 在實(shí)際操作時(shí)往往使用ROC曲線配合求出KS值
from sklearn.metrics import roc_curve
y_pred = [0, 1, 1, 0, 1, 1, 0, 1, 1, 1]
y_true = [0, 1, 1, 0, 1, 0, 1, 1, 1, 1]
FPR,TPR,thresholds=roc_curve(y_true, y_pred)
KS=abs(FPR-TPR).max()
print('KS值:',KS)
KS值: 0.5238095238095237
1.5 拓展知識——評分卡
評分卡是一張擁有分?jǐn)?shù)刻度會讓相應(yīng)閾值的表。信用評分卡是用于用戶信用的一張刻度表。以下代碼是一個(gè)非標(biāo)準(zhǔn)評分卡的代碼流程,用于刻畫用戶的信用評分。評分卡是金融風(fēng)控中常用的一種對于用戶信用進(jìn)行刻畫的手段哦!
#評分卡 不是標(biāo)準(zhǔn)評分卡
def Score(prob,P0=600,PDO=20,badrate=None,goodrate=None):
P0 = P0
PDO = PDO
theta0 = badrate/goodrate
B = PDO/np.log(2)
A = P0 + B*np.log(2*theta0)
score = A-B*np.log(prob/(1-prob))
return score
2.數(shù)據(jù)探索性分析
目的:
-
1.EDA價(jià)值主要在于熟悉了解整個(gè)數(shù)據(jù)集的基本情況(缺失值,異常值),對數(shù)據(jù)集進(jìn)行驗(yàn)證是否可以進(jìn)行接下來的機(jī)器學(xué)習(xí)或者深度學(xué)習(xí)建模.
-
2.了解變量間的相互關(guān)系、變量與預(yù)測值之間的存在關(guān)系。
-
3.為特征工程做準(zhǔn)備
2.1 代碼示例
2.1.1 導(dǎo)入數(shù)據(jù)分析及可視化過程需要的庫
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import datetime
import warnings
warnings.filterwarnings('ignore')
/Users/exudingtao/opt/anaconda3/lib/python3.7/site-packages/statsmodels/tools/_testing.py:19: FutureWarning: pandas.util.testing is deprecated. Use the functions in the public API at pandas.testing instead.
import pandas.util.testing as tm
以上庫都是pip install 安裝就好,如果本機(jī)有python2,python3兩個(gè)python環(huán)境傻傻分不清哪個(gè)的話,可以pip3 install ?;蛘咧苯釉趎otebook中'!pip3 install ****'安裝。
2.1.2 讀取文件
data_train = pd.read_csv('./train.csv')
data_test_a = pd.read_csv('./testA.csv')
- 讀取文件的拓展知識
- pandas讀取數(shù)據(jù)時(shí)相對路徑載入報(bào)錯(cuò)時(shí),嘗試使用os.getcwd()查看當(dāng)前工作目錄。
- TSV與CSV的區(qū)別:
- 從名稱上即可知道,TSV是用制表符(Tab,'\t')作為字段值的分隔符;CSV是用半角逗號(',')作為字段值的分隔符;
- Python對TSV文件的支持:
Python的csv模塊準(zhǔn)確的講應(yīng)該叫做dsv模塊,因?yàn)樗鼘?shí)際上是支持范式的分隔符分隔值文件(DSV,delimiter-separated values)的。
delimiter參數(shù)值默認(rèn)為半角逗號,即默認(rèn)將被處理文件視為CSV。當(dāng)delimiter='\t'時(shí),被處理文件就是TSV。
- 讀取文件的部分(適用于文件特別大的場景)
- 通過nrows參數(shù),來設(shè)置讀取文件的前多少行,nrows是一個(gè)大于等于0的整數(shù)。
- 分塊讀取
data_train_sample = pd.read_csv("./train.csv",nrows=5)
#設(shè)置chunksize參數(shù),來控制每次迭代數(shù)據(jù)的大小
chunker = pd.read_csv("./train.csv",chunksize=5)
for item in chunker:
print(type(item))
#<class 'pandas.core.frame.DataFrame'>
print(len(item))
#5
查看數(shù)據(jù)集的樣本個(gè)數(shù)和原始特征維度
data_test_a.shape
(200000, 48)
data_train.shape
(800000, 47)
data_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', 'n2.1', 'n4', 'n5', 'n6', 'n7', 'n8',
'n9', 'n10', 'n11', 'n12', 'n13', 'n14'],
dtype='object')
查看一下具體的列名,賽題理解部分已經(jīng)給出具體的特征含義,這里方便閱讀再給一下:
- id 為貸款清單分配的唯一信用證標(biāo)識
- loanAmnt 貸款金額
- term 貸款期限(year)
- interestRate 貸款利率
- installment 分期付款金額
- grade 貸款等級
- subGrade 貸款等級之子級
- employmentTitle 就業(yè)職稱
- employmentLength 就業(yè)年限(年)
- homeOwnership 借款人在登記時(shí)提供的房屋所有權(quán)狀況
- annualIncome 年收入
- verificationStatus 驗(yàn)證狀態(tài)
- issueDate 貸款發(fā)放的月份
- purpose 借款人在貸款申請時(shí)的貸款用途類別
- postCode 借款人在貸款申請中提供的郵政編碼的前3位數(shù)字
- regionCode 地區(qū)編碼
- dti 債務(wù)收入比
- delinquency_2years 借款人過去2年信用檔案中逾期30天以上的違約事件數(shù)
- ficoRangeLow 借款人在貸款發(fā)放時(shí)的fico所屬的下限范圍
- ficoRangeHigh 借款人在貸款發(fā)放時(shí)的fico所屬的上限范圍
- openAcc 借款人信用檔案中未結(jié)信用額度的數(shù)量
- pubRec 貶損公共記錄的數(shù)量
- pubRecBankruptcies 公開記錄清除的數(shù)量
- revolBal 信貸周轉(zhuǎn)余額合計(jì)
- revolUtil 循環(huán)額度利用率,或借款人使用的相對于所有可用循環(huán)信貸的信貸金額
- totalAcc 借款人信用檔案中當(dāng)前的信用額度總數(shù)
- initialListStatus 貸款的初始列表狀態(tài)
- applicationType 表明貸款是個(gè)人申請還是與兩個(gè)共同借款人的聯(lián)合申請
- earliesCreditLine 借款人最早報(bào)告的信用額度開立的月份
- title 借款人提供的貸款名稱
- policyCode 公開可用的策略_代碼=1新產(chǎn)品不公開可用的策略_代碼=2
- n系列匿名特征 匿名特征n0-n14,為一些貸款人行為計(jì)數(shù)特征的處理
通過info()來熟悉數(shù)據(jù)類型
data_train.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 n2.1 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
總體粗略的查看數(shù)據(jù)集各個(gè)特征的一些基本統(tǒng)計(jì)量
data_train.describe()
data_train.head(3).append(data_train.tail(3))
2.1.3查看數(shù)據(jù)集中特征缺失值,唯一值等
查看缺失值
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.
上面得到訓(xùn)練集有22列特征有缺失值,進(jìn)一步查看缺失特征中缺失率大于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
fea_null_moreThanHalf
{}
具體的查看缺失特征及缺失率
#nan可視化
missing = data_train.isnull().sum()/len(data_train)
missing = missing[missing > 0]
missing.sort_values(inplace=True)
missing.plot.bar()
<matplotlib.axes._subplots.AxesSubplot at 0x1229ab890>
- 縱向了解哪些列存在 “nan”, 并可以把nan的個(gè)數(shù)打印,主要的目的在于查看某一列nan存在的個(gè)數(shù)是否真的很大,如果nan存在的過多,說明這一列對label的影響幾乎不起作用了,可以考慮刪掉。如果缺失值很小一般可以選擇填充。
- 另外可以橫向比較,如果在數(shù)據(jù)集中,某些樣本數(shù)據(jù)的大部分列都是缺失的且樣本足夠的情況下可以考慮刪除。
Tips:
比賽大殺器lgb模型可以自動處理缺失值,Task4模型會具體學(xué)習(xí)模型了解模型哦!
查看訓(xùn)練集測試集中特征屬性只有一值的特征
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]
one_value_fea
['policyCode']
one_value_fea_test
['policyCode']
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.')
There are 1 columns in train dataset with one unique value.
There are 1 columns in test dataset with one unique value.
47列數(shù)據(jù)中有22列都缺少數(shù)據(jù),這在現(xiàn)實(shí)世界中很正常?!畃olicyCode’具有一個(gè)唯一值(或全部缺失)。有很多連續(xù)變量和一些分類變量。
2.1.4 查看特征的數(shù)值類型有哪些,對象類型有哪些
- 特征一般都是由類別型特征和數(shù)值型特征組成,而數(shù)值型特征又分為連續(xù)型和離散型。
- 類別型特征有時(shí)具有非數(shù)值關(guān)系,有時(shí)也具有數(shù)值關(guān)系。比如‘grade’中的等級A,B,C等,是否只是單純的分類,還是A優(yōu)于其他要結(jié)合業(yè)務(wù)判斷。
- 數(shù)值型特征本是可以直接入模的,但往往風(fēng)控人員要對其做分箱,轉(zhuǎn)化為WOE編碼進(jìn)而做標(biāo)準(zhǔn)評分卡等操作。從模型效果上來看,特征分箱主要是為了降低變量的復(fù)雜性,減少變量噪音對模型的影響,提高自變量和因變量的相關(guān)度。從而使模型更加穩(wěn)定。
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)))
numerical_fea
category_fea
['grade', 'subGrade', 'employmentLength', 'issueDate', 'earliesCreditLine']
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
- 劃分?jǐn)?shù)值型變量中的連續(xù)變量和離散型變量
#過濾數(shù)值型類別特征
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)
numerical_serial_fea
numerical_noserial_fea
['term',
'homeOwnership',
'verificationStatus',
'isDefault',
'initialListStatus',
'applicationType',
'policyCode',
'n11',
'n12']
- 數(shù)值類別型變量分析
data_train['term'].value_counts()#離散型變量
3 606902
5 193098
Name: term, dtype: int64
data_train['homeOwnership'].value_counts()#離散型變量
0 395732
1 317660
2 86309
3 185
5 81
4 33
Name: homeOwnership, dtype: int64
data_train['verificationStatus'].value_counts()#離散型變量
1 309810
2 248968
0 241222
Name: verificationStatus, dtype: int64
data_train['initialListStatus'].value_counts()#離散型變量
0 466438
1 333562
Name: initialListStatus, dtype: int64
data_train['applicationType'].value_counts()#離散型變量
0 784586
1 15414
Name: applicationType, dtype: int64
data_train['policyCode'].value_counts()#離散型變量,無用,全部一個(gè)值
1.0 800000
Name: policyCode, dtype: int64
data_train['n11'].value_counts()#離散型變量,相差懸殊,用不用再分析
0.0 729682
1.0 540
2.0 24
4.0 1
3.0 1
Name: n11, dtype: int64
data_train['n12'].value_counts()#離散型變量,相差懸殊,用不用再分析
0.0 757315
1.0 2281
2.0 115
3.0 16
4.0 3
Name: n12, dtype: int64
- 數(shù)值連續(xù)型變量分析
#每個(gè)數(shù)字特征得分布可視化
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")
- 查看某一個(gè)數(shù)值型變量的分布,查看變量是否符合正態(tài)分布,如果不符合正太分布的變量可以log化后再觀察下是否符合正態(tài)分布。
- 如果想統(tǒng)一處理一批數(shù)據(jù)變標(biāo)準(zhǔn)化 必須把這些之前已經(jīng)正態(tài)化的數(shù)據(jù)提出
- 正態(tài)化的原因:一些情況下正態(tài)非正態(tài)可以讓模型更快的收斂,一些模型要求數(shù)據(jù)正態(tài)(eg. GMM、KNN),保證數(shù)據(jù)不要過偏態(tài)即可,過于偏態(tài)可能會影響模型預(yù)測結(jié)果。
#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)
Text(0, 0.5, 'Probability')
- 非數(shù)值類別型變量分析
category_fea
['grade', 'subGrade', 'employmentLength', 'issueDate', 'earliesCreditLine']
data_train['grade'].value_counts()
B 233690
C 227118
A 139661
D 119453
E 55661
F 19053
G 5364
Name: grade, dtype: int64
data_train['subGrade'].value_counts()
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
data_train['employmentLength'].value_counts()
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
data_train['issueDate'].value_counts()
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
data_train['earliesCreditLine'].value_counts()
Aug-2001 5567
Sep-2003 5403
Aug-2002 5403
Oct-2001 5258
Aug-2000 5246
...
May-1960 1
Apr-1958 1
Feb-1960 1
Aug-1946 1
Mar-1958 1
Name: earliesCreditLine, Length: 720, dtype: int64
data_train['isDefault'].value_counts()
0 640390
1 159610
Name: isDefault, dtype: int64
2.1.4 小結(jié):
- 上面我們用value_counts()等函數(shù)看了特征屬性的分布,但是圖表是概括原始信息最便捷的方式。
- 數(shù)無形時(shí)少直覺。
- 同一份數(shù)據(jù)集,在不同的尺度刻畫上顯示出來的圖形反映的規(guī)律是不一樣的。python將數(shù)據(jù)轉(zhuǎn)化成圖表,但結(jié)論是否正確需要由你保證。
2.2 變量分布可視化
2.2.1單一變量分布可視化
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()
2.2.2根絕y值不同可視化x某個(gè)特征的分布
- 首先查看類別型變量在不同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()
- 其次查看連續(xù)型變量在不同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)
<matplotlib.axes._subplots.AxesSubplot at 0x126a44b50>
total = len(data_train)
total_amt = data_train.groupby(['isDefault'])['loanAmnt'].sum().sum()
plt.figure(figsize=(12,5))
plt.subplot(121)##1代表行,2代表列,所以一共有2個(gè)圖,1代表此時(shí)繪制第一個(gè)圖。
plot_tr = sns.countplot(x='isDefault',data=data_train)#data_train‘isDefault’這個(gè)特征每種類別的數(shù)量**
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)
2.2.3 時(shí)間格式數(shù)據(jù)處理及查看
#轉(zhuǎn)化成時(shí)間格式 issueDateDT特征表示數(shù)據(jù)日期離數(shù)據(jù)集中日期最早的日期(2007-06-01)的天數(shù)
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
#轉(zhuǎn)化成時(shí)間格式
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 日期有重疊 所以使用基于時(shí)間的分割進(jìn)行驗(yàn)證是不明智的
2.3.4 掌握透視圖可以讓我們更好的了解數(shù)據(jù)
#透視圖 索引可以有多個(gè),“columns(列)”是可選的,聚合函數(shù)aggfunc最后是被應(yīng)用到了變量“values”中你所列舉的項(xiàng)目上。
pivot = pd.pivot_table(data_train, index=['grade'], columns=['issueDateDT'], values=['loanAmnt'], aggfunc=np.sum)
pivot
2.3. 用pandas_profiling生成數(shù)據(jù)報(bào)告
import pandas_profiling
pfr = pandas_profiling.ProfileReport(data_train)
pfr.to_file("./example.html")
2.4 總結(jié)
數(shù)據(jù)探索性分析是我們初步了解數(shù)據(jù),熟悉數(shù)據(jù)為特征工程做準(zhǔn)備的階段,甚至很多時(shí)候EDA階段提取出來的特征可以直接當(dāng)作規(guī)則來用??梢奅DA的重要性,這個(gè)階段的主要工作還是借助于各個(gè)簡單的統(tǒng)計(jì)量來對數(shù)據(jù)整體的了解,分析各個(gè)類型變量相互之間的關(guān)系,以及用合適的圖形可視化出來直觀觀察。希望本節(jié)內(nèi)容能給初學(xué)者帶來幫助,更期待各位學(xué)習(xí)者對其中的不足提出建議。
3.特征工程
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import datetime
from tqdm import tqdm
from sklearn.preprocessing import LabelEncoder
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2
from sklearn.preprocessing import MinMaxScaler
import xgboost as xgb
import lightgbm as lgb
from catboost import CatBoostRegressor
import warnings
from sklearn.model_selection import StratifiedKFold, KFold
from sklearn.metrics import accuracy_score, f1_score, roc_auc_score, log_loss
warnings.filterwarnings('ignore')
data_train =pd.read_csv('../train.csv')
data_test_a = pd.read_csv('../testA.csv')
3.1 特征預(yù)處理
- 數(shù)據(jù)EDA部分我們已經(jīng)對數(shù)據(jù)的大概和某些特征分布有了了解,數(shù)據(jù)預(yù)處理部分一般我們要處理一些EDA階段分析出來的問題,這里介紹了數(shù)據(jù)缺失值的填充,時(shí)間格式特征的轉(zhuǎn)化處理,某些對象類別特征的處理。
首先我們查找出數(shù)據(jù)中的對象特征和數(shù)值特征
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)))
label = 'isDefault'
numerical_fea.remove(label)
在比賽中數(shù)據(jù)預(yù)處理是必不可少的一部分,對于缺失值的填充往往會影響比賽的結(jié)果,在比賽中不妨嘗試多種填充然后比較結(jié)果選擇結(jié)果最優(yōu)的一種;
比賽數(shù)據(jù)相比真實(shí)場景的數(shù)據(jù)相對要“干凈”一些,但是還是會有一定的“臟”數(shù)據(jù)存在,清洗一些異常值往往會獲得意想不到的效果。
-
把所有缺失值替換為指定的值0
data_train = data_train.fillna(0)
-
向用缺失值上面的值替換缺失值
data_train = data_train.fillna(axis=0,method='ffill')
-
縱向用缺失值下面的值替換缺失值,且設(shè)置最多只填充兩個(gè)連續(xù)的缺失值
data_train = data_train.fillna(axis=0,method='bfill',limit=2)
#查看缺失值情況
data_train.isnull().sum()
#按照平均數(shù)填充數(shù)值型特征
data_train[numerical_fea] = data_train[numerical_fea].fillna(data_train[numerical_fea].median())
data_test_a[numerical_fea] = data_test_a[numerical_fea].fillna(data_train[numerical_fea].median())
#按照眾數(shù)填充類別型特征
data_train[category_fea] = data_train[category_fea].fillna(data_train[category_fea].mode())
data_test_a[category_fea] = data_test_a[category_fea].fillna(data_train[category_fea].mode())
data_train.isnull().sum()
#查看類別特征
category_fea
['grade', 'subGrade', 'employmentLength', 'issueDate', 'earliesCreditLine']
- category_fea:對象型類別特征需要進(jìn)行預(yù)處理,其中['issueDate']為時(shí)間格式特征。
#轉(zhuǎn)化成時(shí)間格式
for data in [data_train, data_test_a]:
data['issueDate'] = pd.to_datetime(data['issueDate'],format='%Y-%m-%d')
startdate = datetime.datetime.strptime('2007-06-01', '%Y-%m-%d')
#構(gòu)造時(shí)間特征
data['issueDateDT'] = data['issueDate'].apply(lambda x: x-startdate).dt.days
data_train['employmentLength'].value_counts(dropna=False).sort_index()
1 year 52489
10+ years 262753
2 years 72358
3 years 64152
4 years 47985
5 years 50102
6 years 37254
7 years 35407
8 years 36192
9 years 30272
< 1 year 64237
NaN 46799
Name: employmentLength, dtype: int64
def employmentLength_to_int(s):
if pd.isnull(s):
return s
else:
return np.int8(s.split()[0])
for data in [data_train, data_test_a]:
data['employmentLength'].replace(to_replace='10+ years', value='10 years', inplace=True)
data['employmentLength'].replace('< 1 year', '0 years', inplace=True)
data['employmentLength'] = data['employmentLength'].apply(employmentLength_to_int)
data['employmentLength'].value_counts(dropna=False).sort_index()
0.0 15989
1.0 13182
2.0 18207
3.0 16011
4.0 11833
5.0 12543
6.0 9328
7.0 8823
8.0 8976
9.0 7594
10.0 65772
NaN 11742
Name: employmentLength, dtype: int64
- 對earliesCreditLine進(jìn)行預(yù)處理
data_train['earliesCreditLine'].sample(5)
519915 Sep-2002
564368 Dec-1996
768209 May-2004
453092 Nov-1995
763866 Sep-2000
Name: earliesCreditLine, dtype: object
for data in [data_train, data_test_a]:
data['earliesCreditLine'] = data['earliesCreditLine'].apply(lambda s: int(s[-4:]))
# 部分類別特征
cate_features = ['grade', 'subGrade', 'employmentTitle', 'homeOwnership', 'verificationStatus', 'purpose', 'postCode', 'regionCode', \
'applicationType', 'initialListStatus', 'title', 'policyCode']
for f in cate_features:
print(f, '類型數(shù):', data[f].nunique())
grade 類型數(shù): 7
subGrade 類型數(shù): 35
employmentTitle 類型數(shù): 79282
homeOwnership 類型數(shù): 6
verificationStatus 類型數(shù): 3
purpose 類型數(shù): 14
postCode 類型數(shù): 889
regionCode 類型數(shù): 51
applicationType 類型數(shù): 2
initialListStatus 類型數(shù): 2
title 類型數(shù): 12058
policyCode 類型數(shù): 1
像等級這種類別特征,是有優(yōu)先級的可以labelencode或者自映射
for data in [data_train, data_test_a]:
data['grade'] = data['grade'].map({'A':1,'B':2,'C':3,'D':4,'E':5,'F':6,'G':7})
# 類型數(shù)在2之上,又不是高維稀疏的,且純分類特征
for data in [data_train, data_test_a]:
data = pd.get_dummies(data, columns=['subGrade', 'homeOwnership', 'verificationStatus', 'purpose', 'regionCode'], drop_first=True)
3.2 異常值處理
- 當(dāng)你發(fā)現(xiàn)異常值后,一定要先分清是什么原因?qū)е碌漠惓V?,然后再考慮如何處理。首先,如果這一異常值并不代表一種規(guī)律性的,而是極其偶然的現(xiàn)象,或者說你并不想研究這種偶然的現(xiàn)象,這時(shí)可以將其刪除。其次,如果異常值存在且代表了一種真實(shí)存在的現(xiàn)象,那就不能隨便刪除。在現(xiàn)有的欺詐場景中很多時(shí)候欺詐數(shù)據(jù)本身相對于正常數(shù)據(jù)勒說就是異常的,我們要把這些異常點(diǎn)納入,重新擬合模型,研究其規(guī)律。能用監(jiān)督的用監(jiān)督模型,不能用的還可以考慮用異常檢測的算法來做。
- 注意test的數(shù)據(jù)不能刪。
3.2.1 檢測異常的方法一:均方差
在統(tǒng)計(jì)學(xué)中,如果一個(gè)數(shù)據(jù)分布近似正態(tài),那么大約 68% 的數(shù)據(jù)值會在均值的一個(gè)標(biāo)準(zhǔn)差范圍內(nèi),大約 95% 會在兩個(gè)標(biāo)準(zhǔn)差范圍內(nèi),大約 99.7% 會在三個(gè)標(biāo)準(zhǔn)差范圍內(nèi)。
def find_outliers_by_3segama(data,fea):
data_std = np.std(data[fea])
data_mean = np.mean(data[fea])
outliers_cut_off = data_std * 3
lower_rule = data_mean - outliers_cut_off
upper_rule = data_mean + outliers_cut_off
data[fea+'_outliers'] = data[fea].apply(lambda x:str('異常值') if x > upper_rule or x < lower_rule else '正常值')
return data
- 得到特征的異常值后可以進(jìn)一步分析變量異常值和目標(biāo)變量的關(guān)系
data_train = data_train.copy()
for fea in numerical_fea:
data_train = find_outliers_by_3segama(data_train,fea)
print(data_train[fea+'_outliers'].value_counts())
print(data_train.groupby(fea+'_outliers')['isDefault'].sum())
print('*'*10)
- 例如可以看到異常值在兩個(gè)變量上的分布幾乎復(fù)合整體的分布,如果異常值都屬于為1的用戶數(shù)據(jù)里面代表什么呢?
#刪除異常值
for fea in numerical_fea:
data_train = data_train[data_train[fea+'_outliers']=='正常值']
data_train = data_train.reset_index(drop=True)
3.2.1檢測異常的方法二:箱型圖
- 總結(jié)一句話:四分位數(shù)會將數(shù)據(jù)分為三個(gè)點(diǎn)和四個(gè)區(qū)間,IQR = Q3 -Q1,下觸須=Q1 ? 1.5x IQR,上觸須=Q3 + 1.5x IQR;
3.3 數(shù)據(jù)分桶
-
特征分箱的目的:
- 從模型效果上來看,特征分箱主要是為了降低變量的復(fù)雜性,減少變量噪音對模型的影響,提高自變量和因變量的相關(guān)度。從而使模型更加穩(wěn)定。
-
數(shù)據(jù)分桶的對象:
- 將連續(xù)變量離散化
- 將多狀態(tài)的離散變量合并成少狀態(tài)
-
分箱的原因:
- 數(shù)據(jù)的特征內(nèi)的值跨度可能比較大,對有監(jiān)督和無監(jiān)督中如k-均值聚類它使用歐氏距離作為相似度函數(shù)來測量數(shù)據(jù)點(diǎn)之間的相似度。都會造成大吃小的影響,其中一種解決方法是對計(jì)數(shù)值進(jìn)行區(qū)間量化即數(shù)據(jù)分桶也叫做數(shù)據(jù)分箱,然后使用量化后的結(jié)果。
-
分箱的優(yōu)點(diǎn):
- 處理缺失值:當(dāng)數(shù)據(jù)源可能存在缺失值,此時(shí)可以把null單獨(dú)作為一個(gè)分箱。
- 處理異常值:當(dāng)數(shù)據(jù)中存在離群點(diǎn)時(shí),可以把其通過分箱離散化處理,從而提高變量的魯棒性(抗干擾能力)。例如,age若出現(xiàn)200這種異常值,可分入“age > 60”這個(gè)分箱里,排除影響。
- 業(yè)務(wù)解釋性:我們習(xí)慣于線性判斷變量的作用,當(dāng)x越來越大,y就越來越大。但實(shí)際x與y之間經(jīng)常存在著非線性關(guān)系,此時(shí)可經(jīng)過WOE變換。
-
特別要注意一下分箱的基本原則:
- (1)最小分箱占比不低于5%
- (2)箱內(nèi)不能全部是好客戶
- (3)連續(xù)箱單調(diào)
- 固定寬度分箱
當(dāng)數(shù)值橫跨多個(gè)數(shù)量級時(shí),最好按照 10 的冪(或任何常數(shù)的冪)來進(jìn)行分組:09、1099、100999、10009999,等等。固定寬度分箱非常容易計(jì)算,但如果計(jì)數(shù)值中有比較大的缺口,就會產(chǎn)生很多沒有任何數(shù)據(jù)的空箱子。
#通過除法映射到間隔均勻的分箱中,每個(gè)分箱的取值范圍都是loanAmnt/1000
data['loanAmnt_bin1'] = np.floor_divide(data['loanAmnt'], 1000)
##通過對數(shù)函數(shù)映射到指數(shù)寬度分箱
data['loanAmnt_bin2'] = np.floor(np.log10(data['loanAmnt']))
- 分位數(shù)分箱
data['loanAmnt_bin3'] = pd.qcut(data['loanAmnt'], 10, labels=False)
- 卡方分箱及其他分箱方法的嘗試
- 這一部分屬于進(jìn)階部分,學(xué)有余力的同學(xué)可以自行搜索嘗試。
3.4 特征交互
- 交互特征的構(gòu)造非常簡單,使用起來卻代價(jià)不菲。如果線性模型中包含有交互特征對,那它的訓(xùn)練時(shí)間和評分時(shí)間就會從 O(n) 增加到 O(n2),其中 n 是單一特征的數(shù)量。
for col in ['grade', 'subGrade']:
temp_dict = data_train.groupby([col])['isDefault'].agg(['mean']).reset_index().rename(columns={'mean': col + '_target_mean'})
temp_dict.index = temp_dict[col].values
temp_dict = temp_dict[col + '_target_mean'].to_dict()
data_train[col + '_target_mean'] = data_train[col].map(temp_dict)
data_test_a[col + '_target_mean'] = data_test_a[col].map(temp_dict)
# 其他衍生變量 mean 和 std
for df in [data_train, data_test_a]:
for item in ['n0','n1','n2','n2.1','n4','n5','n6','n7','n8','n9','n10','n11','n12','n13','n14']:
df['grade_to_mean_' + item] = df['grade'] / df.groupby([item])['grade'].transform('mean')
df['grade_to_std_' + item] = df['grade'] / df.groupby([item])['grade'].transform('std')
這里給出一些特征交互的思路,但特征和特征間的交互衍生出新的特征還遠(yuǎn)遠(yuǎn)不止于此,拋磚引玉,希望大家多多探索。請學(xué)習(xí)者嘗試其他的特征交互方法。
3.5 特征編碼
3.5.1labelEncode 直接放入樹模型中
#label-encode:subGrade,postCode,title
#高維類別特征需要進(jìn)行轉(zhuǎn)換
for col in tqdm(['employmentTitle', 'postCode', 'title','subGrade']):
le = LabelEncoder()
le.fit(list(data_train[col].astype(str).values) + list(data_test_a[col].astype(str).values))
data_train[col] = le.transform(list(data_train[col].astype(str).values))
data_test_a[col] = le.transform(list(data_test_a[col].astype(str).values))
print('Label Encoding 完成')
100%|██████████| 4/4 [00:08<00:00, 2.04s/it]
Label Encoding 完成
3.5.2邏輯回歸等模型要單獨(dú)增加的特征工程
- 對特征做歸一化,去除相關(guān)性高的特征
- 歸一化目的是讓訓(xùn)練過程更好更快的收斂,避免特征大吃小的問題
- 去除相關(guān)性是增加模型的可解釋性,加快預(yù)測過程。
# 舉例歸一化過程
#偽代碼
for fea in [要?dú)w一化的特征列表]:
data[fea] = ((data[fea] - np.min(data[fea])) / (np.max(data[fea]) - np.min(data[fea])))
3.6 特征選擇
- 特征選擇技術(shù)可以精簡掉無用的特征,以降低最終模型的復(fù)雜性,它的最終目的是得到一個(gè)簡約模型,在不降低預(yù)測準(zhǔn)確率或?qū)︻A(yù)測準(zhǔn)確率影響不大的情況下提高計(jì)算速度。特征選擇不是為了減少訓(xùn)練時(shí)間(實(shí)際上,一些技術(shù)會增加總體訓(xùn)練時(shí)間),而是為了減少模型評分時(shí)間。
特征選擇的方法:
- 1 Filter
- 方差選擇法
- 相關(guān)系數(shù)法(pearson 相關(guān)系數(shù))
- 卡方檢驗(yàn)
- 互信息法
- 2 Wrapper (RFE)
- 遞歸特征消除法
- 3 Embedded
- 基于懲罰項(xiàng)的特征選擇法
- 基于樹模型的特征選擇
3.6.1Filter(方差選擇法、相關(guān)系數(shù)法)
- 基于特征間的關(guān)系進(jìn)行篩選
方差選擇法
- 方差選擇法中,先要計(jì)算各個(gè)特征的方差,然后根據(jù)設(shè)定的閾值,選擇方差大于閾值的特征
from sklearn.feature_selection import VarianceThreshold
#其中參數(shù)threshold為方差的閾值
VarianceThreshold(threshold=3).fit_transform(train,target_train)
相關(guān)系數(shù)法
- Pearson 相關(guān)系數(shù)
皮爾森相關(guān)系數(shù)是一種最簡單的,可以幫助理解特征和響應(yīng)變量之間關(guān)系的方法,該方法衡量的是變量之間的線性相關(guān)性。
結(jié)果的取值區(qū)間為 [-1,1] , -1 表示完全的負(fù)相關(guān), +1表示完全的正相關(guān),0 表示沒有線性相關(guān)。
from sklearn.feature_selection import SelectKBest
from scipy.stats import pearsonr
#選擇K個(gè)最好的特征,返回選擇特征后的數(shù)據(jù)
#第一個(gè)參數(shù)為計(jì)算評估特征是否好的函數(shù),該函數(shù)輸入特征矩陣和目標(biāo)向量,
#輸出二元組(評分,P值)的數(shù)組,數(shù)組第i項(xiàng)為第i個(gè)特征的評分和P值。在此定義為計(jì)算相關(guān)系數(shù)
#參數(shù)k為選擇的特征個(gè)數(shù)
SelectKBest(k=5).fit_transform(train,target_train)
卡方檢驗(yàn)
- 經(jīng)典的卡方檢驗(yàn)是用于檢驗(yàn)自變量對因變量的相關(guān)性。 假設(shè)自變量有N種取值,因變量有M種取值,考慮自變量等于i且因變量等于j的樣本頻數(shù)的觀察值與期望的差距。 其統(tǒng)計(jì)量如下: χ2=∑(A?T)2T,其中A為實(shí)際值,T為理論值
- (注:卡方只能運(yùn)用在正定矩陣上,否則會報(bào)錯(cuò)Input X must be non-negative)
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2
#參數(shù)k為選擇的特征個(gè)數(shù)
SelectKBest(chi2, k=5).fit_transform(train,target_train)
互信息法
- 經(jīng)典的互信息也是評價(jià)自變量對因變量的相關(guān)性的。 在feature_selection庫的SelectKBest類結(jié)合最大信息系數(shù)法可以用于選擇特征,相關(guān)代碼如下:
from sklearn.feature_selection import SelectKBest
from minepy import MINE
#由于MINE的設(shè)計(jì)不是函數(shù)式的,定義mic方法將其為函數(shù)式的,
#返回一個(gè)二元組,二元組的第2項(xiàng)設(shè)置成固定的P值0.5
def mic(x, y):
m = MINE()
m.compute_score(x, y)
return (m.mic(), 0.5)
#參數(shù)k為選擇的特征個(gè)數(shù)
SelectKBest(lambda X, Y: array(map(lambda x:mic(x, Y), X.T)).T, k=2).fit_transform(train,target_train)
3.6.2Wrapper (遞歸特征法)
- 遞歸特征消除法 遞歸消除特征法使用一個(gè)基模型來進(jìn)行多輪訓(xùn)練,每輪訓(xùn)練后,消除若干權(quán)值系數(shù)的特征,再基于新的特征集進(jìn)行下一輪訓(xùn)練。 在feature_selection庫的RFE類可以用于選擇特征,相關(guān)代碼如下(以邏輯回歸為例):
from sklearn.feature_selection import RFE
from sklearn.linear_model import LogisticRegression
#遞歸特征消除法,返回特征選擇后的數(shù)據(jù)
#參數(shù)estimator為基模型
#參數(shù)n_features_to_select為選擇的特征個(gè)數(shù)
RFE(estimator=LogisticRegression(), n_features_to_select=2).fit_transform(train,target_train)
3.6.3Embedded( 懲罰項(xiàng)的特征選擇法、樹模型的特征選擇)
- 基于懲罰項(xiàng)的特征選擇法 使用帶懲罰項(xiàng)的基模型,除了篩選出特征外,同時(shí)也進(jìn)行了降維。 在feature_selection庫的SelectFromModel類結(jié)合邏輯回歸模型可以用于選擇特征,相關(guān)代碼如下:
from sklearn.feature_selection import SelectFromModel
from sklearn.linear_model import LogisticRegression
#帶L1懲罰項(xiàng)的邏輯回歸作為基模型的特征選擇
SelectFromModel(LogisticRegression(penalty="l1", C=0.1)).fit_transform(train,target_train)
- 基于樹模型的特征選擇 樹模型中GBDT也可用來作為基模型進(jìn)行特征選擇。 在feature_selection庫的SelectFromModel類結(jié)合GBDT模型可以用于選擇特征,相關(guān)代碼如下:
from sklearn.feature_selection import SelectFromModel
from sklearn.ensemble import GradientBoostingClassifier
#GBDT作為基模型的特征選擇
SelectFromModel(GradientBoostingClassifier()).fit_transform(train,target_train)
本數(shù)據(jù)集中我們刪除非入模特征后,并對缺失值填充,然后用計(jì)算協(xié)方差的方式看一下特征間相關(guān)性,然后進(jìn)行模型訓(xùn)練
# 刪除不需要的數(shù)據(jù)
for data in [data_train, data_test_a]:
data.drop(['issueDate','id'], axis=1,inplace=True)
"縱向用缺失值上面的值替換缺失值"
data_train = data_train.fillna(axis=0,method='ffill')
x_train = data_train.drop(['isDefault','id'], axis=1)
#計(jì)算協(xié)方差
data_corr = x_train.corrwith(data_train.isDefault) #計(jì)算相關(guān)性
result = pd.DataFrame(columns=['features', 'corr'])
result['features'] = data_corr.index
result['corr'] = data_corr.values
# 當(dāng)然也可以直接看圖
data_numeric = data_train[numerical_fea]
correlation = data_numeric.corr()
f , ax = plt.subplots(figsize = (7, 7))
plt.title('Correlation of Numeric Features with Price',y=1,size=16)
sns.heatmap(correlation,square = True, vmax=0.8)
<matplotlib.axes._subplots.AxesSubplot at 0x12d88ad10>
features = [f for f in data_train.columns if f not in ['id','issueDate','isDefault'] and '_outliers' not in f]
x_train = data_train[features]
x_test = data_test_a[features]
y_train = data_train['isDefault']
def cv_model(clf, train_x, train_y, test_x, clf_name):
folds = 5
seed = 2020
kf = KFold(n_splits=folds, shuffle=True, random_state=seed)
train = np.zeros(train_x.shape[0])
test = np.zeros(test_x.shape[0])
cv_scores = []
for i, (train_index, valid_index) in enumerate(kf.split(train_x, train_y)):
print('************************************ {} ************************************'.format(str(i+1)))
trn_x, trn_y, val_x, val_y = train_x.iloc[train_index], train_y[train_index], train_x.iloc[valid_index], train_y[valid_index]
if clf_name == "lgb":
train_matrix = clf.Dataset(trn_x, label=trn_y)
valid_matrix = clf.Dataset(val_x, label=val_y)
params = {
'boosting_type': 'gbdt',
'objective': 'binary',
'metric': 'auc',
'min_child_weight': 5,
'num_leaves': 2 ** 5,
'lambda_l2': 10,
'feature_fraction': 0.8,
'bagging_fraction': 0.8,
'bagging_freq': 4,
'learning_rate': 0.1,
'seed': 2020,
'nthread': 28,
'n_jobs':24,
'silent': True,
'verbose': -1,
}
model = clf.train(params, train_matrix, 50000, valid_sets=[train_matrix, valid_matrix], verbose_eval=200,early_stopping_rounds=200)
val_pred = model.predict(val_x, num_iteration=model.best_iteration)
test_pred = model.predict(test_x, num_iteration=model.best_iteration)
# print(list(sorted(zip(features, model.feature_importance("gain")), key=lambda x: x[1], reverse=True))[:20])
if clf_name == "xgb":
train_matrix = clf.DMatrix(trn_x , label=trn_y)
valid_matrix = clf.DMatrix(val_x , label=val_y)
params = {'booster': 'gbtree',
'objective': 'binary:logistic',
'eval_metric': 'auc',
'gamma': 1,
'min_child_weight': 1.5,
'max_depth': 5,
'lambda': 10,
'subsample': 0.7,
'colsample_bytree': 0.7,
'colsample_bylevel': 0.7,
'eta': 0.04,
'tree_method': 'exact',
'seed': 2020,
'nthread': 36,
"silent": True,
}
watchlist = [(train_matrix, 'train'),(valid_matrix, 'eval')]
model = clf.train(params, train_matrix, num_boost_round=50000, evals=watchlist, verbose_eval=200, early_stopping_rounds=200)
val_pred = model.predict(valid_matrix, ntree_limit=model.best_ntree_limit)
test_pred = model.predict(test_x , ntree_limit=model.best_ntree_limit)
if clf_name == "cat":
params = {'learning_rate': 0.05, 'depth': 5, 'l2_leaf_reg': 10, 'bootstrap_type': 'Bernoulli',
'od_type': 'Iter', 'od_wait': 50, 'random_seed': 11, 'allow_writing_files': False}
model = clf(iterations=20000, **params)
model.fit(trn_x, trn_y, eval_set=(val_x, val_y),
cat_features=[], use_best_model=True, verbose=500)
val_pred = model.predict(val_x)
test_pred = model.predict(test_x)
train[valid_index] = val_pred
test = test_pred / kf.n_splits
cv_scores.append(roc_auc_score(val_y, val_pred))
print(cv_scores)
print("%s_scotrainre_list:" % clf_name, cv_scores)
print("%s_score_mean:" % clf_name, np.mean(cv_scores))
print("%s_score_std:" % clf_name, np.std(cv_scores))
return train, test
def lgb_model(x_train, y_train, x_test):
lgb_train, lgb_test = cv_model(lgb, x_train, y_train, x_test, "lgb")
return lgb_train, lgb_test
def xgb_model(x_train, y_train, x_test):
xgb_train, xgb_test = cv_model(xgb, x_train, y_train, x_test, "xgb")
return xgb_train, xgb_test
def cat_model(x_train, y_train, x_test):
cat_train, cat_test = cv_model(CatBoostRegressor, x_train, y_train, x_test, "cat")
lgb_train, lgb_test = lgb_model(x_train, y_train, x_test)
************************************ 1 ************************************
Training until validation scores don't improve for 200 rounds
[200] training's auc: 0.749225 valid_1's auc: 0.729679
[400] training's auc: 0.765075 valid_1's auc: 0.730496
[600] training's auc: 0.778745 valid_1's auc: 0.730435
Early stopping, best iteration is:
[455] training's auc: 0.769202 valid_1's auc: 0.730686
[0.7306859913754798]
************************************ 2 ************************************
Training until validation scores don't improve for 200 rounds
[200] training's auc: 0.749221 valid_1's auc: 0.731315
[400] training's auc: 0.765117 valid_1's auc: 0.731658
[600] training's auc: 0.778542 valid_1's auc: 0.731333
Early stopping, best iteration is:
[407] training's auc: 0.765671 valid_1's auc: 0.73173
[0.7306859913754798, 0.7317304414673989]
************************************ 3 ************************************
Training until validation scores don't improve for 200 rounds
[200] training's auc: 0.748436 valid_1's auc: 0.732775
[400] training's auc: 0.764216 valid_1's auc: 0.733173
Early stopping, best iteration is:
[386] training's auc: 0.763261 valid_1's auc: 0.733261
[0.7306859913754798, 0.7317304414673989, 0.7332610441015461]
************************************ 4 ************************************
Training until validation scores don't improve for 200 rounds
[200] training's auc: 0.749631 valid_1's auc: 0.728327
[400] training's auc: 0.765139 valid_1's auc: 0.728845
Early stopping, best iteration is:
[286] training's auc: 0.756978 valid_1's auc: 0.728976
[0.7306859913754798, 0.7317304414673989, 0.7332610441015461, 0.7289759386807912]
************************************ 5 ************************************
Training until validation scores don't improve for 200 rounds
[200] training's auc: 0.748414 valid_1's auc: 0.732727
[400] training's auc: 0.763727 valid_1's auc: 0.733531
[600] training's auc: 0.777489 valid_1's auc: 0.733566
Early stopping, best iteration is:
[524] training's auc: 0.772372 valid_1's auc: 0.733772
[0.7306859913754798, 0.7317304414673989, 0.7332610441015461, 0.7289759386807912, 0.7337723979789789]
lgb_scotrainre_list: [0.7306859913754798, 0.7317304414673989, 0.7332610441015461, 0.7289759386807912, 0.7337723979789789]
lgb_score_mean: 0.7316851627208389
lgb_score_std: 0.0017424259863954693
testA_result = pd.read_csv('../testA_result.csv')
roc_auc_score(testA_result['isDefault'].values, lgb_test)
0.7290917729487896
3.8總結(jié)
特征工程是機(jī)器學(xué)習(xí),甚至是深度學(xué)習(xí)中最為重要的一部分,在實(shí)際應(yīng)用中往往也是所花費(fèi)時(shí)間最多的一步。各種算法書中對特征工程部分的講解往往少得可憐,因?yàn)樘卣鞴こ毯途唧w的數(shù)據(jù)結(jié)合的太緊密,很難系統(tǒng)地覆蓋所有場景。本章主要是通過一些常用的方法來做介紹,例如缺失值異常值的處理方法詳細(xì)對任何數(shù)據(jù)集來說都是適用的。但對于分箱等操作本章給出了具體的幾種思路,需要讀者自己探索。在特征工程中比賽和具體的應(yīng)用還是有所不同的,在實(shí)際的金融風(fēng)控評分卡制作過程中,由于強(qiáng)調(diào)特征的可解釋性,特征分箱尤其重要。
項(xiàng)目鏈接以及碼源
數(shù)據(jù)挖掘?qū)?/p>
數(shù)據(jù)挖掘?qū)嵺`(金融風(fēng)控):金融風(fēng)控之貸款違約預(yù)測挑戰(zhàn)賽(上篇)文章來源:http://www.zghlxwxcb.cn/news/detail-447901.html
數(shù)據(jù)挖掘?qū)嵺`(金融風(fēng)控):金融風(fēng)控之貸款違約預(yù)測挑戰(zhàn)賽(下篇)文章來源地址http://www.zghlxwxcb.cn/news/detail-447901.html
到了這里,關(guān)于數(shù)據(jù)挖掘?qū)嵺`(金融風(fēng)控):金融風(fēng)控之貸款違約預(yù)測挑戰(zhàn)賽(上篇)[xgboots/lightgbm/Catboost等模型]--模型融合:stacking、blending的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!