Time Series Dataset to Tabular Dataset for Machine Learning Deep Learning 时间序列表格化,用于机器学习/深度学习
引言 #
阅读本文的前提:已知机器学习相关基本概念,详见 /ml-do-it )。
由于表格形式的数据集(tabular dataset,例如下图),是大部分机器学习算法所接受的格式,因此各种数据的表格化是非常重要的思维范式。
表格化的方法关键词:时滞 OR 前导,搜索关键词 lag OR lead,对应 python 函数 shift。
而时间序列数据(time-series data 简称 时序数据),如下图所示,一般包括时间戳和当时的数据数值。大部分数据集内相邻的采样点是时间间隔相等,例如 10 分钟(更低频率采样的数据集可能是 1 小时 、1 天等等)为一个采样时间单位。
| 时间戳或时序序号 | 对应数据 |
|---|---|
| 2026-01-01 00:00 | 601 |
| 2026-01-01 00:10 | 602 |
| 2026-01-01 00:20 | 603 |
| 2026-01-01 00:30 | 604 |
| 2026-01-01 00:40 | 605 |
| 2026-01-01 00:50 | 606 |
| 2026-01-01 01:00 | 607 |
| 2026-01-01 01:10 | 608 |
| 2026-01-01 01:20 | 609 |
| 2026-01-01 01:30 | 610 |
| 2026-01-01 01:40 | 611 |
| 2026-01-01 01:50 | 612 |
| 2026-01-01 02:00 | 613 |
| 2026-01-01 02:10 | 614 |
| 2026-01-01 02:20 | 615 |
| 2026-01-01 02:30 | 616 |
| 2026-01-01 02:40 | 617 |
| 2026-01-01 02:50 | 618 |
| 2026-01-01 03:00 | 619 |
对于这个时序数据,现在的任务是根据已有的数据,预测未来一个时间单位后的时间点的数值。现在是 03:00,要预测 03:10 的数值。当然,之后时间前进了,例如到 3:30,要预测的时间点也前进到 3:40。那就要建立一个各时间点通用的模型(不只是用于预测 03:10),即映射关系,从“某个时刻的状态 (state) x“ 到 ”未来 1 个时间单位后的时间点的数值 y“ 的映射关系。
被预测的标签?是“未来” #
对于现有的数据,“某时刻的状态“对于 00:00 ~ 03:00 都有,”未来 1 个时间单位后的时间点的数值 y“ 对于 00:00 ~ 02:50 都有,因此可以形成一个表格,有 19 个采样(19 行,但是最后一行无法使用):
| 时间戳或时序序号 | 对应数据 (当前状态, x) | 未来 1 个时间单位数值 = label = y = shift(-1) |
|---|---|---|
| 2026-01-01 00:00 | 601 | 602 |
| 2026-01-01 00:10 | 602 | 603 |
| 2026-01-01 00:20 | 603 | 604 |
| 2026-01-01 00:30 | 604 | 605 |
| 2026-01-01 00:40 | 605 | 606 |
| 2026-01-01 00:50 | 606 | 607 |
| 2026-01-01 01:00 | 607 | 608 |
| 2026-01-01 01:10 | 608 | 609 |
| 2026-01-01 01:20 | 609 | 610 |
| 2026-01-01 01:30 | 610 | 611 |
| 2026-01-01 01:40 | 611 | 612 |
| 2026-01-01 01:50 | 612 | 613 |
| 2026-01-01 02:00 | 613 | 614 |
| 2026-01-01 02:10 | 614 | 615 |
| 2026-01-01 02:20 | 615 | 616 |
| 2026-01-01 02:30 | 616 | 617 |
| 2026-01-01 02:40 | 617 | 618 |
| 2026-01-01 02:50 | 618 | 619 |
| 2026-01-01 03:00 | 619 | / |
如上,“未来 1 个时间单位数值”,实际上就是 “当前状态” 整体上移一行,对应 python 函数 shift(-1)。(具体代码和合并细节略)
时间戳或时序序号 一般是不能作为特征的,去掉;最后一行不可能有 label y,实际上无法使用。
| 对应数据 = 当前状态 = 特征 = x | 未来 1 个时间单位数值 = shift(-1) = label = y |
|---|---|
| 对应数据 | 未来 1 个时间单位 |
| 601 | 602 |
| 602 | 603 |
| 603 | 604 |
| 604 | 605 |
| 605 | 606 |
| 606 | 607 |
| 607 | 608 |
| 608 | 609 |
| 609 | 610 |
| 610 | 611 |
| 611 | 612 |
| 612 | 613 |
| 613 | 614 |
| 614 | 615 |
| 615 | 616 |
| 616 | 617 |
| 617 | 618 |
| 618 | 619 |
| 619 | / |
历史的馈赠 #
从“当前状态 x”,预测 “未来 1 个时间单位数值 label y”,目前的数据自变量“特征”太单薄,怎么增加特征呢?既然时间已经到了某个时间点,那么此时间点之前的数据当然也可以用!先来增加一个特征:“之前 1 个时间单位的数值”。
| 之前 1 个时间单位数值 (历史状态) = shift(1) = 新特征 $x_2$ | 当前 (不含历史) 状态 = 特征 $x_1$ | 未来 1 个时间单位数值 = shift(-1) = label = $y$ |
|---|---|---|
| / | 602 | 603 |
| 602 | 603 | 604 |
| 603 | 604 | 605 |
| 604 | 605 | 606 |
| 605 | 606 | 607 |
| 606 | 607 | 608 |
| 607 | 608 | 609 |
| 608 | 609 | 610 |
| 609 | 610 | 611 |
| 610 | 611 | 612 |
| 611 | 612 | 613 |
| 612 | 613 | 614 |
| 613 | 614 | 615 |
| 614 | 615 | 616 |
| 615 | 616 | 617 |
| 616 | 617 | 618 |
| 617 | 618 | 619 |
| 618 | 619 | / |
如上,“之前 1 个时间单位数值”,操作上就是 “当前状态” 整体下移一行,对应 python 函数 shift(1)。
其实,以此类推,可以增加更多“之前的数值”来丰富“特征”:
| 特征 $x_4$ = shift(3) | 特征 $x_3$ = shift(2) | 特征 $x_2$ = shift(1) | 特征 $x_1$ | label_target = $y$ = shift(-1) |
|---|---|---|---|---|
| / | / | / | 602 | 603 |
| / | / | 602 | 603 | 604 |
| / | 602 | 603 | 604 | 605 |
| 602 | 603 | 604 | 605 | 606 |
| 603 | 604 | 605 | 606 | 607 |
| 604 | 605 | 606 | 607 | 608 |
| 605 | 606 | 607 | 608 | 609 |
| 606 | 607 | 608 | 609 | 610 |
| 607 | 608 | 609 | 610 | 611 |
| 608 | 609 | 610 | 611 | 612 |
| 609 | 610 | 611 | 612 | 613 |
| 610 | 611 | 612 | 613 | 614 |
| 611 | 612 | 613 | 614 | 615 |
| 612 | 613 | 614 | 615 | 616 |
| 613 | 614 | 615 | 616 | 617 |
| 614 | 615 | 616 | 617 | 618 |
| 615 | 616 | 617 | 618 | 619 |
| 616 | 617 | 618 | 619 | / |
对于当前时间,我们一般记为 t0,向历史要数据,后退(时滞) 1 个时间单位,记为 t-1,又由于编程时变量命名有限制,因此实践中为了方便使用列名,表格可以写为:
| t_3 | t_2 | t_1 | t0 | label_target_t1 |
|---|---|---|---|---|
| / | / | / | 602 | 603 |
| / | / | 602 | 603 | 604 |
| / | 602 | 603 | 604 | 605 |
| 602 | 603 | 604 | 605 | 606 |
| 603 | 604 | 605 | 606 | 607 |
| 604 | 605 | 606 | 607 | 608 |
| 605 | 606 | 607 | 608 | 609 |
| 606 | 607 | 608 | 609 | 610 |
| 607 | 608 | 609 | 610 | 611 |
| 608 | 609 | 610 | 611 | 612 |
| 609 | 610 | 611 | 612 | 613 |
| 610 | 611 | 612 | 613 | 614 |
| 611 | 612 | 613 | 614 | 615 |
| 612 | 613 | 614 | 615 | 616 |
| 613 | 614 | 615 | 616 | 617 |
| 614 | 615 | 616 | 617 | 618 |
| 615 | 616 | 617 | 618 | 619 |
| 616 | 617 | 618 | 619 | / |
其中,下划线代替减号,t1 是 t+1 省略了加号。当然,还可以继续添加更多的时滞数据。
一般的,features = [t_3, t_2, t_1, t0]; label_target = [label_target_t1]。
最后的整理 #
对于现在上面这张表,由于:1. 最后一行没有 target,没用; 2. 第一到第三行含有缺失值,大多数算法不能处理缺失值;因此删除删除这几行,变为:
| t_3 | t_2 | t_1 | t0 | label_target_t1 |
|---|---|---|---|---|
| 602 | 603 | 604 | 605 | 606 |
| 603 | 604 | 605 | 606 | 607 |
| 604 | 605 | 606 | 607 | 608 |
| 605 | 606 | 607 | 608 | 609 |
| 606 | 607 | 608 | 609 | 610 |
| 607 | 608 | 609 | 610 | 611 |
| 608 | 609 | 610 | 611 | 612 |
| 609 | 610 | 611 | 612 | 613 |
| 610 | 611 | 612 | 613 | 614 |
| 611 | 612 | 613 | 614 | 615 |
| 612 | 613 | 614 | 615 | 616 |
| 613 | 614 | 615 | 616 | 617 |
| 614 | 615 | 616 | 617 | 618 |
| 615 | 616 | 617 | 618 | 619 |
范式:把新的问题变为已知的问题 #
现在,时序问题已经转化为已知的普通的机器学习问题,可以使用普通的机器学习算法处理了。
ps1. shift 函数的参数正负号正好与数学的 t±n 相反,注意别写错。
ps2. 术语注意:features 表示了 “当前状态 state”,时序处理中,“当前状态 state” 包括了时滞产生的数据,也就是 当前状态 = 当前 (含时滞) 状态