imputing-missing-values-through-various-strategies

处理缺失值

实践中数值计算不可或缺,好在有很多方法可用,这个主题将介绍其中一些。不过,这些方法未必能解决你的问题。

scikit-learn有一些常见的计算方法,它可以对现有数据进行变换填补NA值。但是,如果数据集中的缺失值是有意而为之的——例如,服务器响应时间超过100ms——那么更合适的方法是用其他包解决,像处理贝叶斯问题的PyMC,处理风险模型的lifelines,或者自己设计一套方法。

Getting ready

处理缺失值的第一步是创建缺失值。Numpy可以很方便的实现:

In [2]:
from sklearn import datasets
import numpy as np
iris = datasets.load_iris()
iris_X = iris.data
masking_array = np.random.binomial(1, .25, iris_X.shape).astype(bool)
iris_X[masking_array] = np.nan

让我们看看这几行代码,Numpy和平时用法不太一样,这里是在数组中用了一个数组作为索引。为了创建了随机的缺失值,先创建一个随机布尔值数组,其形状和iris_X数据集的维度相同。然后,根据布尔值数组分配缺失值。因为每次运行都是随机数据,所以masking_array每次都会不同。

In [3]:
masking_array[:5]
Out[3]:
array([[False,  True, False, False],
       [False,  True, False, False],
       [False, False, False, False],
       [ True, False, False,  True],
       [False, False,  True, False]], dtype=bool)
In [4]:
iris_X[:5]
Out[4]:
array([[ 5.1,  nan,  1.4,  0.2],
       [ 4.9,  nan,  1.4,  0.2],
       [ 4.7,  3.2,  1.3,  0.2],
       [ nan,  3.1,  1.5,  nan],
       [ 5. ,  3.6,  nan,  0.2]])

How to do it...

本书贯穿始终的一条原则(由于scikit-learn的存在)就是那些拟合与转换数据集的类都是可用的,可以在其他数据集中继续使用。具体演示如下所示:

In [5]:
from sklearn import preprocessing
impute = preprocessing.Imputer()
iris_X_prime = impute.fit_transform(iris_X)
iris_X_prime[:5]
Out[5]:
array([[ 5.1       ,  3.05221239,  1.4       ,  0.2       ],
       [ 4.9       ,  3.05221239,  1.4       ,  0.2       ],
       [ 4.7       ,  3.2       ,  1.3       ,  0.2       ],
       [ 5.86306306,  3.1       ,  1.5       ,  1.21388889],
       [ 5.        ,  3.6       ,  3.82685185,  0.2       ]])

注意[3,0]位置的不同:

In [6]:
iris_X_prime[3,0]
Out[6]:
5.8630630630630645
In [8]:
iris_X[3,0]
Out[8]:
nan

How it works...

上面的计算可以通过不同的方法实现。默认是均值mean,一共是三种:

  • 均值mean(默认方法)
  • 中位数median
  • 众数most_frequent

scikit-learn会用指定的方法计算数据集中的每个缺失值,然后把它们填充好。

例如,用median方法重新计算iris_X,重新初始化impute即可:

In [9]:
impute = preprocessing.Imputer(strategy='median')
iris_X_prime = impute.fit_transform(iris_X)
iris_X_prime[:5]
Out[9]:
array([[ 5.1 ,  3.  ,  1.4 ,  0.2 ],
       [ 4.9 ,  3.  ,  1.4 ,  0.2 ],
       [ 4.7 ,  3.2 ,  1.3 ,  0.2 ],
       [ 5.8 ,  3.1 ,  1.5 ,  1.3 ],
       [ 5.  ,  3.6 ,  4.45,  0.2 ]])

如果数据有缺失值,后面计算过程中可能会出问题。例如,在How to do it...一节里面,np.nan作为默认缺失值,但是缺失值有很多表现形式。有时用-1表示。为了处理这些缺失值,可以在方法中指定那些值是缺失值。方法默认缺失值表现形式是Nan,就是np.nan的值。

假设我们将iris_X的缺失值都用-1表示。看着很奇怪,但是iris数据集的度量值不可能是负数,因此用-1表示缺失值完全合理:

In [10]:
iris_X[np.isnan(iris_X)] = -1
iris_X[:5]
Out[10]:
array([[ 5.1, -1. ,  1.4,  0.2],
       [ 4.9, -1. ,  1.4,  0.2],
       [ 4.7,  3.2,  1.3,  0.2],
       [-1. ,  3.1,  1.5, -1. ],
       [ 5. ,  3.6, -1. ,  0.2]])

填充这些缺失值也很简单:

In [11]:
impute = preprocessing.Imputer(missing_values=-1)
iris_X_prime = impute.fit_transform(iris_X)
iris_X_prime[:5]
Out[11]:
array([[ 5.1       ,  3.05221239,  1.4       ,  0.2       ],
       [ 4.9       ,  3.05221239,  1.4       ,  0.2       ],
       [ 4.7       ,  3.2       ,  1.3       ,  0.2       ],
       [ 5.86306306,  3.1       ,  1.5       ,  1.21388889],
       [ 5.        ,  3.6       ,  3.82685185,  0.2       ]])

There's more...

pandas库也可以处理缺失值,而且更加灵活,但是重用性较弱:

In [12]:
import pandas as pd
iris_X[masking_array] = np.nan
iris_df = pd.DataFrame(iris_X, columns=iris.feature_names)
iris_df.fillna(iris_df.mean())['sepal length (cm)'].head(5)
Out[12]:
0    5.100000
1    4.900000
2    4.700000
3    5.863063
4    5.000000
Name: sepal length (cm), dtype: float64

其灵活性在于,fillna可以填充任意统计参数值:

In [13]:
iris_df.fillna(iris_df.max())['sepal length (cm)'].head(5)
Out[13]:
0    5.1
1    4.9
2    4.7
3    7.9
4    5.0
Name: sepal length (cm), dtype: float64