K_DM Book
用語集
対象者 標準
対象者別の入口 →

対象者の設定はこのブラウザの中にだけ保存されます。

言語 JA

MSTL分解

04.11.5

MSTL分解

最終更新 2020-03-11 読了時間 4 分
まとめ
MSTL は STL 分解(LOESS による季節・トレンド分解)を拡張し、周期 24 と周期 168 のように複数の季節パターンを同時に取り出せる手法です。STL を周期ごとに繰り返し当てる仕組みで、statsmodels 0.14.0 以降の MSTL(series, periods=(24, 168)).fit() の 1 行で使えます。このページでは np.sin と np.cos の 2 本の波に対数トレンドを重ねた 1000 日の人工データを分解し、.seasonal が 2 列になって周期ごとに分かれること、.resid がほぼ 0 に収まることを 4 段のプロットで確かめます。

直感 #

電力消費のデータには「1 日の中で昼に高く夜に低い」24 時間周期と、「平日に高く週末に低い」7 日周期が同時に乗っています。STL は周期を 1 つしか指定できないので、24 時間で分解すると 7 日周期がトレンドか残差に紛れ込み、7 日で分解すると 24 時間周期が残差に残ります。MSTL は「まず短い周期 24 で STL を当てて季節成分を引き、残りに長い周期 168 で STL を当てる」という手順を繰り返して、周期ごとの季節成分を別々の列に取り出します。

複数の周期性が重なったデータをトレンド・季節/周期性・残差に分解する #

MSTL は STL 分解(LOESS による季節・トレンド分解)手法を拡張したもので、複数の季節パターンを持つ時系列の分解が可能です。statsmodels の version==0.14.0 以降でのみ使用できます。引数の詳細はドキュメントを確認してください。

K. Bandura, R.J. Hyndman, and C. Bergmeir (2021) MSTL: A Seasonal-Trend Decomposition Algorithm for Time Series with Multiple Seasonal Patterns. arXiv preprint arXiv:2107.13462.

Anacondaを仮想環境として使用している場合、conda install -c conda-forge statsmodelsでインストールされるものは0.13.Xとなっています(2022/11/1時点)。その場合、作業中の仮想環境の中で以下のコマンドを使用して最近のバージョンをインストールしてください。

pip install git+https://github.com/statsmodels/statsmodels

使うライブラリ #

1
2
3
4
5
6
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns

from statsmodels.tsa.seasonal import MSTL

何が起きているか(ステップで見る) #

コードを読む前に、この手法が図の上で何をしているかを追ってみましょう。「再生」を押すか、スライダーを動かすと 1 手順ずつ進みます。

見るポイント:元データ = トレンド + 季節成分 + 残差。残差に残った大きな山が異常の候補。

サンプルデータを作成 #

周期の異なる三角関数を 2 本足し、さらに i % 14 のノコギリ波と np.log(i**3 + 1) + np.sqrt(i) の上昇トレンドを重ねます。答えを知っているデータなので、分解後の .seasonal 2 列がそれぞれどの波に対応するかを確かめられます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
date_list = pd.date_range("2018-01-01", periods=1000, freq="D")
value_list = [
    10
    + i % 14
    + 2 * np.sin(10 * np.pi * i / 24)
    + 5 * np.cos(2 * np.pi * i / (24 * 7)) * 2
    + np.log(i**3 + 1)
    + np.sqrt(i)
    for i, di in enumerate(date_list)
]

df = pd.DataFrame(
    {
        "日付": date_list,
        "観測値": value_list,
    }
)

df.head(10)

日付観測値
02018-01-0120.000000
12018-01-0224.618006
22018-01-0326.583476
32018-01-0426.587164
42018-01-0528.330645
52018-01-0632.415653
62018-01-0735.578666
72018-01-0835.663289
82018-01-0934.892380
92018-01-1036.617664

折れ線で全体の形を見ます。大きなうねり(周期 168)の上に細かいギザギザ(周期 4.8 と 14)が乗り、全体が右上がりになっています。

1
2
3
4
plt.figure(figsize=(15, 5))
sns.lineplot(x=df["日付"], y=df["観測値"])
plt.grid(axis="x")
plt.show()

サンプルデータを作成 figure

トレンド・季節/周期性・残差に分解する #

periods に取り出したい周期をタプルで渡し、.fit() を呼ぶだけで分解が終わります。周期は短いものから並べます。

1
2
periods = (24, 24 * 7)
mstl = MSTL(df["観測値"], periods=periods).fit()

分解結果のトレンド(.trend)、季節/周期性(.seasonal)、残差(.resid)をそれぞれプロットします。今回は周期の異なる三角関数を 2 本足しているので .seasonal には 2 つの列が含まれています。残差のプロットにところどころ山があるものの、ほとんどの領域で残差が 0 に近く、きれいに分解できていることが確認できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
_, axes = plt.subplots(figsize=(12, 8), ncols=1, nrows=4, sharex=True)

axes[0].set_title("観測値")
axes[0].plot(mstl.observed)
axes[0].grid()

axes[1].set_title("トレンド")
axes[1].plot(mstl.trend)
axes[1].grid()

axes[2].set_title("季節性")
axes[2].plot(mstl.seasonal.iloc[:, 0], label="周期的な要素1")
axes[2].plot(mstl.seasonal.iloc[:, 1], label="周期的な要素2")
axes[2].legend()
axes[2].grid()

axes[3].set_title("その他の要因・残差")
axes[3].plot(mstl.resid)
axes[3].grid()

plt.tight_layout()
plt.show()

残差のプロットにところどころ山があるものの、ほとんどの領域で残差が0に近い(=きれいに分解できている)ことが確認できます… figure

使いどころと落とし穴 #

MSTL は電力・来店数・Web アクセスのように「1 日」と「1 週間」の周期が重なるデータで威力を発揮します。周期の候補は ACF や FFT のピークから拾い、periods を昇順に並べて渡します。周期を入れ忘れると、その成分はこのページの i % 14 のようにトレンドや残差に紛れるので、分解後は必ず .resid に周期的な山が残っていないかを確かめてください。1 周期しかないデータなら STL で十分で、statsmodels 0.13 以前の環境では MSTL 自体が import できない点にも注意が必要です。