天天看點

卡方分箱(chi-square)1卡方分箱之python代碼實

統計學,風控模組化經常遇到卡方分箱算法ChiMerge。卡方分箱在金融信貸風控領域是邏輯回歸評分卡的核心,讓分箱具有統計學意義(單調性)。卡方分箱在生物醫藥領域可以比較兩種藥物或兩組病人是否具有顯著差別。但很多模組化人員搞不清楚卡方分箱原理。先給大家介紹一下經常被提到的卡方分布和卡方檢驗是什麼。歡迎各位同學學習更多相關知識python金融風控評分卡模型和資料分析:https://edu.csdn.net/combo/detail/1927

一、卡方分布

卡方分布(chi-square distribution, χ2-distribution)是機率統計裡常用的一種機率分布,也是統計推斷裡應用最廣泛的機率分布之一,在假設檢驗與置信區間的計算中經常能見到卡方分布的身影。

卡方分布的定義如下:

若k個獨立的随機變量Z1, Z2,..., Zk 滿足标準正态分布 N(0,1) , 則這k個随機變量的平方和:

卡方分箱(chi-square)1卡方分箱之python代碼實

為服從自由度為k的卡方分布,記作:

卡方分箱(chi-square)1卡方分箱之python代碼實

或者記作

卡方分箱(chi-square)1卡方分箱之python代碼實

二、卡方檢驗

χ2檢驗是以χ2分布為基礎的一種假設檢驗方法,主要用于分類變量之間的獨立性檢驗。

其基本思想是根據樣本資料推斷總體的分布與期望分布是否有顯著性差異,或者推斷兩個分類變量是否相關或者獨立。

一般可以設原假設為 :觀察頻數與期望頻數沒有差異,或者兩個變量互相獨立不相關。

實際應用中,我們先假設原假設成立,計算出卡方的值,卡方表示觀察值與理論值間的偏離程度。

卡方值的計算公式為:

卡方分箱(chi-square)1卡方分箱之python代碼實

其中A為實際頻數,E為期望頻數。卡方值用于衡量實際值與理論值的差異程度,這也是卡方檢驗的核心思想。

卡方值包含了以下兩個資訊:

1.實際值與理論值偏差的絕對大小。2.差異程度與理論值的相對大小。

上述計算的卡方值服從卡方分布。根據卡方分布,卡方統計量以及自由度,可以确定在原假設成立的情況下獲得目前統計量以及更極端情況的機率p。如果p很小,說明觀察值與理論值的偏離程度大,應該拒絕原假設。否則不能拒絕原假設。

三、卡方檢驗執行個體

某醫院對某種病症的患者使用了A,B兩種不同的療法,結果如表1,問兩種療法有無差别?

表1 兩種療法治療卵巢癌的療效比較

卡方分箱(chi-square)1卡方分箱之python代碼實

可以計算出各格内的期望頻數。

第1行1列:43×53/87=26.2

第1行2列:43×34/87=16.8

第2行1列:44×53/87=26.8

第2行2列:4×34/87=17.2

先建立原假設:A、B兩種療法沒有差別。根據卡方值的計算公式,計算:

卡方分箱(chi-square)1卡方分箱之python代碼實

算得卡方值=10.01。

得到卡方值以後,接下來需要查詢卡方分布表來判斷p值,進而做出接受或拒絕原假設的決定。

首先我們明确自由度的概念:自由度k=(行數-1)*(列數-1)。這裡k=1.然後看卡方分布的臨界機率表,我們可以用如下代碼生成:

#python金融風控評分卡模型和資料分析:https://edu.csdn.net/combo/detail/1927
#講師csdn學院教學首頁:https://edu.csdn.net/lecturer/5602
import numpy as np
from scipy.stats import chi2
import pandas as pd
# chi square distribution
percents = [ 0.95, 0.90, 0.5,0.1, 0.05, 0.025, 0.01, 0.005]
df =pd.DataFrame(np.array([chi2.isf(percents, df=i) for i in range(1, 30)]))
df.columns = percents
df.index =df.index+1
pd.set_option('precision', 3)
           
卡方分箱(chi-square)1卡方分箱之python代碼實

查表自由度為1,p=0.05的卡方值為3.841,而此例卡方值10.01>3.841,是以 p < 0.05,說明原假設在0.05的顯著性水準下是可以拒絕的。也就是說,原假設不成立。

四、ChiMerge分箱算法

ChiMerge卡方分箱算法由Kerber于1992提出。

它主要包括兩個階段:初始化階段和自底向上的合并階段。

1.初始化階段:

首先按照屬性值的大小進行排序(對于非連續特征,需要先做數值轉換,比如轉為壞人率,然後排序),然後每個屬性值單獨作為一組。

2.合并階段:

(1)對每一對相鄰的組,計算卡方值。

(2)根據計算的卡方值,對其中最小的一對鄰組合并為一組。

(3)不斷重複(1),(2)直到計算出的卡方值都不低于事先設定的門檻值,或者分組數達到一定的條件(如最小分組數5,最大分組數8)。

值得注意的是,小編之前發現有的實作方法在合并階段,計算的并非相鄰組的卡方值(隻考慮在此兩組内的樣本,并計算期望頻數),因為他們用整體樣本來計算此相鄰兩組的期望頻數。

下圖是著名的鸢尾花資料集sepal-length屬性值的分組及相鄰組的卡方值。最左側是屬性值,中間3列是class的頻數,最右是卡方值。這個分箱是以卡方門檻值1.4的結果。可以看出,最小的組為[6.7,7.0),它的卡方值是1.5。

卡方分箱(chi-square)1卡方分箱之python代碼實

如果進一步提高門檻值,如設定為4.6,那麼以上分箱還将繼續合并,最終的分箱如下圖:

卡方分箱(chi-square)1卡方分箱之python代碼實

卡方分箱除了用門檻值來做限制條件,還可以進一步的加入分箱數限制,以及最小箱占比,壞人率限制等。

卡方分箱之python代碼實

在上篇文章中,介紹了卡方分箱的基本思想和方法,都是概念性的東西,也沒有給出具體的代碼實作。這篇文章就來介紹下小編寫的ChiMerge算法的實作。

卡方值計算

計算卡方值的函數需要輸入numpy格式的頻數表。對于pandas資料集,隻需使用pd.crosstab計算即可,例如變量“總賬戶數” 與 目标變量 “是否壞客戶” 的頻數表,如下圖:

卡方分箱(chi-square)1卡方分箱之python代碼實

每一行代表一個區間(組)的頻數,如上圖中第一行表示 總賬戶數在[2,3) 這個組内對應的好客戶3個, 壞客戶1個。

将頻數表轉成numpy數組,然後調用函數計算卡方值,計算邏輯如下:

1) 計算第 i 行的總數。

2) 計算第 j 列的總數。

3) 計算總頻數 N。

4) 計算 第 i,j 格的期望頻數。

5)求的每個格中的卡方:

卡方分箱(chi-square)1卡方分箱之python代碼實

6) 由于期望頻數 Ei,j有可能是0,此時上一步計算出來的結果無意義,需要清除,不計入最終結果。

7) 把所有格的卡方相加得到卡方值。

代碼如下

'author:xiaodongxu&monica'

ChiMerge分箱算法

卡方分箱函數可以根據最大分組數目和卡方門檻值來控制最終的分箱數。

如果調用時既沒有設定最大分組數,也沒有指定門檻值,那麼函數會自動使用95%的置信度設定門檻值。

分箱邏輯是:

1)初始時,所有變量值都自成一組,統計頻數。

2)然後按照各組起始值從小到大,依次掃描,取出兩組拼成計算卡方值。

如果目前計算出的卡方值小于已觀察到的最小卡方值,則标記目前坐标,并更新已觀察最小卡方值為目前值。

3)掃描一遍後,如果目前分組數大于最大分組數,或者最小卡方值小于門檻值,就将最小卡方值對應的兩組頻數合并,區間也合并。并回第2步執行。否則,停止合并。輸出目前各組的區間切分點。

代碼如下

'author:xiaodongxu&monica'

變量值轉分組

卡方分箱完成後,得到了各個分組的區間起始值。對于任給的一個變量值x,可以使用如下的函數獲得分組值。

代碼如下

'author:xiaodongxu&monica'

需要注意的是,如果需要轉換的值x不在分箱區間之内,很有可能是異常值,不應該期望上面的函數來處理這種情況,而應采用專門的異常值處理程式。

評分卡模組化中的使用執行個體

下面介紹一下評分卡模組化中的卡方分箱的使用。先來看看資料集。

卡方分箱(chi-square)1卡方分箱之python代碼實

除了y變量外,還有3個變量:貸款額度(loan_amnt,數值型),總賬戶數(total_acc,數值型),位址州(addr_state,類别型)。

對總賬戶數total_acc進行分箱:

根據分箱結果進行轉換,衍生新的分組變量:

卡方分箱(chi-square)1卡方分箱之python代碼實

現在已經将 total_acc衍生成為新的類别型變量 total_acc_chi2_group ,接下來可以用WOE編碼繼續加工,然後進入模型啦。

python卡方分箱實戰腳本

 對資料框中的某個變量進行有監督的分箱操作

#python金融風控評分卡模型和資料分析:https://edu.csdn.net/combo/detail/1927
#講師csdn學院教學首頁:https://edu.csdn.net/lecturer/5602
import pandas as pd
import numpy as np
data = pd.read_csv('sample_data.csv', sep="\t", na_values=['', '?'])
temp = data[['x','y']]
 
 
# 定義一個卡方分箱(可設定參數置信度水準與箱的個數)停止條件為大于置信水準且小于bin的數目
def ChiMerge(df, variable, flag, confidenceVal=3.841, bin=10, sample = None): 
    '''
    運作前需要 import pandas as pd 和 import numpy as np
    df:傳入一個資料框僅包含一個需要卡方分箱的變量與正負樣本辨別(正樣本為1,負樣本為0)
    variable:需要卡方分箱的變量名稱(字元串)
    flag:正負樣本辨別的名稱(字元串)
    confidenceVal:置信度水準(預設是不進行抽樣95%)
    bin:最多箱的數目
    sample: 為抽樣的數目(預設是不進行抽樣),因為如果觀測值過多運作會較慢
    '''
#進行是否抽樣操作
    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
 
#調用函數參數示例
bins = ChiMerge(temp, 'x','y', confidenceVal=3.841, bin=10,sample=None)
bins
           

歡迎通路講師csdn學院教學首頁:https://edu.csdn.net/lecturer/5602,學習更多python金融模型實戰。