編者按:在現實生活中,大多數資料都需要進行清洗和預處理,以便在使用資料時達到最佳效果。機器學習流程只能處理數字,因此需要找到一種方法將非數字特徵轉化為數字表示。本文還介紹了三種缺失值型別:完全缺失、隨機缺失和非隨機缺失,並教授如何使用Python來檢測和處理缺失值。
透過閱讀本文,我相信你將瞭解什麼是資料清洗,還能掌握資料清洗的步驟以及如何進行實操。
以下是譯文,Enjoy!
作者 | Fuad Adio
編譯 | 嶽揚
資料清洗(Data Cleaning)是透過修改、新增或刪除資料的方式為資料分析做準備的過程,這個過程通常也被稱為資料預處理(Data Preprocessing)。對於資料科學家和機器學習工程師來說,熟練掌握資料清洗全流程至關重要,因為資料預處理後的質量將直接影響他們或訓練的模型從資料中獲得的所有結論和觀點(insights) 。
在這篇博文中,我們將重點講述類別型特徵(categorical feature)方面的資料清理概念(本文第5部分),給出合適的圖片以幫助理解,並提供程式碼演示等。
在本文結束時,我相信你不僅會了解什麼是資料清洗,還能完美地掌握資料清洗的步驟,如何進行實操,以及最重要的是如何從你的資料中得到最好的結果。
01 為什麼我們需要清洗資料?
有時,在我們能夠從資料中提取有用的資訊之前,需要對資料進行清洗/預處理。大多數現實生活中的資料都有很多需要處理的地方,如缺失值、非資訊特徵等,因此,在使用資料之前,我們一直需要對其進行清洗,以便在使用資料時達到最佳效果。
以機器學習流程為例,它的工作物件只有數字。如果我們打算使用具有文字、日期時間特徵和其他非數字特徵的資料,我們需要找到一種方法,用數字來表示它們,而不丟失它們所攜帶的資訊。
比如說一個簡單的邏輯迴歸模型只是用一個線性函式將因變數對映到自變數上,如:y= wx + b。其中,w是權重,可以是任何數字,b是偏差,也是一個數字。
如果給你一組資料;[小、中、大],你會發現不可能計算出:y=w*small+b。
但如果你把“小”編碼為1,“中”編碼為2,“大”的編碼為3,從而把你的資料轉化為[1,2,3],你會發現你不僅能夠計算y=w*1+b,而且其中編碼的資訊仍然被保留。
02 資料清洗有哪些步驟?
資料清洗是我們在為資料分析做準備的過程中對資料進行的一系列操作的統稱。
資料清洗的步驟包括:
- 處理缺失值(Handling missing values)
- 對類別型特徵進行編碼(Encoding categorical features)
- 異常值檢測(Outliers detection)
- 變換(Transformations)
- ...
後續我們將重點展開處理缺失值和對類別型特徵進行編碼。
03 處理缺失值(Handling missing values)
在進行資料預處理時遇到的最常見的問題之一就是我們資料中存在缺失值。這個情況是非常常見的,可能因為 :
- 填寫資料的人有意或無意的遺漏,或者資料根本不適合填在此處。
- 工作人員將資料輸入電腦時出現遺漏。
資料科學家如果不注意,可能會從包含缺失值的資料中得出錯誤的推論,這就是為什麼我們需要研究缺失值並學會有效地解決它。
早期的機器學習庫(比如scikit learn)不允許將缺失值傳入。這就會帶來一定的挑戰,因為資料科學家在將資料傳遞給scikit learn ML模型之前,需要迭代很多方法來處理缺失值。最新的機器學習模型和平臺已經解除了這一障礙,特別是基於梯度提升機(gradient boosting machines)的一些演算法或工具,如Xgboost、Catboost、LightGBM等等。
我特別看好的是Catboost方法,它允許使用者在三個選項(Forbidden, Min, and Max)中進行選擇。Forbidden將缺失值視為錯誤,而Min將缺失值設定為小於特定特徵(列)中的所有其他值。這樣,我們就可以肯定,在對特徵進行基本的決策樹分裂(decision tree splitting)時,這些缺失值也會被考慮在內。
LightGBM[1]和XGboost[2]也能以相當方便的方式處理缺失值。然而,在進行預處理時,要儘可能多地嘗試各種方法。使用像我們上面討論的那些庫,其他的工具,如automl等等。
缺失值一般分為三類,即:
1) 完全隨機缺失(MCAR) 。如果我們沒有任何資訊、理由或任何可以幫助計算它的東西,那麼這個缺失值就是完全隨機缺失。例如,"我是一個很困的研究生,不小心把咖啡打翻在我們收集的紙質調查表上,讓我們失去了所有本來我們會有的資料。"
2) 隨機缺失(MAR) 。如果我們有資訊、理由或任何東西(特別是來自其他已知值)可以幫助計算,那麼這個缺失值就是隨機缺失。例如,"我進行一項調查,其中有一個關於個人收入的問題。但是女性不太可能直接回答關於收入的問題。"
3) 非隨機缺失(NMAR) 。缺失的變數的值與它缺失的原因有關。例如,"如果我進行的調查包括一個關於個人收入的問題。那些低收入的人明顯不太可能回答這個問題"。因此,我們知道為什麼這種資料點可能缺失。
04 如何使用Python檢測缺失值?
在我們處理缺失值之前,我們理應學習如何檢測它們,並根據缺失值的數量、我們有多少資料等等來決定如何處理我們的資料。我喜歡並經常使用的一個方法是為某一列設定一個閾值,以決定它是可以修復還是無法修復。
下面,你會看到一個函式,它實現了我們在之前討論的一些想法。
def missing_removal(df, thresh, confirm= None):
holder= {}
for col in df.columns:
rate= df[col].isnull().sum() / df.shape[0]
if rate > thresh:
holder[col]= rate
if confirm==True:
df.drop(columns= [i for i in holder], inplace= True)
return df
else:
print(f' Number of columns that have Nan values above the thresh specified{len(holder)}')
return holder
Quick note
如果confirm引數設定為True,所有缺失值百分比高於設定閾值的列都會被刪除;如果confirm引數設定為None或False,該函式會返回資料中所有列的缺失值百分比列表。現在去你的資料上試試吧!
4.1 統計歸納法(Statistical imputation)
這是一種被長期證明有效的方法。只需要簡單地用某一列的平均數、中位數、模式來填補該列中的缺失資料。這很有效,正在閱讀的夥伴們,請相信我!
Scikit-learn提供了一個名為SimpleImputer[3]的子類,就是以這種方式處理我們的缺失值。
下面是一個簡短的程式碼描述,能夠有助於我們更好地理解統計歸因法。
from sklearn.impute import SimpleImputer
# store the columns of the dataframe 儲存dataframe的所有列
cols= df.columns
#instantiate the SimpleImputer subclass 例項化SimpleImputer子類
#Depending on how you want to imput, you can use most_frequent, mean, median and constant 根據你想輸入的方式,你可以使用most_frequent、mean、median和constant。
imputer= SimpleImputer(strategy= 'most_frequent')
df= imputer.fit_transform(df)
# convert back to dataframe 轉換回dataframe
df= pd.DataFrame(df, columns= cols)
4.2 鏈式方程多重填補 Multiple Imputation by Chained Equations (MICE)
在這種方法中,每一列和它的缺失值被建模為資料中其他列的函式。 這個過程不斷重複,直到之前計算出的值與當前值之間的公差非常小,並且低於給定的閾值。
Scikit-learn提供了一個名為IterativeImputer[4]的子類,可以用它處理缺失值。
下面是一個簡短的程式碼描述,希望能幫你理解這種方法。
from sklearn.experimental import enable_iterative_imputer
from sklearn.impute import IterativeImputer
# store the columns of the dataframe 儲存dataframe的所有列
cols= df.columns
#instantiate IterativeImputer subclass 例項化IterativeImputer子類
imputer= IterativeImputer(max_iter= 3)
df= imputer.fit_transform(df)
# convert back to dataframe 轉換回dataframe
df= pd.DataFrame(df, columns= cols)
希望上述這些內容足以讓你有效地處理缺失值。
關於缺失值的更多資訊,請檢視Matt Brems的這份材料[5]。
05 對類別型特徵進行編碼(Encoding categorical features)
5.1 什麼是類別型特徵
類別型特徵是隻取離散值的特徵。它們不具有連續值,如3.45,2.67等。一個類別型特徵的值可以是大、中、小,1-5的排名,是和不是,1和0,黃色、紅色、藍色等等。它們基本上代表類別,如年齡組、國家、顏色、性別等。
很多時候類別型特徵是以文字格式出現的,有些時候它們是以數字格式出現的(大多數人經常無法識別這種格式的類別型特徵)。
5.2 如何識別類別型特徵?
如何識別類別型特徵不應該是一個大問題,大多數時候,分類特徵是以文字格式出現的。
如果有一種排名形式,而且它們實際上是以數字格式出現的,那怎麼辦?此時要做的就是檢查一列中唯一值的數量,並將其與該列中的行數進行比較。 例如,一列有2000行,只有5或10個唯一值,你很可能不需要別人來告訴你該列是分類列。這沒有什麼規則,只能依靠直覺,你可能是對的,也可能是錯的。
5.3 類別型特徵編碼方法
- 給定一個類別型特徵,正如前文我們所研究的那樣,我們面臨的問題是將每個特徵中的獨特類別轉換為數字,同時不丟失其中編碼的資訊。基於一些可觀察的特徵,有各種類別型特徵的編碼方法。同時也有兩類類別型特徵:
- 有序的類別型特徵:在這一特定特徵中,類別之間存在固有的順序或關係,例如大小(小、大、中)、年齡組等。
- 無序的類別型特徵:該特徵的類別之間沒有合理的順序,如國家、城市、名稱等。
處理上述兩類的方法是不同的。我在處理無序的類別型特徵時使用下面這些方法:
- 獨熱編碼(One-hot encoding)
- 頻數編碼(Frequency/count encoding)
- 目標編碼/均值編碼(Target mean encoding)
- 有序整數編碼(Ordered integer encoding)
- 二進位制編碼(Binary encoding)
- 留一法(Leave-one-out)編碼
- 證據權重編碼(Weight of evidence encoding)
對於有序的類別型特徵,我們只使用
- 標籤編碼或序號編碼(Label encoding or ordinal encoding)
現在,我們將嘗試一個接一個地研究其中的一些方法。並儘可能地用Python實現它們,主要是使用名為category_encoders的Python庫。
可以使用 pip install category_encoders 來安裝這個庫。
1) 獨熱編碼(One-hot encoding)
獨熱編碼是對無序的類別型特徵(nominal categorical features)進行編碼的最有效方法之一。這種方法為列中的每個類別建立一個新的二進位制列。理想情況下,我們會刪除其中一列以避免各列之間的共線性,因此,具有K個唯一類別的特徵會在資料中產生額外的K-1列。
這種方法的缺點是,當一個特徵有許多唯一的類別或資料中有許多類別型特徵時,它就會擴充套件特徵空間(feature space)。
上圖解釋了獨熱編碼的概念,以該種方式對特徵進行編碼能夠消除所有形式的分層結構。
下面介紹如何在Python中實現這種方法。
import pandas as pd
data= pd.get_dummies(data, columns, drop_first= True)
#check https://pandas.pydata.org/docs/reference/api/pandas.get_dummies.html for more info
2)頻數編碼(Frequency/count encoding)
這種方法同樣非常有效。它根據無序的類別型特徵在特徵(列)中出現的頻率為無序的類別型特徵引入了分層結構(hierarchy)。 它與計數編碼非常相似,因為計數編碼可以取任何值,而頻率編碼則規一化為0到1之間。
下面介紹頻數編碼在Python中的實現:
# Frequency encoding 頻率編碼
# cols is the columns we wish to encode cols引數是我們要編碼的那些列
#df is the dataFrame df引數是dataFrame
def freq_enc(df, cols):
for col in cols:
df[col]= df[col].map(round(df[col].value_counts()/len(df),4))
return df
# count encoding 計數編碼
def count_enc(df, cols):
for col in cols:
df[col]= df[col].map(round(df[col].value_counts()))
return df
3) 目標編碼/均值編碼(Target mean encoding)
這種方法的思路是非常好的!它有一個非常獨特的地方,就是它在計算過程中使用了目標列,這在一般的機器學習應用過程中是非常罕見的。類別型特徵中的每一個類別都被替換成該類別目標列的平均值。
該方法非常好,但是如果它編碼了太多關於目標列的資訊,可能會導致過擬合。因此,在使用該方法之前,我們需要確保這個類別型特徵與目標列沒有高度關聯。
測試資料透過對映使用來自訓練資料的儲存值進行編碼。
下面介紹目標編碼/均值編碼在Python中的實現:
# target mean encoding
#target_col is the name of the target column (str)
def target_mean_enc(df, cols, target_col):
mean_holder= {}
for col in cols:
col_mean= {}
cat= list(df[col].unique())
for i in cat:
data= df[df[col]== i]
mean= np.mean(data[target_col])
col_mean[i]= mean
mean_holder[col]= col_mean
return mean_holder
上面的函式返回一個字典,其中包含被編碼列的平均值,然後將字典對映到資料上。見下圖:
4) 有序整數編碼(Ordered integer encoding)
這種方法與目標編碼/均值編碼非常相似,只是它更進一步,它根據目標均值(target mean)的大小對類別進行排序。
在實現有序整數編碼後,dataframe看起來像這樣:
下面介紹有序整數編碼在Python中的實現:
def ordered_interger_encoder(data, cols, target_col):
mean_holder= {}
for col in cols:
labels = list(enumerate(data.groupby([col])[target_col].mean().sort_values().index))
col_mean= {value:order for order,value in labels}
mean_holder[col]= col_mean
return mean_holder
5) 二進位制編碼(Binary encoding)
二進位制編碼的工作原理是獨一無二的,幾乎與獨熱編碼類似,不過還是有很多創新點。首先,它根據那些唯一特徵(unique features)在資料中的出現方式為其分配level(不過這個level沒有任何意義) 。然後,這些level被轉換為二進位制。最後各個數字被劃分到不同的列。
下面介紹了使用category_encoders庫進行二進位制編碼的演示:
from category_encoders import BinaryEncoder
binary= BinaryEncoder(cols= ['STATUS'])
binary.fit(data)
train= binary.transform(train_data)
test= binary.transform(test_data)
6) 留一法(Leave-one-out)編碼
這種方法也非常類似於目標編碼/均值編碼,只是在每個級別上都計算目標均值,而不是隻考慮在特定級別上。不過,目標編碼/均值編碼仍用於測試資料(檢視更多資訊[6])。
下面介紹留一法(Leave-one-out)編碼在Python中的實現:
from category_encoders import leave_one_out
binary= leave_one_out(cols= ['STATUS'])
binary.fit(data)
train= binary.transform(train_data)
test= binary.transform(test_data)
7) 證據權重編碼(Weight of evidence encoding)
這是一種已經在信用風險分析中使用了長達七十年的方法。它通常用於邏輯迴歸任務的特徵轉化,因為它有助於揭示我們之前可能看不到的特徵之間的相關性。這種方法只能用於分類任務。
它透過對列中的每個類別應用ln(p(good)/p(bad))來轉換分類列。
p(good)是目標列的一個類別,例如p(1),而p(bad)是第二種類別,可能只是p(0)。
下面介紹了使用category_encoders庫進行證據權重編碼的演示:
from category_encoders import WOEEncoder
binary= WOEEncoder(cols= ['STATUS'])
binary.fit(data)
train= binary.transform(train_data)
test= binary.transform(test_data)
8) 標籤編碼(Label Encoding)和序號編碼(Ordinal Encoding)
這種方法用於對無序的類別型特徵進行編碼,我們只需要根據我們可以推斷出的大小為每個類別分配數字。
下面介紹這種方法在Python中的實現:
df['size']= df['size'].map({'small':1, 'medium':2, 'big':3})
df
06 資料清洗工具和庫
在該文章中提到了一些機器學習庫,如scikit learn[7]和category_encoders[8]。還有其他一些有助於資料預處理的Python庫,包括:Numpy, Pandas, Seaborn, Matplotlib, Imblearn等等。
然而,如果你是那種不喜歡寫太多程式碼的人,你可以看看OpenRefine[9]、Trifacta Wrangler[10]等這些工具。
07 總結
到此為止,我們所討論的大部分概念的程式碼實現與例項都可以在本文找到。我希望這篇文章能夠讓你對特徵編碼(Categorical Encoding) 的概念和如何填補缺失值有一個較深刻的認識。
如果你要問應該使用哪種方法?
資料清洗是一個反覆的過程,因此,你可以隨意嘗試,並堅持使用對你的資料最有效的那一種。
END
參考資料
1.https://lightgbm.readthedocs.io/en/latest/Advanced-Topics.html
3.https://scikit-learn.org/stable/modules/generated/sklearn.imp...
4.https://scikit-learn.org/stable/modules/generated/sklearn.imp...
5.https://github.com/Fuad28/Data-Cleaning-Article/blob/main/Analysis%20with%20Missing%20Data.pdf
6.https://datascience.stackexchange.com/questions/10839/what-is...
7.https://scikit-learn.org/stable/
8.https://contrib.scikit-learn.org/category_encoders
10.https://www.trifacta.com/products/wrangler/
本文經原作者授權,由Baihai IDP編譯。如需轉載譯文,請聯絡獲取授權。
原文連結: