機器學習 第2篇:資料預處理(缺失值)

悅光陰發表於2020-12-27

在真實的世界中,缺失資料是經常出現的,並可能對分析的結果造成影響。我們需要了解資料缺失的原因和資料缺失的型別,並從資料中識別缺失值,探索資料缺失的模式,進而處理缺失的資料。本文概述處理資料缺失的方法。

一,資料缺失的原因

首先我們應該知道:資料為什麼缺失?資料的缺失是我們無法避免的,可能的原因有很多種,博主總結有以下三大類:

  • 無意的:資訊被遺漏,比如由於工作人員的疏忽,忘記而缺失;或者由於資料採集器等故障等原因造成的缺失,比如系統實時性要求較高的時候,機器來不及判斷和決策而造成缺失;
  • 有意的:有些資料集在特徵描述中會規定將缺失值也作為一種特徵值,這時候缺失值就可以看作是一種特殊的特徵值;
  • 不存在:有些特徵屬性根本就是不存在的,比如一個未婚者的配偶名字就沒法填寫,再如一個孩子的收入狀況也無法填寫;

總而言之,對於造成缺失值的原因,我們需要明確:是因為疏忽或遺漏無意而造成的,還是說故意造成的,或者說根本不存在。只有知道了它的來源,我們才能對症下藥,做相應的處理。

二,資料缺失的型別

在對缺失資料進行處理前,瞭解資料缺失的機制和形式是十分必要的。將資料集中不含缺失值的變數稱為完全變數,資料集中含有缺失值的變數稱為不完全變數。而從缺失的分佈來將缺失可以分為完全隨機缺失,隨機缺失和完全非隨機缺失。

缺失資料主要分為以下三類:

  • 完全隨機缺失(missing completely at random,MCAR):資料的缺失是完全隨機的,不依賴於其他變數,跟其他變數不相關,不影響樣本的無偏性,如家庭地址缺失;
  • 隨機缺失(missing at random,MAR):資料的缺失不是完全隨機的,依賴於其他完全變數,如財務資料缺失情況與企業的大小有關;
  • 非隨機缺失(missing not at random,MNAR):資料的缺失與不完全變數的取值有關,如高收入人群不原意提供家庭收入;

對於隨機缺失和非隨機缺失,直接刪除記錄是不合適的,上面的定義已經給出原因。隨機缺失可以通過已知變數對缺失值進行估計,而非隨機缺失的非隨機性還沒有很好的解決辦法。而對於完全隨機缺失可以直接刪除。

三,識別缺失值

Python使用NnN代表缺失值,NaN(不是一個數)代表不可能的值,符號Inf和-Inf分別代表正無窮和負無窮。

判斷一個資料集是否存在缺失資料,通常從兩個方面入手,

  • 一個是變數的角度,即判斷每個變數中是否包含缺失值;
  • 另一個是資料行的角度,即判斷每行資料中是否包含缺失值。

在Python環境中,可以使用DataFrame物件的isnull()方法來判斷缺失值,isnull()方法返回與原資料集的shape相同的矩陣,矩陣的元素是bool型別的值,可以稱作原始資料集的影子矩陣,如果影子矩陣的元素值是1,表示在原始資料集中對應該位置的值是缺失的,如果影子矩陣元素的值是0,表示在原始資料集中對應該位置的值是有效的。下面使用isnull()方法對df資料進行判斷,統計輸出的結果如下表所示。

# 判斷各變數中是否存在缺失值
df.isnull().any(axis = 0)
# 各變數中缺失值的數量
df.isnull().sum(axis = 0)
# 各變數中缺失值的比例
df.isnull().sum(axis = 0)/df.shape[0]

如上結果所示,資料集df中有三個變數存在缺失值,即gender、age和edu,它們的缺失數量分別為136、100和1,927,缺失比例分別為4.53%、3.33%和64.23%。

讀者可能對程式碼中的“axis=0”感到困惑,下面通過圖表的形式來說明axis引數的用法:

假設上圖為學生的考試成績表,axis=0表示按學科分別計算每個學科的平均分,就是上圖中從上到下的轉換。

axis=1 表示按學生分別計算每個學生的總分,將是上圖從左到右的轉換。

1,判斷每列是否存在缺失值

為了得到每一列的判斷結果,需要any()方法(且設定方法內的axis引數為0);

# 判斷各變數中是否存在缺失值
df.isnull().any(axis = 0)

2,識別資料行的缺失值分佈情況

統計各變數的缺失值個數可以在isnull()的基礎上使用sum()方法(同樣需要設定axis引數為0);計算缺失比例就是在缺失數量的基礎上除以總的樣本量(shape方法返回資料集的行數和列數,[0]表示取出對應的資料行數)。程式碼如下:

# 缺失觀測的行數
df.isnull().any(axis = 1).sum()
# 缺失觀測的比例
df.isnull().any(axis = 1).sum()/df.shape[0]

程式碼中使用了兩次any“方法”,第一次用於判斷每一行對應的True(即行內有缺失值)或False值(即行內沒有缺失值);第二次則用於綜合判斷所有資料行中是否包含缺失值。同理,進一步還可以判斷缺失行的具體數量和佔比,

如結果所示,3000行的資料集中有2024行存在缺失值,缺失行的比例約67.47%。不管是變數角度的缺失值判斷,還是資料行角度的缺失值判斷,一旦發現缺失值,都需要對其作相應的處理,否則一定程度上都會影響資料分析或挖掘的準確性。

四,探索缺失值的模式

在決定如何處理缺失資料前,瞭解哪些變數有缺失值、數目有多少、是什麼組合等資訊,是非常有用的。

1,視覺化缺失值的分佈

 檢視缺失值的缺失數量以及比例

import pandas as pd

# 統計缺失值數量
missing=data.isnull().sum().reset_index().rename(columns={0:'missNum'})
# 計算缺失比例
missing['missRate']=missing['missNum']/data.shape[0]
# 按照缺失率排序顯示
miss_analy=missing[missing.missRate>0].sort_values(by='missRate',ascending=False)
# miss_analy 儲存的是每個變數缺失情況的資料框

import matplotlib.pyplot as plt
import pylab as pl

fig = plt.figure(figsize=(18,6))
plt.bar(np.arange(miss_analy.shape[0]), list(miss_analy.missRate.values), align = 'center'
    ,color=['red','green','yellow','steelblue'])

plt.title('Histogram of missing value of variables')
plt.xlabel('variables names')
plt.ylabel('missing rate')
# 新增x軸標籤,並旋轉90度
plt.xticks(np.arange(miss_analy.shape[0]),list(miss_analy['index']))
pl.xticks(rotation=90)
# 新增數值顯示
for x,y in enumerate(list(miss_analy.missRate.values)):
    plt.text(x,y+0.12,'{:.2%}'.format(y),ha='center',rotation=90)    
plt.ylim([0,1.2])
    
plt.show()

這樣的統計計算以及視覺化基本已經看出哪些變數缺失,以及缺失比例情況,對資料即有個缺失概況。

2,用相關性探索缺失資料

變數之間有時存在很強的相關性,有時候,某一個變數缺失會導致其他變數也缺失,這就是缺失資料之間的相關性。

用指示變數代替資料集中的資料(1代表缺失,0代表存在),這樣生成的矩陣有時被稱作影子矩陣,求這些指示變數之間的相關性,有助於觀察哪些變數經常一起缺失。

missing=data.isnull()
corr_matrix = missing.corr()
corr_matrix["missing_variable"].sort_values(ascending=False)

五,缺失資料的處理

識別缺失資料的數目、分佈和模式,有兩個目的:分析生成缺失資料的潛在機制,評價缺失資料對回答實質性問題的影響。具體來講,需要弄清楚以下幾個問題:

  • 缺失資料的比例有多大?
  • 缺失資料是否集中在少數幾個變數上,抑或廣泛存在?
  • 缺失是隨機產生的嗎?
  • 缺失資料間的相關性或與可觀測資料之間的相關性,是否可以表明產生缺失值的機制。

回答這些問題將有助於採用合適的方法來處理缺失資料。

  • 如果缺失資料集中在幾個相對不重要的變數上,那麼可以刪除這些變數。
  • 如果有一小部分資料(如小於10%)隨機分佈在整個資料集中(MCAR),那麼你可以刪除存在缺失資料的行,而只分析資料完整的例項,這樣扔可以得到可靠且有效的結果。
  • 如果可以假定資料是MCAR或MAR,那麼可以應用多重插補法來獲得有效的結論。

1,推理恢復

根據變數之間的關係來填補或恢復缺失值,通過推理,資料的恢復可能是準確無誤的或近似準確的,例如,如果一個資料物件的age是20,那麼該人的學歷大概率是學士。

2,刪除法

刪除法是指將缺失值所在的行刪除(前提是變數缺失的比例非常低,如5%以內),或者刪除缺失值所對應的變數(前提是該變數中包含的缺失值比例非常高,比如80%左右)。把包含一個或多個缺失值的行刪除,稱作行刪除法,或個案刪除(case-wise deletion),大部分統計軟體包預設採用的是行刪除法。

# 刪除欄位,例如刪除缺失率非常高的edu變數
df.drop(labels = 'edu', axis = 1, inplace=True)

如果變數的缺失比例非常大,或者行缺失的比例非常小,那麼使用刪除法是一個不錯的選擇,反之,刪除發將會使模型丟失大量的資料資訊而得不償失。

刪除法的缺點:

  • 犧牲了大量的資料,通過減少歷史資料換取完整的資訊,這樣可能丟失了很多隱藏的重要資訊;
  • 當缺失資料比例較大時,特別是缺失資料非隨機分佈時,直接刪除可能會導致資料發生偏離,比如原本的正態分佈變為非正太;

這種方法在樣本資料量十分大且缺失值不多的情況下非常有效,但如果樣本量本身不大且缺失也不少,那麼不建議使用。

3,均值替換法

均值替換法也叫均值插補,是指對存在缺失值的變數,直接利用該變數的均值、中位數或眾數替換該變數的缺失值,其好處是缺失值的處理速度快,缺點是容易產生有偏估計,導致缺失值替換的準確性下降。

把資料集的屬性分為定性屬性和定量屬性來分別進行處理: 

  • 如果變數是數值型的,那麼計算該變數在其他所有資料行的取值的平均值或中位數來替換缺失值; 
  • 如果變數是文字型的,那麼根據統計學中的眾數原理,計算出該屬性在其他所有資料行的取值次數最多的值(即出現頻率最高的值)來替換缺失值。

這就意味著,對於定性資料,使用眾數(mode)填補,比如一個學校的男生和女生的數量,男生500人,女生50人,那麼對於其餘的缺失值,我們會用人數較多的男生來填補。對於定量(定比)資料,使用平均數(mean)或中位數(median)填補,比如一個班級學生的身高特徵,對於一些同學缺失的身高值就可以使用全班同學身高的平均值或中位數來填補。

一般情況下,如果特徵分佈為正太分佈時,使用平均值效果比較好,而當分佈由於異常值存在而不是正太分佈的情況下,使用中位數效果比較好。

替換法對於非MCAR的資料會產生有偏向的結果,適用於缺失資料的數量較小的資料集。均值替換是在低缺失率下首選的插補方法,缺點是不能反映缺失值的變異性。

# 替換法處理缺失值
df.fillna(value = {'gender': df['gender'].mode()[0], # 使用性別的眾數替換缺失性別                 
'age':df['age'].mean() # 使用年齡的平均值替換缺失年齡                 
}, inplace = True )

缺失值的填充使用的是fillna()方法,其中value引數可以通過字典的形式對不同的變數指定不同的值。需要強調的是,如果計算某個變數的眾數,一定要使用索引技術,例如程式碼中的[0],表示取出眾數序列中的第一個(我們知道,眾數是指出現頻次最高的值,假設一個變數中有多個值共享最高頻次,那麼Python將會把這些值以序列的形式儲存起來,故取出指定的眾數值,必須使用索引)。

注:此方法雖然簡單,但是不夠精準,可能會引入噪聲,或者會改變特徵原有的分佈。下圖左為填補前的特徵分佈,圖右為填補後的分佈,明顯發生了畸變。因此,如果缺失值是隨機性的,那麼用平均值比較適合保證無偏,否則會改變原分佈。

4,隨機差值

採用某種插入模式進行填充,比如取缺失值前後值的均值進行填充:

#  interpolate()插值法,缺失值前後數值的均值,但是若缺失值前後也存在缺失,則不進行計算插補。
df['a'] = df['a'].interpolate()

# 用前面的值替換, 當第一行有缺失值時,該行利用向前替換無值可取,仍缺失
df.fillna(method='pad')

# 用後面的值替換,當最後一行有缺失值時,該行利用向後替換無值可取,仍缺失
df.fillna(method='backfill')#用後面的值替換

5,插補法

請閱讀《機器學習 第3篇:》

 

參考文件:

資料分析——缺失值處理詳解(理論篇)

【Python資料分析基礎】: 資料缺失值處理

Python資料清洗(二):缺失值識別與處理

相關文章