6.4 缺失值插补

由于各种原因,现实世界的许多数据集包含缺失值,通常将其编码为空白,NaN或其他占位符。但是,此类数据集与scikit-learn估计器不兼容,后者假定数组中的所有值都是具有含义的数字。使用不完整数据集的基本策略是舍弃包含缺失值的整行或整列。但是,这是以丢失可能有价值的数据为代价的(即使数据不完整)。更好的策略是估算缺失值,即从数据的已知部分推断出缺失值。有关插补,请参见 常用术语表和API元素条目。

6.4.1 单变量与多变量插补

一类是单变量的插补算法,它仅使用第i个特征维度中的非缺失值(例如impute.SimpleImputer)插补第i个特征维度中的值。相比之下,多元插补算法使用整个可用特征维度集来估计缺失值(例如impute.IterativeImputer)。

6.4.2 单变量插补

SimpleImputer类提供了插补缺失值的基本策略。可以使用提供的常量或使用缺失值所在各列的统计量(平均值,中位数或众数)来估算缺失值。此类还支持不同的缺失值编码。

以下代码段演示了如何使用包含缺失值的列(axis 0)的平均值替换编码为np.nan的缺失值:

>>> import numpy as np
>>> from sklearn.impute import SimpleImputer
>>> imp = SimpleImputer(missing_values=np.nan, strategy='mean')
>>> imp.fit([[12], [np.nan, 3], [76]])
SimpleImputer()
>>> X = [[np.nan, 2], [6, np.nan], [76]]
>>> print(imp.transform(X))
[[4.          2.        ]
 [6.          3.666...]
 [7.          6.        ]]

SimpleImputer类还支持稀疏矩阵:

>>> import scipy.sparse as sp
>>> X = sp.csc_matrix([[12], [0-1], [84]])
>>> imp = SimpleImputer(missing_values=-1, strategy='mean')
>>> imp.fit(X)
SimpleImputer(missing_values=-1)
>>> X_test = sp.csc_matrix([[-12], [6-1], [76]])
>>> print(imp.transform(X_test).toarray())
[[3. 2.]
 [6. 3.]
 [7. 6.]]

请注意,这种格式并不打算用于在矩阵中隐式存储丢失的值,因为它将在转换时将其压缩。由0编码的缺失值必须与密集输入配合使用。

请注意,这种格式并不意味着在矩阵中隐式储存缺失值,因为它会在转换时将其密集化。编码为0的缺失值必须与密集输入一起使用。

当strategy参数设置为'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。回归器通过fit (X, y) 得到 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([[12], [36], [48], [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.]]

SimpleImputerIterativeImputer均可以在管道中使用以构建支持插补的复合估计器。在构建估算器之前,请参见估算缺失值

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],但与之不同之处在于,这里返回了一个插补而不是多个插补。但是,当 sample_posterior=True时,IterativeImputer通过重复应用于具有不同随机种子的同一数据集, 也可以用于多个插补。有关多重插补和单一插补的更多讨论,请参见[2]第4章。

当用户对由于缺失值而引起的不确定性不感兴趣时,在预测和分类中单一插补与多重插补的用处仍是一个尚待解决的问题。

请注意,不允许调用IterativeImputertransform方法更改样本数。因此,无法通过单次调用来实现多个插补。

6.4.4 参考

[1]Stef van Buuren,Karin Groothuis-Oudshoorn(2011)。“mice:R中的链式方程进行的多元归因”。统计软件杂志45:1-67。

[2]Roderick JA Little和Donald B Rubin(1986)。“缺失数据的统计分析”。John Wiley & Sons, Inc., New York, NY, USA.

6.4.5 最近邻归因

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 = [[12, nan], [343], [nan, 65], [887]]
>>> imputer = KNNImputer(n_neighbors=2, weights="uniform")
>>> imputer.fit_transform(X)
array([[1. , 2. , 4. ],
       [3. , 4. , 3. ],
       [5.56. , 5. ],
       [8. , 8. , 7. ]])

[OL2001]Olga Troyanskaya, Michael Cantor, Gavin Sherlock, Pat Brown, Trevor Hastie, Robert Tibshirani, David Botstein and Russ B. Altman, Missing value estimation methods for DNA microarrays, BIOINFORMATICS Vol. 17 no. 6, 2001 Pages 520-525.

6.4.6 标记估算值

MissingIndicator转换器是用于将数据集转换成相应二进制矩阵,以指示数据集中是否存在缺失值。此转换与插补结合使用非常有用。使用插补时,保留缺失值的信息可能会提供丰富的参考信息。请注意,SimpleImputerIterativeImputer都具有布尔参数add_indicator (默认情况下为False),将其设置为时True,将提供一种方便的方式来将MissingIndicator转换器的输出与imputer的输出堆叠在一起。

NaN通常用作缺失值的占位符。但是,它强制数据类型为浮点型。参数missing_values允许指定其他占位符,例如整数。在以下示例中,我们将使用-1作为缺失值:

>>> from sklearn.impute import MissingIndicator
>>> X = np.array([[-1-113],
...               [4-10-1],
...               [8-110]])
>>> indicator = MissingIndicator(missing_values=-1)
>>> mask_missing_values_only = indicator.fit_transform(X)
>>> mask_missing_values_only
array([[ True,  TrueFalse],
       [False,  True,  True],
       [False,  TrueFalse]])

features参数用于选择为其构建掩码的特征。默认情况下,该参数的取值是'missing-only',在fit时返回包含缺失值的特征的掩码:

>>> indicator.features_
array([013])

features参数可以设置为'all'返回所有特征,无论它们是否包含缺失值:

>>> indicator = MissingIndicator(missing_values=-1, features="all")
>>> mask_all = indicator.fit_transform(X)
>>> mask_all
array([[ True,  TrueFalseFalse],
       [False,  TrueFalse,  True],
       [False,  TrueFalseFalse]])
>>> indicator.features_
array([0123])

Pipeline中使用MissingIndicator时,一定要使用FeatureUnionColumnTransformer添加指示特征到常规特征中。首先,我们获得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(02, size=X.shape).astype(np.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
(1008)

当然,我们不能使用转换器进行任何预测。我们应该使用分类器将其包装在Pipeline中(例如DecisionTreeClassifier), 以便能够进行预测。

>>> clf = make_pipeline(transformer, DecisionTreeClassifier())
>>> clf = clf.fit(X_train, y_train)
>>> results = clf.predict(X_test)
>>> results.shape
(100,)