2025 年度 OSS リテラシ 3 : 代表値・分散 + 移動平均

はじめに

本資料では,データ処理の基礎として,代表値,分散,移動平均を取り扱う.

代表値・分散

集団の中心的傾向を示す値を「代表値」という. 代表値としては、一般に平均値が使われるが,分布の形によっては 最頻値や中央値を代表値にする場合もある.

一方で,データ集団は代表値だけではデータ全体の様子をとらえる事ができない. データの散らばり具合にも着目しなければならず, そのために最大値・最小値・偏差・度数分布などが用いられる.

この演習では,センサーのデータから代表値およびデータの散らばり具合を示す指標を可視化することを目指す.

移動平均

時系列データを扱う場合,代表値は「移動平均」することが一般的である. 例えば,1 時間平均と言った時には,9:00~10:00,10:00~11:00,11:00~12:00,と いった具合に,平均をとる時間をずらしながら計算を行う.

日々のニュースで流れる気象データは,まさに移動平均している. 詳細は気象観測統計の解説 を参照のこと.

この演習では,SQL:2003 で規定された「ウィンドウ関数」を用いて移動平均を行うことにする.

SQL ウィンドウ関数の利用方法

具体的な SQL 文は次節で挙げるとして,まずは平均を取る部分に 平均を取る範囲の指定の仕方としては,「行数 (rows) を指定」する形となる.以下のように,

avg(temp) over (order by time rows between 2 preceding and current row)

と書けば,2 つ前の行 (2 preceding) から現在の行 (current row) まで (between) の温度の平均 (avg(temp)) を取ることができる.

また,以下のように,

avg(temp) over (order by time rows between current row and 2 following)

と書けば,現在の行 (current row) から2 つ後ろの行 (2 following) まで (between) の温度の平均 (avg(temp)) を取ることができる.

ウィンドウ関数を用いる場合は,「時間」ではなく「行数」で平均の対象となる範囲を指定するため, 時系列データを扱う場合は前処理として欠損値処理が必須である.

SQL ウィンドウ関数の利用の例

以下のようにデータが入っているものとする.この例では 21:00 と 22:00 のデータが欠損している.

MariaDB [iotex]> select name, time, temp from j4oss3 where name like 'hoge';

  +------+---------------------+------+
  | name | time                | temp |
  +------+---------------------+------+
  | hoge | 2024-11-08 00:00:00 |   10 |
  | hoge | 2024-11-08 01:00:00 |   11 |
  | hoge | 2024-11-08 02:00:00 |   12 |
  | hoge | 2024-11-08 03:00:00 |   13 |
  | hoge | 2024-11-08 04:00:00 |   14 |
  | hoge | 2024-11-08 05:00:00 |   15 |
  | hoge | 2024-11-08 06:00:00 |   16 |
  | hoge | 2024-11-08 07:00:00 |   17 |
  | hoge | 2024-11-08 08:00:00 |   18 |
  | hoge | 2024-11-08 09:00:00 |   19 |
  | hoge | 2024-11-08 10:00:00 |   20 |
  | hoge | 2024-11-08 11:00:00 |   21 |
  | hoge | 2024-11-08 12:00:00 |   22 |
  | hoge | 2024-11-08 13:00:00 |   23 |
  | hoge | 2024-11-08 14:00:00 |   24 |
  | hoge | 2024-11-08 15:00:00 |   25 |
  | hoge | 2024-11-08 16:00:00 |   26 |
  | hoge | 2024-11-08 17:00:00 |   27 |
  | hoge | 2024-11-08 18:00:00 |   28 |
  | hoge | 2024-11-08 19:00:00 |   29 |
  | hoge | 2024-11-08 20:00:00 |   30 |
  | hoge | 2024-11-08 23:00:00 |   33 | <-- 20:00 の次が 23:00 !
  +------+---------------------+------+
  22 rows in set (0.002 sec)

自分自身と前 2 点 (rows between 2 preceding and current row) を用いて平均を作ってみると 以下のような結果となる.欠損値があるために,平均するのに使う時間幅が 揃っていないことが分かる.具体的には, 時刻 23:00:00 に対して求めた平均値は,19:00:00 と 20:00:00 の 値を使っている. 計測という意味では,他の時刻と同様,23:00:00 の値は 21:00:00 ~ 23:00:00 の値の 平均値になっているべきである.

MariaDB [iotex]> select name, time, temp, avg(temp) over (order by time rows between 2 preceding and current row) as moving_avg from j4oss3 where name like 'hoge';

  +------+---------------------+------+--------------------+
  | name | time                | temp | moving_avg         |
  +------+---------------------+------+--------------------+
  | hoge | 2024-11-08 00:00:00 |   10 |                 10 | <-- 10 / 1 (前に値が無いので)
  | hoge | 2024-11-08 01:00:00 |   11 |               10.5 | <-- ( 10 + 11 ) / 2
  | hoge | 2024-11-08 02:00:00 |   12 |                 11 | <-- ( 10 + 11 + 12 ) / 3
  | hoge | 2024-11-08 03:00:00 |   13 |                 12 |
  | hoge | 2024-11-08 04:00:00 |   14 |                 13 |
  | hoge | 2024-11-08 05:00:00 |   15 |                 14 |
  | hoge | 2024-11-08 06:00:00 |   16 |                 15 |
  | hoge | 2024-11-08 07:00:00 |   17 |                 16 |
  | hoge | 2024-11-08 08:00:00 |   18 |                 17 |
  | hoge | 2024-11-08 09:00:00 |   19 |                 18 |
  | hoge | 2024-11-08 10:00:00 |   20 |                 19 |
  | hoge | 2024-11-08 11:00:00 |   21 |                 20 |
  | hoge | 2024-11-08 12:00:00 |   22 |                 21 |
  | hoge | 2024-11-08 13:00:00 |   23 |                 22 |
  | hoge | 2024-11-08 14:00:00 |   24 |                 23 |
  | hoge | 2024-11-08 15:00:00 |   25 |                 24 |
  | hoge | 2024-11-08 16:00:00 |   26 |                 25 |
  | hoge | 2024-11-08 17:00:00 |   27 |                 26 |
  | hoge | 2024-11-08 18:00:00 |   28 |                 27 |
  | hoge | 2024-11-08 19:00:00 |   29 |                 28 | <-- ( 27 + 28 + 29 ) / 3
  | hoge | 2024-11-08 20:00:00 |   30 |                 29 | <-- ( 28 + 29 + 30 ) / 3
  | hoge | 2024-11-08 23:00:00 |   33 | 30.666666666666668 | <-- ( 29 + 30 + 33 ) / 3
  +------+---------------------+------+--------------------+
  22 rows in set (0.001 sec)

自分自身と後ろ 2 点 (rows between current row and 2 following) を用いて平均を作ってみると 以下のような結果となる.欠損値があるために,前の例と同様に,平均するのに使う時間幅が 揃っていないことが分かる.

MariaDB [iotex]> select name, time, temp, avg(temp) over (order by time rows between current row and 2 following) as moving_avg from j4oss3 where name like 'hoge';

+------+---------------------+------+--------------------+
| name | time                | temp | moving_avg         |
+------+---------------------+------+--------------------+
| hoge | 2024-11-08 00:00:00 |   10 |                 11 | <-- ( 10 + 11 + 12 ) / 3
| hoge | 2024-11-08 01:00:00 |   11 |                 12 | <-- ( 11 + 12 + 13 ) / 3
| hoge | 2024-11-08 02:00:00 |   12 |                 13 |
| hoge | 2024-11-08 03:00:00 |   13 |                 14 |
| hoge | 2024-11-08 04:00:00 |   14 |                 15 |
| hoge | 2024-11-08 05:00:00 |   15 |                 16 |
| hoge | 2024-11-08 06:00:00 |   16 |                 17 |
| hoge | 2024-11-08 07:00:00 |   17 |                 18 |
| hoge | 2024-11-08 08:00:00 |   18 |                 19 |
| hoge | 2024-11-08 09:00:00 |   19 |                 20 |
| hoge | 2024-11-08 10:00:00 |   20 |                 21 |
| hoge | 2024-11-08 11:00:00 |   21 |                 22 |
| hoge | 2024-11-08 12:00:00 |   22 |                 23 |
| hoge | 2024-11-08 13:00:00 |   23 |                 24 |
| hoge | 2024-11-08 14:00:00 |   24 |                 25 |
| hoge | 2024-11-08 15:00:00 |   25 |                 26 |
| hoge | 2024-11-08 16:00:00 |   26 |                 27 |
| hoge | 2024-11-08 17:00:00 |   27 |                 28 |
| hoge | 2024-11-08 18:00:00 |   28 |                 29 | <-- ( 28 + 29 + 30 ) / 3
| hoge | 2024-11-08 19:00:00 |   29 | 30.666666666666668 | <-- ( 29 + 30 + 33 ) / 3
| hoge | 2024-11-08 20:00:00 |   30 |               31.5 | <-- ( 30 + 33 ) / 2
| hoge | 2024-11-08 23:00:00 |   33 |                 33 | <-- 33 / 1 (後ろに値が無いので)
+------+---------------------+------+--------------------+
22 rows in set (0.003 sec)

次に欠損値処理したデータを用いて同じことを行ってみる. 以下の例のように,21:00:00 と 22:00:00 の温度として NULL を入力しておく.

MariaDB [iotex]> select name, time, temp from j4oss3 where name like 'hoge';

  +------+---------------------+------+
  | name | time                | temp |
  +------+---------------------+------+
  | hoge | 2024-11-08 00:00:00 |   10 |
  | hoge | 2024-11-08 01:00:00 |   11 |
  | hoge | 2024-11-08 02:00:00 |   12 |
  | hoge | 2024-11-08 03:00:00 |   13 |
  | hoge | 2024-11-08 04:00:00 |   14 |
  | hoge | 2024-11-08 05:00:00 |   15 |
  | hoge | 2024-11-08 06:00:00 |   16 |
  | hoge | 2024-11-08 07:00:00 |   17 |
  | hoge | 2024-11-08 08:00:00 |   18 |
  | hoge | 2024-11-08 09:00:00 |   19 |
  | hoge | 2024-11-08 10:00:00 |   20 |
  | hoge | 2024-11-08 11:00:00 |   21 |
  | hoge | 2024-11-08 12:00:00 |   22 |
  | hoge | 2024-11-08 13:00:00 |   23 |
  | hoge | 2024-11-08 14:00:00 |   24 |
  | hoge | 2024-11-08 15:00:00 |   25 |
  | hoge | 2024-11-08 16:00:00 |   26 |
  | hoge | 2024-11-08 17:00:00 |   27 |
  | hoge | 2024-11-08 18:00:00 |   28 |
  | hoge | 2024-11-08 19:00:00 |   29 |
  | hoge | 2024-11-08 20:00:00 |   30 |
  | hoge | 2024-11-08 21:00:00 | NULL | <-- 欠損値!!
  | hoge | 2024-11-08 22:00:00 | NULL | <-- 欠損値!!
  | hoge | 2024-11-08 23:00:00 |   33 |
  +------+---------------------+------+
  24 rows in set (0.002 sec)

自分自身と前 2 点 (rows between 2 preceding and current row) を用いて平均を作ってみると 以下のような結果となる.期待通りとなっていると思う.

MariaDB [iotex]> select name, time, temp, avg(temp) over (order by time rows between 2 preceding and current row) as moving_avg from j4oss3 where name like 'hoge';

  +------+---------------------+------+------------+
  | name | time                | temp | moving_avg |
  +------+---------------------+------+------------+
  | hoge | 2024-11-08 00:00:00 |   10 |         10 | <-- 10 / 1 (前に値が無いので)
  | hoge | 2024-11-08 01:00:00 |   11 |       10.5 | <-- ( 10 + 11 ) / 2
  | hoge | 2024-11-08 02:00:00 |   12 |         11 | <-- ( 10 + 11 + 12 ) / 3
  | hoge | 2024-11-08 03:00:00 |   13 |         12 |
  | hoge | 2024-11-08 04:00:00 |   14 |         13 |
  | hoge | 2024-11-08 05:00:00 |   15 |         14 |
  | hoge | 2024-11-08 06:00:00 |   16 |         15 |
  | hoge | 2024-11-08 07:00:00 |   17 |         16 |
  | hoge | 2024-11-08 08:00:00 |   18 |         17 |
  | hoge | 2024-11-08 09:00:00 |   19 |         18 |
  | hoge | 2024-11-08 10:00:00 |   20 |         19 |
  | hoge | 2024-11-08 11:00:00 |   21 |         20 |
  | hoge | 2024-11-08 12:00:00 |   22 |         21 |
  | hoge | 2024-11-08 13:00:00 |   23 |         22 |
  | hoge | 2024-11-08 14:00:00 |   24 |         23 |
  | hoge | 2024-11-08 15:00:00 |   25 |         24 |
  | hoge | 2024-11-08 16:00:00 |   26 |         25 |
  | hoge | 2024-11-08 17:00:00 |   27 |         26 |
  | hoge | 2024-11-08 18:00:00 |   28 |         27 |
  | hoge | 2024-11-08 19:00:00 |   29 |         28 | <-- ( 27 + 28 + 29 ) / 3
  | hoge | 2024-11-08 20:00:00 |   30 |         29 | <-- ( 28 + 29 + 30 ) / 3
  | hoge | 2024-11-08 21:00:00 | NULL |       29.5 | <-- ( 29 + 30 ) / 2       (NULL はカウントされない)
  | hoge | 2024-11-08 22:00:00 | NULL |         30 | <-- ( 30 ) / 1            (NULL はカウントされない)
  | hoge | 2024-11-08 23:00:00 |   33 |         33 | <-- ( 33 ) / 1            (NULL はカウントされない)
  +------+---------------------+------+------------+
  24 rows in set (0.001 sec)

自分自身と後ろ 2 点 (rows between current row and 2 following) を用いて平均を作ってみると 以下のような結果となる.こちらも期待通りであろう.

MariaDB [iotex]> select name, time, temp, avg(temp) over (order by time rows between current row and 2 following) as moving_avg from j4oss3 where name like 'hoge';

  +------+---------------------+------+------------+
  | name | time                | temp | moving_avg |
  +------+---------------------+------+------------+
  | hoge | 2024-11-08 00:00:00 |   10 |         11 | <-- ( 10 + 11 + 12 ) / 3
  | hoge | 2024-11-08 01:00:00 |   11 |         12 | <-- ( 11 + 12 + 13 ) / 3
  | hoge | 2024-11-08 02:00:00 |   12 |         13 |
  | hoge | 2024-11-08 03:00:00 |   13 |         14 |
  | hoge | 2024-11-08 04:00:00 |   14 |         15 |
  | hoge | 2024-11-08 05:00:00 |   15 |         16 |
  | hoge | 2024-11-08 06:00:00 |   16 |         17 |
  | hoge | 2024-11-08 07:00:00 |   17 |         18 |
  | hoge | 2024-11-08 08:00:00 |   18 |         19 |
  | hoge | 2024-11-08 09:00:00 |   19 |         20 |
  | hoge | 2024-11-08 10:00:00 |   20 |         21 |
  | hoge | 2024-11-08 11:00:00 |   21 |         22 |
  | hoge | 2024-11-08 12:00:00 |   22 |         23 |
  | hoge | 2024-11-08 13:00:00 |   23 |         24 |
  | hoge | 2024-11-08 14:00:00 |   24 |         25 |
  | hoge | 2024-11-08 15:00:00 |   25 |         26 |
  | hoge | 2024-11-08 16:00:00 |   26 |         27 |
  | hoge | 2024-11-08 17:00:00 |   27 |         28 |
  | hoge | 2024-11-08 18:00:00 |   28 |         29 | <-- ( 28 + 29 + 30 ) / 3
  | hoge | 2024-11-08 19:00:00 |   29 |       29.5 | <-- ( 29 + 30 ) / 2       (NULL はカウントされない)
  | hoge | 2024-11-08 20:00:00 |   30 |         30 | <-- ( 30 ) / 1            (NULL はカウントされない)
  | hoge | 2024-11-08 21:00:00 | NULL |         33 | <-- ( 33 ) / 1            (NULL はカウントされない)
  | hoge | 2024-11-08 22:00:00 | NULL |         33 | <-- ( 33 ) / 1            (NULL はカウントされない)
  | hoge | 2024-11-08 23:00:00 |   33 |         33 | <-- ( 33 ) / 1            (NULL はカウントされない)
  +------+---------------------+------+------------+
  24 rows in set (0.003 sec)

課題:代表値・分散を示すダッシュボードの作成

テーブル sample2024 に含まれるデータから以下と全く同じ grafana のダッシュボードを新たに作成せよ. 以下に示すように,2024-11-12 19:00:00 から 2024-11-12 21:00:00 の範囲について 表示し,そのスナップショットを提出すること.スナップショットには必ず URL 欄を含めること.

ある程度ブラウザの幅を広げないと,以下の例のように,データを示すマル印が表示されないことに注意されたい.

注:SQL が間違っていないことを確認するために,複数のグラフを重ねている.

ダッシュボードに表示するもの

1枚目
  • 1 分毎のデータ (欠損値処理済)
  • 前 10 分平均値
    • 09:10 時点での 10 分平均値は,09:01~09:10 の範囲の平均値
    • 1 分毎に表示をすること.
  • 前 10 分平均値
    • 09:10 時点での 10 分平均値は,09:01~09:10 の範囲の平均値
    • 10 分毎に表示をすること.
2枚目
  • 1 分毎のデータ (欠損値処理済)
  • 前 10 分の最大値
    • 09:10 時点での最大値は,09:01~09:10 の範囲の最大値とする
    • 10 分毎に表示をすること.
  • 前 10 分の最小値
    • 09:10 時点での最小値は,09:01~09:10 の範囲の最小値とする
    • 10 分毎に表示をすること.
3枚目
  • 前 10 分の標準偏差
    • 09:10 時点での標準偏差は,09:01~09:10 の範囲より計算すること.
    • 10 分毎に表示をすること.
  • 前 1 時間の標準偏差
    • 09:00 時点での標準偏差は,08:01~09:00 の範囲より計算すること.
    • 1 時間毎に表示をすること.

編集について

1 つのグラフに複数の折れ線グラフが書かれているが,以下の編集画面で示すように, 「+ Query」アイコンをクリックすると,複数の SQL を書くことができる.

SQL の例

1 分毎の測定結果を表示する SQL の例.

grafana 回の資料 で示した SQL を若干改造し,特定のホスト (以下の例は sugiyama) のみ表示するようにしている.

SELECT
   UNIX_TIMESTAMP(time) as time_sec,
   temp as value,
   "1分データ" as metric
FROM sample2024
WHERE $__timeFilter(time) AND name like 'sugiyama'
ORDER BY time ASC

前 10 分平均値を 1 分毎にプロットする SQL の例.

先に述べた Window 関数を用いて 9 つ前の行から現在の行まで (合計 10 行分 = 10 分) のデータを平均している.

SELECT
   UNIX_TIMESTAMP(time) AS time_sec,
   avg(temp) over (order by time rows between 9 preceding and current row) as avg_temp,
   '前10分平均値_1分毎' AS metric
FROM sample2024
WHERE $__timeFilter(time) AND name LIKE 'sugiyama'
ORDER BY time ASC

前 10 分平均値を 10 分毎にプロットする SQL の例.

サブクエリを使って,前 10 分平均値を作った後に時刻の末尾が 0:00 (すなわち 10 分間隔) なデータを抽出している.

SELECT
   UNIX_TIMESTAMP(avg_time) AS time_sec,
   avg_temp,
   '前10分平均値_10分毎' AS metric
   FROM (
       SELECT
          time as avg_time,
          avg(temp) over (order by time rows between 9 preceding and current row) as avg_temp
          FROM sample2024
       WHERE $__timeFilter(time) AND name LIKE 'sugiyama'
   ) AS subquery
   where avg_time like '%0:00'
   ORDER BY avg_time ASC