基于时序数据的回归预测问题,在工作中经常遇到的。它与一般的监督学习的回归模型的区别在于数据本身是基于时序的。而常用的时序预测模型,比如arima等,添加其他特征时又不方便,不得不求助于经典的监督学习预测模型。本文初步介绍了对时序数据建模时,如何构建有效的特征工程。
原文翻译自: Basic Feature Engineering With Time Series Data in Python
时间序列数据,在我们可以为之使用机器学习算法建模之前,必须先重新构建为一个监督数据集。
在时间序列中,没有输入输出特征的概念。相反,我们必须选择要预测的变量,并使用的特征工程构建所有的输入,以预测未来的时间序列。
这篇教程中,你将学会如何用Python,构建特征工程,使用机器学习算法对时间序列问题建模。
看完本教程,你将知道:
- 基于时间序列的特征工程的原理与目标
- 如何提取基本的时序输入特征
- 如何提取更复杂的lag和sliding window的统计特征
时序的特征工程
一个时序数据集必须被转换,才能用监督学习算法建模。
如下的一个时序数据:
time 1, value 1
time 2, value 2
time 3, value 3
转为如下的格式:
input 1, output 1
input 2, output 2
input 3, output 3
这样,我们才能训练一个监督学习算法。
机器学习领域中,输入变量成为特征,我们的任务是从时序数据中,构造或挖掘新的输入特征。理想情况下,我们只想要那些最能帮助模型拟合输入(X)和输出(Y)的特征。
本教程中,我们将介绍可以从时间序列提取的三类特征:
- 日期时间特征:这些是观察样本的时间步长的基本属性
- Lag特征:这些是先前时间点的值
- WIndow特征:这些是先前时间观察值的统计值
在我们深入研究从时间序列数据挖掘输入特征的方法之前,先回顾一下特征工程的目标。
特征工程的目标
特征工程的目标是,为输入特征和输出提供鲁棒且简单的关系,便于监督学习算法建模。
实际上,我们正变得复杂。
复杂性体现在输入与输出数据的关系。在时序数据场景下,没有输入变量和输出变量的概念;我们必须也要创造这些,从头开始构建有监督的学习问题。
或许,我们可以依靠复杂模型的能力去破译问题的复杂性。如果我们能更好地揭示数据中的输入与输出的关系,就更容易地使模型工作(甚至使用更简单的模型)。
问题是,我们不知道待解决问题中的输入与输出的潜在的固有的函数关系。如果知道的话,也许就不需要机器学习了。
因此,唯一的反馈就是监督数据集或者问题“视图”上的模型性能及表现。实际上,最好的默认策略是使用所有可用的知识从时序数据中构建许多优秀的数据集,并利用模型的性能(或其他的项目要求)来确定好的特征和“视图”。
为清楚起见,我们将重点关注示例中的单变量(一个变量)时间序列数据集,但这些方法同样适用于多变量时间序列问题。接下来,让我们看一下我们将在本教程中使用的数据集。
Minimum Daily Temperatures数据集
本文将使用Minimum Daily Temperatures数据集。该数据集描述了澳大利亚墨尔本10年(1981-1990)的每天的最低温度。单位为摄氏度,有3,650个观测值。数据来源为澳大利亚气象局。
下面是前5行数据的示例,包括标题行。
"Date","Temperature"
"1981-01-01",20.7
"1981-01-02",17.9
"1981-01-03",18.8
"1981-01-04",14.6
"1981-01-05",15.8
下面是从Data Market获取的整个数据集的图表。
数据集显示了增长的趋势,可能还有一些季节性成分。
下载并了解数据集的更多
提醒:下载的文件包含一些问号(“?”)字符,在使用数据集之前必须删除这些字符。在文本编辑器中打开文件并删除“?”字符。同时删除文件中的任何页脚信息。
日期时间特征
让我们从一些最简单的特征开始。
这些是每次观察的日期/时间的特征。事实上,这些仅仅是特征提取的简单的开始,后面慢慢进入复杂的专有领域
我们首先提取的两个特征是,每个观察的整型的月份和日。我们可以想象,有监督的学习算法可能能够利用这些输入来帮助梳理出一年中的时间或时间类型的季节性信息。
我们提出的监督学习问题是,给定月份和日,预测每日最低温度,如下:
Month, Day, Temperature
Month, Day, Temperature
Month, Day, Temperature
我们可以使用Pandas做这些。首先,时间序列加载为一个Pandas Series。然后,为转换的数据集构建一个新的Pandas DataFrame。
接下来,每次添加一列,值为从series中的每个观察的时间戳信息中提取月和日信息。
下面是执行此操作的Python代码。
from pandas import Series
from pandas import DataFrame
series = Series.from_csv('daily-minimum-temperatures-in-me.csv', header=0)
dataframe = DataFrame()
dataframe['month'] = [series.index[i].month for i in range(len(series))]
dataframe['day'] = [series.index[i].day for i in range(len(series))]
dataframe['temperature'] = [series[i] for i in range(len(series))]
print(dataframe.head(5))
运行上面的代码,输出转换过的数据的前5行:
month day temperature
0 1 1 20.7
1 1 2 17.9
2 1 3 18.8
3 1 4 14.6
4 1 5 15.8
仅使用月份和日的信息来预测温度并不复杂,并且很可能导致模型不佳。然而,这些信息与其他工程特征相结合可能最终会产生更好的模型。
您可以枚举时间戳的所有属性,并考虑可能对您的问题有用的内容,例如:
- 一天的分钟数。
- 一天中的一小时。
- 营业时间与否。
- 周末与否。
- 一年中的季节。
- 一年中的业务季度。
- 夏令时与否。
- 公共假期与否。
- 闰年与否。
从这些示例中,可以看到特征并不限于原始整数值,也可以使用二进制标记功能,例如是否在公共假日。
在最低温度数据集的情况下,季节可能更相关。则季节属于与问题背景相关的特征,这些特征更有可能为您的模型增加价值。
基于日期时间的特征是一个良好的开端,但包含先前时间的观察值通常更有用。这些被称为lag值,我们将在下一节中介绍添加这些特征。
Lag特征
Lag特征是将时间序列预测问题转化为监督学习问题的经典方式。
最简单的方法是在给定前一时间(t-1)的值的情况下预测下一次(t + 1)的值。具有移位值的监督学习问题如下所示:
Value(t-1), Value(t+1)
Value(t-1), Value(t+1)
Value(t-1), Value(t+1)
Pandas库提供shift()函数,以帮助从时间序列数据集创建这些移位或滞后特征。将数据集移动1会创建t-1列,为第一行添加NaN(未知)值。没有移位的时间序列数据集表示t + 1。
让我们以一个例子来具体化。温度数据集的前3个值分别为20.7,17.9和18.8。因此,前3个观测值的移位和未移位温度列表如下:
Shifted, Original
NaN, 20.7
20.7, 17.9
17.9, 18.8
我们可以使用沿列轴(axis= 1)的concat()函数将移位列合并到一个新的DataFrame中。
综合这些,下面是为我们的日常温度数据集创建Lag特征的示例。从加载的序列中提取值,并创建这些值的移位和未移位列表。为清晰起见,每个列也在DataFrame中命名。
from pandas import Series
from pandas import DataFrame
from pandas import concat
series = Series.from_csv('daily-minimum-temperatures-in-me.csv', header=0)
temps = DataFrame(series.values)
dataframe = concat([temps.shift(1), temps], axis=1)
dataframe.columns = ['t-1', 't+1']
print(dataframe.head(5))
运行上述代码,打印包含Lag特征的新数据集的前五行:
t-1 t+1
0 NaN 20.7
1 20.7 17.9
2 17.9 18.8
3 18.8 14.6
4 14.6 15.8
可以看到,我们必须丢弃第一行才能使用数据集来训练监督学习模型,因为它没有全部列的数据可供使用。
Lag特征的添加称为滑动窗口方法,在这种情况下,窗口宽度为1。就好像我们在每个观察的时间序列中滑动焦点,只关注窗口宽度内的内容。
我们可以扩展窗口宽度并包含更多Lag特征。例如,下面的上述情况被修改为包括最后的3个观察值以预测下一个时间步的值。
from pandas import Series
from pandas import DataFrame
from pandas import concat
series = Series.from_csv('daily-minimum-temperatures-in-me.csv', header=0)
temps = DataFrame(series.values)
dataframe = concat([temps.shift(3), temps.shift(2), temps.shift(1), temps], axis=1)
dataframe.columns = ['t-3', 't-2', 't-1', 't+1']
print(dataframe.head(5))
运行上述代码,打印新的数据集的前五行:
t-3 t-2 t-1 t+1
0 NaN NaN NaN 20.7
1 NaN NaN 20.7 17.9
2 NaN 20.7 17.9 18.8
3 20.7 17.9 18.8 14.6
4 17.9 18.8 14.6 15.8
同样地,可以看到我们必须丢弃没有足够数据训练监督模型的前几行。
滑动窗口方法的一个难点是,基于实际的问题,如何确定为窗口的大小。
或许,一个很好的起点是执行敏感性分析,尝试一组不同的窗口宽度,从而创建一组不同的数据集“视图”,并查看哪些结果表现更好的模型。会有一个收益递减点。
另外,为什么要停止使用线性窗口?也许您需要上周,上个月和去年的滞后值。同样,这归结于特定领域。
在温度数据集的情况下,来自前一年或前几年的同一天的Lag值可能是有用的。
我们可以使用窗口做更多事情而不是包含原始值。在下一节中,我们将介绍包含在窗口中汇总统计信息的特征。
滚动窗口统计
除了原始Lag值作为特征,还可以使用先前时间观察值的统计信息及汇总。
我们可以计算滑动窗口中值的汇总统计数据,并将它们作为我们数据集中的特征包含在内。也许最有用的是前几个值的平均值,也称为滚动均值。
例如,我们可以计算前两个观察值的平均值,并使用它来预测下一个值。对于温度数据,我们必须等待3个时间步,然后才能使用前2个值来取平均值,并使用该值来预测第3个值。
举例:
mean(t-2, t-1), t+1
mean(20.7, 17.9), 18.8
19.3, 18.8
Pandas提供了一个rolling()函数,它在每个时间步都创建一个带有值窗口的新数据结构。然后,我们可以在为每个收集时间步的窗口上执行统计函数,例如计算平均值。
首先,series需要移动。然后滚动数据集被创建,并在每个窗口上计算两个值的平均值。
以下是前三个滚动窗口中的值:
#, Window Values
1, NaN
2, NaN, 20.7
3, 20.7, 17.9
这表明我们在第3行之前不会有可用的数据。
最后,与上一节一样,我们可以使用concat()函数构建一个只包含新列的新数据集。
下面的示例演示了如何使用窗口大小为2的Pandas执行此操作。
from pandas import Series
from pandas import DataFrame
from pandas import concat
series = Series.from_csv('daily-minimum-temperatures-in-me.csv', header=0)
temps = DataFrame(series.values)
shifted = temps.shift(1)
window = shifted.rolling(window=2)
means = window.mean()
dataframe = concat([means, temps], axis=1)
dataframe.columns = ['mean(t-2,t-1)', 't+1']
print(dataframe.head(5))
运行该示例将打印新数据集的前5行。我们可以看到前两行没用。
- 第一个NaN是由系列的转变创造的。
- 第二个因为NaN不能用于计算平均值。
- 最后,第三行显示,使用期望的值19.30(20.7和17.9的均值)去预测series中的第三个值18.8。
mean(t-2,t-1) t+1
0 NaN 20.7
1 NaN 17.9
2 19.30 18.8
3 18.35 14.6
4 16.70 15.8
我们可以计算出更多的统计数据,甚至可以用不同的数学方法来计算“窗口”的定义。
下面是另一个示例,显示窗口宽度为3,数据集包含更多摘要统计信息,特别是窗口中的最小值,平均值和最大值。
您可以在代码中看到我们明确指定滑动窗口宽度为命名变量。这使我们可以在计算系列的正确移位和将窗口宽度指定为rolling()函数时使用它。
在这种情况下,窗口宽度为3意味着我们必须将系列向前移动2个时间步长。这使得前两行NaN。接下来,我们需要计算窗口统计信息,每个窗口有3个值。在我们甚至从窗口中的系列中获得足够的数据以开始计算统计数据之前,它需要3行。前5个窗口中的值如下:
#, Window Values
1, NaN
2, NaN, NaN
3, NaN, NaN, 20.7
4, NaN, 20.7, 17.9
5, 20.7, 17.9, 18.8
这表明我们不会期望至少在第5行(数组索引4)之前可用的数据。
from pandas import Series
from pandas import DataFrame
from pandas import concat
series = Series.from_csv('daily-minimum-temperatures-in-me.csv', header=0)
temps = DataFrame(series.values)
width = 3
shifted = temps.shift(width - 1)
window = shifted.rolling(window=width)
dataframe = concat([window.min(), window.mean(), window.max(), temps], axis=1)
dataframe.columns = ['min', 'mean', 'max', 't+1']
print(dataframe.head(5))
运行代码将打印新数据集的前5行。
我们可以检查第5行(数组索引4)上值的正确性。我们可以看到确实17.9是最小值,20.7是[20.7,17.9,18.8]窗口中值的最大值。
min mean max t+1
0 NaN NaN NaN 20.7
1 NaN NaN NaN 17.9
2 NaN NaN NaN 18.8
3 NaN NaN NaN 14.6
4 17.9 19.133333 20.7 15.8
扩展窗口统计
另一种可能有用的窗,包括该series中的所有先前数据。
这称为扩展窗口,可以帮助跟踪可观察数据的边界。与DataFrame上的rolling()函数一样,Pandas提供了一个expanded()函数,该函数收集每个时间步的所有先前值的集合。
汇总每个列表先前的观察值并将其作为新特征包括在内。例如,下面是该系列的前5个步骤的展开窗口中的数字列表:
#, Window Values
1, 20.7
2, 20.7, 17.9,
3, 20.7, 17.9, 18.8
4, 20.7, 17.9, 18.8, 14.6
5, 20.7, 17.9, 18.8, 14.6, 15.8
同样,您可以看到,我们对series移动一步,以确保我们希望预测的输出值从这些窗口值中排除。因此输入窗口如下所示:
#, Window Values
1, NaN
2, NaN, 20.7
3, NaN, 20.7, 17.9,
4, NaN, 20.7, 17.9, 18.8
5, NaN, 20.7, 17.9, 18.8, 14.6
值得庆幸的是,统计计算不包括扩展窗口中的NaN值,这意味着不需要进一步修改。
下面是计算每日温度数据集上展开窗口的最小值,平均值和最大值的示例。
# create expanding window features
from pandas import Series
from pandas import DataFrame
from pandas import concat
series = Series.from_csv('daily-minimum-temperatures.csv', header=0)
temps = DataFrame(series.values)
window = temps.expanding()
dataframe = concat([window.min(), window.mean(), window.max(), temps.shift(-1)], axis=1)
dataframe.columns = ['min', 'mean', 'max', 't+1']
print(dataframe.head(5))
运行该示例将打印数据集的前5行。
检查扩展的最小值,平均值和最大值的点显示具有预期效果的示例
min mean max t+1
0 20.7 20.700000 20.7 17.9
1 17.9 19.300000 20.7 18.8
2 17.9 19.133333 20.7 14.6
3 14.6 18.000000 20.7 15.8
总结
在本教程中,您了解了如何使用特征工程将时间序列数据集转换为用于机器学习的监督学习数据集。
具体来说,你学到了:
- 对时间序列数据做特征工程的重要性和目标。
- 如何开发基于日期时间和Lag的特征。
- 如何开发滑动和扩展窗口汇总统计特征。