專欄 | 基於 Jupyter 的特徵工程手冊:資料預處理(三)

紅色石頭發表於2020-04-11

基於 Jupyter 的特徵工程手冊:資料預處理的上一篇:

專欄 | 基於 Jupyter 的特徵工程手冊:資料預處理(一)

專欄 | 基於 Jupyter 的特徵工程手冊:資料預處理(二)

專案地址:

https://github.com/YC-Coder-Chen/feature-engineering-handbook

本專案將探討資料預處理部分:介紹瞭如何利用 scikit-learn 處理靜態的連續變數,利用 Category Encoders 處理靜態的類別變數以及利用 Featuretools 處理常見的時間序列變數。

目錄

特徵工程的資料預處理我們將分為三大部分來介紹:

  • 靜態連續變數
  • 靜態類別變數
  • 時間序列變數

本文將介紹 1.2 靜態類別變數的資料預處理(下部分,即1.2.7-1.2.11)。下面將結合 Jupyter,使用 sklearn,進行詳解。

1.2 Static Categorical Variables 靜態類別變數

真實世界的資料集還往往包含類別特徵。但是由於scikit-learn中的模型只能處理數值特徵,因此我們需要將類別特徵編碼為數值特徵但是,很多新的模型開始直接提供類別變數支援,例如lightGBM和Catboost。這裡我們使用category_encoders包,因為它涵蓋了更多的編碼方法。

1.2.7 M-estimate Encoding M估計量編碼

M估計量編碼是目標編碼的一個簡化版本。與目標編碼器相比,M估計量編碼僅具有一個可調引數(m),而目標編碼器具有兩個可調引數(min_samples_leaf和smoothing)。

公式:

其中m為使用者定義的引數;
m:m為非負數, m的值越高,先驗概率的權重則更大。
?′?為類別特徵X中類別k的編碼值;
Prior:目標變數的先驗概率/期望值;
?+:訓練集中特徵X類別為k,而且具有正因變數標籤的樣本數;
?+:訓練集中具有正因變數標籤的樣本數;

參考文獻:Micci-Barreca, D. (2001). A preprocessing scheme for high-cardinality categorical attributes in classification and prediction problems. ACM SIGKDD Explorations Newsletter, 3(1), 27-32.

import numpy as np
import pandas as pd
from category_encoders.m_estimate import MEstimateEncoder
# category_encoders 直接支援dataframe

# 隨機生成一些訓練集
train_set = pd.DataFrame(np.array([['male',10],['female', 20], ['male',10], 
                       ['female',20],['female',15]]),
             columns = ['Sex','Type'])
train_y = np.array([False, True, True, False, False])

# 隨機生成一些測試集, 並有意讓其包含未在訓練集出現過的類別與缺失值
test_set = pd.DataFrame(np.array([['female',20],['male', 20], ['others',15], 
                       ['male',20],['female',40], ['male', 25]]),
             columns = ['Sex','Type'])
test_set.loc[4,'Type'] = np.nan
train_set # 原始訓練集

test_set # 原始測試集

encoder = MEstimateEncoder(cols=['Sex','Type'], 
                           handle_unknown='value',  
                           handle_missing='value').fit(train_set,train_y) # 在訓練集上訓練
encoded_train = encoder.transform(train_set) # 轉換訓練集
encoded_test = encoder.transform(test_set) # 轉換測試集

# handle_unknown 和 handle_missing 被設定為 'value'
# 在目標編碼中,handle_unknown 和 handle_missing 僅接受 ‘error’, ‘return_nan’ 及 ‘value’ 設定
# 兩者的預設值均為 ‘value’, 即對未知類別或缺失值填充訓練集的因變數平均值

encoded_test # 編碼後的變數數與原類別變數數一致

# 驗證一下計算的結果,在測試集中,‘male’類別的編碼值為 0.466667

y_positive = 2 # 在訓練集中,共有兩個樣本有正的因變數標籤
n_positive = 1 # 在訓練集中,共有兩個樣本在變數‘Sex’中有‘male’標籤,在兩個樣本中僅有一個有正的因變數標籤
prior = train_y.mean() # 訓練集因變數先驗概率
m = 1.0 # 預設值
male_encode = (n_positive + prior * m)/(y_positive + m)
male_encode # return 0.4666666666666666,與要驗證的值吻合

0.4666666666666666

ncoded_train # 訓練集結果

1.2.8 James-Stein Encoder James-Stein 編碼

James-Stein編碼也是一種基於目標編碼的編碼方法。與M估計量編碼一樣,James-Stein編碼器也嘗試通過引數B來平衡先驗概率與觀測到的條件概率。但與目標編碼與M估計量編碼不同的是,James-Stein編碼器通過方差比而不是樣本大小來平衡兩個概率。

James-Stein編碼可使用獨立方法,合併方法等多種方法來估計引數B。有關更多資訊,請參閱category_encoders官方網站:

http://contrib.scikit-learn.org/categorical-encoding/jamesstein.html

James-Stein編碼假定服從正態分佈。因此為了滿足所需的假設,Category Encoders預設使用對數比來轉換二分類問題。

獨立方法的公式:

其中,
?′?是類別特徵X中類別k的編碼值;
先驗概率:目標變數的先驗概率/期望值;
?+:在訓練集中,在類別特徵X上的標籤為k且具有因變數正標籤的樣本數;
?: 在訓練集中,在類別特徵X上標籤為k的樣本數;

???(??):訓練集中,在特徵X上標籤為k的樣本因變數方差;

???(?):總體因變數的方差;

???(??)和???(?)都應通過樣本統計資料進行估算。

從直覺的角度來講,B起到來平衡先驗概率與觀測到的條件概率的作用,若條件概率的均值不可靠(y_k具有高方差),則我們應當對先驗概率賦予更大的權重。

import numpy as np
import pandas as pd
from category_encoders.james_stein import JamesSteinEncoder
# category_encoders 直接支援dataframe

# 隨機生成一些訓練集
train_set = pd.DataFrame(np.array([['male',10],['female', 20], ['male',10], 
                       ['female',20],['female',15]]),
             columns = ['Sex','Type'])
train_y = np.array([False, True, True, False, False])

# 隨機生成一些測試集, 並有意讓其包含未在訓練集出現過的類別與缺失值
test_set = pd.DataFrame(np.array([['female',20],['male', 20], ['others',15], 
                       ['male',20],['female',40], ['male', 25]]),
             columns = ['Sex','Type'])
test_set.loc[4,'Type'] = np.nan
train_set # 原始訓練集

test_set # 原始測試集

encoder = JamesSteinEncoder(cols=['Sex','Type'], 
                           handle_unknown='value', 
                           model='independent',  
                           handle_missing='value').fit(train_set,train_y) # 在訓練集上訓練
encoded_train = encoder.transform(train_set) # 轉換訓練集
encoded_test = encoder.transform(test_set) # 轉換測試集

# handle_unknown 和 handle_missing 被設定為 'value'
# 在目標編碼中,handle_unknown 和 handle_missing 僅接受 ‘error’, ‘return_nan’ 及 ‘value’ 設定
# 兩者的預設值均為 ‘value’, 即對未知類別或缺失值填充訓練集的因變數平均值

encoded_test # 編碼後的變數數與原類別變數數一致

# 因為在category_encoders中,其對前文所述的公式做了一些修改,故此處不會進一步驗證結果

encoded_train # 訓練集結果

1.2.9 Weight of Evidence Encoder 證據權重編碼

與上述方法類似,證據權重編碼器也是根據類別變數與因變數的關係對分類變數進行編碼。

公式:

以上是WoE的原始定義,但在category_encoders中,它還增加了正則項以應對過擬合。帶正則項的 ?????????????????????? , ?????????????????????? 如下所示:

import numpy as np
import pandas as pd
from category_encoders.woe import WOEEncoder
# category_encoders 直接支援dataframe

# 隨機生成一些訓練集
train_set = pd.DataFrame(np.array([['male',10],['female', 20], ['male',10], 
                       ['female',20],['female',15]]),
             columns = ['Sex','Type'])
train_y = np.array([False, True, True, False, False])

# 隨機生成一些測試集, 並有意讓其包含未在訓練集出現過的類別與缺失值
test_set = pd.DataFrame(np.array([['female',20],['male', 20], ['others',15], 
                       ['male',20],['female',40], ['male', 25]]),
             columns = ['Sex','Type'])
test_set.loc[4,'Type'] = np.nan
train_set # 原始訓練集

test_set # 原始測試集

encoder = WOEEncoder(cols=['Sex','Type'], 
                     handle_unknown='value',  
                     handle_missing='value').fit(train_set,train_y)  # 在訓練集上訓練
encoded_train = encoder.transform(train_set) # 轉換訓練集
encoded_test = encoder.transform(test_set) # 轉換測試集

# handle_unknown 和 handle_missing 被設定為 'value'
# 在目標編碼中,handle_unknown 和 handle_missing 僅接受 ‘error’, ‘return_nan’ 及 ‘value’ 設定
# 兩者的預設值均為 ‘value’, 即對未知類別或缺失值填充訓練集的因變數平均值

encoded_test # 編碼後的變數數與原類別變數數一致

# 驗證一下計算的結果,在測試集中,‘male’類別的編碼值為 0.223144

y = 5 # 訓練集中一共有5個樣本
y_positive = 2 # 訓練集中2個樣本有正標籤

n = 2 # 訓練集中有2個樣本在Sex變數上有‘male’ 標籤
n_positive = 1 # 這兩個樣本中僅有一個有正標籤

regularization = 1.0 # 預設值

dis_postive = (n_positive + regularization) / (y_positive + 2 * regularization)
dis_negative = (n - n_positive + regularization) / (y - y_positive + 2 * regularization)
male_encode = np.log(dis_postive / dis_negative)
male_encode # return 0.22314355131420976,與要驗證的值吻合

0.22314355131420976

encoded_train # 訓練集結果

1.2.10 Leave One Out Encoder 留一法編碼

留一法編碼器通過組因變數均值對每個組進行編碼。此處組指的是類別變數中的不同類別。

留一法同時考慮了過擬合問題,訓練集中的每一個樣本的編碼值是除去該樣本後的組因變數均值。因此,在訓練集中,其可以將處於相同組的每個樣本編碼為不同的值。

留一法以不同的方式對測試集進行編碼。測試集中的每個樣本均由訓練集中的組均值編碼,計算過程中沒有考慮去除該樣本。

公式:

此處,若樣本j擁有k標籤,則( ??==? )返回1,否則返回0

??? 為樣本i的標籤為k情形下的編碼值

import numpy as np
import pandas as pd
from category_encoders.leave_one_out import LeaveOneOutEncoder
# category_encoders 直接支援dataframe

# 隨機生成一些訓練集
train_set = pd.DataFrame(np.array([['male',10],['female', 20], ['male',10], 
                       ['female',20],['female',15]]),
             columns = ['Sex','Type'])
train_y = np.array([False, True, True, False, False])

# 隨機生成一些測試集, 並有意讓其包含未在訓練集出現過的類別與缺失值
test_set = pd.DataFrame(np.array([['female',20],['male', 20], ['others',15], 
                       ['male',20],['female',40], ['male', 25]]),
             columns = ['Sex','Type'])
test_set.loc[4,'Type'] = np.nan
train_set # 原始訓練集

test_set # 原始測試集

encoder = LeaveOneOutEncoder(cols=['Sex','Type'], 
                             handle_unknown='value',  
                             handle_missing='value').fit(train_set,train_y)  # 在訓練集上訓練
encoded_train = encoder.transform(train_set) # 轉換訓練集
encoded_test = encoder.transform(test_set) # 轉換測試集

# handle_unknown 和 handle_missing 被設定為 'value'
# 在目標編碼中,handle_unknown 和 handle_missing 僅接受 ‘error’, ‘return_nan’ 及 ‘value’ 設定
# 兩者的預設值均為 ‘value’, 即對未知類別或缺失值填充訓練集的因變數平均值

encoded_test # 編碼後的變數數與原類別變數數一致
# 結果可見,所有類別值都被編碼為訓練集中的類別因變數均值

# 訓練集結果
LeaveOneOutEncoder(cols=['Sex','Type'],
                   handle_unknown='value',  
                   handle_missing='value').fit_transform(train_set,train_y)

# 進行小驗算:
# 對第一個樣本而言,其在Sex變數上的標籤為‘male’
# 除去該樣本後,‘male’標籤樣本的因變數平均值為1.0 (僅剩樣本3有‘male’標籤,且其有正的因變數標籤)
# 同理,對第三個同樣有‘male’標籤的樣本,除去它後標籤樣本的因變數平均值變為了0.0

1.2.11 Catboost Encoder Catboost 編碼

CatBoost是一個基於樹的梯度提升模型。其在包含大量類別特徵的資料集問題中具有出色的效果。該模型針對分類特徵提出了一種基於“留一法編碼器”的新編碼系統。在使用Catboost編碼器之前,必須先對訓練資料隨機排列,因為在Catboost中,編碼是基於“時間”的概念,即資料集中觀測值的順序。

公式:

其中,若樣本j擁有k標籤,則( ??==? )返回1,否則返回0

??? 為樣本i的標籤為k情形下的編碼值

Prior 為因變數的先驗概率/期望值

a為正則化係數

import numpy as np
import pandas as pd
from category_encoders.cat_boost import CatBoostEncoder
# category_encoders 直接支援dataframe

# 隨機生成一些訓練集
train_set = pd.DataFrame(np.array([['male',10],['female', 20], ['male',10], 
                       ['female',20],['female',15]]),
             columns = ['Sex','Type'])
train_y = np.array([False, True, True, False, False])

# 隨機生成一些測試集, 並有意讓其包含未在訓練集出現過的類別與缺失值
test_set = pd.DataFrame(np.array([['female',20],['male', 20], ['others',15], 
                       ['male',20],['female',40], ['male', 25]]),
             columns = ['Sex','Type'])
test_set.loc[4,'Type'] = np.nan
train_set # 原始訓練集

test_set # 原始測試集

# 事實上,在使用Catboost編碼前,我們本應先打亂資料順序
# 但由於我們的資料本身已經是隨機生成的,故無需打亂

encoder = CatBoostEncoder(cols=['Sex','Type'], 
                          handle_unknown='value',  
                          handle_missing='value').fit(train_set,train_y)   # 在訓練集上訓練
encoded_train = encoder.transform(train_set) # 轉換訓練集
encoded_test = encoder.transform(test_set) # 轉換測試集

# handle_unknown 和 handle_missing 被設定為 'value'
# 在目標編碼中,handle_unknown 和 handle_missing 僅接受 ‘error’, ‘return_nan’ 及 ‘value’ 設定
# 兩者的預設值均為 ‘value’, 即對未知類別或缺失值填充訓練集的因變數平均值

encoded_test # 編碼後的變數數與原類別變數數一致

# 驗證一下計算的結果,在測試集中,‘male’類別的編碼值為 0.466667

Prior = train_y.mean() # 先驗概率
n = 2 # 在訓練集中,兩個樣本在Sex變數上具有‘male’標籤
n_positive = 1 # 這兩個樣本中,僅有一個擁有正標籤
a = 1 # 正則化係數, 預設值為1

encoded_male = (n_positive + a * prior) / (n + a) 
encoded_male # return 0.4666666666666666,與要驗證的值吻合

0.4666666666666666

# 驗證一下訓練集的結果
CatBoostEncoder(cols=['Sex','Type'],
                handle_unknown='value',  
                handle_missing='value').fit_transform(train_set,train_y)

# 訓練集中第三個樣本在Sex這一變數上有‘male’標籤,其編碼值為0.2
Prior = train_y.mean() # 先驗概率
n = 1 # 在第三個樣本前僅有一個樣本有‘male’標籤
n_positive = 0 # 這僅有的一個樣本沒有正標籤
a = 1 # 正則化係數

encoded_male = (n_positive + a * Prior) / (n + a)
encoded_male # return 0.2

0.2

好了,以上就是關於靜態類別變數(下部分)的資料預處理介紹。建議讀者結合程式碼,在 Jupyter 中實操一遍。

目前該專案完整中文版正在製作中,請持續關注哦~

中文版 Jupyter 地址:


本文首發於公眾號:AI有道(ID: redstonewill),歡迎關注!

相關文章