# 缺失值检测
缺失数据就是我们没有的数据。如果数据集是由向量表示的特征组成,那么缺失值可能表现为某些样本的一个或多个特征因为某些原因而没有测量的值。通常情况下,缺失值由特殊的编码方式。如果正常值都是正数,那么缺失值可能被标记为-1;如果正常值是字符串,那么缺失值可能是空的字符串;如果正常值是浮点数,那么缺失值可能是NaN。
在python中,我们能够通过使用np.isnan来判断数据是否为NaN:
```
>>> import numpy as np
>>> a = np.arange(10, dtype="float64")
>>> a[3] = np.nan
>>> np.isnan(a[3])
True
>>> a[3] == np.nan
False
>>> a[3] is np.nan
False
>>> np.isnan(a)
array([False, False, False,True, False, False, False, False, False,
False])
```
可以看到,只能用np.isnan来判断,“==”和“is”进行比较是无效的。
# 缺失值处理
假定我们已经知道数据中存在缺失值,那么我们就需要寻找一些手段来对其进行处理,从而避免对模型训练产生负面影响。
首先我们先生成一组数据,其中包含1000个样本,且每个样本都具有四维特征:
```
>>> import numpy as np
>>> N=1000
>>> np.random.seed(100)
>>> x = np.zeros((N,4))
>>> x[:,0] = 5*np.random.random(N)
>>> x[:,1] = np.random.normal(10,1,size=N)
>>> x[:,2] = 3*np.random.beta(5,2,N)
>>> x[:,3] = 0.3*np.random.lognormal(size=N)
```
其中我们固定随机种子,从而确保结果的可复现性,并且1-4维的数据分别来自均匀分布、正态分布、贝塔分布和对数正态分布。之后我们向其中随机添加5%的缺失值:
```
>>> i = np.random.randint(0,N,size=int(0.05*N))
>>> x[i,0] = np.nan
>>> i = np.random.randint(0,N,size=int(0.05*N))
>>> x[i,1] = np.nan
>>> i = np.random.randint(0,N,size=int(0.05*N))
>>> x[i,2] = np.nan
>>> i = np.random.randint(0,N,size=int(0.05*N))
>>> x[i,3] = np.nan
```
那么我们应该如何来处理这些缺失值呢?下面给出两种方法。
### 1. 直接丢弃
如果我们拥有的数据集较为庞大,而缺失值的占比很小,且分布较为均匀,那么直接丢弃这些缺失值也是可以的。但是如果缺失值与某个类别存在相关性,直接丢掉这些样本可能在某种程度上会导致数据集有偏,进而影响模型的准确性。
### 2. 均值/中位数填充
我们还可以参考剔除缺失值后数据的分布形态,选择使用数据均值或者中位数来对缺失值进行填充。我们首先剔除数据中的缺失值,并使用matplotlib.pyplot中的boxplot函数来查看剔除缺失值后数据的箱形图:
```
import matplotlib.pyplot as plt
# 剔除缺失值
def remove_nan_columnwise(data):
"""从每一列中剔除NaN值"""
return [data[~np.isnan(data[:, col]), col] for col in range(data.shape[1])]
# 应用函数到数据
cleaned_data = remove_nan_columnwise(x)
# 绘制箱形图
plt.figure(figsize=(10, 6))
plt.boxplot(cleaned_data, labels=['Column 1', 'Column 2', 'Column 3', 'Column 4'])
plt.title('Box Plot of Data After Removing Missing Values')
plt.ylabel('Value')
plt.show()
```
从图中可以看出,第1、2维的数据偏差较小,且箱形框较为对称;第3维的数据偏上,第4维的数据偏下。因此我们使用均值来替代第1、2维的缺失数据,使用中位数来替换第3、4维的数据。
```
i = np.where(np.isnan(x[:,0]) == False)
m = np.mean(x[i,0])
i = np.where(np.isnan(x[:,0]) == True)
x[i,0] = m
i = np.where(np.isnan(x[:,1]) == False)
m = np.mean(x[i,1])
i = np.where(np.isnan(x[:,1]) == True)
x[i,1] = m
i = np.where(np.isnan(x[:,2]) == False)
m = np.median(x[i,2])
i = np.where(np.isnan(x[:,2]) == True)
x[i,2] = m
i = np.where(np.isnan(x[:,3]) == False)
m = np.median(x[i,3])
i = np.where(np.isnan(x[:,3]) == True)
x[i,3] = m
```
关于何时用中位数替代缺失值,何时用均值替代缺失值,书中给出的指导方案为:接近正态分布用均值进行替换,不接近正态分布则用中位数进行替换。
参考GPT给出的详细参考如下:
#### 使用中位数填充的情况
1. 当数据是偏斜分布或存在异常值时:如果数据不是正态分布,而是偏向一侧(即有长尾),或者存在极端值(outliers),那么使用中位数可能是更好的选择。因为中位数不受极值影响,可以更好地代表中心趋势。
2. 对于非正态分布的数据:如果变量不符合正态分布,中位数可能是一个更稳健的选择,因为它更能反映大多数观测值的集中位置。
3. 对于有序分类数据:当处理有序分类变量(例如,教育水平、满意度评分等)时,中位数也可能是更合适的选择。
4. 当需要保持数据的原始分布特征时:在某些情况下,使用中位数可以帮助保持数据集的原有分布特性,尤其是在存在明显偏斜的情况下。
#### 使用平均值填充的情况
1. 当数据接近正态分布且没有显著异常值时:如果数据大致呈正态分布,并且不存在明显的异常值,那么使用平均值填充可能是合适的,因为它会考虑所有观测值。
2. 对于数值型数据:平均值通常用于数值型数据,尤其是那些期望值具有实际意义的变量。
3. 当想要最小化总平方误差时:平均值是使得预测误差平方和最小化的估计量,因此在某些统计分析中,它可能是首选。
4. 对于回归分析中的连续变量:在进行回归分析时,如果变量是连续的,并且你认为缺失值应该反映总体的“平均水平”,那么平均值可能是一个合理的选择。