金融风控--申请评分卡模型--特征工程(特征分箱,WOE编码) 标签: 金融特征分箱-WOE编码 2017-07-16 21:26 4086人阅读 评论(2) 收藏 举报 分类: 金融风
版權聲明:本文為博主原創文章,未經博主允許不得轉載。
這篇博文主要講在申請評分卡模型中常用的一些特征工程方法,申請評分卡模型最多的還是logsitic模型。
先看數據,我們現在有三張表:
已加工成型的信息:
Master表
idx:每一筆貸款的unique key,可以與另外2個文件里的idx相匹配。
UserInfo_*:借款人特征字段
WeblogInfo_*:Info網絡行為字段
Education_Info*:學歷學籍字段
ThirdParty_Info_PeriodN_*:第三方數據時間段N字段
SocialNetwork_*:社交網絡字段
ListingInfo:借款成交時間
Target:違約標簽(1 = 貸款違約,0 = 正常還款)
需要衍生的信息
借款人的登陸信息表
ListingInfo:借款成交時間
LogInfo1:操作代碼
LogInfo2:操作類別
LogInfo3:登陸時間
idx:每一筆貸款的unique key
客戶在不同的時間段內有著不同的操作,故我們最好做個時間切片,在每個時間切片內統計一些特征。從而衍生出一些特征。
時間切片:
兩個時刻間的跨度
例: 申請日期之前30天內的登錄次數 申請日期之前第30天至第59天內的登錄次數基于時間切片的衍生
申請日期之前180天內,平均每月(30天)的登錄次數常用的時間切片
(1、2個)月,(1、2個)季度,半年,1年,1年半,2年時間切片的選擇
不能太長:保證大多數樣本都能覆蓋到 不能太短:丟失信息我們希望最大時間切片不能太長,都是最好又能包含大部分信息。那么最大切片應該多大呢?
#coding:utf-8 import pandas as pd import datetime import collections import numpy as np import randomimport matplotlib.pyplot as pltdef TimeWindowSelection(df, daysCol, time_windows):''':param df: the dataset containg variabel of days:param daysCol: the column of days:param time_windows: the list of time window,可分別取30,60,90,,,360:return:'''freq_tw = {}for tw in time_windows:freq = sum(df[daysCol].apply(lambda x: int(x<=tw))) ##統計在tw時間切片內客戶操作的總次數freq_tw[tw] = freq/float(len(df)) ##tw時間切片內客戶總操作數占總的操作數比例return freq_twdata1 = pd.read_csv('PPD_LogInfo_3_1_Training_Set.csv', header = 0) ### Extract the applying date of each applicant data1['logInfo'] = data1['LogInfo3'].map(lambda x: datetime.datetime.strptime(x,'%Y-%m-%d')) data1['Listinginfo'] = data1['Listinginfo1'].map(lambda x: datetime.datetime.strptime(x,'%Y-%m-%d')) data1['ListingGap'] = data1[['logInfo','Listinginfo']].apply(lambda x: (x[1]-x[0]).days,axis = 1) timeWindows = TimeWindowSelection(data1, 'ListingGap', range(30,361,30)) fig=plt.figure() ax=fig.add_subplot(1,1,1) ax.plot(list(timeWindows.keys()),list(timeWindows.values()),marker='o') ax.set_xticks([0,30,60,90,120,150,180,210,240,270,300,330,360]) ax.grid() plt.show()- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
由上圖可以看出,在0-180天的時間切片內的操作數占總的操作數的95%,180天以后的覆蓋度增長很慢。所以我們選擇180天為最大的時間切片。凡是不超過180天的時間切片,都可以用來做個特征衍生。
選取[7,30,60,90,120,150,180]做為不同的切片,衍生變量。
那么我們來選擇提取哪些有用的特征:
- 統計下LogInfo1和LogInfo2在每個時間切片內被操作的次數m1。
- 統計下LogInfo1和LogInfo2在每個時間切片內不同的操作次數m2。
- 統計下LogInfo1和LogInfo2在每個時間切片內m1/m2的值。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
數據清洗
對于類別型變量
刪除缺失率超過50%的變量剩余變量中的缺失做為一種狀態對于連續型變量
刪除缺失率超過30%的變量利用隨機抽樣法對剩余變量中的缺失進行補缺注:連續變量中的缺失也可以當成一種狀態
特征分箱(連續變量離散化或類別型變量使其更少類別)
分箱的定義
- 將連續變量離散化
- 將多狀態的離散變量合并成少狀態
分箱的重要性及其優勢
特征分箱的方法
這里我們主要講有監督的卡方分箱法(ChiMerge)。
自底向上的(即基于合并的)數據離散化方法。它依賴于卡方檢驗:具有最小卡方值的相鄰區間合并在一起,直到滿足確定的停止準則。
基本思想:對于精確的離散化,相對類頻率在一個區間內應當完全一致。因此,如果兩個相鄰的區間具有非常類似的類分布,則這兩個區間可以合并;否則,它們應當保持分開。而低卡方值表明它們具有相似的類分布。
分箱步驟:
這里需要注意初始化時需要對實例進行排序,在排序的基礎上進行合并。
卡方閾值的確定:
根據顯著性水平和自由度得到卡方值
自由度比類別數量小1。例如:有3類,自由度為2,則90%置信度(10%顯著性水平)下,卡方的值為4.6。
閾值的意義
類別和屬性獨立時,有90%的可能性,計算得到的卡方值會小于4.6。
大于閾值4.6的卡方值就說明屬性和類不是相互獨立的,不能合并。如果閾值選的大,區間合并就會進行很多次,離散后的區間數量少、區間大。
注:
1,ChiMerge算法推薦使用0.90、0.95、0.99置信度,最大區間數取10到15之間.
2,也可以不考慮卡方閾值,此時可以考慮最小區間數或者最大區間數。指定區間數量的上限和下限,最多幾個區間,最少幾個區間。
3,對于類別型變量,需要分箱時需要按照某種方式進行排序。
按照最大區間數進行分箱代碼:
def Chi2(df, total_col, bad_col, overallRate):''':param df: the dataset containing the total count and bad count:param total_col: total count of each value in the variable:param bad_col: bad count of each value in the variable:param overallRate: the overall bad rate of the training set:return: the chi-square value'''df2 = df.copy()df2['expected'] = df[total_col].apply(lambda x: x*overallRate)combined = zip(df2['expected'], df2[bad_col])chi = [(i[0]-i[1])**2/i[0] for i in combined]chi2 = sum(chi)return chi2### ChiMerge_MaxInterval: split the continuous variable using Chi-square value by specifying the max number of intervals def ChiMerge_MaxInterval_Original(df, col, target, max_interval = 5):''':param df: the dataframe containing splitted column, and target column with 1-0:param col: splitted column:param target: target column with 1-0:param max_interval: the maximum number of intervals. If the raw column has attributes less than this parameter, the function will not work:return: the combined bins'''colLevels = set(df[col])# since we always combined the neighbours of intervals, we need to sort the attributescolLevels = sorted(list(colLevels)) ## 先對這列數據進行排序,然后在計算分箱N_distinct = len(colLevels)if N_distinct <= max_interval: #If the raw column has attributes less than this parameter, the function will not workprint "The number of original levels for {} is less than or equal to max intervals".format(col)return colLevels[:-1]else:#Step 1: group the dataset by col and work out the total count & bad count in each level of the raw columntotal = df.groupby([col])[target].count()total = pd.DataFrame({'total':total})bad = df.groupby([col])[target].sum()bad = pd.DataFrame({'bad':bad})regroup = total.merge(bad,left_index=True,right_index=True, how='left')##將左側,右側的索引用作其連接鍵。regroup.reset_index(level=0, inplace=True)N = sum(regroup['total'])B = sum(regroup['bad'])#the overall bad rate will be used in calculating expected bad countoverallRate = B*1.0/N ## 統計壞樣本率# initially, each single attribute forms a single intervalgroupIntervals = [[i] for i in colLevels]## 類似于[[1],[2],[3,4]]其中每個[.]為一箱groupNum = len(groupIntervals)while(len(groupIntervals)>max_interval): #the termination condition: the number of intervals is equal to the pre-specified threshold# in each step of iteration, we calcualte the chi-square value of each atttributechisqList = []for interval in groupIntervals:df2 = regroup.loc[regroup[col].isin(interval)]chisq = Chi2(df2, 'total','bad',overallRate)chisqList.append(chisq)#find the interval corresponding to minimum chi-square, and combine with the neighbore with smaller chi-squaremin_position = chisqList.index(min(chisqList))if min_position == 0:## 如果最小位置為0,則要與其結合的位置為1combinedPosition = 1elif min_position == groupNum - 1:combinedPosition = min_position -1else:## 如果在中間,則選擇左右兩邊卡方值較小的與其結合if chisqList[min_position - 1]<=chisqList[min_position + 1]:combinedPosition = min_position - 1else:combinedPosition = min_position + 1groupIntervals[min_position] = groupIntervals[min_position]+groupIntervals[combinedPosition]# after combining two intervals, we need to remove one of themgroupIntervals.remove(groupIntervals[combinedPosition])groupNum = len(groupIntervals)groupIntervals = [sorted(i) for i in groupIntervals] ## 對每組的數據安從小到大排序cutOffPoints = [i[-1] for i in groupIntervals[:-1]] ## 提取出每組的最大值,也就是分割點return cutOffPoints- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
以卡方閾值作為終止分箱條件:
def ChiMerge_MinChisq(df, col, target, confidenceVal = 3.841):''':param df: the dataframe containing splitted column, and target column with 1-0:param col: splitted column:param target: target column with 1-0:param confidenceVal: the specified chi-square thresold, by default the degree of freedom is 1 and using confidence level as 0.95:return: the splitted bins'''colLevels = set(df[col])total = df.groupby([col])[target].count()total = pd.DataFrame({'total':total})bad = df.groupby([col])[target].sum()bad = pd.DataFrame({'bad':bad})regroup = total.merge(bad,left_index=True,right_index=True, how='left')regroup.reset_index(level=0, inplace=True)N = sum(regroup['total'])B = sum(regroup['bad'])overallRate = B*1.0/NcolLevels =sorted(list(colLevels))groupIntervals = [[i] for i in colLevels]groupNum = len(groupIntervals)while(1): #the termination condition: all the attributes form a single interval; or all the chi-square is above the threshouldif len(groupIntervals) == 1:breakchisqList = []for interval in groupIntervals:df2 = regroup.loc[regroup[col].isin(interval)]chisq = Chi2(df2, 'total','bad',overallRate)chisqList.append(chisq)min_position = chisqList.index(min(chisqList))if min(chisqList) >=confidenceVal:breakif min_position == 0:combinedPosition = 1elif min_position == groupNum - 1:combinedPosition = min_position -1else:if chisqList[min_position - 1]<=chisqList[min_position + 1]:combinedPosition = min_position - 1else:combinedPosition = min_position + 1groupIntervals[min_position] = groupIntervals[min_position]+groupIntervals[combinedPosition]groupIntervals.remove(groupIntervals[combinedPosition])groupNum = len(groupIntervals)return groupIntervals- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
無監督分箱法:
等距劃分、等頻劃分
等距分箱
從最小值到最大值之間,均分為 N 等份, 這樣, 如果 A,B 為最小最大值, 則每個區間的長度為 W=(B?A)/N , 則區間邊界值為A+W,A+2W,….A+(N?1)W 。這里只考慮邊界,每個等份里面的實例數量可能不等。
等頻分箱
區間的邊界值要經過選擇,使得每個區間包含大致相等的實例數量。比如說 N=10 ,每個區間應該包含大約10%的實例。
以上兩種算法的弊端
比如,等寬區間劃分,劃分為5區間,最高工資為50000,則所有工資低于10000的人都被劃分到同一區間。等頻區間可能正好相反,所有工資高于50000的人都會被劃分到50000這一區間中。這兩種算法都忽略了實例所屬的類型,落在正確區間里的偶然性很大。
我們對特征進行分箱后,需要對分箱后的每組(箱)進行woe編碼,然后才能放進模型訓練。
WOE編碼
WOE(weight of evidence, 證據權重)
一種有監督的編碼方式,將預測類別的集中度的屬性作為編碼的數值
優勢
將特征的值規范到相近的尺度上。
(經驗上講,WOE的絕對值波動范圍在0.1~3之間)。
具有業務含義。
缺點
需要每箱中同時包含好、壞兩個類別。
特征信息度
IV(Information Value), 衡量特征包含預測變量濃度的一種指標
特征信息度解構:
其中Gi,Bi表示箱i中好壞樣本占全體好壞樣本的比例。
WOE表示兩類樣本分布的差異性。
(Gi-Bi):衡量差異的重要性。
特征信息度的作用
選擇變量:
- 非負指標
- 高IV表示該特征和目標變量的關聯度高
- 目標變量只能是二分類
- 過高的IV,可能有潛在的風險
- 特征分箱越細,IV越高
- 常用的閾值有:
< =0.02: 沒有預測性,不可用
0.02 to 0.1: 弱預測性
0.1 to 0.2: 有一定的預測性
0.2 +: 高預測性
注意上面說的IV是指一個變量里面所有箱的IV之和。
計算WOE和IV代碼:
def CalcWOE(df, col, target):''':param df: dataframe containing feature and target:param col: 注意col這列已經經過分箱了,現在計算每箱的WOE和總的IV。:param target: good/bad indicator:return: 返回每箱的WOE(字典類型)和總的IV之和。'''total = df.groupby([col])[target].count()total = pd.DataFrame({'total': total})bad = df.groupby([col])[target].sum()bad = pd.DataFrame({'bad': bad})regroup = total.merge(bad, left_index=True, right_index=True, how='left')regroup.reset_index(level=0, inplace=True)N = sum(regroup['total'])B = sum(regroup['bad'])regroup['good'] = regroup['total'] - regroup['bad']G = N - Bregroup['bad_pcnt'] = regroup['bad'].map(lambda x: x*1.0/B)regroup['good_pcnt'] = regroup['good'].map(lambda x: x * 1.0 / G)regroup['WOE'] = regroup.apply(lambda x: np.log(x.good_pcnt*1.0/x.bad_pcnt),axis = 1)WOE_dict = regroup[[col,'WOE']].set_index(col).to_dict(orient='index')IV = regroup.apply(lambda x: (x.good_pcnt-x.bad_pcnt)*np.log(x.good_pcnt*1.0/x.bad_pcnt),axis = 1)IV = sum(IV)return {"WOE": WOE_dict, 'IV':IV}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
那么可能有人會問,以上都是有監督的分箱,有監督的WOE編碼,如何能將這些有監督的方法應用到預測集上呢?
我們觀察下有監督的卡方分箱法和有監督的woe編碼的計算公式不難發現,其計算結果都是以一個比值結果呈現(卡方分箱法:(壞樣本數量-期望壞樣本數量)/期望壞樣本數量的比值形式;有監督的woe類似),比如我們發現預測集里面好壞樣本不平衡,需要對壞樣本進行一個欠采樣或者是好樣本進行過采樣,只要是一個均勻采樣,理論上這個有監督的卡方分箱的比值結果是不變的,其woe的比值結果也是不變的。即預測集上的卡方分組和woe編碼和訓練集上一樣。
那么,在訓練集中我們對一個連續型變量進行分箱以后,對照這這個連續型變量每個值,如果這個值在某個箱中,那么就用這個箱子的woe編碼代替他放進模型進行訓練。
在預測集中類似,但是預測集中的這個連續型變量的某個值可能不在任一個箱中,比如在訓練集中我對[x1,x2]分為一箱,[x3,x4]分為一箱,預測集中這個連續變量某個值可能為(x2+x3)/2即不在任意一箱中,如果把[x1,x2]分為一箱,那么這一箱的變量應該是x1<=x< x2;第二箱應該是x2<=x< x4等等。即預測集中連續變量某一個值大于等于第i-1個箱的最大值,小于第i個箱子的最大值,那么這個變量就應該對應第i個箱子。這樣分箱就覆蓋所有訓練樣本外可能存在的值。預測集中任意的一個值都可以找到對應的箱,和對應的woe編碼。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
如果我們發現分箱以后能完全能區分出好壞樣本,那么得注意了這個連續變量會不會是個事后變量。
分箱的注意點
對于連續型變量做法:
- 使用ChiMerge進行分箱
- 如果有特殊值,把特殊值單獨分為一組,例如把-1單獨分為一箱。
- 計算這個連續型變量的每個值屬于那個箱子,得出箱子編號。以所屬箱子編號代替原始值。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 檢查分箱以后每箱的bad_rate的單調性,如果不滿足,那么繼續進行相鄰的兩箱合并,知道bad_rate單調為止。(可以放寬到U型)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
上述過程是收斂的,因為當箱數為2時,bad rate自然單調
- 檢查最大箱,如果最大箱里面數據數量占總數據的90%以上,那么棄用這個變量
- 1
- 2
- 3
- 4
- 5
對于類別型變量:
- 當類別數較少時,原則上不需要分箱
- 否則,當類別較多時,以bad rate代替原有值,轉成連續型變量再進行分箱計算。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
-
否則, 檢查最大箱,如果最大箱里面數據數量占總數據的90%以上,那么棄用這個變量
-
當某個或者幾個類別的bad rate為0時,需要和最小的非0bad rate的箱進行合并。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 當該變量可以完全區分目標變量時,需要認真檢查該變量的合理性。(可能是事后變量)
單變量分析
- 用IV檢驗該變量有效性(一般閾值區間在(0.0.2,0.8))
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 連續變量bad rate的單調性(可以放寬到U型)
- 單一區間的占比不宜過高(一般不能超過90%,如果超過則棄用這個變量)
多變量分析
變量的兩兩相關性,當相關性高時,只能保留一個:
- 可以選擇IV高的留下
- 或者選擇分箱均衡的留下(后期評分得分會均勻)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
多變量分析:變量的多重共線性
通常用VIF來衡量,要求VIF<10:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
當發現vif>10時,需要逐一剔除變量,當剔除變量Xk時,發現vif<10時,此時剔除{Xi,Xk}中IV小的那個變量。
通常情況下,計算vif這一步不是必須的,在進行單變量處理以后,放進邏輯回歸模型進行訓練預測,如果效果非常不好時,才需要做多變量分析,消除多重共線性。
本篇博文總結:
總結
以上是生活随笔為你收集整理的金融风控--申请评分卡模型--特征工程(特征分箱,WOE编码) 标签: 金融特征分箱-WOE编码 2017-07-16 21:26 4086人阅读 评论(2) 收藏 举报 分类: 金融风的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 神经网络贷款风险评估(base on k
- 下一篇: 2017年度盘点:15个最流行的GitH