2.4.1 缺失值处理

缺失值的出现很常见,例如在医疗场景中,一个时间序列数据出现缺失可能有以下原因:

  • 病人没有遵从医嘱

  • 病人的健康状态很好,因此没必要在每个时刻都记录

  • 病人被忘记了

  • 医疗设备出现随机性的技术故障

  • 数据录入问题

最常用的处理缺失值的方法包括填补(imputation)和删除(deletion)两种。

Imputation:基于完整数据集的其他值填补缺失值

Deletion:直接删除有缺失值的时间段

一般来说,我们更倾向于保留数据而不是删掉,避免造成信息损失。在实际案例中,采取何种方式要考虑是否可以承受删除特定数据的损失。

本节将重点讨论三种数据填补方法,并用python演示如何使用:

  • Forward fill

  • Moving average

  • Interpolation

用到的数据集是美国年度失业率数据,数据集来自OECD官网

Forward fill

前向填充法是用来填补数据最简单的方法之一,核心思想是用缺失值之前出现的最近一个时间点的数值来填补当前缺失值。使用这种方法不需要任何数学或复杂逻辑。

与前向填充相对应的,还有一种backward fill的方法,顾名思义,是指用缺失值之后出现的最近一个时间点的数值来填充。但是使用这种方法需要特别谨慎,因为这种方法是一种lookahead行为,只有当你不需要预测未来数据的时候才能考虑使用。

总结前向填充法的优点,计算简单,很容易用于实时流媒体数据。

Moving average

移动平均法是填补数据的另一种方法,核心思想是取出缺失值发生之前的一段滚动时间内的值,计算其平均值或中位数来填补缺失。在有些场景下,这种方法会比前向填充效果更好,例如数据的噪声很大,对于单个数据点有很大的波动,但用移动平均的方法就可以弱化这些噪声。

同样的,你也可以使用缺失值发生之后的时间点计算均值,但需要注意lookahead问题。

另外一个小trick是,计算均值时可以根据实际情况采取多种方法,如指数加权,给最近的数据点赋予更高的权重。

Interpolation

插值是另一种确定缺失数据点值的方法,主要基于我们希望整体数据如何表现的各种图像上的约束。 例如,线性插值要求缺失数据和邻近点之间满足一定的线性拟合关系。因此插值法是一种先验方法,使用插值法时需要代入一些业务经验。

在许多情况下,线性(或样条)插值都是非常合适的。例如考虑平均每周温度,其中存在已知的上升或上升趋势,气温下降取决于一年中的时间。或者考虑一个已知年度销售数据 不断增长的业务。在这些场景下,使用插值法都能取得不错的效果。

当然也有很多情况不适合线性(或样条)插值的场景。例如在天气数据集中缺少降水数据,就不应在已知天数之间进行线性推断,因为降水的规律不是这样的。同样,如果我们查看某人每天的睡眠时间,我们也不应该利用已知天数的睡眠时间线性外推。

Python代码实现

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
pd.set_option('max_row',1000)
# 导入美国年度失业率数据
unemploy = pd.read_csv('data\\unemployment.csv')
unemploy.head()
# 构建一列随机缺失值列
unemploy['missing'] = unemploy['rate']
# 随机选择10%行手动填充缺失值
mis_index = unemploy.sample(frac=0.1,random_state=999).index
unemploy.loc[mis_index,'missing']=None

1.使用 forward fill填补缺失值

unemploy['f_fill'] = unemploy['missing']
unemploy['f_fill'].ffill(inplace=True)
# 观察填充效果
plt.scatter(unemploy.year,unemploy.rate,s=10)
plt.plot(unemploy.year,unemploy.rate,label='real')
plt.scatter(unemploy[~unemploy.index.isin(mis_index)].year,unemploy[~unemploy.index.isin(mis_index)].f_fill,s=10,c='r')
plt.scatter(unemploy.loc[mis_index].year,unemploy.loc[mis_index].f_fill,s=50,c='r',marker='v')
plt.plot(unemploy.year,unemploy.f_fill,label='forward fill')
plt.legend()

2.使用moving average填补缺失值

unemploy['moveavg']=np.where(unemploy['missing'].isnull(),
                             unemploy['missing'].shift(1).rolling(3,min_periods=1).mean(),
                             unemploy['missing'])
# 观察填充效果
plt.scatter(unemploy.year,unemploy.rate,s=10)
plt.plot(unemploy.year,unemploy.rate,label='real')
plt.scatter(unemploy[~unemploy.index.isin(mis_index)].year,unemploy[~unemploy.index.isin(mis_index)].f_fill,s=10,c='r')
plt.scatter(unemploy.loc[mis_index].year,unemploy.loc[mis_index].f_fill,s=50,c='r',marker='v')
plt.plot(unemploy.year,unemploy.f_fill,label='forward fill',c='r',linestyle = '--')
plt.scatter(unemploy[~unemploy.index.isin(mis_index)].year,unemploy[~unemploy.index.isin(mis_index)].moveavg,s=10,c='r')
plt.scatter(unemploy.loc[mis_index].year,unemploy.loc[mis_index].moveavg,s=50,c='g',marker='^')
plt.plot(unemploy.year,unemploy.moveavg,label='moving average',c='g',linestyle = '--')
plt.legend()

3.使用interpolation填补缺失值

# 尝试线性插值和多项式插值
unemploy['inter_lin']=unemploy['missing'].interpolate(method='linear')
unemploy['inter_poly']=unemploy['missing'].interpolate(method='polynomial', order=3)
# 观察填充效果
plt.plot(unemploy.year,unemploy.rate,label='real')
plt.plot(unemploy.year,unemploy.inter_lin,label='linear interpolation',c='r',linestyle = '--')
plt.plot(unemploy.year,unemploy.inter_poly,label='polynomial interpolation',c='g',linestyle = '--')
plt.legend()

Last updated