不論是自己爬蟲獲取的還是從公開數據源上獲取的數據集,都不能保證數據集是完全準確的,難免會有一些缺失值。而以這樣數據集為基礎進行建模或者數據分析時,缺失值會對結果產生一定的影響,所以提前處理缺失值是十分必要的。
對於缺失值的處理大致可分為以下三方面:
不處理應該是效果最差的了,刪除雖然可以有效處理缺失值,但是會損傷數據集,好不容易統計的數據因為一個特徵的缺失說刪就刪實在說不過去。填充缺失值應該是最常用且有效的處理方式了,下面介紹四種處理缺失值的常用Tips。
我自己構建了一個簡易的含有缺失值的DataFrame,所有操作都基於這個數據集進行。
1、刪除缺失值刪除雖說是一個可行的方式,但肯定是不能隨便刪除的,比如一個樣本中僅有一個特徵的值缺失,這樣的情況下填充取得的效果一定會優於刪除,所以在刪除缺失值時,我們需要一個衡量的標準。
刪除的方式無非有兩種,一是刪除缺失值所在行,也就是含有缺失值的樣本;二就是刪除缺失值所在列,也就是含有缺失值的特徵,下面以後者為例。
首先需要確定的是刪除的標準是什麼?比如一個特徵的缺失值所佔比例已經超過了50%,如果選擇填充的話,就表明該特徵超五成的值都是自己猜測填入的,導致誤差可能比刪除這個特徵還要大。
def find_missing(data):
#統計缺失值個數
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在確定了這個標準之後,就可以利用一個自定義函數,將我們期望實現的功能封裝至函數中。比如上面這個函數,先確定每個特徵的缺失值個數並降序排列,然後計算缺失值比例,最後利用布爾索引得到需要刪除的特徵名。
data2 = data.copy()
data2.drop(find_missing(data2),axis = 1)在數據集上應用這個函數,可以看到缺失值佔比超50%的特徵C被刪除了。
這個衡量標準自己可以依據情況設定,然後刪除樣本的方式可以類比上述刪除特徵的方式。
2、pandas填充pandas中的fillna()應該是最常用的一種填充缺失值方法,可以指定填充指定列或者整個數據集。
data['A'].fillna(value = data['A'].mean(),limit=1)比如上面這句代碼,就是只填充特徵A一列,填充的選擇可以利用平均數、中位數、眾數等等,limit是限制要填充的個數,如果有兩個缺失值,但是參數limit=1的話,按順序填充第一個。
value參數也允許傳入字典格式,鍵為要填充的特徵名,值為要填充的缺失值。
values = {'A':4,'B':3,'C':4}
data.fillna(value=values)填充之後結果如下:
fillna()方法固然簡單,但前提是含有缺失值的特徵比較少,如果很多的話,代碼就會很冗雜,客觀性也比較差。
3、sklearn填充第二種填充方式是利用sklearn中自帶的API進行填充。
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']
'''首先利用布爾索引得到數據集含有缺失值的特徵,後續操作只針對含有缺失值的特徵。
miss_list = []
for i in miss_index:
#將一維數組轉化為二維
miss_list.append(data1[i].values.reshape(-1,1))
for i in range(len(miss_list)):
#利用眾數進行填充
imp_most = SimpleImputer(strategy='most_frequent')
imp_most = imp_most.fit_transform(miss_list[i])
data1.loc[:,miss_index[i]] = imp_most最需要注意的一點是SimpleImputer傳入的參數至少要是二維,如果將直接索引出的一列特徵傳入的話,是會發生報錯的,所以必須利用reshape()將一維轉化為二維。之後的操作就是先實例化、然後訓練模型,最後用填充後的數據覆蓋之前的數據。
參數strategy共有四個選項可填:
SimpleImputer優於fillna()之處在於前者可以一行語句指定填充值的形式,而利用fillna()需要多行重複語句才能實現,或者需要提前計算某列的平均值、中位數或者眾數。
4、利用算法填充我們都知道一般的算法建模是通過n個特徵來預測標籤變量,也就是說特徵與標籤標量之間存在某種關係,那麼通過標籤變量與(n-1)個特徵是否能預測出剩下的一個特徵呢?答案肯定是可以的。
實際上標籤變量和特徵之間可以相互轉化,所以利用這種方法就可以填補特徵矩陣中含有缺失值的特徵,尤其適用於一個特徵缺失值很多,其餘特徵數據很完整,特別標籤變量那一列的數據要完整。
但是往往一個特徵矩陣中很多特徵都含有缺失值,對於這種情況,可以從特徵缺失值最少的一個開始,因為缺失值越少的特徵需要的信息也就越少。
當預測一個特徵時,其餘特徵的缺失值都需要用0暫時填補,每當預測完一列特徵,就用預測出的結果代替原數據集對應的特徵,然後預測下一特徵,直至最後一個含有缺失值的特徵,此時特徵矩陣中應該沒有需要利用0填補的缺失值了,表示數據集已經完整。
以隨機森林算法為例,實現上面表述填充缺失值的過程。
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)
'''第一步就是通過布爾索引得到含有缺失值的特徵,並且根據缺失值的多少進行由小到大排序,這裡選擇利用argsort,因為返回的排序是特徵在特徵矩陣中的索引。
for i in sort_miss_index:
data3_list = data3.columns.tolist() #特徵名
data3_copy = data3.copy()
fillc = data3_copy.iloc[:,i] #需要填充缺失值的一列
# 從特徵矩陣中刪除這列,因為要根據已有信息預測這列
df = data3_copy.drop(data3_list[i],axis = 1)
#將已有信息的缺失值暫用0填補
df_0 = SimpleImputer(missing_values=np.nan,strategy='constant',fill_value=0).fit_transform(df)
Ytrain = fillc[fillc.notnull()]#訓練集標籤為填充列含有數據的一部分
Ytest = fillc[fillc.isnull()]#測試集標籤為填充列含有缺失值的一部分
Xtrain = df_0[Ytrain.index,:]#通過索引獲取Xtrain和Xtest
Xtest = df_0[Ytest.index,:]
rfc = RandomForestRegressor(n_estimators = 100)#實例化
rfc = rfc.fit(Xtrain,Ytrain) # 導入訓練集進行訓練
Ypredict = rfc.predict(Xtest) # 將Xtest傳入predict方法中,得到預測結果
#獲取原填充列中缺失值的索引
the_index = data3[data3.iloc[:,i].isnull()==True].index.tolist()
data3.iloc[the_index,i] = Ypredict# 將預測好的特徵填充至原始特徵矩陣中這部分代碼主要的思想就是,先將需預測的一列特徵暫定為標籤,然後預測列中含有數據的一部分作為訓練集,含有缺失值的一部分作為測試集,通過隨機森林在訓練集上建模,利用模型在測試集的基礎上得到缺失值那部分的數據,最後填充值原特徵矩陣中。
最後預測出的結果如下:
可以看到原特徵矩陣中缺失值的一部分被填充好了,這種利用算法填充缺失值的方法應該是精度最高的,因為缺失值是在原有數據的基礎上預測出的,而不是隨意猜測的,但缺點就是沒有前幾種便利,當特徵或缺失值較多時會比較耗時。
說在最後缺失值處理是特徵工程至關重要的一步,而特徵工程和數據本身往往決定著一個模型的上限,所以數據集中的缺失值在一個項目中值得我們花些時間去處理,而不是用自己的幸運數字隨意填充,一句話總結就是"你不要你覺得,而是模型覺得"。
小貼士
隱藏菜單:返回上一級 回復 「 1024 "關鍵詞,即可獲取內部學習資料