在本教程中,您将学习三种处理缺失值的方法。然后,您将在实际数据集上比较这些方法的有效性。

简介

数据中出现缺失值的原因有很多。例如:

  • 一套两居室的房子不会包含第三间卧室的大小值。
  • 调查受访者可能选择不分享他的收入。

如果您尝试使用包含缺失值的数据构建模型,大多数机器学习库(包括 scikit-learn)都会报错。因此,您需要选择以下策略之一。

三种方法

1) 一种简单方法:删除包含缺失值的列

最简单的方法是删除包含缺失值的列。

tut2_approach1

除非被删除列中的大多数值缺失,否则使用这种方法,模型将失去大量(可能有用的!)信息。举一个极端的例子,假设一个包含 10,000 行数据的数据集,其中一个重要的列缺少一个条目。这种方法会完全删除该列!

2) 更好的选择:插补

插补 会用某个数字填充缺失值。例如,我们可以用平均值填充每列。

tut2_approach2

在大多数情况下,估算值不会完全正确,但通常比完全删除该列能得到更准确的模型。

3) 估算的扩展

估算是标准方法,通常效果良好。然而,估算值可能会系统性地高于或低于其实际值(实际值未包含在数据集中)。或者,缺失值的行可能以其他方式独一无二。在这种情况下,您的模型可以通过考虑哪些值最初是缺失的来做出更好的预测。

tut3_approach3

在这种方法中,我们像以前一样对缺失值进行插补。此外,对于原始数据集中每列缺失的数据,我们都会添加一个新列来显示插补数据的位置。

在某些情况下,这会显著改善结果。但在其他情况下,它根本没有帮助。

示例

在本例中,我们将使用墨尔本住房数据集。我们的模型将使用房间数量和土地面积等信息来预测房价。

我们不会重点介绍数据加载步骤。您可以假设您已经拥有 X_trainX_validy_trainy_valid 中的训练和验证数据。

import pandas as pd
from sklearn.model_selection import train_test_split

# Load the data
data = pd.read_csv('../input/melbourne-housing-snapshot/melb_data.csv')

# Select target
y = data.Price

# To keep things simple, we'll use only numerical predictors
melb_predictors = data.drop(['Price'], axis=1)
X = melb_predictors.select_dtypes(exclude=['object'])

# Divide data into training and validation subsets
X_train, X_valid, y_train, y_valid = train_test_split(X, y, train_size=0.8, test_size=0.2,
random_state=0)

定义函数来衡量每种方法的质量

我们定义了一个函数 score_dataset() 来比较处理缺失值的不同方法。该函数报告随机森林模型的平均绝对误差 (MAE)。

from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_absolute_error

# Function for comparing different approaches
def score_dataset(X_train, X_valid, y_train, y_valid):
model = RandomForestRegressor(n_estimators=10, random_state=0)
model.fit(X_train, y_train)
preds = model.predict(X_valid)
return mean_absolute_error(y_valid, preds)

方法 1 的得分(删除缺失值的列)

由于我们同时处理训练集和验证集,因此我们会小心地在两个 DataFrame 中删除相同的列。

In [3]:

# Get names of columns with missing values
cols_with_missing = [col for col in X_train.columns
if X_train[col].isnull().any()]

# Drop columns in training and validation data
reduced_X_train = X_train.drop(cols_with_missing, axis=1)
reduced_X_valid = X_valid.drop(cols_with_missing, axis=1)

print("MAE from Approach 1 (Drop columns with missing values):")
print(score_dataset(reduced_X_train, reduced_X_valid, y_train, y_valid))
MAE from Approach 1 (Drop columns with missing values):
183550.22137772635

方法 2(插补)的得分

接下来,我们使用 SimpleImputer 将每列的缺失值替换为平均值。

虽然方法很简单,但填充平均值通常效果很好(但这因数据集而异)。虽然统计学家已经尝试了更复杂的方法来确定插补值(例如回归插补),但一旦将结果输入到复杂的机器学习模型中,这些复杂的策略通常不会带来额外的好处。

In [4]:

from sklearn.impute import SimpleImputer

# Imputation
my_imputer = SimpleImputer()
imputed_X_train = pd.DataFrame(my_imputer.fit_transform(X_train))
imputed_X_valid = pd.DataFrame(my_imputer.transform(X_valid))

# Imputation removed column names; put them back
imputed_X_train.columns = X_train.columns
imputed_X_valid.columns = X_valid.columns

print("MAE from Approach 2 (Imputation):")
print(score_dataset(imputed_X_train, imputed_X_valid, y_train, y_valid))
MAE from Approach 2 (Imputation):
178166.46269899711

我们发现方法 2 的 MAE 低于方法 1,因此方法 2 在该数据集上表现更佳。

方法 3 的得分(插补的扩展)

接下来,我们插补缺失值,同时跟踪哪些值被插补。

In [5]:

# Make copy to avoid changing original data (when imputing)
X_train_plus = X_train.copy()
X_valid_plus = X_valid.copy()

# Make new columns indicating what will be imputed
for col in cols_with_missing:
X_train_plus[col + '_was_missing'] = X_train_plus[col].isnull()
X_valid_plus[col + '_was_missing'] = X_valid_plus[col].isnull()

# Imputation
my_imputer = SimpleImputer()
imputed_X_train_plus = pd.DataFrame(my_imputer.fit_transform(X_train_plus))
imputed_X_valid_plus = pd.DataFrame(my_imputer.transform(X_valid_plus))

# Imputation removed column names; put them back
imputed_X_train_plus.columns = X_train_plus.columns
imputed_X_valid_plus.columns = X_valid_plus.columns

print("MAE from Approach 3 (An Extension to Imputation):")
print(score_dataset(imputed_X_train_plus, imputed_X_valid_plus, y_train, y_valid))
MAE from Approach 3 (An Extension to Imputation):
178927.503183954

我们可以看到,方法 3 的表现略逊于方法 2

那么,为什么插补法比删除列的效果更好呢?

训练数据有 10864 行和 12 列,其中 3 列包含缺失数据。对于每列,缺失的数据都少于一半。因此,删除列会删除大量有用信息,因此插补法的效果更好也是情理之中的。

In [6]:

# Shape of training data (num_rows, num_columns)
print(X_train.shape)

# Number of missing values in each column of training data
missing_val_count_by_column = (X_train.isnull().sum())
print(missing_val_count_by_column[missing_val_count_by_column > 0])
(10864, 12)
Car 49
BuildingArea 5156
YearBuilt 4307
dtype: int64

Conclusion

通常,相对于简单地删除具有缺失值的列(在方法 1中),估算缺失值(在方法 2方法 3中)会产生更好的结果。

Your Turn

在本练习中,您可以比较这些方法来自己处理缺失值!