1. 程式人生 > >連續變數最優分箱--基於CART演算法

連續變數最優分箱--基於CART演算法

關於變數分箱主要分為兩大類:有監督型和無監督型

對應的分箱方法:

A. 無監督:(1) 等寬 (2) 等頻 (3) 聚類

B. 有監督:(1) 卡方分箱法(ChiMerge) (2) ID3、C4.5、CART等單變數決策樹演算法 (3)  信用評分建模的IV最大化分箱 等

本篇使用python,基於CART演算法對連續變數進行最優分箱

由於CART是決策樹分類演算法,所以相當於是單變數決策樹分類。

簡單介紹下理論:

CART是二叉樹,每次僅進行二元分類,對於連續性變數,方法是依次計算相鄰兩元素值的中位數,將資料集一分為二,計算該點作為切割點時的基尼值較分割前的基尼值下降程度,每次切分時,選擇基尼下降程度最大的點為最優切分點,再將切分後的資料集按同樣原則切分,直至終止條件為止。

關於CART分類的終止條件:視實際情況而定,我的案例設定為 a.每個葉子節點的樣本量>=總樣本量的5%   b.內部節點再劃分所需的最小樣本數>=總樣本量的10%

python程式碼實現:

  1. import pandas as pd
  2. import numpy as np
  3. #讀取資料集,至少包含變數和target兩列
  4. sample_set = pd.read_excel('/資料樣本.xlsx')
  5. def calc_score_median(sample_set, var):
  6.     '''
  7.     計算相鄰評分的中位數,以便進行決策樹二元切分
  8.     param sample_set: 待切分樣本
  9.     param var: 分割變數名稱
  10.     '''
  11.     var_list = list(np.unique(sample_set[var]))
  12.     var_median_list = []
  13.     for i in range(len(var_list) -1):
  14.         var_median = (var_list[i] + var_list[i+1]) / 2
  15.         var_median_list.append(var_median)
  16.     return var_median_list

var表示需要進行分箱的變數名,返回一個樣本變數中位數的list

  1. def choose_best_split(sample_set, var, min_sample)
    :
  2.     '''
  3.     使用CART分類決策樹選擇最好的樣本切分點
  4.     返回切分點
  5.     param sample_set: 待切分樣本
  6.     param var: 分割變數名稱
  7.     param min_sample: 待切分樣本的最小樣本量(限制條件)
  8.     '''
  9.     # 根據樣本評分計算相鄰不同分數的中間值
  10.     score_median_list = calc_score_median(sample_set, var)
  11.     median_len = len(score_median_list)
  12.     sample_cnt = sample_set.shape[0]
  13.     sample1_cnt = sum(sample_set['target'])
  14.     sample0_cnt =  sample_cnt- sample1_cnt
  15.     Gini = 1 - np.square(sample1_cnt / sample_cnt) - np.square(sample0_cnt / sample_cnt)
  16.     bestGini = 0.0; bestSplit_point = 0.0; bestSplit_position = 0.0
  17.     for i in range(median_len):
  18.         left = sample_set[sample_set[var] < score_median_list[i]]
  19.         right = sample_set[sample_set[var] > score_median_list[i]]
  20.         left_cnt = left.shape[0]; right_cnt = right.shape[0]
  21.         left1_cnt = sum(left['target']); right1_cnt = sum(right['target'])
  22.         left0_cnt =  left_cnt - left1_cnt; right0_cnt =  right_cnt - right1_cnt
  23.         left_ratio = left_cnt / sample_cnt; right_ratio = right_cnt / sample_cnt
  24.         if left_cnt < min_sample or right_cnt < min_sample:
  25.             continue
  26.         Gini_left = 1 - np.square(left1_cnt / left_cnt) - np.square(left0_cnt / left_cnt)
  27.         Gini_right = 1 - np.square(right1_cnt / right_cnt) - np.square(right0_cnt / right_cnt)
  28.         Gini_temp = Gini - (left_ratio * Gini_left + right_ratio * Gini_right)
  29.         if Gini_temp > bestGini:
  30.             bestGini = Gini_temp; bestSplit_point = score_median_list[i]
  31.             if median_len > 1:
  32.                 bestSplit_position = i / (median_len - 1)
  33.             else:
  34.                 bestSplit_position = i / median_len
  35.         else:
  36.             continue
  37.     Gini = Gini - bestGini
  38.     return bestSplit_point, bestSplit_position

min_sample 引數為最小葉子節點的樣本閾值,如果小於該閾值則不進行切分,如前面所述設定為整體樣本量的5%

返回的結果我這裡只返回了最優分割點,如果需要返回其他的比如GINI值,可以自行新增。

  1. def bining_data_split(sample_set, var, min_sample, split_list):
  2.     '''
  3.     劃分資料找到最優分割點list
  4.     param sample_set: 待切分樣本
  5.     param var: 分割變數名稱
  6.     param min_sample: 待切分樣本的最小樣本量(限制條件)
  7.     param split_list: 最優分割點list
  8.     '''
  9.     split, position = choose_best_split(sample_set, var, min_sample)
  10.     if split != 0.0:
  11.         split_list.append(split)
  12.     # 根據分割點劃分資料集,繼續進行劃分
  13.     sample_set_left = sample_set[sample_set[var] < split]
  14.     sample_set_right = sample_set[sample_set[var] > split]
  15.     # 如果左子樹樣本量超過2倍最小樣本量,且分割點不是第一個分割點,則切分左子樹
  16.     if len(sample_set_left) >= min_sample * 2 and position not in [0.0, 1.0]:
  17.         bining_data_split(sample_set_left, var, min_sample, split_list)
  18.     else:
  19.         None
  20.     # 如果右子樹樣本量超過2倍最小樣本量,且分割點不是最後一個分割點,則切分右子樹
  21.     if len(sample_set_right) >= min_sample * 2 and position not in [0.0, 1.0]:
  22.         bining_data_split(sample_set_right, var, min_sample, split_list)
  23.     else:
  24.         None

split_list 引數是用來儲存返回的切分點,每次切分後返回的切分點存入該list

在這裡判斷切分點分割的左子樹和右子樹是否滿足“內部節點再劃分所需的最小樣本數>=總樣本量的10%”的條件,如果滿足則進行遞迴呼叫。

  1. def get_bestsplit_list(sample_set, var):
  2.     '''
  3.     根據分箱得到最優分割點list
  4.     param sample_set: 待切分樣本
  5.     param var: 分割變數名稱
  6.     '''
  7.     # 計算最小樣本閾值(終止條件)
  8.     min_df = sample_set.shape[0] * 0.05
  9.     split_list = []
  10.     # 計算第一個和最後一個分割點
  11.     bining_data_split(sample_set, var, min_df, split_list)
  12.     return split_list

最後整合以下來個函式呼叫,返回一個分割點list。

可以使用sklearn庫的決策樹測試一下單變數分類對結果進行驗證,在分類方法相同,剪枝條件一致的情況下結果是一致的。