3000字詳解四種常用的缺失值處理方法
?不論是自己爬蟲獲取的還是從公開數(shù)據(jù)源上獲取的數(shù)據(jù)集,都不能保證數(shù)據(jù)集是完全準(zhǔn)確的,難免會有一些缺失值。而以這樣數(shù)據(jù)集為基礎(chǔ)進(jìn)行建?;蛘邤?shù)據(jù)分析時,缺失值會對結(jié)果產(chǎn)生一定的影響,所以提前處理缺失值是十分必要的。
對于缺失值的處理大致可分為以下三方面:
不處理
刪除含有缺失值的樣本
填充缺失值
不處理應(yīng)該是效果最差的了,刪除雖然可以有效處理缺失值,但是會損傷數(shù)據(jù)集,好不容易統(tǒng)計的數(shù)據(jù)因為一個特征的缺失說刪就刪實在說不過去。填充缺失值應(yīng)該是最常用且有效的處理方式了,下面介紹四種處理缺失值的常用Tips。
我自己構(gòu)建了一個簡易的含有缺失值的DataFrame,所有操作都基于這個數(shù)據(jù)集進(jìn)行。

1、刪除缺失值
刪除雖說是一個可行的方式,但肯定是不能隨便刪除的,比如一個樣本中僅有一個特征的值缺失,這樣的情況下填充取得的效果一定會優(yōu)于刪除,所以在刪除缺失值時,我們需要一個衡量的標(biāo)準(zhǔn)。
刪除的方式無非有兩種,一是刪除缺失值所在行,也就是含有缺失值的樣本;二就是刪除缺失值所在列,也就是含有缺失值的特征,下面以后者為例。
首先需要確定的是刪除的標(biāo)準(zhǔn)是什么?比如一個特征的缺失值所占比例已經(jīng)超過了50%,如果選擇填充的話,就表明該特征超五成的值都是自己猜測填入的,導(dǎo)致誤差可能比刪除這個特征還要大。
def?find_missing(data):
????#統(tǒng)計缺失值個數(shù)
????missing_num?=?data.isna().sum(axis=0).sort_values(ascending=False)
????missing_prop?=?missing_num/float(len(data))?#計算缺失值比例
????drop_index?=?missing_prop[missing_prop>0.5].index.tolist()?#過濾要刪除特征名
????return?drop_index
在確定了這個標(biāo)準(zhǔn)之后,就可以利用一個自定義函數(shù),將我們期望實現(xiàn)的功能封裝至函數(shù)中。比如上面這個函數(shù),先確定每個特征的缺失值個數(shù)并降序排列,然后計算缺失值比例,最后利用布爾索引得到需要刪除的特征名。
data2?=?data.copy()
data2.drop(find_missing(data2),axis?=?1)
在數(shù)據(jù)集上應(yīng)用這個函數(shù),可以看到缺失值占比超50%的特征C被刪除了。

這個衡量標(biāo)準(zhǔn)自己可以依據(jù)情況設(shè)定,然后刪除樣本的方式可以類比上述刪除特征的方式。
2、pandas填充
pandas中的fillna()應(yīng)該是最常用的一種填充缺失值方法,可以指定填充指定列或者整個數(shù)據(jù)集。
data['A'].fillna(value?=?data['A'].mean(),limit=1)
比如上面這句代碼,就是只填充特征A一列,填充的選擇可以利用平均數(shù)、中位數(shù)、眾數(shù)等等,limit是限制要填充的個數(shù),如果有兩個缺失值,但是參數(shù)limit=1的話,按順序填充第一個。

value參數(shù)也允許傳入字典格式,鍵為要填充的特征名,值為要填充的缺失值。
values?=?{'A':4,'B':3,'C':4}
data.fillna(value=values)
填充之后結(jié)果如下:

fillna()方法固然簡單,但前提是含有缺失值的特征比較少,如果很多的話,代碼就會很冗雜,客觀性也比較差。
3、sklearn填充
第二種填充方式是利用sklearn中自帶的API進(jìn)行填充。
from?sklearn.impute?import?SimpleImputer
data1?=?data.copy()
#得到含有缺失值的特征
miss_index?=?data1.isna().any()[data1.isna().any().values?==?True].index.tolist()
print(miss_index)
'''
['A',?'B',?'C']
'''
首先利用布爾索引得到數(shù)據(jù)集含有缺失值的特征,后續(xù)操作只針對含有缺失值的特征。
miss_list?=?[]
for?i?in?miss_index:
????#將一維數(shù)組轉(zhuǎn)化為二維
????miss_list.append(data1[i].values.reshape(-1,1))
for?i?in?range(len(miss_list)):
????#利用眾數(shù)進(jìn)行填充
????imp_most?=?SimpleImputer(strategy='most_frequent')
????imp_most?=?imp_most.fit_transform(miss_list[i])
????data1.loc[:,miss_index[i]]?=?imp_most
最需要注意的一點是SimpleImputer傳入的參數(shù)至少要是二維,如果將直接索引出的一列特征傳入的話,是會發(fā)生報錯的,所以必須利用reshape()將一維轉(zhuǎn)化為二維。之后的操作就是先實例化、然后訓(xùn)練模型,最后用填充后的數(shù)據(jù)覆蓋之前的數(shù)據(jù)。
參數(shù)strategy共有四個選項可填:
1、mean:平均數(shù)
2、median:中位數(shù)
3、most_frequent:眾數(shù)
4、constant:如果參數(shù)指定這個,將會選擇另一個參數(shù)fill_value中的值作為填充值。
SimpleImputer優(yōu)于fillna()之處在于前者可以一行語句指定填充值的形式,而利用fillna()需要多行重復(fù)語句才能實現(xiàn),或者需要提前計算某列的平均值、中位數(shù)或者眾數(shù)。
4、利用算法填充
我們都知道一般的算法建模是通過n個特征來預(yù)測標(biāo)簽變量,也就是說特征與標(biāo)簽標(biāo)量之間存在某種關(guān)系,那么通過標(biāo)簽變量與(n-1)個特征是否能預(yù)測出剩下的一個特征呢?答案肯定是可以的。
實際上標(biāo)簽變量和特征之間可以相互轉(zhuǎn)化,所以利用這種方法就可以填補(bǔ)特征矩陣中含有缺失值的特征,尤其適用于一個特征缺失值很多,其余特征數(shù)據(jù)很完整,特別標(biāo)簽變量那一列的數(shù)據(jù)要完整。
但是往往一個特征矩陣中很多特征都含有缺失值,對于這種情況,可以從特征缺失值最少的一個開始,因為缺失值越少的特征需要的信息也就越少。
當(dāng)預(yù)測一個特征時,其余特征的缺失值都需要用0暫時填補(bǔ),每當(dāng)預(yù)測完一列特征,就用預(yù)測出的結(jié)果代替原數(shù)據(jù)集對應(yīng)的特征,然后預(yù)測下一特征,直至最后一個含有缺失值的特征,此時特征矩陣中應(yīng)該沒有需要利用0填補(bǔ)的缺失值了,表示數(shù)據(jù)集已經(jīng)完整。
以隨機(jī)森林算法為例,實現(xiàn)上面表述填充缺失值的過程。
data3?=?data.copy()
#獲取含有缺失值的特征
miss_index?=?data3.isna().any()[data3.isna().any().values?==?True].index.tolist()
#按照缺失值多少,由小至大排序,并返回索引
sort_miss_index?=?np.argsort(data3[miss_index].isna().sum(axis?=?0)).values
sort_miss_index
'''
array([1,?0,?2],?dtype=int64)
'''
第一步就是通過布爾索引得到含有缺失值的特征,并且根據(jù)缺失值的多少進(jìn)行由小到大排序,這里選擇利用argsort,因為返回的排序是特征在特征矩陣中的索引。
for?i?in?sort_miss_index:
????data3_list?=??data3.columns.tolist()?#特征名
????data3_copy?=?data3.copy()?
????fillc?=?data3_copy.iloc[:,i]?#需要填充缺失值的一列??
????#?從特征矩陣中刪除這列,因為要根據(jù)已有信息預(yù)測這列
????df?=?data3_copy.drop(data3_list[i],axis?=?1)?
????#將已有信息的缺失值暫用0填補(bǔ)
????df_0?=?SimpleImputer(missing_values=np.nan,strategy='constant',fill_value=0).fit_transform(df)
????
????Ytrain?=?fillc[fillc.notnull()]#訓(xùn)練集標(biāo)簽為填充列含有數(shù)據(jù)的一部分
????Ytest?=?fillc[fillc.isnull()]#測試集標(biāo)簽為填充列含有缺失值的一部分
????
????Xtrain?=?df_0[Ytrain.index,:]#通過索引獲取Xtrain和Xtest
????Xtest?=?df_0[Ytest.index,:]
????
????rfc?=?RandomForestRegressor(n_estimators?=?100)#實例化
????rfc?=?rfc.fit(Xtrain,Ytrain)??#?導(dǎo)入訓(xùn)練集進(jìn)行訓(xùn)練
????Ypredict?=?rfc.predict(Xtest)?#?將Xtest傳入predict方法中,得到預(yù)測結(jié)果
????#獲取原填充列中缺失值的索引
????the_index?=?data3[data3.iloc[:,i].isnull()==True].index.tolist()
????data3.iloc[the_index,i]?=?Ypredict#?將預(yù)測好的特征填充至原始特征矩陣中
這部分代碼主要的思想就是,先將需預(yù)測的一列特征暫定為標(biāo)簽,然后預(yù)測列中含有數(shù)據(jù)的一部分作為訓(xùn)練集,含有缺失值的一部分作為測試集,通過隨機(jī)森林在訓(xùn)練集上建模,利用模型在測試集的基礎(chǔ)上得到缺失值那部分的數(shù)據(jù),最后填充值原特征矩陣中。
最后預(yù)測出的結(jié)果如下:

可以看到原特征矩陣中缺失值的一部分被填充好了,這種利用算法填充缺失值的方法應(yīng)該是精度最高的,因為缺失值是在原有數(shù)據(jù)的基礎(chǔ)上預(yù)測出的,而不是隨意猜測的,但缺點就是沒有前幾種便利,當(dāng)特征或缺失值較多時會比較耗時。
說在最后
缺失值處理是特征工程至關(guān)重要的一步,而特征工程和數(shù)據(jù)本身往往決定著一個模型的上限,所以數(shù)據(jù)集中的缺失值在一個項目中值得我們花些時間去處理,而不是用自己的幸運數(shù)字隨意填充,一句話總結(jié)就是"你不要你覺得,而是模型覺得"。
