機器學習 第3篇:數據預處理(使用插補法處理缺失值)

插補法可以在一定程度上減少偏差,常用的插補法是熱卡插補、擬合插補和多重插補。擬合插補,要求變量間存在強的相關性;多重插補(MCMC法),是在高缺失率下的首選插補方法,優點是考慮了缺失值的不確定性。

一,熱卡插補

熱卡填充(Hot deck imputation)也叫就近補齊,對於一個包含空值的對象,熱卡填充法在完整數據中找到一個與它最相似的對象,然後用這個相似對象的值來進行填充。通常會找到超出一個的相似對象,在所有匹配對象中沒有最好的,而是從中隨機的挑選一個作為填充值。這個問題關鍵是不同的問題可能會選用不同的標準來對相似進行判定,以及如何制定這個判定標準。該方法概念上很簡單,且利用了數據間的關係來進行空值估計,但缺點在於難以定義相似標準,主觀因素較多。

二,擬合插補

擬合插補法則是利用有監督的機器學習方法,比如回歸、最鄰近、隨機森林、支持向量機等模型,對缺失值作預測,其優勢在於預測的準確性高,缺點是需要大量的計算,導致缺失值的處理速度大打折扣。雖然替換法思想簡單、效率高效,但是其替換的值往往不具有很高的準確性,於是出現了插補方法。

1,回歸插補

基於完整的數據集,建立回歸方程。對於包含空值的對象,將已知屬性值代入方程來估計未知屬性值,以此估計值來進行填充。當變量不是線性相關時會導致有偏差的估計。缺失值是連續的,即定量的類型,才可以使用回歸來預測。

2,最鄰近填充

利用knn算法填充,其實是把目標列當做目標標量,利用非缺失的數據進行knn算法擬合,最後對目標列缺失進行預測。(對於連續特徵一般是加權平均,對於離散特徵一般是加權投票)

from sklearn.neighbors import KNeighborsClassifier, KNeighborsRegressor

def knn_filled_func(x_train, y_train, test, k = 3, dispersed = True):
    # params: x_train 為目標列不含缺失值的數據(不包括目標列)
    # params: y_train 為不含缺失值的目標列
    # params: test 為目標列為缺失值的數據(不包括目標列)
    if dispersed:
        knn= KNeighborsClassifier(n_neighbors = k, weights = "distance")
    else:
        knn= KNeighborsRegressor(n_neighbors = k, weights = "distance")
    
    knn.fit(x_train, y_train)

3,隨機森林插補

隨機森林算法填充的思想和knn填充是類似的,即利用已有數據擬合模型,對缺失變量進行預測。

from sklearn.ensemble import RandomForestRegressor, RandomForestClassifier

def knn_filled_func(x_train, y_train, test, k = 3, dispersed = True):
    # params: x_train 為目標列不含缺失值的數據(不包括目標列)
    # params: y_train 為不含缺失值的目標列
    # params: test 為目標列為缺失值的數據(不包括目標列)
    if dispersed:
        rf= RandomForestRegressor()
    else:
        rf= RandomForestClassifier()
    
    rf.fit(x_train, y_train)
    return test.index, rf.predict(test)

三,多重插補

多重插補(Mutiple imputation,MI)的思想來源於貝葉斯估計,認為待插補的值是隨機的,它的值來自於已觀測到的值。具體實踐上通常是估計出待插補的值,然後再加上不同的噪聲,形成多組可選插補值。根據某種選擇依據,選取最合適的插補值。

對於擬合插補和均值替換等處理缺失值的方法都是單一的插補方法,而多重插補彌補了單一插補的缺陷,它並沒有試圖去通過模擬值去估計每個缺失值,而是提出缺失數據值的一個隨機樣本(這些樣本可以是不同的模型擬合結果的組合)。這種程序的實施恰當地反映了由於缺失值引起的不確定性,使得統計推斷有效。

註:使用多重插補要求數據缺失值為隨機性缺失,一般重複次數20-50次精準度很高,但是計算也很複雜,需要大量計算。 

1,多重插補的實現

多重插補是一種基於重複模擬的用於處理缺失值的方法,它從一個包含缺失值的數據集中生成一組數據完整的數據集(即不包含缺失值的數據集,通常是3-10個)。每個完整數據集都是通過對原始數據中的缺失數據進行插補而生成的。在每個完整的數據集上引用標準的統計方法,最後,把這些單獨的分析結果整合為一組結果。

多重插補法大致分為三步:

  • 為每個空值產生一套可能的插補值,這些值反映了模型的不確定性;每個值都被用來插補數據集中的缺失值,產生若干個完整數據集合。
  • 每個插補數據集合都用針對完整數據集的統計方法進行統計分析。
  • 對來自各個插補數據集的結果進行整合,產生最終的統計推斷,這一推斷考慮到了由於數據插補而產生的不確定性。該方法將空缺值視為隨機樣本,這樣計算出來的統計推斷可能受到空缺值的不確定性的影響。

2,MICE簡介

通過鏈式方程進行的多元插補(MICE,Multiple Imputation by Chained Equations),與單個插補(例如均值)相比,創建多個插補可解決缺失值的不確定性。MICE假定缺失的數據是隨機(MAR)的,這意味着,一個值丟失概率上觀測值僅取決於並且可以使用它們來預測。通過為每個變量指定插補模型,可以按變量插補數據。

例如:假設我們有X1,X2….Xk變量。如果X1缺少值,那麼它將在其他變量X2到Xk上回歸。然後,將X1中的缺失值替換為獲得的預測值。同樣,如果X2缺少值,則X1,X3至Xk變量將在預測模型中用作自變量。稍後,缺失值將被替換為預測值。

默認情況下,線性回歸用於預測連續缺失值。Logistic回歸用於分類缺失值。一旦完成此循環,就會生成多個數據集。這些數據集僅在估算的缺失值上有所不同。通常,將這些數據集分別構建模型並組合其結果被認為是一個好習慣。

本文使用R語言中的mice包來執行這些操作,首先我們來看mice包的操作思路:

首先,mice()函數從一個包含缺失數據的數據框開始,返回一個包含多個(默認為5個)完整數據集的對象。每個完整數據集都是通過對原始數據框中的缺失數據進行插補而生成的。由於插補有隨機的成分,因此每個完整數據集都略有不同。

然後,with()函數可依次對每個完整的數據集應用統計模型(如線性模型或廣義線性模型)。

最後,pool()函數把這些單獨的分析結果整合為一組結果。

最終模型的標準差和p值都準確地反映出由於缺失值和多重插補而產生的不確定性。

缺失值的插補法通過Gibbs抽樣完成,每個包含缺失值的變量都默認可通過數據集中的其他變量預測的來,於是這些預測方程便可用於預測數據的有效值。該方程不斷迭代直到所有的缺失值都收斂為止。默認情況下,預測的均值用於替換連續性變量中的缺失數據。

3,基於R的mice包的分析過程

library(mice)
imp <- mice(data, m)
fit <- with(imp, analysis)
pooled <- pool(fit)
summary(pooled)

data:包含缺失值的矩陣或數據框;
imp:一個包含m個插補數據集的列表對象,同時還含有完成插補過程的信息。默認為5。
analysis:用來設定應用於m個插補數據集的統計分析方法。比如線性回歸模型的lm()函數,舉個例子lm(Dream ~ Span + Gest),表達式在函數的括號中,~左邊是因變量,右邊是自變量,用+符號分隔開。這個例子中Dream是因變量,Span和Gest是自變量;
fit:一個包含m個單獨統計分析結果的列表對象;
pooled:一個包含m個統計分析平均結果的列表對象。

四,Python的MICE算法

在處理缺失值時,可以通過鏈式方程的多重插補估算缺失值:

 鏈式方程的多重插補,也稱為「完全條件規範」,其定義如下:

從技術上講,任何能夠推理的預測模型都可以用於MICE。 在本文中,我們使用miceforest Python庫估算了數據集,該庫使用隨機森林。 出於以下幾個原因,隨機森林可與MICE算法配合使用:

  • 不需要太多的超參數調整
  • 輕鬆處理數據中的非線性關係
  • 可以廉價地返回OOB性能
  • 幾乎可以並行化
  • 可以返回功能重要性以進行診斷

 

 代碼如下:

 

import miceforest as mf
from sklearn.datasets import load_iris
import pandas as pd

# Load and format data
iris = pd.concat(load_iris(as_frame=True,return_X_y=True),axis=1)
iris.rename(columns = {'target':'species'}, inplace = True)
iris['species'] = iris['species'].astype('category')

# Introduce missing values
iris_amp = mf.ampute_data(iris,perc=0.25,random_state=1991)

# Create kernels. 
kernel = mf.MultipleImputedKernel(
  data=iris_amp,
  save_all_iterations=True,
  random_state=1991
)

# Run the MICE algorithm for 3 iterations on each of the datasets
kernel.mice(3,verbose=True)


kernel.plot_correlations(wspace=0.4,hspace=0.5)

# Our new dataset
new_data = iris_amp.iloc[range(50)]# Make a multiple imputed dataset with our new data
new_data_imputed = kernel.impute_new_data(new_data)# Return a completed dataset
new_completed_data = new_data_imputed.complete_data(0)


new_data_imputed.plot_imputed_distributions(wspace=0.35,hspace=0.4)

from sklearn.linear_model import LinearRegression
# For each imputed dataset, train a linear regression
# on 'sepal length (cm)'
intercepts = []
target = 'sepal length (cm)'
for d in range(kernel.dataset_count()):
    comp_dat = kernel.complete_data(d)
    comp_dat = pd.get_dummies(comp_dat)
    X, y = comp_dat.drop(target,1), comp_dat[target]
    model = LinearRegression()
    model.fit(X,y)
    intercepts.append(model.intercept_)# Load packages for plotting
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import norm

# Make plot.
x_axis = np.arange(1.93, 2.01, 0.0001)
avg_intercept = round(np.mean(intercepts),2)
var_intercept = round(np.var(intercepts),4)
plt.plot(
    x_axis,
    norm.pdf(x_axis,avg_intercept,var_intercept)
)
plt.title(f"""
    Assumed Distribution of Intercept Term
    n=5, mean = {avg_intercept}, variance = {var_intercept}
    """
)

 

 

參考文檔:

【Python數據分析基礎】: 數據缺失值處理

數據分析——缺失值處理詳解(理論篇)

處理缺失值之多重插補(Multiple Imputation)

Multiple Imputation with Random Forests in Python

miceforest 2.0.3