データ分析:移動平均を効率的に計算する方法 – インゲージ開発者ブログ

こんにちは、@knsk765 です。
データ分析をしていると、「移動平均」を計算する場面がよくあります。たとえば、売上データのトレンドを把握したり、ノイズを減らしてスムーズなデータ傾向を見たいときに便利です。

この記事では、SQLのウィンドウ関数を使って移動平均を計算する方法を、初心者にもわかりやすく解説します。
サンプルコードと具体例を交えて、すぐに実務で使える知識をお届けします!

目次

移動平均とは、一定期間のデータの平均値を計算し、時間とともに「窓」をずらしながら計算を繰り返す手法です。

たとえば、7日間の移動平均なら、過去7日間のデータを平均して1つの値を作り、次の日ではその次の7日間の平均を計算します。
これにより、データの短期的な変動を抑え、長期的な傾向を把握しやすくなります。特に、外れ値(異常な高値や低値)が含まれるデータでは、移動平均の平滑化効果が顕著に現れます。

SQLのウィンドウ関数を使うと、こうした計算が驚くほど簡単になります。それでは、さっそく具体例を見ていきましょう!

ウィンドウ関数は、データベース内の行に対して「窓(ウィンドウ)」を定義し、その範囲内で計算を行うSQLの機能です。
GROUP BY とは異なり、ウィンドウ関数は元のデータを集約せず、各行に対して計算結果を付加します。移動平均を計算するには、AVG() 関数をウィンドウ関数として使うのが一般的です。

基本的な構文は以下のとおり

AVG(列名) OVER (
  PARTITION BY グループ分けする列(オプション)
  ORDER BY 順序を決める列
  ROWS BETWEEN 範囲指定
)

PARTITION BY:データをグループ分け(例:店舗ごと、商品ごとなど)。省略可
ORDER BY:データの並び順を指定(通常は日付や時系列)。
ROWS BETWEEN:計算対象の範囲(例:直前の2行など)を指定。

これを移動平均にどう活かすか、具体例で見ていきましょう。

サンプルデータ

以下のような売上データがあるとします。テーブル名は sales で、構造は以下の通りです。今回は31レコード(2025-08-01 から 08-31 までの31日分)用意しました。
外れ値(例:8/6 に 5000)を意図的に入れており、平均化による平滑化の効果を強調しています。

date store_id sales_amount
2025-08-01 A 902
2025-08-02 A 1148
2025-08-03 A 1070
2025-08-04 A 906
2025-08-05 A 871
2025-08-06 A 5000
2025-08-07 A 820
2025-08-08 A 902
2025-08-09 A 921
2025-08-10 A 1014
2025-08-11 A 200
2025-08-12 A 887
2025-08-13 A 1172
2025-08-14 A 899
2025-08-15 A 1159
2025-08-16 A 951
2025-08-17 A 930
2025-08-18 A 949
2025-08-19 A 1108
2025-08-20 A 1057
2025-08-01 B 860
2025-08-02 B 1013
2025-08-03 B 721
2025-08-04 B 952
2025-08-05 B 935
2025-08-06 B 1044
2025-08-07 B 748
2025-08-08 B 4000
2025-08-09 B 869
2025-08-10 B 887
2025-08-11 B 970
2025-08-12 B 889
2025-08-13 B 874
2025-08-14 B 750
2025-08-15 B 1063
2025-08-16 B 100
2025-08-17 B 943
2025-08-18 B 1019
2025-08-19 B 830
2025-08-20 B 1006

チャートで見るとこんな感じです。

このデータを使って、直近7日間の移動平均を計算してみましょう。外れ値があることで、売上額の急変動が移動平均でどう抑えられるかがわかります。

サンプルデータテーブルを作るクエリは以下です。

CREATE TABLE sales (
    date DATE NOT NULL,
    store_id VARCHAR(1) NOT NULL,
    sales_amount INTEGER NOT NULL
);

INSERT INTO sales (date, store_id, sales_amount) VALUES
('2025-08-01', 'A', 902),
('2025-08-02', 'A', 1148),
('2025-08-03', 'A', 1070),
('2025-08-04', 'A', 906),
('2025-08-05', 'A', 871),
('2025-08-06', 'A', 5000),
('2025-08-07', 'A', 820),
('2025-08-08', 'A', 902),
('2025-08-09', 'A', 921),
('2025-08-10', 'A', 1014),
('2025-08-11', 'A', 200),
('2025-08-12', 'A', 887),
('2025-08-13', 'A', 1172),
('2025-08-14', 'A', 899),
('2025-08-15', 'A', 1159),
('2025-08-16', 'A', 951),
('2025-08-17', 'A', 930),
('2025-08-18', 'A', 949),
('2025-08-19', 'A', 1108),
('2025-08-20', 'A', 1057),
('2025-08-01', 'B', 860),
('2025-08-02', 'B', 1013),
('2025-08-03', 'B', 721),
('2025-08-04', 'B', 952),
('2025-08-05', 'B', 935),
('2025-08-06', 'B', 1044),
('2025-08-07', 'B', 748),
('2025-08-08', 'B', 4000),
('2025-08-09', 'B', 869),
('2025-08-10', 'B', 887),
('2025-08-11', 'B', 970),
('2025-08-12', 'B', 889),
('2025-08-13', 'B', 874),
('2025-08-14', 'B', 750),
('2025-08-15', 'B', 1063),
('2025-08-16', 'B', 100),
('2025-08-17', 'B', 943),
('2025-08-18', 'B', 1019),
('2025-08-19', 'B', 830),
('2025-08-20', 'B', 1006);

7日間移動平均のSQL

以下のクエリで、店舗ごとの7日間移動平均を計算できます

SELECT
  date,
  store_id,
  sales_amount,
  ROUND(AVG(sales_amount) OVER (
    PARTITION BY store_id
    ORDER BY date
    ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
  )) AS moving_average
FROM sales
ORDER BY store_id, date;

クエリの解説

PARTITION BY store_id
店舗ごとにデータをグループ化。店舗Aと店舗Bの計算は独立して行われます。

ORDER BY date
日付順にデータを並べ、時系列での計算を保証。

ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
直前の6行と現在の行(合計7行)を計算対象に指定。

AVG(sales_amount)
指定した範囲のsales_amountの平均を計算。

結果例

上記のクエリを実行すると、以下のような結果が得られます。
外れ値(例:Aの5000や200)があっても、移動平均は急激な変動を抑え、トレンドを安定させてくれます。

店舗A

date store_id sales_amount moving_average
2025-08-01 A 902 902
2025-08-02 A 1148 1025
2025-08-03 A 1070 1040
2025-08-04 A 906 1007
2025-08-05 A 871 979
2025-08-06 A 5000 1650
2025-08-07 A 820 1531
2025-08-08 A 902 1531
2025-08-09 A 921 1499
2025-08-10 A 1014 1491
2025-08-11 A 200 1390
2025-08-12 A 887 1392
2025-08-13 A 1172 845
2025-08-14 A 899 856
2025-08-15 A 1159 893
2025-08-16 A 951 897
2025-08-17 A 930 885
2025-08-18 A 949 992
2025-08-19 A 1108 1024
2025-08-20 A 1057 1008

8/6 の 5000(高外れ値)で移動平均が一時的に上がるが、次の日以降で徐々に平滑化。
8/11 の 200(低外れ値)で平均が下がるが、周囲のデータで緩和される。

店舗B

date store_id sales_amount moving_average
2025-08-01 B 860 860
2025-08-02 B 1013 937
2025-08-03 B 721 865
2025-08-04 B 952 887
2025-08-05 B 935 896
2025-08-06 B 1044 921
2025-08-07 B 748 896
2025-08-08 B 4000 1345
2025-08-09 B 869 1324
2025-08-10 B 887 1348
2025-08-11 B 970 1350
2025-08-12 B 889 1344
2025-08-13 B 874 1320
2025-08-14 B 750 1320
2025-08-15 B 1063 900
2025-08-16 B 100 790
2025-08-17 B 943 798
2025-08-18 B 1019 805
2025-08-19 B 830 797
2025-08-20 B 1006 816

8/8 の 4000 で平均が跳ね上がるが、以降で抑えられる。
8/16 の 100 で平均が下がるが、平滑化効果でトレンドが崩れにくい。

データ量が多いと、こうした外れ値の影響が全体トレンドの中でどう吸収されるかがよくわかります。チャートにするとこうですね。

応用例:30日間移動平均や全期間平均

30日間移動平均
ROWS BETWEEN 29 PRECEDING AND CURRENT ROWに変更。外れ値の影響がさらに薄まる。

全期間の平均
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROWで、開始日から現在までの平均を計算。

中心移動平均(前後3日の平均):
ROWS BETWEEN 3 PRECEDING AND 3 FOLLOWINGで、前後3日を含む7日間の平均を計算。

NULL値の扱い
データに欠損がある場合、AVG() は NULL を無視します。必要に応じて COALESCE などで補完を。

パフォーマンス
大きなデータセットでは、PARTITION BY や範囲指定がパフォーマンスに影響します。インデックスを適切に設定しましょう。

データベースの互換性
ウィンドウ関数は PostgreSQL、MySQL(8.0以降)、SQL Server、Oracle などで使えますが、構文やサポート範囲が異なる場合があります。使用するDBのドキュメントを確認してください。

外れ値の考慮
外れ値を含むデータでは、移動平均の窓幅を調整して影響をコントロール。狭い窓だと変動が残り、広い窓だと平滑化が強まります。

売上分析:日次売上のブレ(外れ値含む)を抑えてトレンドを把握。
在庫管理:需要予測のために過去の出荷データの移動平均を計算。
センサーデータ:IoTデバイスのノイズを平滑化して異常検知に活用。

SQLのウィンドウ関数を使えば、移動平均の計算が驚くほど簡単にできます。
AVG()OVER句を組み合わせるだけで、柔軟にデータ範囲を指定して分析が可能。
今回の例のように、外れ値を入れてみると、平均化の効果が実感しやすいですよ!ぜひ自分のデータで試してみてください。

ウィンドウ関数に興味を持たれた方はこちらの記事もぜひ。

blog.ingage.jp

blog.ingage.jp

blog.ingage.jp




Source link

関連記事

コメント

この記事へのコメントはありません。