「金曜は元々アクセス多い」のに「金曜のアクセスが普段より多い」を異常検知したい── 季節性を考慮した時系列異常検知の話。
ダミーデータの生成(実走できるサンプル)
週次季節性 + 異常 5 件のダミー時系列
Python
import numpy as npimport pandas as pd
np.random.seed(42)dates = pd.date_range("2026-01-01", periods=180, freq="D")trend = np.linspace(100, 120, 180)weekly = 10 * np.sin(2 * np.pi * np.arange(180) / 7) # 週次季節性noise = np.random.normal(0, 3, 180)series = pd.Series(trend + weekly + noise, index=dates)
# 異常を 5 件埋め込むanomaly_idx = [30, 60, 100, 130, 160]series.iloc[anomaly_idx] += np.random.choice([-30, 30], 5)
df = series.reset_index()df.columns = ["ds", "y"]print(df.head())STL 分解で異常検知
Seasonal-Trend decomposition using LOESS。時系列を trend + seasonal + residual に分解し、residual の異常を検知する。 EP.21 の移動平均と並ぶ時系列前処理の定石。
statsmodels で STL 分解 → 異常検出
Python
from statsmodels.tsa.seasonal import STLimport matplotlib.pyplot as pltimport japanize_matplotlib # noqa: F401
stl = STL(series, period=7).fit()residual = stl.resid
# residual に対して 3σthreshold = 3 * residual.std()detected = residual[residual.abs() > threshold]print(f"検出件数: {len(detected)}")print(f"検出日: {list(detected.index.strftime('%Y-%m-%d'))}")
# 可視化fig, axes = plt.subplots(4, 1, figsize=(12, 10), sharex=True)stl.observed.plot(ax=axes[0]); axes[0].set_title("元データ")stl.trend.plot(ax=axes[1]); axes[1].set_title("トレンド")stl.seasonal.plot(ax=axes[2]); axes[2].set_title("季節性")stl.resid.plot(ax=axes[3]); axes[3].set_title("残差")axes[3].axhline(threshold, color="r", linestyle="--", alpha=0.5, label=f"+{threshold:.1f}σ")axes[3].axhline(-threshold, color="r", linestyle="--", alpha=0.5)axes[3].scatter(detected.index, detected.values, color="red", s=80, zorder=5, label="異常")axes[3].legend()plt.tight_layout(); plt.show()Prophet で異常検知(信頼区間外)
Facebook が開発した時系列予測ライブラリ。uncertainty interval(信頼区間) を出してくれるので、「予測区間を外れたら異常」という判定に使える。ホリデー設定で日本特有の祝日も組み込める。
Prophet で異常検知(実走例)
Python
# !pip install prophetfrom prophet import Prophetimport pandas as pd
m = Prophet( weekly_seasonality=True, yearly_seasonality=False, interval_width=0.95, # 95% 信頼区間)# 日本の祝日を組み込むm.add_country_holidays(country_name="JP")m.fit(df)
# 既存日付を予測forecast = m.predict(df[["ds"]])
# 信頼区間外を異常とするresult = df.merge(forecast[["ds", "yhat", "yhat_lower", "yhat_upper"]], on="ds")result["anomaly"] = (result["y"] < result["yhat_lower"]) | (result["y"] > result["yhat_upper"])print(f"異常検出: {result['anomaly'].sum()} 件")print(result[result['anomaly']][["ds", "y", "yhat", "yhat_lower", "yhat_upper"]].head())
# 可視化(Prophet 標準)fig = m.plot(forecast)ax = fig.gca()anomalies = result[result["anomaly"]]ax.scatter(anomalies["ds"], anomalies["y"], color="red", s=80, label="異常", zorder=5)ax.legend(); plt.tight_layout(); plt.show()STL vs Prophet の使い分け
| 観点 | STL | Prophet |
|---|---|---|
| 学習速度 | 数秒 | 数秒〜数十秒 |
| 祝日対応 | なし(自前) | 国別ビルトイン |
| 季節性 | 1 種類のみ | 複数(週・年・カスタム) |
| 外れ値耐性 | 中(LOESS で頑健) | 高 |
| 未来予測 | △ | ◎(本来用途) |
| 運用コスト | 低 | 中(依存重め) |
ふくふくの進め方
「 の異常検知に季節性を組み込みたい」というご相談には、STL → Prophet の段階導入を 1〜2 週間で。
次回予告
EP.05 は多変量異常検知:PCA と Mahalanobis 距離。
この記事の感想を教えてください
あなたの 1 クリックで、本当にこの記事は更新されます。「もっと詳しく」「続編希望」が一定数集まった記事は、 ふくふくが 実際に内容を拡充したり続編記事を公開 します。 送信したリアクションはお使いのブラウザに記録され、再カウントされません。