風(fēng)控ML[3] | 風(fēng)控建模的WOE與IV


「風(fēng)控ML」系列文章,主要是分享一下自己多年以來做金融風(fēng)控的一些事一些情,當(dāng)然也包括風(fēng)控建模、機(jī)器學(xué)習(xí)、大數(shù)據(jù)風(fēng)控等相關(guān)技術(shù)分享,歡迎同行交流與新同學(xué)的加入,共同學(xué)習(xí),進(jìn)步!

??
第一次接觸這兩個(gè)名詞是在做風(fēng)控模型的時(shí)候,老師教我們可以用IV去做變量篩選,IV(Information Value),中文名是信息值,簡單來說這個(gè)指標(biāo)的作用就是來衡量變量的預(yù)測能力強(qiáng)弱的,然后IV又是WOE算出來的。姑且先不管原理哈,我們先給出來一下結(jié)論。| IV范圍 | 變量預(yù)測力 |
|---|---|
| <0.02 | 無預(yù)測力?? |
| 0.02~0.10 | 弱?? |
| 0.10~0.30 | 中等?? |
| `> 0.30 | 強(qiáng)?? |

01 WOE的原理
WOE是weight of evidence的縮寫,是一種編碼形式,首先我們要知道WOE是針對類別變量而言的,所以連續(xù)性變量需要提前做好分組(這里也是一個(gè)很好的考點(diǎn),也有會說分箱、離散化的,變量優(yōu)化也可以從這個(gè)角度出發(fā))。
先給出數(shù)學(xué)計(jì)算公式,對于第i組的WOE可以這么計(jì)算:
從公式上可以看出,第i組的WOE值等于這個(gè)組的響應(yīng)客戶占所有響應(yīng)客戶的比例與未響應(yīng)客戶占所有未響應(yīng)客戶的比例的比值取對數(shù)。對于上面的公式我們還可以 簡單做一下轉(zhuǎn)化:
所以,WOE主要就是體現(xiàn)組內(nèi)的好壞占比與整體的差異化程度大小,WOE越大,差異越大。
02 IV的原理
上面我們介紹了如何計(jì)算一個(gè)分組的WOE值,那么我們就可以把變量所有分組的WOE值給算出來了,對應(yīng)地,每個(gè)分組也有一個(gè)IV值,我們叫 ? ,其中:
計(jì)算這個(gè)變量的IV值就是這樣子就可以了,把每個(gè)分組的IV值給加起來。
03 實(shí)際案例
好了,上面的理論也講了一些了,還是拿一個(gè)實(shí)際的變量來計(jì)算一下。
我們來假設(shè)一個(gè)場景,我們需要賣茶葉,然后我們不知道從哪里拿來了一份1000人的營銷名單(手機(jī)號碼),然后就批量添加微信好友,最后有500個(gè)手機(jī)號碼可以成功搜索到微信號的,進(jìn)而進(jìn)行了好友添加,最終有100人成功添加到好友了。
我們這份名單上,有客戶的年齡字段,那么我們可以拿來計(jì)算一下這個(gè)字段對于是否成功添加好友(響應(yīng))有多大的預(yù)測能力,我們在Excel中進(jìn)行實(shí)現(xiàn):

可以看出來,這個(gè)變量對于我們是否可以成功加到客戶微信好友有著很強(qiáng)的預(yù)測能力。
04 Python實(shí)現(xiàn)
我們知道,針對連續(xù)型變量,是需要先轉(zhuǎn)換為類別變量才可以進(jìn)行IV值的計(jì)算的,現(xiàn)在我們把數(shù)據(jù)導(dǎo)入到Python中,原始變量是連續(xù)型變量,那么我們?nèi)绾卧赑ython里實(shí)現(xiàn)IV值的計(jì)算呢?如下圖:(其中target=1代表響應(yīng),target=0代表未響應(yīng))

核心代碼就是下面的:
def?iv_count(data_bad,?data_good):
????'''計(jì)算iv值'''
????value_list?=?set(data_bad.unique())?|?set(data_good.unique())
????iv?=?0
????len_bad?=?len(data_bad)
????len_good?=?len(data_good)
????for?value?in?value_list:
????????#?判斷是否某類是否為0,避免出現(xiàn)無窮小值和無窮大值
????????if?sum(data_bad?==?value)?==?0:
????????????bad_rate?=?1?/?len_bad
????????else:
????????????bad_rate?=?sum(data_bad?==?value)?/?len_bad
????????if?sum(data_good?==?value)?==?0:
????????????good_rate?=?1?/?len_good
????????else:
????????????good_rate?=?sum(data_good?==?value)?/?len_good
????????iv?+=?(good_rate?-?bad_rate)?*?math.log(good_rate?/?bad_rate,2)
????????print(value,iv)
????return?iv
那么我們?nèi)绾问褂媚?,一步一步來?/p>
Step1:導(dǎo)入數(shù)據(jù)
測試數(shù)據(jù)集可以后臺回復(fù) 'age' 進(jìn)行獲取。
data?=?pd.read_csv('./data/age.csv')
#?定義必要的參數(shù)
feature?=?data.loc[:,['age']]
labels?=?data['target']
keep_cols?=?['age']
cut_bin_dict?=?{'age':[0,18,25,30,40,50,100]}
Step2:按照指定閾值分箱
按照我們之前Excel相同的分箱邏輯進(jìn)行分箱:
cut_bin?=?cut_bin_dict['age']
#?按照分箱閾值分箱,并將缺失值替換成Blank,區(qū)分好壞樣本
data_bad?=?pd.cut(feature['age'],?cut_bin,?right=False).cat.add_categories(['Blank']).fillna('Blank')[labels?==?1]
data_good?=?pd.cut(feature['age'],?cut_bin,?right=False
???????????????????).cat.add_categories(['Blank']).fillna('Blank')[labels?==?0]
value_list?=?set(data_bad.unique())?|?set(data_good.unique())
value_list

Step3:調(diào)用函數(shù)計(jì)算IV
iv_series['age']?=?iv_count(data_bad,?data_good)
iv_series

可以看得出,和我們Excel計(jì)算的結(jié)果完全一致!
05 “我要打10個(gè)”版本
嗯,上面針對單個(gè)的變量IV計(jì)算是會了,那么如果有一堆需要你計(jì)算IV的變量,可以如何處理呢?其實(shí),原理很簡單,就是寫個(gè)循環(huán),這里呢已經(jīng)寫好了一個(gè),大家可以參考一下的。這邊有一些細(xì)節(jié)的東西需要說明一下的。
1)注意區(qū)分變量類型,數(shù)值型變量和類別型變量要區(qū)分對待。
2)注意分組后是否出現(xiàn)某組內(nèi)的響應(yīng)(未響應(yīng))數(shù)量為零的情況,如果為零需要處理一下。
代碼放上,大家可以試著運(yùn)行一下:
def?get_iv_series(feature,?labels,?keep_cols=None,?cut_bin_dict=None):
????'''
????計(jì)算各變量最大的iv值,get_iv_series方法出入?yún)⑷缦?
????------------------------------------------------------------
????入?yún)⒔Y(jié)果如下:
????????feature:?數(shù)據(jù)集的特征空間
????????labels:?數(shù)據(jù)集的輸出空間
????????keep_cols:?需計(jì)算iv值的變量列表
????????cut_bin_dict:?數(shù)值型變量要進(jìn)行分箱的閾值字典,格式為{'col1':[value1,value2,...],?'col2':[value1,value2,...],?...}
????------------------------------------------------------------
????入?yún)⒔Y(jié)果如下:
????????iv_series:?各變量最大的IV值
????'''
????def?iv_count(data_bad,?data_good):
????????'''計(jì)算iv值'''
????????value_list?=?set(data_bad.unique())?|?set(data_good.unique())
????????iv?=?0
????????len_bad?=?len(data_bad)
????????len_good?=?len(data_good)
????????for?value?in?value_list:
????????????#?判斷是否某類是否為0,避免出現(xiàn)無窮小值和無窮大值
????????????if?sum(data_bad?==?value)?==?0:
????????????????bad_rate?=?1?/?len_bad
????????????else:
????????????????bad_rate?=?sum(data_bad?==?value)?/?len_bad
????????????if?sum(data_good?==?value)?==?0:
????????????????good_rate?=?1?/?len_good
????????????else:
????????????????good_rate?=?sum(data_good?==?value)?/?len_good
????????????iv?+=?(good_rate?-?bad_rate)?*?math.log(good_rate?/?bad_rate,2)
????????return?iv
????if?keep_cols?is?None:
????????keep_cols?=?sorted(list(feature.columns))
????col_types?=?feature[keep_cols].dtypes
????categorical_feature?=?list(col_types[col_types?==?'object'].index)
????numerical_feature?=?list(col_types[col_types?!=?'object'].index)
????iv_series?=?pd.Series()
????#?遍歷數(shù)值變量計(jì)算iv值
????for?col?in?numerical_feature:
????????cut_bin?=?cut_bin_dict[col]
????????#?按照分箱閾值分箱,并將缺失值替換成Blank,區(qū)分好壞樣本
????????data_bad?=?pd.cut(feature[col],?cut_bin,?right=False).cat.add_categories(['Blank']).fillna('Blank')[labels?==?1]
????????data_good?=?pd.cut(feature[col],?cut_bin,?right=False
???????????????????????????).cat.add_categories(['Blank']).fillna('Blank')[labels?==?0]
????????iv_series[col]?=?iv_count(data_bad,?data_good)
????#?遍歷類別變量計(jì)算iv值
????for?col?in?categorical_feature:
????????#?將缺失值替換成Blank,區(qū)分好壞樣本
????????data_bad?=?feature[col].fillna('Blank')[labels?==?1]
????????data_good?=?feature[col].fillna('Blank')[labels?==?0]
????????iv_series[col]?=?iv_count(data_bad,?data_good)
????return?iv_series
調(diào)用demo:
iv_series?=?get_iv_series(feature,?labels,?keep_cols,?cut_bin_dict=cut_bin_dict)
iv_series
#?age????0.434409
06 總結(jié)一下
記住IV值的預(yù)測能力映射:
| IV范圍 | 變量預(yù)測力 |
|---|---|
| <0.02 | 無預(yù)測力?? |
| 0.02~0.10 | 弱?? |
| 0.10~0.30 | 中等?? |
| `> 0.30 | 強(qiáng)?? |
如果想復(fù)現(xiàn)代碼,可以從我的公號后臺輸出 ?'age' 去獲取測試集吧,或者拿自己目前的數(shù)據(jù)集來玩玩也可以,不過得注意一些細(xì)節(jié),轉(zhuǎn)換數(shù)據(jù)格式。?
