📚 背景知識(読んでから問題へ)
y(t) = トレンド(t) + 季節性(t) + イベント効果(t) + 誤差 という足し算で時系列を表現します。「今日の売上 = ①長期的な右肩上がり/下がり + ②曜日や季節で毎回繰り返すパターン + ③セール日など特別な日の上乗せ + ④説明しきれない揺れ」という考え方です。「なぜこの日にこの値を予測したのか」を成分ごとに分解して人に説明しやすいのが最大の強みです。ds / y: Prophetが要求する入力列名(ds=日付、y=予測したい値)。列名を変えると動かないchangepoint: トレンドの傾きが変わる地点Fourier order: 季節性を表すサイン・コサインの本数(多いほど複雑な周期形状を表現できるが過学習しやすい)holidays: 特定の日にだけ効く補正項を定義するためのDataFrame
3つの成分の作り方 — 「トレンド」「季節性」「イベント効果」
トレンド (trend)
区間ごとに傾きが変わる折れ線。傾きが変わる地点(changepoint)を自動検出し、その間は直線的に伸ばす
季節性 (seasonality)
毎週・毎年、同じ形で繰り返す波。サイン・コサインを何本も重ね合わせ(フーリエ級数)、複雑な周期も表現する
イベント/祝日効果 (holidays)
セール日・祝日などカレンダー上の特別な日を指定すると、その日だけの補正値を学習する
🔍 加法モデルの分解図 — 3つの波を足し合わせて1本の予測にする
トレンド(右肩上がりの直線)、週次季節性(規則正しいサイン波)、イベント効果(セール日だけの縦のスパイク)を別々に学習し、最後に単純に足し合わせることで最終的な予測曲線ができる。LSTMのように1つのネットワークが全部を同時に処理するのではなく、成分ごとに分けて学習・可視化できる点がProphetの最大の特徴。
🗂️ データスキーマ
| 列名 | 型 | 説明 |
|---|---|---|
| ds | datetime64 | Prophet専用の日付列名。2026-01-01から220日分の連続した日付 |
| y | float | Prophet専用の目的変数列名。売上 = 100 + トレンド(day×0.5) + 週次季節性(振幅15) + ノイズ(σ=5) + セール効果(+25、30日おき) |
| 変数 | 値 | 役割 |
|---|---|---|
| TRAIN_LEN | 180 | 先頭180日をtrain、残り40日をtest(未来予測の検証用)に分割する境界 |
| sale_days | day % 30 == 29 | 30日おきの「月末セール」日(例: day=29, 59, 89, ...)。holidaysとしてモデルに渡す |
🎯 問題
Day121と同じ「架空店舗の日次売上データ」を220日分に拡張し、さらに30日おきの「月末セール」による売上の上乗せを加えたデータが与えられます。
import numpy as np import pandas as pd np.random.seed(42) days = np.arange(220) trend = days * 0.5 weekly_seasonality = 15 * np.sin(2 * np.pi * days / 7) noise = np.random.normal(0, 5, size=220) sales = 100 + trend + weekly_seasonality + noise # 30日おき(day=29, 59, 89, ...)に「月末セール」で売上が+25される sale_days = days[(days % 30) == 29] sale_effect = np.where(np.isin(days, sale_days), 25, 0) sales = sales + sale_effect dates = pd.date_range(start="2026-01-01", periods=220, freq="D") df = pd.DataFrame({"ds": dates, "y": sales}) TRAIN_LEN = 180 # 先頭180日をtrain、残り40日をtest(未来予測の検証用)
holidays形式に変換 → Prophetモデルをtrainで学習 → make_future_dataframeで40日先を予測 → test RMSEを算出 → 「最後の値を40回コピー」するナイーブベースライン(persistence forecast)と比較する💡 ヒント
Prophetはds(日付)とy(値)という決まった列名の2列DataFrameを要求します。holidaysはイベントの起きた日付の一覧を「いつ・何という名前のイベントか」という形で渡す仕組みです。今回の「セール日」は毎回同じ効果(+25相当)なので、Prophetの目線では「祝日」と全く同じ扱いができます。
pip install prophet(旧fbprophet)が必要。from prophet import Prophetでインポートする- holidaysのDataFrameは最低限
holiday列(イベント名の文字列)とds列(日付)が必要 model.fit()にはtrain部分(先頭180日)のds,y列だけを渡す。test部分の情報を学習に混ぜてはいけない(Day121と同じ「未来の情報を混ぜない」原則)make_future_dataframe(periods=40)は「学習データの最終日から40日分の未来の日付」を自動生成する。学習に使った180日分の予測値も含めて返ってくるので、末尾40行がテスト期間の予測に対応するforecast["yhat"]が予測値。persistence forecastはnp.full(40, train_df["y"].iloc[-1])のように、trainの最後の値を40回繰り返すだけでよい
from prophet import Prophet holidays_df = pd.DataFrame({ "holiday": "monthly_sale", "ds": pd.to_datetime(dates[np.isin(days, sale_days)]), }) train_df = df.iloc[:TRAIN_LEN].reset_index(drop=True) test_df = df.iloc[TRAIN_LEN:].reset_index(drop=True) model = Prophet( weekly_seasonality=True, yearly_seasonality=False, daily_seasonality=False, holidays=holidays_df, ) model.fit(train_df[["ds", "y"]]) future = model.make_future_dataframe(periods=40) forecast = model.predict(future) pred = forecast["yhat"].values[-40:] actual = test_df["y"].values # ここからRMSEを計算する ...
✅ 模範解答
タスク1: ProphetとLSTMの得意・不得意(解答例)
holidaysとして直接教えられるため、少ないデータ量でも安定してこのパターンを学習しやすい。タスク2: Prophetでの実装と評価
import numpy as np import pandas as pd from prophet import Prophet # --- サンプルデータ生成 --- np.random.seed(42) days = np.arange(220) trend = days * 0.5 weekly_seasonality = 15 * np.sin(2 * np.pi * days / 7) noise = np.random.normal(0, 5, size=220) sales = 100 + trend + weekly_seasonality + noise sale_days = days[(days % 30) == 29] sale_effect = np.where(np.isin(days, sale_days), 25, 0) sales = sales + sale_effect dates = pd.date_range(start="2026-01-01", periods=220, freq="D") df = pd.DataFrame({"ds": dates, "y": sales}) TRAIN_LEN = 180 # --- ステップ1: holidays(セールイベント)のDataFrameを作る --- holidays_df = pd.DataFrame({ "holiday": "monthly_sale", "ds": pd.to_datetime(dates[np.isin(days, sale_days)]), }) # --- ステップ2: train/testに時系列順で分割し、Prophetモデルを学習する --- train_df = df.iloc[:TRAIN_LEN].reset_index(drop=True) test_df = df.iloc[TRAIN_LEN:].reset_index(drop=True) model = Prophet( weekly_seasonality=True, yearly_seasonality=False, daily_seasonality=False, holidays=holidays_df, changepoint_prior_scale=0.05, # デフォルト値。トレンドの折れやすさを控えめに設定 ) model.fit(train_df[["ds", "y"]]) # --- ステップ3: 40日先まで予測する --- future = model.make_future_dataframe(periods=40) forecast = model.predict(future) # --- ステップ4: test RMSEを算出する --- pred = forecast["yhat"].values[-40:] actual = test_df["y"].values prophet_rmse = np.sqrt(np.mean((pred - actual) ** 2)) # --- ステップ5: persistence forecast(最後の値を40回コピー)と比較する --- last_train_value = train_df["y"].iloc[-1] naive_pred = np.full(40, last_train_value) naive_rmse = np.sqrt(np.mean((naive_pred - actual) ** 2)) print(f"Prophet test RMSE : {prophet_rmse:.3f}") print(f"Naive test RMSE : {naive_rmse:.3f}") # 出力例: # Prophet test RMSE : 8.2xx # Naive test RMSE : 38.7xx # → naiveは「40日先まで同じ値を出し続ける」ため、右肩上がりのトレンドと # 週次の波を完全に無視した予測になり誤差が急速に拡大する。 # Prophetはトレンド・週次季節性・セール効果を分解して先まで延長できるため # 長期の複数ステップ予測で圧倒的に有利になる。
model.plot_components(forecast)を実行すると、トレンド・週次季節性・holidaysの3つの成分がそれぞれ別々のグラフとして描画される。「セール日にだけ上乗せがある」という生成過程どおりの形がholidaysの成分に現れていれば、Prophetがデータの構造を正しく分解できている証拠になる。📊 RMSE比較の可視化(test 40日先、値が小さいほど良い)
🪜 Step-by-Step 解説
1holidaysはイベント名と日付の対応表として渡す
holidays_df = pd.DataFrame({ "holiday": "monthly_sale", "ds": pd.to_datetime(dates[np.isin(days, sale_days)]), })
holidays引数は「祝日」専用ではなく、「特定の日付にだけ効く一時的な補正項」を表現する汎用的な仕組み。holiday列に同じ名前("monthly_sale")を繰り返すことで、「このイベントは毎回同じ効果を持つ」と教えることができる。異なる種類のイベントを混在させたい場合は、holiday列に異なる文字列を入れればProphetが別々の効果として学習してくれる。2model.fit()にはtrain部分だけを渡す
model.fit(train_df[["ds", "y"]])
3make_future_dataframeは「学習データの続き」の日付を自動生成する
future = model.make_future_dataframe(periods=40) forecast = model.predict(future)
periods=40と指定するだけで学習データの最終日の翌日から40日分の日付が自動生成され、その日付それぞれについてトレンド・季節性・holidays成分を計算して足し合わせる形で予測できる。4ナイーブをpersistence forecast(最後の値を複製)にする理由
naive_pred = np.full(40, last_train_value)
y[t-1]が使えたが、今回は40日先まで複数ステップ先を予測するタスク。複数ステップ先の予測では「1つ前の実測値」がそもそも存在しない(実測値は180日目までしかない)ため、「最後に分かっている値をそのまま複製し続ける」persistence forecastが素朴なベースラインとして使われる。トレンドがあるデータではこのベースラインの誤差は日が進むごとに大きくなりやすい。🧮 数学・統計の補足(文系向け)
s(t) = Σ [a_n cos(2πnt/P) + b_n sin(2πnt/P)](n=1〜N)→ これは季節性成分の計算式。「周期P(週次なら7日)の波をN本、少しずつ違う周波数で重ね合わせて、係数a_n, b_nは学習で決める」という意味。「波を何本重ねて複雑な形を作るか」というイメージで捉えれば十分。
🏆 Kaggleでの実践的な使い方
よく使われるコンペカテゴリ: ☐ 表形式データ(Tabular) / ☐ 自然言語処理(NLP) / ☐ 画像認識(CV) / ☑ 時系列(Time Series)
🔬 ベースライン・診断ツールとしての利用
plot_componentsで成分構造を可視化
学習が速く(GPUも不要)、plot_componentsで「トレンドが急に折れていないか」「季節性が想定通りの形か」を可視化できるため、コンペ最初期に「データの成分構造」を把握する探索的な用途で重宝される。上位解法そのものへの採用は少ないが、EDA代わりに使われることは多い。
🌐 Web Traffic Forecasting系コンペ
明確な季節性・イベントがある系列で有効
系列数が少なく、明確な週次・年次季節性やイベント(記事のバズり、祝日アクセス増)がある系列では、Prophetのholidays機構が効果的に働くことがある。ただし数万系列を一括で扱う大規模コンペでは、系列ごとに個別学習が必要なProphetは計算コストの面で不利になりやすい。
⚖️ 解釈性・速さ重視ならProphet
大規模・非線形にはGBDT/LSTM
「成分ごとの解釈性」「学習の速さ」「明確なイベント日程が分かっている」場合はProphetが強い。「大量の系列を一括で学習したい」「非線形な相互作用が強い」場合はGBDT(Day098-100)やLSTM(Day121)に軍配が上がりやすい。
⚠️ よくある誤解・ミス
| 誤解・ミス | なぜ起こるか | 正しい理解 |
|---|---|---|
列名をds/y以外(例: date/sales)のままfitしようとしてエラーになる | pandasの他の分析では自由に列名を付けられる習慣がある | Prophetは列名を固定で要求するAPI設計。rename(columns={"date": "ds", "sales": "y"})のように必ず変換してから渡す |
make_future_dataframe(periods=40)がtrain全体を予測し直していることに気づかず、末尾40行だけを取り出し忘れる | 「未来だけの予測結果が返ってくる」と思い込んでしまう | 返ってくるforecastはtrain期間+未来期間の全行を含む。評価に使うのはforecast.tail(periods)(今回は末尾40行)だけ |
| holidaysに「効果の強さ」を数値で渡そうとする | 数値を渡す設計だと思い込んでしまう | holidaysは「いつ・何という名前のイベントか」を渡すだけで、効果の大きさ自体はProphet内部の学習で推定される。事前に効果量を指定する引数ではない |
| Prophetは「万能で高精度なSOTAモデル」だと過大評価してしまう | ライブラリ名や知名度から「強いモデル」という印象を持ってしまう | Prophetは「解釈性の高さ」と「学習の速さ・簡便さ」に強みがあるモデルであり、精度そのものはLightGBMやLSTM/Transformer系に劣ることが多い。Kaggleでは「まず全体構造を把握するための道具」として位置づけるのが実践的 |
🚀 次のステップ
- 発展:
changepoint_prior_scaleを0.01(硬いトレンド)と0.5(柔らかいトレンド)に変えて再学習し、model.plot(forecast)で40日先の予測がどう変わるかを比較してみましょう。またholidays_dfを意図的に空にして学習し直し、セール効果を無視した場合にtest RMSEがどれだけ悪化するかを確認すると、「イベント情報をモデルに教えることの価値」が数値で実感できます - 次回予告: Day 123 — Temporal Fusion Transformer(TFT)入門。Day121のLSTM(時間を1ステップずつ処理)、Day122のProphet(成分に分解して予測)に続き、次回はAttention機構を使って「どの過去のタイムステップ・どの特徴量が予測に重要か」を学習しながら予測するTFTを扱います。時系列ブロック(Day121-123)の最終回として、3つのアプローチの使い分けを整理します
Phase 5 の学習マップ(全20テーマ予定)
Day121でLSTM(ニューラルネットワークで時間の流れを直接扱うアプローチ)を学び、今日Day122ではProphet(成分に分解して予測するアプローチ)を学んだ。次回Day123のTemporal Fusion Transformerで、時系列ブロック(16-18)が完了する。
📝 自己評価(解いた後に記入)
自分の回答・気づき・メモ: