##############缺失值填充的幾種方法
常見的缺失值填充方法有填充預設值、均值、眾數、KNN填充、以及把缺失值作為新的label通過模型來預測等方式,為了介紹這幾種填充方法的使用以及填充效果,本文將在真實資料集上進行簡單比較。
1. 資料集介紹
資料集來源於 天池精準醫療大賽——人工智慧輔助糖尿病遺傳風險預測。該資料集共有1000條資料,特徵共83維,加上id和label共85列,每維特徵缺失數量範圍為0~911。為了簡單比較各種填充方法的效果,我們選取最簡單的二分類模型(邏輯迴歸),選取F1 score作為評測指標。
讀取資料集程式碼如下:
train_data = pd.read_csv('train_data.csv', encoding='gbk') # 讀取資料集
filter_feature = ['id','label'] # 過濾無用的維度
features = []
for x in train_data.columns: # 取特徵
if x not in filter_feature:
features.append(x)
train_data_x = train_data[features]
train_data_y = train_data['label']
X_train, X_test, y_train, y_test = train_test_split(train_data_x, train_data_y, random_state=1) # 劃分訓練集、測試集
2. 常見的填充方法
(1)填充固定值
選取某個固定值/預設值填充缺失值。
train_data.fillna(0, inplace=True) # 填充 0
(2)填充均值
對每一列的缺失值,填充當列的均值。
train_data.fillna(train_data.mean(),inplace=True) # 填充均值
(3)填充中位數
對每一列的缺失值,填充當列的中位數。
train_data.fillna(train_data.median(),inplace=True) # 填充中位數
(4)填充眾數
對每一列的缺失值,填充當列的眾數。由於存在某列缺失值過多,眾數為nan的情況,因此這裡取的是每列刪除掉nan值後的眾數。
train_data.fillna(train_data.mode(),inplace=True) # 填充眾數,該資料缺失太多眾數出現為nan的情況
features_mode = {}
for f in features:
print f,':', list(train_data[f].dropna().mode().values)
features_mode[f] = list(train_data[f].dropna().mode().values)[0]
train_data.fillna(features_mode,inplace=True)
(5)填充上下條的資料
對每一條資料的缺失值,填充其上下條資料的值。
train_data.fillna(method='pad', inplace=True) # 填充前一條資料的值,但是前一條也不一定有值
train_data.fillna(0, inplace=True)
train_data.fillna(method='bfill', inplace=True) # 填充後一條資料的值,但是後一條也不一定有值
train_data.fillna(0, inplace=True)
(6)填充插值得到的資料
用插值法擬合出缺失的資料,然後進行填充。
for f in features: # 插值法填充
train_data[f] = train_data[f].interpolate()
train_data.dropna(inplace=True)
(7)填充KNN資料
填充近鄰的資料,先利用knn計算臨近的k個數據,然後填充他們的均值。(安裝fancyimpute)除了knn填充,fancyimpute還提供了其他填充方法。
from fancyimpute import KNN
train_data_x = pd.DataFrame(KNN(k=6).fit_transform(train_data_x), columns=features)
(8)填充模型預測的值
把缺失值作為新的label,建立模型得到預測值,然後進行填充。這裡選擇某個缺失值數量適當的特徵採用隨機森林RF進行擬合,其他缺失特徵採用均值進行填充。
new_label = 'SNP46'
new_features = []
for f in features:
if f != new_label:
new_features.append(f)
new_train_x = train_data[train_data[new_label].isnull()==False][new_features]
new_train_x.fillna(new_train_x.mean(), inplace=True) # 其他列填充均值
new_train_y = train_data[train_data[new_label].isnull()==False][new_label]
new_predict_x = train_data[train_data[new_label].isnull()==True][new_features]
new_predict_x.fillna(new_predict_x.mean(), inplace=True) # 其他列填充均值
new_predict_y = train_data[train_data[new_label].isnull()==True][new_label]
rfr = RandomForestRegressor(random_state=666, n_estimators=10, n_jobs=-1)
rfr.fit(new_train_x, new_train_y)
new_predict_y = rfr.predict(new_predict_x)
new_predict_y = pd.DataFrame(new_predict_y, columns=[new_label], index=new_predict_x.index)
new_predict_y = pd.concat([new_predict_x, new_predict_y], axis=1)
new_train_y = pd.concat([new_train_x, new_train_y], axis=1)
new_train_data = pd.concat([new_predict_y,new_train_y])
train_data_x = new_train_data[features]
train_data_y = train_data['label']
3. 實驗對比
(1)評測指標
選取F1 score進行評測。
def countF1(train, predict):
count = 0 # 統計預測的正確的正樣本數
for i in range(len(train)):
if predict[i] == 1 and train[i] == 1:
count += 1
pre = count * 1.0 / sum(predict) # 準確率
recall = count * 1.0 / sum(train) # 召回率
return 2 * pre * recall / (pre + recall)
(2)對比結果
填充方式 訓練集_F1 測試集_F1
預設值0 0.70516717 0.59689922
均值(mean) 0.70186335 0.67768595
中位數(median) 0.70826833 0.67479675
眾數(mode) 0.70479134 0.68852459
上一個資料(pad) 0.70409712 0.62711864
下一個資料(bfill) 0.66981132 0.60169492
插值 0.69018405 0.61333333
KNN_3 0.71076923 0.66393443
KNN_6 0.70897833 0.68852459
KNN_10 0.70479134 0.68032787
隨機森林_feature3 0.571428571 0.4
隨機森林_feature46 0.585139319 0.41509434
(3)實驗小結
對於缺失值的處理,除了直接刪除缺失嚴重的特徵外,還可以選擇各種各樣的填充方法。對於每一種填充方式而言,都有其適用的場景,沒有絕對的好壞之分,因此在做資料預處理時,要多嘗試幾種填充方法,選擇表現最佳的即可。
本文完整程式碼已上傳至git(https://github.com/AHNU/fill_missing_values)
參考文獻
1. 訓練模型填充空值(fill null)的幾種方法
2. https://www.kaggle.com/pmarcelino/comprehensive-data-exploration-with-python