原文首發(fā)于我的博客歡迎關(guān)注
當(dāng)我們拿到一批數(shù)據(jù)的時(shí)候,往往都是“不干凈”的,而缺失值是最常見(jiàn)也是最容易發(fā)現(xiàn)的。不同的缺失值處理方式對(duì)接下來(lái)的特征提取,建模等都有巨大影響。那么缺失值的處理是有一套流程的,我在這里總結(jié)總結(jié):
發(fā)現(xiàn)缺失值
- 統(tǒng)計(jì)每個(gè)特征在所有個(gè)體中缺失的個(gè)數(shù) / 缺失率
這一點(diǎn)是查找缺失的特征
pandas 中 count() 函數(shù)為不為空數(shù)據(jù)的個(gè)數(shù),那么將shape與count做差就得到缺失個(gè)數(shù),缺失率。
(df.shape[0] - df.count())/df.shape[0]
- 對(duì)于每個(gè)個(gè)體所缺失的特征個(gè)數(shù)
這一點(diǎn)是查找缺失的個(gè)體
這個(gè)簡(jiǎn)單,對(duì)數(shù)據(jù) df 轉(zhuǎn)置一下即可
(df.shape[1] - df.T.count())/df.shape[1]
- pandas 其他缺失值函數(shù)
方法 | 說(shuō)明 |
---|---|
dropna | 根據(jù)各標(biāo)簽的值中是否存在缺失數(shù)據(jù)對(duì)軸標(biāo)簽進(jìn)行過(guò)濾, 可通過(guò)閥值調(diào)節(jié)對(duì)缺失值的容忍度 |
fillna | 用指定值或插值方法(如ffill或bfill)填充缺失值 |
isnull | 返回一個(gè)含有bool型的對(duì)象, 這些bool型值表示哪些是缺失值NaN, 該對(duì)象的類(lèi)型與源類(lèi)型一樣 |
notnull | isnull的否定式 |
- 隱藏的缺失值
這里要理解數(shù)據(jù)集內(nèi)容的含義,比如在某些情況下,0代表缺失值。因?yàn)橛行┲禐?的變量是無(wú)意義的,可以表示為缺失值。例如:身高、體重等。
缺失機(jī)制
在對(duì)缺失數(shù)據(jù)進(jìn)行處理前,了解數(shù)據(jù)缺失的機(jī)制和形式是十分必要的。將數(shù)據(jù)集中不含缺失值的變量(屬性)稱(chēng)為完全變量,數(shù)據(jù)集中含有缺失值的變量稱(chēng)為不完全變量,Little 和 Rubin定義了以下三種不同的數(shù)據(jù)缺失機(jī)制:
完全隨機(jī)缺失(Missing Completely at Random,MCAR)。數(shù)據(jù)的缺失與不完全變量以及完全變量都是無(wú)關(guān)的。
隨機(jī)缺失(Missing at Random,MAR)。數(shù)據(jù)的缺失僅僅依賴(lài)于完全變量。
非隨機(jī)、不可忽略缺失(Not Missing at Random,NMAR,or nonignorable)。不完全變量中數(shù)據(jù)的缺失依賴(lài)于不完全變量本身,這種缺失是不可忽略的。
從缺失值的所屬屬性上講,如果所有的缺失值都是同一屬性,那么這種缺失成為單值缺失,如果缺失值屬于不同的屬性,稱(chēng)為任意缺失。另外對(duì)于時(shí)間序列類(lèi)的數(shù)據(jù),可能存在隨著時(shí)間的缺失,這種缺失稱(chēng)為單調(diào)缺失。
空值語(yǔ)義
對(duì)于某個(gè)對(duì)象的屬性值未知的情況,我們稱(chēng)它在該屬性的取值為空值(null value)。空值的來(lái)源有許多種,因此現(xiàn)實(shí)世界中的空值語(yǔ)義也比較復(fù)雜。總的說(shuō)來(lái),可以把空值分成以下三類(lèi):
- 不存在型空值。即無(wú)法填入的值,或稱(chēng)對(duì)象在該屬性上無(wú)法取值,如一個(gè)未婚者的配偶姓名等。
- 存在型空值。即對(duì)象在該屬性上取值是存在的,但暫時(shí)無(wú)法知道。一旦對(duì)象在該屬性上的實(shí)際值被確知以后,人們就可以用相應(yīng)的實(shí)際值來(lái)取代原來(lái)的空值,使信息趨于完全。存在型空值是不確定性的一種表征,該類(lèi)空值的實(shí)際值在當(dāng)前是未知的。但它有確定性的一面,諸如它的實(shí)際值確實(shí)存在,總是落在一個(gè)人們可以確定的區(qū)間內(nèi)。一般情況下,空值是指存在型空值。
- 占位型空值。即無(wú)法確定是不存在型空值還是存在型空值,這要隨著時(shí)間的推移才能夠清楚,是最不確定的一類(lèi)。這種空值除填充空位外,并不代表任何其他信息。
判斷缺失值的重要性
對(duì)于包含有缺失值處理的算法,比如XGB或者LGB,我們可以簡(jiǎn)單的直接把訓(xùn)練數(shù)據(jù)扔到模型中訓(xùn)練,查看feature_importance。(由于XGB等屬于樹(shù)模型,不需要太多的數(shù)據(jù)預(yù)處理過(guò)程,比如歸一化等,也能取得較好的效果,且模型參數(shù)對(duì)特征的重要性程度影響不是很大,我們只需要知道大概的結(jié)果,哪些重要,哪些不重要即可)
缺失值較多且不重要的特征
這些特征我們看情況,可以嘗試著直接刪除,如果不刪除,缺失值又多,處理不好,可能會(huì)引來(lái)噪聲。
至于為什么看情況呢,意思是,做個(gè)對(duì)比試驗(yàn),一組是刪除的,另一組是沒(méi)刪除的,進(jìn)行交叉驗(yàn)證,如果刪除后的結(jié)果比較好,那么就進(jìn)行刪除。
缺失值較少的特征
統(tǒng)計(jì)量填充
這一類(lèi)特征,我們可以簡(jiǎn)單使用統(tǒng)計(jì)量比如:均值、中位數(shù)、眾數(shù) 進(jìn)行填充;
對(duì)于連續(xù)值,推薦使用 中位數(shù) ,可以排除一些特別大或者特別小的異常值造成的影響;
對(duì)于離散值,推薦使用 眾數(shù) ,均值和中位數(shù)用不了吧,那就用眾數(shù)好了。。。
df = df.fillna(df.median(axis=0))
特殊值填充
我們可以填一個(gè)不在正常取值范圍內(nèi)的數(shù)值,比如 -999 ,0 等來(lái)表示缺失值。
df.fillna(-999)
不處理
大家可能都有一個(gè)疑惑,為什么對(duì)很多人說(shuō)XGB或者LGB對(duì)缺失值不敏感呢,當(dāng)用缺失值的訓(xùn)練XGB時(shí),算法不會(huì)報(bào)錯(cuò),其實(shí)這個(gè)不能叫不敏感,而是算法本身自己有一套缺失值處理算法,比如XGB,它會(huì)把含有缺失值的數(shù)據(jù)分別分到左右兩個(gè)子節(jié)點(diǎn),然后計(jì)算著兩種情況的損失,最后,選取較好的劃分結(jié)果和對(duì)應(yīng)的損失。XGB缺失值具體算法可以參考我之前的文章XGBoost筆記。
所以,如果遇到有缺失值的情況,最好還是根據(jù)缺失的情況,自己處理比較好。
分類(lèi)別填充
我們還可以根據(jù)label的類(lèi)別,取值范圍進(jìn)行更高級(jí)的統(tǒng)計(jì)量填充(當(dāng)然這個(gè)只適用于知道label的訓(xùn)練集),即取在該label下數(shù)據(jù)的中位數(shù)、均值等進(jìn)行填充。
插值填充
使用線(xiàn)性,多項(xiàng)式等差值方法,對(duì)于時(shí)間序列的缺失問(wèn)題,可以使用此方法。
df.interpolate()
插補(bǔ)法
- 隨機(jī)插補(bǔ)法----從總體中隨機(jī)抽取某個(gè)樣本代替缺失樣本
- 多重插補(bǔ)法----通過(guò)變量之間的關(guān)系對(duì)缺失數(shù)據(jù)進(jìn)行預(yù)測(cè),利用蒙特卡洛方法生成多個(gè)完整的數(shù)據(jù)集,在對(duì)這些數(shù)據(jù)集進(jìn)行分析,最后對(duì)分析結(jié)果進(jìn)行匯總處理
- 熱平臺(tái)插補(bǔ)----指在非缺失數(shù)據(jù)集中找到一個(gè)與缺失值所在樣本相似的樣本(匹配樣本),利用其中的觀測(cè)值對(duì)缺失值進(jìn)行插補(bǔ)
優(yōu)點(diǎn):簡(jiǎn)單易行,準(zhǔn)去率較高
缺點(diǎn):變量數(shù)量較多時(shí),通常很難找到與需要插補(bǔ)樣本完全相同的樣本。但我們可以按照某些變量將數(shù)據(jù)分層,在層中對(duì)缺失值實(shí)用均值插補(bǔ) - 拉格朗日差值法和牛頓插值法
用預(yù)測(cè)值填充
將缺失的數(shù)據(jù)當(dāng)成label,沒(méi)缺失的作為訓(xùn)練集,缺失的作為測(cè)試集,通過(guò)某種機(jī)器學(xué)習(xí)算法進(jìn)行預(yù)測(cè),填補(bǔ)缺失值。下面代碼以lgb為例:
import lightgbm as lgb
def set_missing(df, predict_list):
for predict_feature in predict_list:
# 原始數(shù)據(jù)分為已知和未知的
known = df[df[predict_feature].notnull()]
unknown = df[df[predict_feature].isnull()]
# 訓(xùn)練集構(gòu)造,從已知的部分構(gòu)造
y = known[predict_feature].as_matrix()
X = known.drop(predict_feature, axis=1).as_matrix()
# 測(cè)試集,從未知的部分構(gòu)造
test_X = unknown.drop(predict_feature, axis=1).as_matrix()
# 用lgb模型進(jìn)行訓(xùn)練
predicted_feature = _model_predict(X, y, test_X)
# 用得到的預(yù)測(cè)結(jié)果填補(bǔ)原缺失數(shù)據(jù)
df.loc[(df[predict_feature].isnull()), predict_feature] = predicted_feature
return df
def _model_predict(X, y, test_X):
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=1)
lgb_train = lgb.Dataset(X_train, y_train)
lgb_eval = lgb.Dataset(X_test, y_test, reference=lgb_train)
params = {
'boosting': 'gbdt',
'objective': 'regression',
'metric': 'rmse',
'num_leaves': 31,
'min_data_in_leaf': 20,
'learning_rate': 0.015,
'cat_smooth': 10,
'feature_fraction': 0.8,
'bagging_freq': 5,
'verbosity': 0
}
gbm = lgb.train(params,
lgb_train,
num_boost_round=1000,
valid_sets=lgb_eval,
early_stopping_rounds=70)
# 用得到的模型進(jìn)行未知年齡結(jié)果預(yù)測(cè)
predicted_feature = gbm.predict(test_X, num_iteration=gbm.best_iteration)
print("---------best_iteration: ", gbm.best_iteration)
return predicted_feature
缺失值比較多的樣本
當(dāng)樣本很多的時(shí)候,而缺失值比較多的樣本,且它們數(shù)目不多時(shí),直接刪掉。