この記事は
時系列データに対して実際にSTL分解をしてみてデータがどのように変化するのか図を使って説明します。実行するためのRコードを添付しているので手元のデータに適応してみてください。
STL分解とは
Loess(局所回帰平滑化)を用いて時系列データからS(季節性成分)とT(トレンド成分)を滑らかに推定する。「残差成分」は元のデータから季節成分・トレンド成分を引き算した後に残る部分。これから実際に下記のデータに対して分解してみます。
データをパット見た限り、1年毎に周期性がありそうです。
また全体的に上昇トレンドが見られそうですね。
最後に実行コードを添付しておきます。
分解される 3 成分
1. Seasonal(季節成分)
一定周期(例:1年、1週間、1日)で繰り返す規則的な振る舞いを示す成分です。
先程のデータに対して実施すると下記のようになります。
1年毎に周期性があるのを可視化できました。
2. Trend(トレンド成分)
データ全体にわたる中長期的な上昇・下降傾向を表す成分です。
先程のデータに対して適応してみます。
上昇トレンドがあることを可視化できました。
3. Remainder/Residual(残差成分)
元データから「季節成分+トレンド成分」を差し引いた後に残る、
ランダムかつ予測困難な揺らぎや外れ値を示します。
$$R_t = Y_t - T_t - S_t$$
ここで、
- $R_t$ は時間 $t$ における残差成分
- $Y_t$ は時間 $t$ における原時系列データ
- $T_t$ は時間 $t$ におけるトレンド成分
- $S_t$ は時間 $t$ における季節成分
この式は、原時系列データからトレンド成分と季節成分を差し引いたものが残差成分であることを示しています。
Rコード
# ライブラリの読み込み
library(tidyverse)
library(stats)
library(forecast)
library(lubridate)
# Step 1: データ生成 (2020-01~2022-12 の36か月分)
set.seed(0) # 再現性のためのシード
dates <- seq(as.Date("2020-01-01"), by = "month", length.out = 36)
trend <- seq(10, 20, length.out = length(dates)) # 緩やかな上昇トレンド
seasonal <- 5 * sin(2 * pi * month(dates) / 12) # 一年周期の季節成分
residual <- rnorm(length(dates), sd = 1) # ランダムノイズ(残差)
original <- trend + seasonal + residual # 合成時系列
# Step 2: データフレームにまとめる
df <- data.frame(
Date = dates,
Original = original
)
# Step 3: 時系列オブジェクトへの変換
ts_data <- ts(df$Original, frequency = 12, start = c(2020, 1))
# Step 4: STL分解の実行
stl_result <- stl(ts_data, s.window = "periodic", robust = TRUE)
# Step 5: 分解結果の抽出
seasonal_comp <- stl_result$time.series[, "seasonal"]
trend_comp <- stl_result$time.series[, "trend"]
residual_comp <- stl_result$time.series[, "remainder"]
# Step 6: 結果を可視化
# 元の時系列の可視化 (青)
# ベクトルを2つ合わせて# ggplot2で可視化するためにデータフレームに変換
p1 <- ggplot(df, aes(x = Date, y = Original)) +
geom_line(color = "blue") +
labs(title = "Original Time Series", x = "Date", y = "Value") +
theme_minimal()
# 季節成分の可視化 (オレンジ)
p2 <- ggplot(data.frame(Date = dates, Seasonal = as.numeric(seasonal_comp)),
aes(x = Date, y = Seasonal)) +
geom_line(color = "orange") +
labs(title = "Seasonal Component", x = "Date", y = "Seasonal Value") +
theme_minimal()
# トレンド成分の可視化 (緑)
p3 <- ggplot(data.frame(Date = dates, Trend = as.numeric(trend_comp)),
aes(x = Date, y = Trend)) +
geom_line(color = "green4") +
labs(title = "Trend Component", x = "Date", y = "Trend Value") +
theme_minimal()
# 残差成分の可視化 (赤)
p4 <- ggplot(data.frame(Date = dates, Residual = as.numeric(residual_comp)),
aes(x = Date, y = Residual)) +
geom_line(color = "red") +
labs(title = "Residual Component", x = "Date", y = "Residual Value") +
theme_minimal()
# 各グラフを表示
print(p1)
print(p2)
print(p3)
print(p4)
# PNG 形式で保存
# オプションです
ggsave("original.png",
plot = p1,
width = 8, height = 4, dpi = 300)
ggsave("seasonal.png",
plot = p2,
width = 8, height = 4, dpi = 300)
ggsave("trend.png",
plot = p3,
width = 8, height = 4, dpi = 300)
ggsave("residual.png",
plot = p4,
width = 8, height = 4, dpi = 300)
実際の活用先
著者が小売業界なので小売業界に事例になります。
実装して終わりではなくどこに活用できるのか考えるのが一番大切です。
Seasonal(季節成分)
-
在庫最適化
STL によって抽出した季節成分を用い、年末商戦やバーゲン時期などの売上ピークを正確に予測し、欠品リスクを低減します。
-
気象連動プロモーション
天候データと組み合わせ、夏の猛暑や冬の寒波といった気象条件に応じた商品需要の変動を季節成分としてモデル化し、適切なタイミングで販促を実施します。
-
スタッフ配置計画
店舗来店客数の季節パターン(例えば週末や連休の混雑)を抽出し、ピーク時の人員シフトを最適化する事例があります。
-
プロモーション時期の最適化
季節成分に基づき、クリスマスやバレンタインなど定例イベントでのキャンペーン効果を最大化するスケジュールを設計します。
Trend(トレンド成分)
-
商品カテゴリ成長分析
長期的な売上トレンドを抽出し、新規カテゴリやベストセラー商品の成長傾向を把握。マーケティング投資の重点領域を決定します。
-
ストア出店戦略
各店舗の定常的な売上ベースライン(Trend)を分析し、出店候補地域の予測収益を評価する際に用いられます。
-
FMCG(高速消費財)中長期予測
STL で抽出したトレンド成分をさらに高度なモデルと組み合わせて、数年先の需要を高精度に予測する手法があります。
Remainder/Residual(残差成分)
-
異常需要検知
残差成分の急激な変動をトリガーに、突発的なキャンペーン効果や競合イベントによる需要増加をリアルタイムに察知します。
-
プロモーション効果の定量評価
残差成分を分析して、季節性・トレンドを除去したうえでプロモーションによる純粋な売上上乗せ効果を測定します。
-
サプライチェーン異常監視
仕入れ遅延や在庫切れなど、通常のパターンと異なる残差を検出し、サプライチェーンの問題を早期に発見する用途があります。
最後に
同じことをPythonでも実装しています。
下記の記事をご覧ください。
https://qiita.com/ktdatascience/items/3f5d0e34d67ccdac0dd7