TimescaleDB は、IoT 機器のセンサーやスマートメーターが生成するデータのように、時系列に沿って発生するデータを扱うのに適した時系列データベースの機能を、リレーショナルデータベース PostgreSQL に追加する拡張モジュールです。
この記事では、TimescaleDB の備える時系列データ向けの分析機能について紹介します。TimescaleDB の概要やインストール、基本的な使い方については前回の TimescaleDB の紹介 を参照してください。
時系列データの分析関数
TimescaleDB には時系列データの分析に便利な関数がいくつかあります。ここでは、前回生成したデータを使って各関数の概要と使い方について紹介していきます。それには、まず、test データベースに接続しておきます。
$ psql test psql (13.2) "help"でヘルプを表示します。 =#
time_bucket 関数
time_bucket 関数は時間を任意の間隔で丸める関数です。同じような関数は PostgreSQL にもあって date_trunc 関数と言います。
date_trunc 関数を使うと、例えば、1 分間隔で収集した温度のデータに対し、1 時間ごとの平均を求められます。
=# SELECT date_trunc('hour', time) AS time_1h,
location,
avg(temperature)
FROM conditions
GROUP BY time_1h, location
ORDER BY time_1h, location;
time_1h | location | avg
---------------------+----------+--------------------
2020-01-30 00:00:00 | nagoya | 19.883333333333333
2020-01-30 00:00:00 | osaka | 20.68333333333333
2020-01-30 00:00:00 | tokyo | 20.73
2020-01-30 01:00:00 | nagoya | 19.65666666666666
2020-01-30 01:00:00 | osaka | 20.82
2020-01-30 01:00:00 | tokyo | 20.04000000000001
2020-01-30 02:00:00 | nagoya | 18.854999999999993
2020-01-30 02:00:00 | osaka | 19.89833333333333
2020-01-30 02:00:00 | tokyo | 20.345000000000006
2020-01-30 03:00:00 | nagoya | 20.338333333333335
2020-01-30 03:00:00 | osaka | 20.395
2020-01-30 03:00:00 | tokyo | 20.781666666666673
(省略)
同じことは time_bucket 関数でもできます。
=# SELECT time_bucket('1 hour', time) AS time_1h,
location,
avg(temperature)
FROM conditions
GROUP BY time_1h, location
ORDER BY time_1h, location;
time_1h | location | avg
---------------------+----------+--------------------
2020-01-30 00:00:00 | nagoya | 19.883333333333333
2020-01-30 00:00:00 | osaka | 20.68333333333333
2020-01-30 00:00:00 | tokyo | 20.73
2020-01-30 01:00:00 | nagoya | 19.65666666666666
2020-01-30 01:00:00 | osaka | 20.82
2020-01-30 01:00:00 | tokyo | 20.04000000000001
2020-01-30 02:00:00 | nagoya | 18.854999999999993
2020-01-30 02:00:00 | osaka | 19.89833333333333
2020-01-30 02:00:00 | tokyo | 20.345000000000006
2020-01-30 03:00:00 | nagoya | 20.338333333333335
2020-01-30 03:00:00 | osaka | 20.395
2020-01-30 03:00:00 | tokyo | 20.781666666666673
(省略)
date_trunc 関数と time_bucket 関数の違いは、date_trunc 関数が 1 時間や 1 日など、固定の時間でしか丸められないのに対し、time_bucket 関数は 3 時間や 5 日など、任意の時間で丸められることです。time_bucket 関数を使うと、例えば、3 時間ごとの平均を求められます。
=# SELECT time_bucket('3 hours', time) AS time_3h,
location,
avg(temperature)
FROM conditions
GROUP BY time_3h, location
ORDER BY time_3h, location;
time_3h | location | avg
---------------------+----------+--------------------
2020-01-30 00:00:00 | nagoya | 19.465
2020-01-30 00:00:00 | osaka | 20.46722222222223
2020-01-30 00:00:00 | tokyo | 20.371666666666673
2020-01-30 03:00:00 | nagoya | 20.202777777777776
2020-01-30 03:00:00 | osaka | 19.89277777777778
2020-01-30 03:00:00 | tokyo | 21.122222222222224
2020-01-30 06:00:00 | nagoya | 19.99055555555557
2020-01-30 06:00:00 | osaka | 19.288333333333338
2020-01-30 06:00:00 | tokyo | 19.353333333333335
2020-01-30 09:00:00 | nagoya | 20.950000000000003
2020-01-30 09:00:00 | osaka | 20.285000000000007
2020-01-30 09:00:00 | tokyo | 20.203333333333326
(省略)
time_bucket_gapfill 関数
time_bucket_gapfill 関数は time_bucket 関数と同じく時間を任意の間隔で丸める関数ですが、欠落したデータを補ってくれることが異なります。
例えば、2020 年 2 月 1 日、東京のデータを削除し、人為的にデータを欠落させ、time_bucket 関数を使って 3 時間ごとの平均を求めると、6 時、9 時のデータが欠落しているのが分かります。
=# DELETE FROM conditions
WHERE time >= '2020-02-01 06:00:00'
AND time < '2020-02-01 12:00:00' AND location = 'tokyo';
DELETE 360
=# SELECT time_bucket('3 hours', time) AS time_3h, location, avg(temperature) FROM conditions WHERE time >= '2020-02-01'
AND time < '2020-02-02'
GROUP BY time_3h, location
ORDER BY time_3h, location;
time_3h | location | avg
---------------------+----------+--------------------
2020-02-01 00:00:00 | nagoya | 19.75833333333333
2020-02-01 00:00:00 | osaka | 20.46555555555556
2020-02-01 00:00:00 | tokyo | 19.315555555555555
2020-02-01 03:00:00 | nagoya | 19.328888888888887
2020-02-01 03:00:00 | osaka | 20.167777777777783
2020-02-01 03:00:00 | tokyo | 19.592777777777773
2020-02-01 06:00:00 | nagoya | 19.87055555555555
2020-02-01 06:00:00 | osaka | 20.07277777777777
2020-02-01 09:00:00 | nagoya | 19.240555555555563
2020-02-01 09:00:00 | osaka | 19.915
2020-02-01 12:00:00 | nagoya | 20.018888888888895
2020-02-01 12:00:00 | osaka | 19.66388888888888
2020-02-01 12:00:00 | tokyo | 19.437777777777782
2020-02-01 15:00:00 | nagoya | 19.904444444444444
2020-02-01 15:00:00 | osaka | 20.11388888888888
2020-02-01 15:00:00 | tokyo | 19.707222222222217
2020-02-01 18:00:00 | nagoya | 20.251111111111115
2020-02-01 18:00:00 | osaka | 20.023888888888884
2020-02-01 18:00:00 | tokyo | 20.571666666666676
2020-02-01 21:00:00 | nagoya | 19.91722222222222
2020-02-01 21:00:00 | osaka | 20.952777777777772
2020-02-01 21:00:00 | tokyo | 19.904444444444447
(22 行)

それに対し、time_bucket_gapfill 関数を使うと、欠落したデータに NULL が補われるようになります。
=# SELECT time_bucket_gapfill('3 hours', time) AS time_3h,
location,
avg(temperature)
FROM conditions
WHERE time >= '2020-02-01'
AND time < '2020-02-02'
GROUP BY time_3h, location
ORDER BY time_3h, location;
time_3h | location | avg
---------------------+----------+--------------------
2020-02-01 00:00:00 | nagoya | 19.75833333333333
2020-02-01 00:00:00 | osaka | 20.46555555555556
2020-02-01 00:00:00 | tokyo | 19.315555555555555
2020-02-01 03:00:00 | nagoya | 19.328888888888887
2020-02-01 03:00:00 | osaka | 20.167777777777783
2020-02-01 03:00:00 | tokyo | 19.592777777777773
2020-02-01 06:00:00 | nagoya | 19.87055555555555
2020-02-01 06:00:00 | osaka | 20.07277777777777
2020-02-01 06:00:00 | tokyo |
2020-02-01 09:00:00 | nagoya | 19.240555555555563
2020-02-01 09:00:00 | osaka | 19.915
2020-02-01 09:00:00 | tokyo |
2020-02-01 12:00:00 | nagoya | 20.018888888888895
2020-02-01 12:00:00 | osaka | 19.66388888888888
2020-02-01 12:00:00 | tokyo | 19.437777777777782
2020-02-01 15:00:00 | nagoya | 19.904444444444444
2020-02-01 15:00:00 | osaka | 20.11388888888888
2020-02-01 15:00:00 | tokyo | 19.707222222222217
2020-02-01 18:00:00 | nagoya | 20.251111111111115
2020-02-01 18:00:00 | osaka | 20.023888888888884
2020-02-01 18:00:00 | tokyo | 20.571666666666676
2020-02-01 21:00:00 | nagoya | 19.91722222222222
2020-02-01 21:00:00 | osaka | 20.952777777777772
2020-02-01 21:00:00 | tokyo | 19.904444444444447
(24 行)
time_bucket_gapfill 関数を使う場合には、どこからどこまでデータを補うかを指定するため、時間の範囲を WHERE 句か start、finish 引数で指定する必要があります。指定しないとエラーになります。
(以下、例示のため、実行不要)
=# SELECT time_bucket_gapfill('3 hours', time) AS time_3h,
location,
avg(temperature)
FROM conditions
GROUP BY time_3h, location
ORDER BY time_3h, location;
ERROR: missing time_bucket_gapfill argument: could not infer start from WHERE clause
HINT: Specify start and finish as arguments or in the WHERE clause.
NULL の代わりに値を補うこともできます。それには、locf または interpolate 関数を使います。
locf (last observation carried forward; 最後に観測された値を繰り越す) 関数を使うと、直前の値がそのまま補われます。ここでは、6 時、9 時には直前の 3 時と同じ 19.592度 (以下、小数点第 4 位以下切り捨て) が補われます。
=# SELECT time_bucket_gapfill('3 hours', time) AS time_3h,
location,
locf(avg(temperature))
FROM conditions
WHERE time >= '2020-02-01'
AND time < '2020-02-02'
GROUP BY time_3h, location
ORDER BY time_3h, location;
time_3h | location | locf
---------------------+----------+--------------------
2020-02-01 00:00:00 | nagoya | 19.75833333333333
2020-02-01 00:00:00 | osaka | 20.46555555555556
2020-02-01 00:00:00 | tokyo | 19.315555555555555
2020-02-01 03:00:00 | nagoya | 19.328888888888887
2020-02-01 03:00:00 | osaka | 20.167777777777783
2020-02-01 03:00:00 | tokyo | 19.592777777777773
2020-02-01 06:00:00 | nagoya | 19.87055555555555
2020-02-01 06:00:00 | osaka | 20.07277777777777
2020-02-01 06:00:00 | tokyo | 19.592777777777773
2020-02-01 09:00:00 | nagoya | 19.240555555555563
2020-02-01 09:00:00 | osaka | 19.915
2020-02-01 09:00:00 | tokyo | 19.592777777777773
2020-02-01 12:00:00 | nagoya | 20.018888888888895
2020-02-01 12:00:00 | osaka | 19.66388888888888
2020-02-01 12:00:00 | tokyo | 19.437777777777782
2020-02-01 15:00:00 | nagoya | 19.904444444444444
2020-02-01 15:00:00 | osaka | 20.11388888888888
2020-02-01 15:00:00 | tokyo | 19.707222222222217
2020-02-01 18:00:00 | nagoya | 20.251111111111115
2020-02-01 18:00:00 | osaka | 20.023888888888884
2020-02-01 18:00:00 | tokyo | 20.571666666666676
2020-02-01 21:00:00 | nagoya | 19.91722222222222
2020-02-01 21:00:00 | osaka | 20.952777777777772
2020-02-01 21:00:00 | tokyo | 19.904444444444447
(24 行)

interpolate 関数を使うと、直前と直後の値をもとに線形補間によって値が補われます。ここでは、直前の 3 時は 19.592 度、直後の 12 時は19.437 度で、6 時は線形補間によって19.592 + (19.437 – 19.592) / 3 = 19.541 度、9 時も同じように 19.592 + (19.437 – 19.592) × 2 / 3 = 19.489 度が補われます。
=# SELECT time_bucket_gapfill('3 hours', time) AS time_3h,
location,
interpolate(avg(temperature))
FROM conditions
WHERE time >= '2020-02-01'
AND time < '2020-02-02'
GROUP BY time_3h, location
ORDER BY time_3h, location;
time_3h | location | interpolate
---------------------+----------+--------------------
2020-02-01 00:00:00 | nagoya | 19.75833333333333
2020-02-01 00:00:00 | osaka | 20.46555555555556
2020-02-01 00:00:00 | tokyo | 19.315555555555555
2020-02-01 03:00:00 | nagoya | 19.328888888888887
2020-02-01 03:00:00 | osaka | 20.167777777777783
2020-02-01 03:00:00 | tokyo | 19.592777777777773
2020-02-01 06:00:00 | nagoya | 19.87055555555555
2020-02-01 06:00:00 | osaka | 20.07277777777777
2020-02-01 06:00:00 | tokyo | 19.54111111111111
2020-02-01 09:00:00 | nagoya | 19.240555555555563
2020-02-01 09:00:00 | osaka | 19.915
2020-02-01 09:00:00 | tokyo | 19.489444444444445
2020-02-01 12:00:00 | nagoya | 20.018888888888895
2020-02-01 12:00:00 | osaka | 19.66388888888888
2020-02-01 12:00:00 | tokyo | 19.437777777777782
2020-02-01 15:00:00 | nagoya | 19.904444444444444
2020-02-01 15:00:00 | osaka | 20.11388888888888
2020-02-01 15:00:00 | tokyo | 19.707222222222217
2020-02-01 18:00:00 | nagoya | 20.251111111111115
2020-02-01 18:00:00 | osaka | 20.023888888888884
2020-02-01 18:00:00 | tokyo | 20.571666666666676
2020-02-01 21:00:00 | nagoya | 19.91722222222222
2020-02-01 21:00:00 | osaka | 20.952777777777772
2020-02-01 21:00:00 | tokyo | 19.904444444444447
(24 行)

first、last 関数
first、last 関数は別の列をキーに並び替え、最初、最後の値を取得する集約関数です。時間ごとに平均などでなく、素の値を抜き出すのに使います。並び替えにインデックスは使えないので、実行に時間がかかる場合があります。
例えば、2020 年 3 月 1 日、東京の温度のデータを 3 時間ごとに最初の値を取得してみます。
=# SELECT time_bucket('3 hours', time) AS time_3h,
first(temperature, time)
FROM conditions
WHERE time >= '2020-03-01'
AND time < '2020-03-02'
AND location = 'tokyo'
GROUP BY time_3h
ORDER BY time_3h;
time_3h | first
---------------------+-------
2020-03-01 00:00:00 | 24.4
2020-03-01 03:00:00 | 18.9
2020-03-01 06:00:00 | 25.1
2020-03-01 09:00:00 | 22.8
2020-03-01 12:00:00 | 25.8
2020-03-01 15:00:00 | 23.1
2020-03-01 18:00:00 | 11.6
2020-03-01 21:00:00 | 21.3
(8 行)
histogram 関数
histogram 関数はヒストグラムを取得する集約関数です。データの度数分布を確認するのに使います。
例えば、2020 年 3 月 1 日、東京の温度のデータを 3 時間ごとに 12.5 度から 27.5 度まで 2.5 度刻みで 6 つに区切ったヒストグラムを取得してみます。
=# SELECT time_bucket('3 hours', time) AS time_3h,
histogram(temperature, 12.5, 27.5, 5)
FROM conditions
WHERE time >= '2020-03-01'
AND time < '2020-03-02'
AND location = 'tokyo'
GROUP BY time_3h
ORDER BY time_3h;
time_3h | histogram
---------------------+---------------------------
2020-03-01 00:00:00 | {23,16,19,21,29,19,27,26}
2020-03-01 03:00:00 | {21,17,21,25,25,21,29,21}
2020-03-01 06:00:00 | {20,29,14,25,22,24,27,19}
2020-03-01 09:00:00 | {17,24,19,27,18,28,28,19}
2020-03-01 12:00:00 | {20,17,25,22,31,22,21,22}
2020-03-01 15:00:00 | {28,23,17,29,25,18,18,22}
2020-03-01 18:00:00 | {23,14,27,18,30,22,25,21}
2020-03-01 21:00:00 | {20,20,20,32,19,27,25,17}
(8 行)
ヒストグラムは波カッコ {} で囲まれ、カンマ , で区切られた配列として出力されます。配列の各要素が区間内の度数を表します。ここでは、最初の要素が 12.5 度未満、次が 12.5 度以上、15 度未満、その次が 15 度以上、17.5 度未満と続いていき、最後が 27.5 超過のデータの度数になります。
approximate_row_count 関数
approximate_row_count 関数は統計情報を使ってテーブルのおおよその行数を取得する関数です。結果が正確でない代わりに、count 関数に比べて高速に動作します。
\timing コマンドで実行時間を比べると、approximate_row_count 関数が高速なのが分かります。
=# \timing
タイミングは on です。
=# SELECT count(*) FROM conditions;
count
---------
1455480
(1 行)
時間: 246.751 ミリ秒
=# SELECT approximate_row_count('conditions');
approximate_row_count
-----------------------
1455840
(1 行)
時間: 4.144 ミリ秒
おわりに
今回は、TimescaleDB の分析関数として、時間を任意の間隔で丸める time_bucket 関数や、別の列をキーに並び替え、最初、最後の値を取得する first、last 関数を取り上げ、関数の概要や使い方について紹介しました。関数については TimescaleDB Documentation の API Reference もあわせて参照してください。
次回は、時系列データの自動集計や削除、圧縮の機能について紹介する予定です。