6.4. 遺失值插補#
由於各種原因,許多真實世界的資料集都包含遺失值,通常編碼為空白、NaN 或其他佔位符。然而,這些資料集與 scikit-learn 估計器不相容,因為估計器假設陣列中的所有值都是數值,並且所有值都有意義。使用不完整資料集的一個基本策略是捨棄包含遺失值的整行和/或整列。然而,這樣做的代價是會損失可能很有價值(即使不完整)的資料。一個更好的策略是插補遺失值,也就是說,從資料的已知部分推斷遺失值。請參閱關於插補的詞彙表條目。
6.4.1. 單變數與多變數插補#
一種插補演算法是單變數的,它僅使用該特徵維度中的非遺失值,來插補第 i 個特徵維度中的值(例如,SimpleImputer
)。相比之下,多變數插補演算法會使用所有可用的特徵維度來估計遺失值(例如,IterativeImputer
)。
6.4.2. 單變數特徵插補#
SimpleImputer
類別提供插補遺失值的基本策略。遺失值可以使用提供的常數值,或使用遺失值所在之每列的統計資料(平均值、中位數或最頻繁值)進行插補。此類別也允許不同的遺失值編碼。
以下程式碼片段示範如何使用包含遺失值之列(軸 0)的平均值,來替換編碼為 np.nan
的遺失值
>>> import numpy as np
>>> from sklearn.impute import SimpleImputer
>>> imp = SimpleImputer(missing_values=np.nan, strategy='mean')
>>> imp.fit([[1, 2], [np.nan, 3], [7, 6]])
SimpleImputer()
>>> X = [[np.nan, 2], [6, np.nan], [7, 6]]
>>> print(imp.transform(X))
[[4. 2. ]
[6. 3.666...]
[7. 6. ]]
SimpleImputer
類別也支援稀疏矩陣
>>> import scipy.sparse as sp
>>> X = sp.csc_matrix([[1, 2], [0, -1], [8, 4]])
>>> imp = SimpleImputer(missing_values=-1, strategy='mean')
>>> imp.fit(X)
SimpleImputer(missing_values=-1)
>>> X_test = sp.csc_matrix([[-1, 2], [6, -1], [7, 6]])
>>> print(imp.transform(X_test).toarray())
[[3. 2.]
[6. 3.]
[7. 6.]]
請注意,這種格式並非用於在矩陣中隱式儲存遺失值,因為這會在轉換時使其稠密化。以 0 編碼的遺失值必須與稠密輸入一起使用。
當使用 'most_frequent'
或 'constant'
策略時,SimpleImputer
類別也支援以字串值或 pandas 分類表示的類別資料
>>> import pandas as pd
>>> df = pd.DataFrame([["a", "x"],
... [np.nan, "y"],
... ["a", np.nan],
... ["b", "y"]], dtype="category")
...
>>> imp = SimpleImputer(strategy="most_frequent")
>>> print(imp.fit_transform(df))
[['a' 'x']
['a' 'y']
['a' 'y']
['b' 'y']]
如需有關使用方式的另一個範例,請參閱在建立估計器之前插補遺失值。
6.4.3. 多變數特徵插補#
一種更複雜的方法是使用IterativeImputer
類別,該類別將每個具有遺失值的特徵建模為其他特徵的函數,並使用該估計值進行插補。它以迭代循環的方式執行此操作:在每個步驟中,將一個特徵列指定為輸出 y
,並將其他特徵列視為輸入 X
。將一個迴歸器擬合到已知 y
的 (X, y)
上。然後,使用迴歸器來預測 y
的遺失值。這會以迭代方式對每個特徵執行,然後重複執行 max_iter
次插補回合。最後插補回合的結果將會傳回。
注意
這個估計器目前仍處於實驗性階段:預設參數或行為細節可能會在沒有任何棄用週期的情況下變更。解決以下問題將有助於穩定 IterativeImputer
:收斂條件(#14338)、預設估計器(#13286)和隨機狀態的使用(#15611)。若要使用它,您需要明確匯入 enable_iterative_imputer
。
>>> import numpy as np
>>> from sklearn.experimental import enable_iterative_imputer
>>> from sklearn.impute import IterativeImputer
>>> imp = IterativeImputer(max_iter=10, random_state=0)
>>> imp.fit([[1, 2], [3, 6], [4, 8], [np.nan, 3], [7, np.nan]])
IterativeImputer(random_state=0)
>>> X_test = [[np.nan, 2], [6, np.nan], [np.nan, 6]]
>>> # the model learns that the second feature is double the first
>>> print(np.round(imp.transform(X_test)))
[[ 1. 2.]
[ 6. 12.]
[ 3. 6.]]
SimpleImputer
和 IterativeImputer
都可以在管線中使用,作為建立支援插補的複合估計器的一種方法。請參閱在建立估計器之前插補遺失值。
6.4.3.1. IterativeImputer 的彈性#
在 R 資料科學生態系統中,有許多成熟的插補套件:Amelia、mi、mice、missForest 等。missForest 很受歡迎,並且事實證明是不同循序插補演算法的特定實例,這些演算法都可以透過將不同的迴歸器傳遞到 IterativeImputer
中,以用於預測遺失的特徵值來實作。在 missForest 的情況下,此迴歸器是隨機森林。請參閱使用 IterativeImputer 的變體來插補遺失值。
6.4.3.2. 多重與單一插補#
在統計社群中,執行多重插補是很常見的做法,例如,為單一特徵矩陣生成 m
個獨立的插補。然後,將這 m
個插補結果中的每一個都放入後續的分析流程(例如,特徵工程、集群、迴歸、分類)。m
個最終分析結果(例如,保留驗證錯誤)讓資料科學家能夠了解,由於遺失值所造成的固有不確定性,分析結果可能會有何不同。上述做法稱為多重插補。
我們實作的 IterativeImputer
的靈感來自 R MICE 套件(使用鏈式方程式進行多變量插補) [1],但它與 R MICE 套件的不同之處在於,它會回傳單一插補,而不是多重插補。然而,當 sample_posterior=True
時,透過重複將 IterativeImputer
應用於具有不同隨機種子的同一個資料集,也可以使用 IterativeImputer
進行多重插補。關於多重與單一插補的更多討論,請參閱 [2] 第 4 章。
當使用者不感興趣測量遺失值所造成的不確定性時,單一插補與多重插補在預測和分類的背景下有多大的用處,目前仍然是一個懸而未決的問題。
請注意,不允許呼叫 IterativeImputer
的 transform
方法來變更樣本數。因此,無法透過單次呼叫 transform
來達成多重插補。
6.4.3.3. 參考文獻#
6.4.4. 最近鄰插補#
KNNImputer
類別提供使用 k 最近鄰方法來填補遺失值的插補。依預設,支援遺失值的歐幾里得距離度量 nan_euclidean_distances
用於尋找最近鄰。每個遺失的特徵都使用具有該特徵值的 n_neighbors
個最近鄰的值來進行插補。鄰居的特徵會均勻地平均,或按與每個鄰居的距離進行加權。如果樣本遺失多個特徵,則該樣本的鄰居可能會因正在插補的特定特徵而有所不同。當可用鄰居的數量小於 n_neighbors
,且沒有到訓練集的已定義距離時,將在插補期間使用該特徵的訓練集平均值。如果至少有一個具有已定義距離的鄰居,則在插補期間將使用其餘鄰居的加權或未加權平均值。如果特徵在訓練中始終遺失,則會在 transform
期間移除該特徵。有關該方法的更多資訊,請參閱參考文獻 [OL2001]。
以下程式碼片段示範如何使用遺失樣本的兩個最近鄰的平均特徵值來取代編碼為 np.nan
的遺失值
>>> import numpy as np
>>> from sklearn.impute import KNNImputer
>>> nan = np.nan
>>> X = [[1, 2, nan], [3, 4, 3], [nan, 6, 5], [8, 8, 7]]
>>> imputer = KNNImputer(n_neighbors=2, weights="uniform")
>>> imputer.fit_transform(X)
array([[1. , 2. , 4. ],
[3. , 4. , 3. ],
[5.5, 6. , 5. ],
[8. , 8. , 7. ]])
如需有關使用方式的另一個範例,請參閱在建立估計器之前插補遺失值。
參考文獻
6.4.5. 保持特徵數量恆定#
依預設,scikit-learn 插補器將會捨棄完全空的特徵,也就是只包含遺失值的欄。例如
>>> imputer = SimpleImputer()
>>> X = np.array([[np.nan, 1], [np.nan, 2], [np.nan, 3]])
>>> imputer.fit_transform(X)
array([[1.],
[2.],
[3.]])
X
中第一個只包含 np.nan
的特徵會在插補後捨棄。雖然此特徵在預測設定中沒有幫助,但捨棄欄將會變更 X
的形狀,這在使用更複雜的機器學習流程中的插補器時可能會造成問題。參數 keep_empty_features
提供使用常數值來插補以保留空特徵的選項。在大多數情況下,此常數值為零
>>> imputer.set_params(keep_empty_features=True)
SimpleImputer(keep_empty_features=True)
>>> imputer.fit_transform(X)
array([[0., 1.],
[0., 2.],
[0., 3.]])
6.4.6. 標記插補值#
MissingIndicator
轉換器適用於將資料集轉換為對應的二元矩陣,指出資料集中是否存在遺失值。此轉換與插補結合使用時很有用。當使用插補時,保留哪些值遺失的相關資訊可能會很有用。請注意,SimpleImputer
和 IterativeImputer
都具有布林參數 add_indicator
(依預設為 False
),當設定為 True
時,可以方便地將 MissingIndicator
轉換器的輸出與插補器的輸出堆疊在一起。
NaN
通常用作遺失值的預留位置。但是,它會強制資料類型為浮點數。參數 missing_values
允許指定其他預留位置,例如整數。在以下範例中,我們將使用 -1
作為遺失值
>>> from sklearn.impute import MissingIndicator
>>> X = np.array([[-1, -1, 1, 3],
... [4, -1, 0, -1],
... [8, -1, 1, 0]])
>>> indicator = MissingIndicator(missing_values=-1)
>>> mask_missing_values_only = indicator.fit_transform(X)
>>> mask_missing_values_only
array([[ True, True, False],
[False, True, True],
[False, True, False]])
features
參數用於選擇要為其建構遮罩的特徵。依預設,它是 'missing-only'
,這會回傳在 fit
時包含遺失值的特徵的插補器遮罩
>>> indicator.features_
array([0, 1, 3])
可以將 features
參數設定為 'all'
,以回傳所有特徵,無論它們是否包含遺失值
>>> indicator = MissingIndicator(missing_values=-1, features="all")
>>> mask_all = indicator.fit_transform(X)
>>> mask_all
array([[ True, True, False, False],
[False, True, False, True],
[False, True, False, False]])
>>> indicator.features_
array([0, 1, 2, 3])
在 Pipeline
中使用 MissingIndicator
時,請務必使用 FeatureUnion
或 ColumnTransformer
,將指標特徵新增至常規特徵。首先,我們取得 iris
資料集,並向其中新增一些遺失值。
>>> from sklearn.datasets import load_iris
>>> from sklearn.impute import SimpleImputer, MissingIndicator
>>> from sklearn.model_selection import train_test_split
>>> from sklearn.pipeline import FeatureUnion, make_pipeline
>>> from sklearn.tree import DecisionTreeClassifier
>>> X, y = load_iris(return_X_y=True)
>>> mask = np.random.randint(0, 2, size=X.shape).astype(bool)
>>> X[mask] = np.nan
>>> X_train, X_test, y_train, _ = train_test_split(X, y, test_size=100,
... random_state=0)
現在,我們建立一個 FeatureUnion
。所有特徵都將使用 SimpleImputer
進行插補,以便讓分類器能夠使用此資料。此外,它還會新增來自 MissingIndicator
的指標變數。
>>> transformer = FeatureUnion(
... transformer_list=[
... ('features', SimpleImputer(strategy='mean')),
... ('indicators', MissingIndicator())])
>>> transformer = transformer.fit(X_train, y_train)
>>> results = transformer.transform(X_test)
>>> results.shape
(100, 8)
當然,我們不能使用轉換器來進行任何預測。我們應該將其包裝在具有分類器(例如 DecisionTreeClassifier
)的 Pipeline
中,以便能夠進行預測。
>>> clf = make_pipeline(transformer, DecisionTreeClassifier())
>>> clf = clf.fit(X_train, y_train)
>>> results = clf.predict(X_test)
>>> results.shape
(100,)
6.4.7. 處理 NaN 值的估算器#
有些估算器被設計為無需預處理即可處理 NaN 值。以下是這些估算器的列表,按類型(群集、回歸器、分類器、轉換器)分類
- 允許 NaN 值的估算器,類型為
cluster
: - 允許 NaN 值的估算器,類型為
regressor
: - 允許 NaN 值的估算器,類型為
classifier
: - 允許 NaN 值的估算器,類型為
transformer
: