數據分箱:等頻分箱,等距分箱,卡方分箱,計算WOE、IV


轉載:https://zhuanlan.zhihu.com/p/38440477

轉載:https://blog.csdn.net/starzhou/article/details/78930490

轉載:https://www.cnblogs.com/wzdLY/p/9649101.html

1.離散的優勢:

(1)離散化后的特征對異常數據有很強的魯棒性:比如一個特征是年齡>30是1,否則0。如果特征沒有離散化,一個異常數據“年齡300歲”會給模型造成很大的干擾;

(2)邏輯回歸屬於廣義線性模型,表達能力受限,單變量離散化為N個后,每個變量有單獨的權重,相當於為模型引入了非線性,能夠提升模型表達能力,加大擬合;

(3)離散化后可以進行特征交叉,由M+N個變量變為M*N個變量,進一步引入非線性,提升表達能力;

(4)可以將缺失作為獨立的一類帶入模型;

(5)將所有變量變換到相似的尺度上。

 

WOE:

WOE的全稱是“Weight of Evidence”,即證據權重,WOE是對原始自變量的一種編碼形式。要對一個變量進行WOE編碼,需要首先把這個變量進行分箱。分箱后,對於第i組,WOE的計算公式如下:

yi是這個分組中響應客戶(即取值為1)的數量,yT是全部樣本中所有響應客戶(即取值為1)的數量

ni是這個分組中未響應客戶(即取值為0)的數量,nT是全部樣本中所有未響應客戶(即取值為0)的數量

 

IV值:

IV的全稱是Information Value,用來衡量自變量的預測能力

對於分組i的IV值:

計算整個變量的IV值,n為變量分組個數:

  • 過高的IV,可能有潛在的風險
  • 特征分箱越細,IV越高

def compute_WOE_IV(df,col,target):
    """
    param df:DataFrame|包含feature和label
    param col:str|feature名稱,col這列已經經過分箱
    param taget:str|label名稱,0,1
    return 每箱的WOE(字典類型)和總的IV之和,注意考慮計算時候分子分母為零的溢出情況
    """
    import numpy as np
    
    total = df.groupby([col])[target].count() #計算col每個分組中的樣本總數
    total = pd.DataFrame({'total': total})
    
    bad   = df.groupby([col])[target].sum()   #計算col每個分組中的目標取值為1的總數,關注的正樣本
    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'] #計算col每個分組中的目標取值為0的總數,關注的負樣本
    G = N - B #負樣本總數
    
    regroup['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}

等頻分箱

區間的邊界值要經過選擇,使得每個區間包含大致相等的實例數量。比如說 N=10 ,每個區間應該包含大約10%的實例。

等距分箱

從最小值到最大值之間,均分為 N 等份。 如果 A,B 為最小最大值, 則每個區間的長度為 W=(B−A)/N , 則區間邊界值為A+W,A+2W,….A+(N−1)W 。這里只考慮邊界,每個等份的實例數量可能不等。

import pandas as pd
import seaborn as sn
from sklearn.model_selection import train_test_split

df = sn.load_dataset(name="titanic")

train,test = train_test_split(df,test_size=0.2)

#####################等頻分箱#################################################
train["age_bin"] = pd.qcut(train["age"],10)
group_by_age_bin = train.groupby(["age_bin"],as_index=True)

df_min_max_bin = pd.DataFrame()#用來記錄每個箱體的最大最小值
df_min_max_bin["min_bin"] = group_by_age_bin.age.min()
df_min_max_bin["max_bin"] = group_by_age_bin.age.max()

df_min_max_bin.reset_index(inplace=True)

#####################等寬分箱###################################################
train["age_bin"] = pd.cut(train["age"],10)
group_by_age_bin = train.groupby(["age_bin"],as_index=True)

df_min_max_bin = pd.DataFrame()#用來記錄每個箱體的最大最小值
df_min_max_bin["min_bin"] = group_by_age_bin.age.min()
df_min_max_bin["max_bin"] = group_by_age_bin.age.max()

df_min_max_bin.reset_index(inplace=True)

卡方分箱

轉載:https://github.com/tatsumiw/ChiMerge/blob/master/ChiMerge.py 

 

# -*- coding: utf-8 -*-
"""
Created on Sun Oct 28 21:39:24 2018

@author: WZD
"""
def ChiMerge(df,variable,flag,confidenceVal=3.841,bin=10,sample=None):  
    '''
    param df:DataFrame| 必須包含標簽列
    param variable:str| 需要卡方分箱的變量名稱(字符串)
    param flag:str    | 正負樣本標識的名稱(字符串)
    param confidenceVal:float| 置信度水平(默認是不進行抽樣95%)
    param bin:int            | 最多箱的數目
    param sample: int          | 為抽樣的數目(默認是不進行抽樣),因為如果觀測值過多運行會較慢
    note: 停止條件為大於置信水平且小於bin的數目
    return :DataFrame|采樣結果
    '''    
    import pandas as pd
    import numpy as np
    
    
    #進行是否抽樣操作
    if sample != None:
        df = df.sample(n=sample)
    else:
        df   
        
    #進行數據格式化錄入
    total_num = df.groupby([variable])[flag].count()  #統計需分箱變量每個值數目
    total_num = pd.DataFrame({'total_num': total_num})  #創建一個數據框保存之前的結果
    positive_class = df.groupby([variable])[flag].sum()  #統計需分箱變量每個值正樣本數
    positive_class = pd.DataFrame({'positive_class': positive_class})  #創建一個數據框保存之前的結果
    regroup = pd.merge(total_num, positive_class, left_index=True, right_index=True,
                       how='inner')  # 組合total_num與positive_class
    regroup.reset_index(inplace=True)
    regroup['negative_class'] = regroup['total_num'] - regroup['positive_class']  #統計需分箱變量每個值負樣本數
    regroup = regroup.drop('total_num', axis=1)
    np_regroup = np.array(regroup)  #把數據框轉化為numpy(提高運行效率)
    #print('已完成數據讀入,正在計算數據初處理')

    #處理連續沒有正樣本或負樣本的區間,並進行區間的合並(以免卡方值計算報錯)
    i = 0
    while (i <= np_regroup.shape[0] - 2):
        if ((np_regroup[i, 1] == 0 and np_regroup[i + 1, 1] == 0) or ( np_regroup[i, 2] == 0 and np_regroup[i + 1, 2] == 0)):
            np_regroup[i, 1] = np_regroup[i, 1] + np_regroup[i + 1, 1]  # 正樣本
            np_regroup[i, 2] = np_regroup[i, 2] + np_regroup[i + 1, 2]  # 負樣本
            np_regroup[i, 0] = np_regroup[i + 1, 0]
            np_regroup = np.delete(np_regroup, i + 1, 0)
            i = i - 1
        i = i + 1
 
    #對相鄰兩個區間進行卡方值計算
    chi_table = np.array([])  # 創建一個數組保存相鄰兩個區間的卡方值
    for i in np.arange(np_regroup.shape[0] - 1):
        chi = (np_regroup[i, 1] * np_regroup[i + 1, 2] - np_regroup[i, 2] * np_regroup[i + 1, 1]) ** 2 \
          * (np_regroup[i, 1] + np_regroup[i, 2] + np_regroup[i + 1, 1] + np_regroup[i + 1, 2]) / \
          ((np_regroup[i, 1] + np_regroup[i, 2]) * (np_regroup[i + 1, 1] + np_regroup[i + 1, 2]) * (
          np_regroup[i, 1] + np_regroup[i + 1, 1]) * (np_regroup[i, 2] + np_regroup[i + 1, 2]))
        chi_table = np.append(chi_table, chi)
    #print('已完成數據初處理,正在進行卡方分箱核心操作')

    #把卡方值最小的兩個區間進行合並(卡方分箱核心)
    while (1):
        if (len(chi_table) <= (bin - 1) and min(chi_table) >= confidenceVal):
            break
        chi_min_index = np.argwhere(chi_table == min(chi_table))[0]  # 找出卡方值最小的位置索引
        np_regroup[chi_min_index, 1] = np_regroup[chi_min_index, 1] + np_regroup[chi_min_index + 1, 1]
        np_regroup[chi_min_index, 2] = np_regroup[chi_min_index, 2] + np_regroup[chi_min_index + 1, 2]
        np_regroup[chi_min_index, 0] = np_regroup[chi_min_index + 1, 0]
        np_regroup = np.delete(np_regroup, chi_min_index + 1, 0)

        if (chi_min_index == np_regroup.shape[0] - 1):  # 最小值試最后兩個區間的時候
            # 計算合並后當前區間與前一個區間的卡方值並替換
            chi_table[chi_min_index - 1] = (np_regroup[chi_min_index - 1, 1] * np_regroup[chi_min_index, 2] - np_regroup[chi_min_index - 1, 2] * np_regroup[chi_min_index, 1]) ** 2 \
                                           * (np_regroup[chi_min_index - 1, 1] + np_regroup[chi_min_index - 1, 2] + np_regroup[chi_min_index, 1] + np_regroup[chi_min_index, 2]) / \
                                       ((np_regroup[chi_min_index - 1, 1] + np_regroup[chi_min_index - 1, 2]) * (np_regroup[chi_min_index, 1] + np_regroup[chi_min_index, 2]) * (np_regroup[chi_min_index - 1, 1] + np_regroup[chi_min_index, 1]) * (np_regroup[chi_min_index - 1, 2] + np_regroup[chi_min_index, 2]))
            # 刪除替換前的卡方值
            chi_table = np.delete(chi_table, chi_min_index, axis=0)

        else:
            # 計算合並后當前區間與前一個區間的卡方值並替換
            chi_table[chi_min_index - 1] = (np_regroup[chi_min_index - 1, 1] * np_regroup[chi_min_index, 2] - np_regroup[chi_min_index - 1, 2] * np_regroup[chi_min_index, 1]) ** 2 \
                                       * (np_regroup[chi_min_index - 1, 1] + np_regroup[chi_min_index - 1, 2] + np_regroup[chi_min_index, 1] + np_regroup[chi_min_index, 2]) / \
                                       ((np_regroup[chi_min_index - 1, 1] + np_regroup[chi_min_index - 1, 2]) * (np_regroup[chi_min_index, 1] + np_regroup[chi_min_index, 2]) * (np_regroup[chi_min_index - 1, 1] + np_regroup[chi_min_index, 1]) * (np_regroup[chi_min_index - 1, 2] + np_regroup[chi_min_index, 2]))
            # 計算合並后當前區間與后一個區間的卡方值並替換
            chi_table[chi_min_index] = (np_regroup[chi_min_index, 1] * np_regroup[chi_min_index + 1, 2] - np_regroup[chi_min_index, 2] * np_regroup[chi_min_index + 1, 1]) ** 2 \
                                       * (np_regroup[chi_min_index, 1] + np_regroup[chi_min_index, 2] + np_regroup[chi_min_index + 1, 1] + np_regroup[chi_min_index + 1, 2]) / \
                                   ((np_regroup[chi_min_index, 1] + np_regroup[chi_min_index, 2]) * (np_regroup[chi_min_index + 1, 1] + np_regroup[chi_min_index + 1, 2]) * (np_regroup[chi_min_index, 1] + np_regroup[chi_min_index + 1, 1]) * (np_regroup[chi_min_index, 2] + np_regroup[chi_min_index + 1, 2]))
            # 刪除替換前的卡方值
            chi_table = np.delete(chi_table, chi_min_index + 1, axis=0)
    #print('已完成卡方分箱核心操作,正在保存結果')

    #把結果保存成一個數據框
    result_data = pd.DataFrame()  # 創建一個保存結果的數據框
    result_data['variable'] = [variable] * np_regroup.shape[0]  # 結果表第一列:變量名
    list_temp = []
    for i in np.arange(np_regroup.shape[0]):
        if i == 0:
            x = '0' + ',' + str(np_regroup[i, 0])
        elif i == np_regroup.shape[0] - 1:
            x = str(np_regroup[i - 1, 0]) + '+'
        else:
            x = str(np_regroup[i - 1, 0]) + ',' + str(np_regroup[i, 0])
        list_temp.append(x)
    result_data['interval'] = list_temp  #結果表第二列:區間
    result_data['flag_0'] = np_regroup[:, 2]  # 結果表第三列:負樣本數目
    result_data['flag_1'] = np_regroup[:, 1]  # 結果表第四列:正樣本數目

    return result_data



##############################測試#############################################
from sklearn.model_selection import train_test_split
import seaborn as sn
import pandas as pd

df = sn.load_dataset(name="titanic")
train,test = train_test_split(df,test_size=0.2)

result_data = ChiMerge(df=df,variable="age",flag="survived",confidenceVal=3.841,bin=10,sample=None)

bins = [] #卡方的區間值
bins.append(-float('inf'))
for i in range(result_data["interval"].shape[0]-1):
    
    St = result_data["interval"][i].split(",")
    bins.append(float(St[1]))

bins.append(float('inf'))


train["age"] = pd.cut(x=train["age"],bins=bins,labels=[1,3,5,7,9,11,13,15,17])
test["age"] = pd.cut(x=test["age"],bins=bins,labels=[1,3,5,7,9,11,13,15,17])
    

 

 

 

 

 

 

 

 

 

 

 

 


免責聲明!

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



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