处理不规则时间序列
作者:Noah Crowley / 产品, 用例, 开发者
2018 年 10 月 26 日
导航至
InfluxDB 的优势之一是能够存储原始事件,这些事件可能以不同的时间间隔到达,形成不规则时间序列。然而,不规则时间序列带来了一些独特的挑战,在某些情况下,对数据的常见操作根本无法工作。幸运的是,InfluxDB 允许您通过计算任意时间窗口内各个值的聚合,将不规则时间序列动态转换为规则时间序列。这为您在捕获系统事件和处理该数据时提供了两全其美的优势。
我们可以查看一些实际数据点,以便更好地理解在处理不规则时间序列时需要考虑哪些因素。为了举例说明,我们将使用五个数据点,并赋予它们 10、20、30、40 和 50 的值。
如果这些数据以规则的时间间隔收集,那么计算值的平均值也将为您提供随时间推移的平均功率使用量。
但是,如果您的数据以不规则的时间间隔收集,那么计算值的平均值将不会给您预期的结果。
将这些数据点添加到 InfluxDB 并进行操作将使我们更好地了解正在发生的事情。使用 Influx CLI,我将这五个值插入到一个名为 m1
的 measurement 中
> select * from m1 where time > '2018-08-14T17:22:14Z' and time < '2018-08-14T17:23:20Z'
name: m1
time value
---- -----
2018-08-14T17:22:14.159637Z 10
2018-08-14T17:22:16.3561521Z 20
2018-08-14T17:22:18.2251241Z 30
2018-08-14T17:22:20.18086Z 40
2018-08-14T17:23:19.8976057Z 50
由于我是手动插入数据,因此这些值的时间戳不是均匀分布的,这正是我们在这个示例中所需要的。前四个发生在同一分钟内,而第五个发生在将近一分钟后。我们可以计算这五个值的平均值
> select mean(*) From m1
name: m1
time mean_value
---- ----------
1970-01-01T00:00:00Z 30
我们得到的结果是 30,结果中的时间戳值是我们计算平均值的时间窗口的开始。
如果这是一个规则时间序列,那么平均值 30 在直觉上是有意义的。但是,由于我们的示例是不规则时间序列,因此在计算最终结果时,测量之间的时间量非常重要。当可视化这些值时,这一点变得更加清晰;以下是这些点在图表上的外观
我们可以看到,大部分时间都花费在 40 和 50 之间(我们在这些点之间进行线性插值以绘制图表)。然后,我们可以合理地猜测,随时间推移的平均值可能更接近 40 而不是 30。
显然,直接计算不规则序列中所有值的平均值是行不通的,因为该操作忽略了值在时间上的分布。我们需要对我们的数据施加某种规律性。
为此,我们可以使用 InfluxQL 的 GROUP BY
子句将数据的时间范围分解为离散单元,然后在这些窗口内聚合数据的值。并非每个间隔都有值,而那些有值的间隔可能不止一个值。作为系统的用户和操作员,我们需要决定如何处理这些情况。与处理数据时经常出现的情况一样,人类的判断和解释起着重要的作用。
如果我们采用足够小的窗口,并使用 mean()
函数计算聚合,我们应该能够相当准确地表示我们的时间序列。以下是我们将点分组为十秒间隔并计算每个组的平均值后,数据的外观
> select mean(*) from m1 where time > '2018-08-14T17:22:14Z' and time < '2018-08-14T17:23:20Z' group by time(10s)
name: m1
time mean_value
---- ----------
2018-08-14T17:22:10Z 20
2018-08-14T17:22:20Z 40
2018-08-14T17:22:30Z
2018-08-14T17:22:40Z
2018-08-14T17:22:50Z
2018-08-14T17:23:00Z
2018-08-14T17:23:10Z 50
对于 2018-08-14T17:22:10Z 开始的 10 秒周期,我们有三个值 10、20 和 30,它们的平均值为 20。对于下一个窗口,我们有一个值,然后我们看到在看到最终值 50 之前,有许多窗口没有值。与之前一样,我们将不得不对如何处理这些空窗口做出判断。
GROUP BY
的 fill()
选项将允许我们为任何这些空窗口填充数据。fill()
可以采用选项 null
、previous
、number
、none
或 linear
。在我们的例子中,linear
是正确的选择,因为我们使用线性插值绘制了这些值,但如果这是真实数据,则其他一些选项可能更合适。
以下是使用 fill(linear)
后数据的外观
> select mean(*) from m1 where time > '2018-08-14T17:22:14Z' and time < '2018-08-14T17:23:20Z' group by time(10s) fill(linear)
name: m1
time mean_value
---- ----------
2018-08-14T17:22:10Z 20
2018-08-14T17:22:20Z 40
2018-08-14T17:22:30Z 42
2018-08-14T17:22:40Z 44
2018-08-14T17:22:50Z 46
2018-08-14T17:23:00Z 48
2018-08-14T17:23:10Z 50
然后,我们可以使用 子查询 计算新的规则时间序列的平均值
> select mean(*) from (select mean(*) from m1 where time > '2018-08-14T17:22:14Z' and time < '2018-08-14T17:23:20Z' group by time(10s) fill(linear))
name: m1
time mean_mean_value
---- ---------------
1970-01-01T00:00:00Z 41.42857142857143
但是,还有另一个注意事项:我们选择的窗口将对我们的最终结果产生影响。如果我们使用 1 秒的窗口而不是 10 秒的窗口会怎么样?
> select mean(*) from (select mean(*) from m1 where time > '2018-08-14T17:22:14Z' and time < '2018-08-14T17:23:20Z' group by time(1s) fill(linear))
name: m1
time mean_mean_value
---- ---------------
1970-01-01T00:00:00Z 42.95454545454546
我们得到了略有不同的结果!处理不规则时间序列并不精确,您的方法会因所讨论的数据而异。不同的聚合方法、间隔大小和插值方法将比其他方法更合适。
如果我们上面的示例中的数据是某种环境数据,可能是环境温度,并且我们序列的不规则性是由于不可靠的数据收集造成的,那么我们使用的方法可能是合适的。
如果数据是在每次状态更改时收集的,也许我们有许多 LED,并且我们以十个为一组地打开它们,那么我们在查询中执行的线性填充不再有意义;相反,基于我们的数据旨在建模的系统的行为,使用 fill(previous)
会更合适。
最终,这是处理不规则时间序列的最大挑战:您需要充分了解您的数据,以便您可以对如何处理该数据做出明智的决策。
如果您对不规则时间序列有疑问,我们的 社区网站 是从 InfluxData 平台的其他用户那里获得帮助的好地方,或者随时通过 Twitter @noahcrowley 直接与我联系!