作者 | 劉順祥 來源 | 數據分析1480
前言
在《Python數據清洗(一):類型轉換和冗餘數據刪除》中分享了有關數據類型轉換和冗餘信息刪除的兩個知識點,接下來繼續講解缺失值的識別和處理辦法。缺失值指的是由於人為或機器等原因導致數據記錄的丟失或隱瞞,缺失值的存在一定程度上會影響後續數據分析和挖掘的結果,所以對他的處理將顯得尤為重要。
缺失值的識別
判斷一個數據集是否存在缺失觀測,通常從兩個方面入手,一個是變量的角度,即判斷每個變量中是否包含缺失值;另一個是數據行的角度,即判斷每行數據中是否包含缺失值。關於缺失值的判斷可以使用isnull方法。下面使用isnull方法對data3數據(數據可至中---下載)進行判斷,統計輸出的結果如下表所示。
# 判斷各變量中是否存在缺失值data3.isnull().any(axis = 0)# 各變量中缺失值的數量data3.isnull().sum(axis = 0)# 各變量中缺失值的比例data3.isnull().sum(axis = 0)/data3.shape[0]
如上結果所示,數據集data3中有三個變量存在缺失值,即gender、age和edu,它們的缺失數量分別為136、100和1,927,缺失比例分別為4.53%、3.33%和64.23%。
需要說明的是,判斷數據是否為缺失值NaN,可以使用isnull「方法」,它會返回與原數據行列數相同的矩陣,並且矩陣的元素為bool類型的值,為了得到每一列的判斷結果,仍然需要any「方法」(且設置「方法」內的axis參數為0);統計各變量的缺失值個數可以在isnull的基礎上使用sum「方法」(同樣需要設置axis參數為0);計算缺失比例就是在缺失數量的基礎上除以總的樣本量(shape方法返回數據集的行數和列數,[0]表示取出對應的數據行數)。
讀者可能對代碼中的「axis=0」感到困惑,它代表了什麼?為什麼是0?是否還可以寫其他值?下面通過圖表的形式來說明axis參數的用法:
假設上圖為學生的考試成績表,如果直接對成績表中的分數進行加和操作,得到的是所有學生的分數總和(很顯然沒有什麼意義),如果按學生分別計算總分,將是上圖從左到右的轉換。該轉換的特徵是列數發生了變化(可以是列數減少,也可以是列數增多),類似於在水平方向上受了外部的壓力或拉力,這樣的外力就理解為軸axis為1的效果(便於理解,可以想像為飛機在有動力的情況下,可以保持水平飛行狀態)。
同樣對於如上的學生成績表,如果直接對成績表中的分數計算平均值,得到的是所有學生的平均分數(很顯然也沒有什麼意義),如果按學科分別計算平均分,將是上圖中從上到下的轉換。該轉換的特徵是行數發生了變化(可以是行數減少,也可以是行數增多),類似於在垂直方向上受了外部的擠壓或拉伸,這樣的外力就理解為軸axis為0的效果(便於理解,可以想像為飛機在沒有動力的情況下,呈下降趨勢)。
如上是關於變量方面的缺失值判斷過程,還可以利用下方的代碼識別數據行的缺失值分布情況:
# 判斷數據行中是否存在缺失值
如上結果所示,返回True值,說明data3中的數據行存在缺失值。代碼中使用了兩次any「方法」,第一次用於判斷每一行對應的True(即行內有缺失值)或False值(即行內沒有缺失值);第二次則用於綜合判斷所有數據行中是否包含缺失值。同理,進一步還可以判斷缺失行的具體數量和佔比,代碼如下:
# 缺失觀測的行數data3.isnull().any(axis = 1).sum()# 缺失觀測的比例data3.isnull().any(axis = 1).sum()/data3.shape[0]
如上結果所示,3000行的數據集中有2024行存在缺失值,缺失行的比例約67.47%。不管是變量角度的缺失值判斷,還是數據行角度的缺失值判斷,一旦發現缺失值,都需要對其作相應的處理,否則一定程度上都會影響數據分析或挖掘的準確性。
缺失值的處理辦法
通常對於缺失值的處理,最常用的方法無外乎刪除法、替換法和插補法。刪除法是指將缺失值所在的觀測行刪除(前提是缺失行的比例非常低,如5%以內),或者刪除缺失值所對應的變量(前提是該變量中包含的缺失值比例非常高,如70%左右);替換法是指直接利用缺失變量的均值、中位數或眾數替換該變量中的缺失值,其好處是缺失值的處理速度快,弊端是易產生有偏估計,導致缺失值替換的準確性下降;插補法則是利用有監督的機器學習方法(如回歸模型、樹模型、網絡模型等)對缺失值作預測,其優勢在於預測的準確性高,缺點是需要大量的計算,導致缺失值的處理速度大打折扣。下面將選擇刪除法、替換法和插補法對缺失值進行處理,代碼如下:
# 刪除欄位 -- 如刪除缺失率非常高的edu變量data3.drop(labels = 'edu', axis = 1, inplace=True)# 數據預覽data3.head()
如上結果所示,表中的edu變量已被成功刪除。對於欄位的刪除可以選擇drop「方法」,其中labels參數用於指定需要刪除的變量名稱,如果是多個變量,則需要將這些變量名稱寫在一對中括號內(如['var1','var2','var3']);刪除變量一定要設置axis參數為1,因為變量個數發生了變化(所以,藉助於axis參數也可以刪除觀測行啦);inplace則表示是否原地修改,即是否直接將原表中的欄位進行刪除,這裡設置為True,如果設置為False,則將刪除變量的預覽效果輸出來,而非真正改變原始數據。
# 刪除觀測,-- 如刪除age變量中所對應的缺失觀測data3_new = data3.drop(labels = data3.index[data3['age'].isnull()], axis = 0)# 查看數據的規模data3_new.shapeout:(2900, 5)
如上結果所示,利用drop「方法」實現了數據行的刪除,但必須將axis參數設置為0,而此時的labels參數則需要指定待刪除的行編號。這裡的行編號是藉助於index「方法」(用於返回原始數據的行編號)和isnull「方法」(用於判斷數據是否為缺失狀態,如果是缺失則返回True)實現的,其邏輯就是將True對應的行編號取出來,傳遞給labels參數。
如果變量的缺失比例非常大,或者缺失行的比例非常小時,使用刪除法是一個不錯的選擇,反之,將會丟失大量的數據信息而得不償失。接下來講解如何使用替換法處理缺失值,代碼如下:
如上結果所示,採用替換法後,原始數據中的變量不再含有缺失值。缺失值的填充使用的是fillna「方法」,其中value參數可以通過字典的形式對不同的變量指定不同的值。需要強調的是,如果計算某個變量的眾數,一定要使用索引技術,例如代碼中的[0],表示取出眾數序列中的第一個(我們知道,眾數是指出現頻次最高的值,假設一個變量中有多個值共享最高頻次,那麼Python將會把這些值以序列的形式存儲起來,故取出指定的眾數值,必須使用索引)。
正如前文所說,雖然替換法思想簡單、效率高效,但是其替換的值往往不具有很高的準確性,於是出現了插補方法。該方法需要使用機器學習算法,不妨以KNN算法為例,對Titanic數據集中的Age變量做插補法完成缺失值的處理。代碼如下:
# 讀取數據titanic = pd.read_csv('Titanic.csv')# 刪除缺失嚴重的Cabin變量titanic.drop(labels='Cabin', axis = 1, inplace=True)# 根據Embarked變量,刪除對應的缺失行titanic.dropna(subset=['Embarked'], inplace=True)# 刪除無關緊要的變量(這些變量對後面預測年齡沒有太多的幫助)titanic.drop(labels=['PassengerId','Name','Ticket','Embarked'], axis = 1, inplace=True)# 將字符型的性別變量映射為數值變量titanic.Sex = titanic.Sex.map({'male':1, 'female':0})# 將數據拆分為兩組,一是年齡缺失組,二是年齡非缺失組,後續基於非缺失值構建KNN模型,再對缺失組做預測nomissing = titanic.loc[~titanic.Age.isnull(),]missing = titanic.loc[titanic.Age.isnull(),]# 導入機器學習的第三方包from sklearn import neighbors# 提取出所有的自變量X = nomissing.columns[nomissing.columns != 'Age']# 構建模型knn = neighbors.KNeighborsRegressor()# 模型擬合knn.fit(nomissing[X], nomissing.Age)# 年齡預測pred_age = knn.predict(missing[X])