5.4 缺失值插补

校验者: @if only 待二次校验 翻译者: @Trembleguy @Loopy

因为各种各样的原因,真实世界中的许多数据集都包含缺失数据,这类数据经常被编码成空格、NaNs,或者是其他的占位符。但是这样的数据集并不能scikit-learn学习算法兼容,因为大多的学习算法都默认假设数组中的元素都是数值,因而所有的元素都有自己的意义。 使用不完整的数据集的一个基本策略就是舍弃掉整行或整列包含缺失值的数据。但是这样就付出了舍弃可能有价值数据(即使是不完整的 )的代价。 处理缺失数值的一个更好的策略就是从已有的数据推断出缺失的数值。有关插补(imputation),请参阅常用术语表和API元素条目

5.4.1 单变量与多变量插补

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

5.4.2 单变量插补

SimpleImputer类提供了计算缺失值的基本策略。缺失值可以用提供的常数值计算,也可以使用缺失值所在的行/列中的统计数据(平均值、中位数或者众数)来计算。这个类也支持不同的缺失值编码。

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

  1. >>> import numpy as np
  2. >>> from sklearn.preprocessing import Imputer
  3. >>> imp = Imputer(missing_values='NaN', strategy='mean', axis=0)
  4. >>> imp.fit([[1, 2], [np.nan, 3], [7, 6]])
  5. Imputer(axis=0, copy=True, missing_values='NaN', strategy='mean', verbose=0)
  6. >>> X = [[np.nan, 2], [6, np.nan], [7, 6]]
  7. >>> print(imp.transform(X))
  8. [[ 4. 2. ]
  9. [ 6. 3.666...]
  10. [ 7. 6. ]]

SimpleImputer类也支持稀疏矩阵:

  1. >>> import scipy.sparse as sp
  2. >>> X = sp.csc_matrix([[1, 2], [0, 3], [7, 6]])
  3. >>> imp = Imputer(missing_values=0, strategy='mean', axis=0)
  4. >>> imp.fit(X)
  5. Imputer(axis=0, copy=True, missing_values=0, strategy='mean', verbose=0)
  6. >>> X_test = sp.csc_matrix([[0, 2], [6, 0], [7, 6]])
  7. >>> print(imp.transform(X_test))
  8. [[ 4. 2. ]
  9. [ 6. 3.666...]
  10. [ 7. 6. ]]

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

当使用 'most_frequent''constant' 策略时,SimpleImputer类还支持以 string values 或 pandas categoricals 表示的分类数据(categorical data)

  1. >>> import pandas as pd
  2. >>> df = pd.DataFrame([["a", "x"],
  3. ... [np.nan, "y"],
  4. ... ["a", np.nan],
  5. ... ["b", "y"]], dtype="category")
  6. ...
  7. >>> imp = SimpleImputer(strategy="most_frequent")
  8. >>> print(imp.fit_transform(df))
  9. [['a' 'x']
  10. ['a' 'y']
  11. ['a' 'y']
  12. ['b' 'y']]

5.4.3 多变量插补

一种更复杂的方法是使用IterativeImputer类,它将每个缺失值的特征建模为其他特征的函数,并使用该估计值进行估算。它以迭代循环方式执行:在每个步骤中,将要素目标列指定为输出y,将其他列视为输入X。使用一个回归器来在已知(未缺失)y的样本上,对(X,y)进行拟合。然后使用这个回归器来预测缺失的y值。这是以迭代的方式对每个特征进行的,然后重复max_iter轮。最后一轮的计算结果被返回。

注意 :这个估计器目前还处于试验阶段:预测和API可能会在没有任何弃用周期的情况下发生变化。要使用它,您需要显式地导入enable_iterative_imputer

  1. >>> import numpy as np
  2. >>> from sklearn.experimental import enable_iterative_imputer
  3. >>> from sklearn.impute import IterativeImputer
  4. >>> imp = IterativeImputer(max_iter=10, random_state=0)
  5. >>> imp.fit([[1, 2], [3, 6], [4, 8], [np.nan, 3], [7, np.nan]])
  6. IterativeImputer(add_indicator=False, estimator=None,
  7. imputation_order='ascending', initial_strategy='mean',
  8. max_iter=10, max_value=None, min_value=None,
  9. missing_values=nan, n_nearest_features=None,
  10. random_state=0, sample_posterior=False, tol=0.001,
  11. verbose=0)
  12. >>> X_test = [[np.nan, 2], [6, np.nan], [np.nan, 6]]
  13. >>> # the model learns that the second feature is double the first
  14. >>> print(np.round(imp.transform(X_test)))
  15. [[ 1. 2.]
  16. [ 6. 12.]
  17. [ 3. 6.]]

SimpleImputerIterativeImputer都可以用来在管道中构建支持计算的复合估计器。在构建估算器之前,请参阅一个估算缺失值的示例

5.4.3.1 多变量插补的灵活性

在R数据科学生态系统中,有许多成熟的估算包:Amelia, mi, mice, missForest等。misforest是一种很流行的算法,它是不同序列计算算法的一个特殊实例,这些算法都可以使用IterativeImputer来实现,通过传递不同的回归函数来预测缺失的特征值。在misforest的情况下,这个回归因子是一个随机森林。请参见Imputing missing values with variants of IterativeImputer

5.4.3.2 单次与多次插补

在统计学界,通常的做法是执行多次计算,例如,为单个特征矩阵生成m个单独的计算。然后,每一个m的估算都通过后续的分析管道(例如,特征工程、聚类、回归、分类)进行。m个最终分析结果(例如,延迟验证错误)允许数据科学家了解由于缺失值所导致的固有不确定性,分析结果可能会有何不同。上述做法称为多重插补。

我们实现IterativeImputer的灵感来自于R的MICE包[1],但与之不同的是,我们返回了一个单一的插补,而不是多个插补。然而,当sample_posterior=True时,IterativeImputer也可以通过重复应用于具有不同随机种子的同一数据集来进行多次计算。参见[2]第4章,以获得更多关于多重和单次估算的讨论。

当用户对由于缺失值而导致的测量不确定性不感兴趣时,单次和多次插补在预测和分类上下文中有多大用处,这仍然是一个有待解决的问题。

注意,调用IterativeImputer的转换方法不允许改变样本的数量。因此,单次调用transform不能实现多次计算。

5.4.4 参考

  • Stef van Buuren, Karin Groothuis-Oudshoorn (2011). “mice: Multivariate Imputation by Chained Equations in R”. Journal of Statistical Software 45: 1-67.
  • Roderick J A Little and Donald B Rubin (1986). “Statistical Analysis with Missing Data”. John Wiley & Sons, Inc., New York, NY, USA.

5.4.5 标记缺失值

MissingIndicator转换器用于将数据集转换为相应的二进制矩阵,以指示数据集中缺失值的存在。这个变换与归算结合起来是有用的。当使用插补时,保存关于哪些值丢失的信息可以提供有用的信息。

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

  1. >>> from sklearn.impute import MissingIndicator
  2. >>> X = np.array([[-1, -1, 1, 3],
  3. ... [4, -1, 0, -1],
  4. ... [8, -1, 1, 0]])
  5. >>> indicator = MissingIndicator(missing_values=-1)
  6. >>> mask_missing_values_only = indicator.fit_transform(X)
  7. >>> mask_missing_values_only
  8. array([[ True, True, False],
  9. [False, True, True],
  10. [False, True, False]])

参数features用于选择构造掩码的特征。默认情况下,它是 ‘missing-only’,在fit时返回包含缺失值的特征的输入掩码

  1. >>> indicator.features_
  2. array([0, 1, 3])

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

  1. >>> indicator = MissingIndicator(missing_values=-1, features="all")
  2. >>> mask_all = indicator.fit_transform(X)
  3. >>> mask_all
  4. array([[ True, True, False, False],
  5. [False, True, False, True],
  6. [False, True, False, False]])
  7. >>> indicator.features_
  8. array([0, 1, 2, 3])

当在 Pipeline 中使用 MissingIndicator时, 务必使用FeatureUnionColumnTransformer来添加 indicator features 到 regular features中. 首先,我们在iris数据集上插补一些缺失值:

  1. >>> from sklearn.datasets import load_iris
  2. >>> from sklearn.impute import SimpleImputer, MissingIndicator
  3. >>> from sklearn.model_selection import train_test_split
  4. >>> from sklearn.pipeline import FeatureUnion, make_pipeline
  5. >>> from sklearn.tree import DecisionTreeClassifier
  6. >>> X, y = load_iris(return_X_y=True)
  7. >>> mask = np.random.randint(0, 2, size=X.shape).astype(np.bool)
  8. >>> X[mask] = np.nan
  9. >>> X_train, X_test, y_train, _ = train_test_split(X, y, test_size=100,
  10. ... random_state=0)

现在我们创建一个FeatureUnion。为了使分类器能够处理这些数据,所有的特征都将使用SimpleImputer进行估算。 此外,它还从 MissingIndicator中添加指示变量。

  1. >>> transformer = FeatureUnion(
  2. ... transformer_list=[
  3. ... ('features', SimpleImputer(strategy='mean')),
  4. ... ('indicators', MissingIndicator())])
  5. >>> transformer = transformer.fit(X_train, y_train)
  6. >>> results = transformer.transform(X_test)
  7. >>> results.shape
  8. (100, 8)

当然,我们不能用transformer来做任何预测。我们应该用分类器(例如,DecisionTreeClassifier)将其封装在pipeline中, 以便能够进行预测。

  1. >>> clf = make_pipeline(transformer, DecisionTreeClassifier())
  2. >>> clf = clf.fit(X_train, y_train)
  3. >>> results = clf.predict(X_test)
  4. >>> results.shape
  5. (100,)