資料分析中的最優分段想法整理
作為入職還不到兩個月的資料分析師萌新,感覺工作剛剛起步就碰到了一些瓶頸,這個瓶頸是啥呢?想必看到標題的人也知道了,就是最優分段。
由於資料分析師在建模時往往會用到成百上千個變數,對變數進行分段是不可能一一與業務方溝通再確定分段方法,另外,業務往往也只是根據其以往常規經驗對我們建模的變數提出一些分段建議,是否科學也很難講。這樣下來,想要快速的推進初版的模型就必須更為有效的方式,即最優分段。然而我在網上搜羅了一圈,關於最優分段的講解卻非常的少。不過經過一些搜刮,還是看到一些最優分段相關理念的文章,在前人基礎上,我也嘗試了下python程式碼實現過程,最終基本實現了這一想法。我這裡也簡要介紹一下我的實現思路。
在這裡,我先簡單介紹我在最優分段實現過程中用到的思想及方法。這裡最為重要的思想便是遞迴思想(用其他方式應該也能實現最優分段,但程式碼估計很難做到簡潔明瞭),用到的指標主要有資訊熵(Information Entropy)以及資訊增益(Information Gain)。廢話不多說,下面就是正文。
#對單個變數進行分段,需要傳遞一個DataFrame格式的data進去,同時指定自變數
#與因變數的名字
class single_Bin(object):
def __init__(self,df,var_name,target_name):
self。df = df
self。var_name = var_name
self。target_name = target_name
#等距分段,這段可忽略,與最優分段無關
def EqWidthBinMap(self,BinWidth=5):
min_var = self。df[self。var_name]。min()
max_var = self。df[self。var_name]。max()
binning = (max_var-min_var+1)/BinWidth
self。df[self。var_name] = np。floor(self。df[self。var_name]/binning)*binning
return self。df
#某個片段對應的資訊熵的計算,假設我們要就一個切片進行二分類處理,那麼首先我們需要
#計算出這個切片的資訊熵。這個非常容易處理,我們只需指定切邊的左邊界以及右邊界,計算
#左邊界到右邊界內資料的資訊熵的值即可。
def BinaryEnt(self,left,right):
eps = 0。000001
y1 = self。df[self。target_name]。iloc[left:right]。sum() + eps
y0 = right-left+1-y1+2*eps
p1 = y1*1。0 / (y1+y0)
p0 = y0*1。0 / (y1+y0)
Ent = -(p1*np。log2(p1) + p0*np。log2(p0))
return Ent
#尋找一個切片的最佳分割點。我們現在知道了如何計算一個切邊的資訊熵,那麼又要如何知道
#這個切邊的最佳分割點在哪呢,以及我們應該如何界定這個分割點是否有效呢。我這裡給出
#了一個界定指標,即資訊增益,我這裡假定當最大資訊增益大於我指定的validGain時,我則認為
#這次分割是有效的。這裡我在尋找最佳分割點時,採取了遍歷left到right的所有點的方式,當資料量
#非常大時,這一方式可能會導致計算速度降低。但作為最初的嘗試,我也不想被如此多的細節干擾。
#程式碼本身應該非常易懂,我就不做過多的介紹了。這裡需要注意的時,由於我現在的實現方式依賴於資料
#的排序,所以我在計算資訊熵時必須進行排序,排序工作在外部完成在,且注意要對索引進行reset。否則會導致最優分段結果異常。
def findBestPoint(self,left,right,validGain):
Ent = self。BinaryEnt(left,right)
totalNum = right-left
OptimalPoint = left
MaxGain = 0
if left+1 < right:
for mid in np。arange(left+1,right-1):
left_Ent = self。BinaryEnt(left,mid)
right_Ent = self。BinaryEnt(mid,right)
left_Num = mid-left
right_Num = right-mid
Gain = Ent - (left_Ent*left_Num/totalNum + right_Ent*right_Num/totalNum)
if Gain > validGain and Gain > MaxGain:
OptimalPoint = mid
MaxGain = Gain
#print(‘left’,left,‘right’,right,‘mid’,mid,‘MaxGain’,MaxGain)
#print(‘OptimalPoint’,OptimalPoint)
return OptimalPoint
#好了,我們現在已經知道怎麼找到一個切片的最佳分割點了。接下來,我們只要對整個資料流採用遞迴
#的方法,就能實現對一個變數進行最優分段。這裡的想法我就不做過多介紹了,也比較易懂。大家只需
#這樣一個情景,在我們已經找到一個切邊的最佳分割點後,這個切邊又將被分割成[left:OptimalPoint]
#切片以及[OptiamlPoint:right]切邊,顯然,我們只要對分割好的子切片再次呼叫我們此前的方法就能實現
#對子切片的分割。直到最終無法進行分割,我們的最優分段也就完成了。
def BestBinMap(self,left,right,validGain,OptimalMap={}):
OptimalPoint = self。findBestPoint(left,right,validGain)
if OptimalPoint == left:
return OptimalMap
OptimalMap[OptimalPoint] = self。df[self。var_name][OptimalPoint]
#print(‘OptimalMap’,OptimalMap)
self。BestBinMap(left,OptimalPoint,validGain,OptimalMap)
self。BestBinMap(OptimalPoint,right,validGain,OptimalMap)
return OptimalMap
最後,需要指出的是,最優分段策略並不是真正完美的方法。最優分段策略的結果很有可能在業務上完全沒有意義。它的意義在於可以讓我們更為有效的進行建模。我們可以想象,我們最終整理好了資料,並嘗試運用一些機器學習模型開始訓練,直到最後,我們的模型剔除了很多變數。這裡面也將包括很多我們此前進行了最優分段的變數。也許你會想,你瞧,我進行了最優分段,但最終它們中的大多數還是被我們的模型捨棄了,難道這一切都毫無意義嗎?我認為不是,最優分段恰好使我們可以避免在建模初期就敲定很多變數細節,我們可以反過來想,如果對變數進行最優分段仍然無法入模,那麼在通常意義上講,按照業務方的經驗判斷對變數進行分段則也無法入模。這樣一來,即使變數越來越多,越來越繁雜,我們仍然可以在資料中游刃有餘,而不被它們的表象所迷惑。