原文:
towardsdatascience.com/how-to-handle-missing-data-for-time-series-680810f648ed
如何:处理时间序列的缺失数据
https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/d30ad994a36a0c060c199b5a946fb618.png
图片由 Mika Baumeister 在 Unsplash 上提供
没有完美无缺的数据集。每个数据科学家都知道在数据探索过程中,当他们调用:
df.info()并看到如下内容时那种感觉:
https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/389cb7a077cffb932261cc18a03fd758.png
UCI 机器学习仓库空气质量数据集 信息 (CC BY 4.0)。图片由作者提供
大多数机器学习模型无法处理 NaN 或空值,因此如果您的特征或目标包含它们,在尝试将模型拟合到数据之前,它们必须得到适当的处理。
在这篇文章中,我将探讨处理时间序列数据集中空值/缺失数据的 3 种简单方法。
1. 删除空值
这可能是处理缺失数据最简单、最直接的方法:就是将其删除。
# Drop any and all nulls across all columnsdf.dropna(inplace=True)默认情况下,pandas 的 dropna 函数会在所有列中搜索空值,并删除任何列中存在空值的行。然而,这可以通过各种参数进行修改。
在这个数据集的情况下,注意列 NMHC(GT) 只有 914 个非空值。因此,如果我们删除所有空值,我们的模型最终最多只有 914 行(可能更少)数据。这与原始的 9,357 行相比是一个巨大的减少!
通过指定subset的列,pandas 将只删除数据框中那些特定列存在空值的行。
df.dropna(subset=['CO(GT)','PT08.S1(CO)'],inplace=True)这样我们就可以混合匹配方法,在一些列中删除空值,而在其他列中采取不同的处理方式。
您也可以指定是否只想删除所有列都为空的行,通过设置参数how为 “all”。默认的 how 值为 “any”。
2. 插值处理空值
另一种填充空值简单的方法是通过插值。Pandas 的 interpolate 方法默认使用线性插值。
线性插值基本上取空值前后两个值,并在它们之间创建一条线。然后它使用这条线来估计缺失数据点的值。Pandas 的 interpolate 方法假设每个数据点都是等间距的。如果你没有每个可能时间戳的行,只要你有 datetime 索引,你可以将插值方法设置为"time"。这样,如果你有两个连续的行,它们之间间隔大于 1 个区间(例如大于 1 天或 1 小时),插值将考虑这个距离。
如果这是第一个索引,由于没有值在空值之前,它将不会进行插值。
https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/5db600e1977266da37366d0ce61b59bf.png
图片由作者提供
在这个例子中,由于有一个空值正好位于两个已知值中间,插值很简单。所有值都均匀分布在 1 小时内。索引为 10 的空值将简单地是前后值的平均值(0.65)。
如果有 2 个或更多连续的 NaNs,它们将考虑它们与已知值之间的距离进行插值。
更多关于线性插值工作原理的详细信息,请参阅这里。
你可以通过limit关键字参数设置连续 NaNs 的最大插值数量。如果有很多连续的 NaNs,你可能想在插值到一定点后直接删除这些值,因为每次插值都会给算法引入不确定性。更多的插值意味着更大的不确定性,尤其是在时间序列的情况下。
3. 填充空值
我要介绍的最后一个方法是填充。填充本质上意味着你用数据的平均值或中位数来填补空值。
做这件事最简单的方法是使用 pandas 的 fillna 函数,并取整个列的中位数。
df.fillna(df['CO(GT)'].median())但是,当涉及到时间序列时,整个数据集的中位数往往不准确。时间序列数据通常具有季节性模式,其中使用量根据一天中的小时、一周中的日子、月份等而变化。
在这个例子中,我决定使用该小时的中位数来填充 CO(GT)列。
为了能够使用中位数进行填充,我提出了自己的解决方案,因为没有直接的方法或库来做这件事(据我所知)。我首先创建了一个包含所有相应小时中位数的 dataframe。
# Create dataframe which contains the median for each column grouped by# hourhour_df=pd.DataFrame(df.groupby([df.index.hour]).median())hour_df.reset_index(inplace=True)https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/7ff4d7b0ae9a4afcda3e8a0732f2c153.png
结果的分组中位数按小时显示的数据框。仅显示前 5 小时和 3 列。图片由作者提供
接下来,我创建了一个名为 get_hour_median 的函数。虽然我只展示了 CO(GT)列,但我使这个函数足够灵活,可以处理任何列名。
defget_hour_median(hour,col_name):median=hour_df[hour_df['Datetime']==hour][col_name].values[0]returnmedian然后我使用 apply 和另一个自定义函数将此函数应用于 CO(GT)列。
# Reset datetime index for easier processing in functions belowdf.reset_index(inplace=True)# Takes in a dataframe row and returns a median value if row# is null, otherwise return original value.deffill_with_hourly_median(row,col_name):ifpd.isnull(row[col_name]):returnget_hour_median(row['Datetime'].hour,col_name)else:returnrow[col_name]# Apply fill_with_hourly_median to CO(GT) columndf['CO(GT)']=df.apply(fill_with_hourly_median,axis=1,col_name='CO(GT)')CO(GT)列现在应该填充了对应小时的中间值,而不是 NaNs。
应该选择哪一个?
很多次,你会为不同的列使用不同方法的组合。例如,由于线性插值无法填充列中的第一个值,如果数据框开头有 null 行,这些行可以在数据框中间的行被插值后删除。
如果你有很多数据,但 null 值不多,删除几行不会造成很大差异。在这种情况下,删除通常是我的首选方法,因为我要输入模型的所有数据都是实际数据。
对于数据集中偶尔出现的小间隔(1-2 个缺失行),我通常会使用插值。然而,如果间隔较大,且有大量连续的 null 值,我会考虑使用中间值直到某个阈值(>6-10,但可能取决于数据的粒度和模式的一致性),之后我会开始删除行。
如你所见,尽管处理缺失数据是常见的情况,但处理它需要考虑很多因素。我提到的方法绝对不是唯一的,但仅使用这三种选项,你就能做很多事情。
我建议彻底探索你的时间序列数据,通过绘制图表并确定 null 值的位置,无论间隔大小,以及存在的季节性模式。随着时间的推移和实践,你会对如何最好地处理数据中的间隔形成更好的直觉。
参考文献
- Vito, Saverio. (2016). Air Quality. UCI Machine Learning Repository.
doi.org/10.24432/C59K5F.