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的缺失值。
SimpleImputer
类还支持使用 'most_frequent'
或 'constant'
策略表示为字符串值或 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
次插补轮次。最终插补轮次的结果会被返回。
Note
这个估计器目前仍然是 实验性的:默认参数或行为细节可能会在没有任何弃用周期的情况下发生变化。解决以下问题将有助于稳定 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]]
>>> # 模型学习到第二个特征是第一个特征的两倍
>>> print(np.round(imp.transform(X_test)))
[[ 1. 2.]
[ 6. 12.]
[ 3. 6.]]
SimpleImputer
和 IterativeImputer
都可以在 Pipeline 中使用,作为一种构建支持插补的复合估计器的方式。参见 在构建估计器之前填补缺失值 。
6.4.3.1. IterativeImputer 的灵活性#
在 R 数据科学生态系统中,有许多成熟的插补包:Amelia、mi、mice、missForest 等。missForest 很受欢迎,并且事实证明它是不同顺序插补算法的特定实例,这些算法都可以通过传递不同的回归器来预测缺失特征值,从而用 IterativeImputer
实现。在 missForest 的情况下,这个回归器是一个随机森林。参见 使用不同变体的迭代插补法填补缺失值 。
6.4.3.2. 多重与单次插补#
在统计学界,进行多次插补是一种常见做法,例如,为单个特征矩阵生成 m
个独立的插补。每个这些 m
个插补随后通过后续的分析流程(例如特征工程、聚类、回归、分类)。这 m
个最终分析结果(例如保留验证误差)使数据科学家能够了解由于缺失值引起的固有不确定性,分析结果可能会有何不同。上述做法称为多重插补。
我们的 IterativeImputer
实现受到 R MICE 包(通过链式方程进行多元插补)[1]_ 的启发,但与之不同的是,它返回单次插补而不是多次插补。然而,通过在 sample_posterior=True
时使用不同的随机种子对同一数据集重复应用 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. ]])
有关另一个使用示例,请参见 在构建估计器之前填补缺失值 。
参考文献
`Olga Troyanskaya, Michael Cantor, Gavin Sherlock, Pat Brown, Trevor Hastie, Robert Tibshirani, David Botstein and Russ B. Altman,
缺失值估计方法在DNA微阵列中的应用,生物信息学 第17卷第6期,2001年,第520-525页。 <https://academic.oup.com/bioinformatics/article/17/6/520/272365>`_
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
和:class:IterativeImputer
都有一个布尔参数 add_indicator
(默认为 False
),当设置为 True
时,提供了一种方便的方法将:class: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])
在使用 MissingIndicator
于 Pipeline
时,请确保使用 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)
当然,我们不能使用这个转换器来进行任何预测。我们应该将其包装在一个 Pipeline
中,并结合一个分类器(例如,一个 DecisionTreeClassifier
),以便能够进行预测。
>>> 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 值。以下是这些估计器的列表,按类型分类(聚类、回归器、分类器、转换器):
- Estimators that allow NaN values for type
cluster
: - Estimators that allow NaN values for type
regressor
: - Estimators that allow NaN values for type
classifier
: - Estimators that allow NaN values for type
transformer
: