Python で時系列予測といえば Prophet。
しかし実務では、データ前処理やハイパーパラメータ調整、追加説明変数(外生変数)との組み合わせが欠かせません。
今回は Prophet を scikit-learn の Pipeline に組み込み、前処理→学習→予測→可視化を一気通貫で回す方法をステップごとに解説します。
完成イメージは以下です。
ローデータ ─▶ 変換器(正規化) ─▶ 予測器(Prophet) ─▶ 予測結果
Contents
なぜ Prophet を Pipeline 化するのか?
Prophet 単体で利用した場合の課題と、 Pipeline 化で得られるメリットです。
課題 | Pipeline 化で得られるメリット |
前処理とモデルを別ファイルで管理→コードが散らかる | 一つのオブジェクトにラップし可読性アップ |
クロスバリデーションや GridSearch が面倒 |
GridSearchCV がそのまま使える |
追加説明変数の数が増えると add_regressor を書き忘れやすい |
ラッパークラス内で自動 add_regressor |
要するに、単なるコード整理を超えて、機械学習ワークフロー全体を Prophet に適用できる点が最大の利点です。
データ準備
ここでは 3 年分のサンプルデータを生成します。ds
列に日付、reg
が外生変数、y
が目的変数です。
以下、コードです。
# 必要なライブラリをインポート import numpy as np import pandas as pd # 乱数のシードを固定して再現性を確保 np.random.seed(0) # データのサンプル数を設定 N = 365 * 3 # 日付データを生成(2022年1月1日からN日間のデータ) dates = pd.date_range('2022-01-01', periods=N, freq='D') # 外生変数を生成(線形データにランダムなノイズを加える) reg = np.linspace(0, 10, N) + np.random.randn(N)*0.2 # 目的変数を生成(外生変数とsin、ノイズの組み合わせ) y = 0.5*reg + np.sin(2*np.pi*np.arange(N)/365) + np.random.randn(N)*0.2 # データフレームを作成 data = pd.DataFrame({ 'ds': dates, # 日付 'reg': reg, # 外生変数 'y': y # 目的変数 }) # データフレームを表示 print(data)
以下、実行結果です。
ds reg y 0 2022-01-01 0.352810 0.158356 1 2022-01-02 0.089172 0.208131 2 2022-01-03 0.214029 0.128339 3 2022-01-04 0.475601 0.359054 4 2022-01-05 0.410075 0.406491 ... ... ... ... 1090 2024-12-26 9.787605 5.057952 1091 2024-12-27 9.926357 4.847469 1092 2024-12-28 9.653957 4.602688 1093 2024-12-29 9.844197 4.680556 1094 2024-12-30 10.429915 5.226078 [1095 rows x 3 columns]
このとき、目的変数と外生変数も分割します。
# 訓練データ(train)とテストデータ(test)に分割 train, test = data.iloc[:-365], data.iloc[-365:] # 訓練データ:外生変数(X_train,)と目的変数(y_train)に分割 X_train, y_train = train.drop(columns='y'), train['y'] # テストデータ:外生変数(X_test)と目的変数(y_test)に分割 X_test, y_test = test.drop(columns='y'), test['y']
直近365データポイントをテストデータとし、それより前のデータを訓練データとしています。
変換器:StandardScaler で外生変数を正規化
変数の数値スケールの差をなくすことで、Prophet 内部の L2 正則化が均一に機能し、係数解釈も安定します。
そのための前処理を実施する変換器を作ります。
以下、コードです。
# 必要なライブラリをインポート from sklearn.preprocessing import StandardScaler from sklearn.base import BaseEstimator, TransformerMixin # カスタムスケーラークラスを定義 class RegScaler(BaseEstimator, TransformerMixin): # 初期化メソッド def __init__(self): # StandardScaler のインスタンスを作成 self.scaler = StandardScaler() # スケーリング対象の列名を格納する変数を初期化 self.cols_ = None # 学習メソッド def fit(self, X, y=None): # 'ds' 列以外の列名を取得して self.cols_ に格納 self.cols_ = [c for c in X.columns if c != 'ds'] # スケーリング対象の列に対して StandardScaler をフィッティング self.scaler.fit(X[self.cols_]) return self # 変換メソッド def transform(self, X): # 入力データフレームをコピー df = X.copy() # スケーリング対象の列を変換 df[self.cols_] = self.scaler.transform(df[self.cols_]) return df
連続値の説明変数が多い場合は、このようなスケーリングをした方がいいでしょう。さらに、Log 変換や Box-Cox と組み合わせても OK です。
カテゴリ変数を含む場合は、One-Hot後にスケールするなど、パイプラインを拡張するのもいいでしょう。
予測器:ProphetRegressor を scikit-learn Estimator 化
ここでは Prophet を scikit-learn 互換にするラッパーを実装します。
以下、コードです。
# 必要なライブラリをインポート from prophet import Prophet from sklearn.base import RegressorMixin, BaseEstimator # Prophetをラップするクラスを定義 class ProphetRegressor(BaseEstimator, RegressorMixin): # 初期化メソッド def __init__( self, n_changepoints=25, # 変化点の数を指定 yearly_seasonality=False, # 年次の季節性を使用するかどうか **prophet_kwargs # その他のProphetのパラメータ ): self.n_changepoints = n_changepoints self.yearly_seasonality = yearly_seasonality self.prophet_kwargs = prophet_kwargs self.model_ = None # 学習メソッド def fit(self, X, y): # 入力データをコピーして目的変数を追加 df = X.copy() # 目的変数をデータフレームに追加 df['y'] = y # Prophetモデルを初期化 self.model_ = Prophet( n_changepoints=self.n_changepoints, yearly_seasonality=self.yearly_seasonality, **self.prophet_kwargs ) # 外生変数をProphetモデルに追加 for col in df.columns: if col not in ['ds', 'y']: self.model_.add_regressor(col) # モデルを学習 self.model_.fit(df) return self # 予測メソッド def predict(self, X): # 予測を実行 forecast = self.model_.predict(X) # 予測結果(yhat列)を返す return forecast['yhat'].values
変換器と予測器を一本化します。
from sklearn.pipeline import Pipeline # パイプラインの設定 pipe = Pipeline([ ('scaler', RegScaler()), # データのスケーリングを行う変換器 ('prophet', ProphetRegressor()), # 時系列予測を行う予測器 ]) # パイプラインのステップを確認 print(pipe.named_steps)
以下、実行結果です。
{'scaler': RegScaler(), 'prophet': ProphetRegressor()}
変換器と予測器を一本化します。
from sklearn.metrics import mean_squared_error, r2_score # 学習(訓練データ) pipe.fit(X_train, y_train) # 予測(テストデータ) y_pred = pipe.predict(X_test) # 予測精度 ## 平均二乗誤差 (MSE) mse = mean_squared_error(y_test, y_pred) ## 決定係数 (R^2) r2 = r2_score(y_test, y_pred) print(f"Mean Squared Error: {mse}") print(f"R^2 Score: {r2}")
以下、実行結果です。
Mean Squared Error: 0.9566794540470825 R^2 Score: -3.0408605232094272
最後に、モデルの当てはまりを直感的にチェックしましょう。
import matplotlib.pyplot as plt plt.figure(figsize=(10,5)) plt.plot(train['ds'], y_train, label='Train') plt.plot(test['ds'], y_test, label='Test (actual)') plt.plot(test['ds'], y_pred, label='Predicted') plt.legend() plt.title('Prophet Pipeline with Preprocessing') plt.tight_layout() plt.show()
以下、実行結果です。
実務ではProphetモデルにはたくさんのハイパーパラメータがあります。たとえば、 Changepoint
数や季節性フラグなどです。
# 必要なライブラリをインポート from sklearn.model_selection import TimeSeriesSplit, GridSearchCV # Prophetのハイパーパラメータを探索するためのパラメータグリッドを定義 param_grid = { # 変化点の数は? 'prophet__n_changepoints': [10, 25, 50], # 年次の季節性を使用するかどうか 'prophet__yearly_seasonality': [True, False], } # 時系列データ用の交差検証を設定 # データを5分割して交差検証を行う tscv = TimeSeriesSplit(n_splits=5) # グリッドサーチを実行 # パイプラインとパラメータグリッドを指定し、評価指標として負の平均絶対誤差を使用 search = GridSearchCV( pipe, # パイプライン param_grid, # 探索するパラメータグリッド cv=tscv, # 時系列データ用の交差検証 scoring='neg_mean_absolute_error' # 評価指標 ) # 訓練データを用いてグリッドサーチを実行 search.fit(X_train, y_train) # 最適なパラメータを出力 print('Best params:', search.best_params_)
以下、実行結果です。
Best params: {'prophet__n_changepoints': 10, 'prophet__yearly_seasonality': True}
# Best paramsを使用してパイプラインを再構築 best_params = search.best_params_ # パイプラインの再構築 pipe_optimized = Pipeline([ ('scaler', RegScaler()), ('prophet', ProphetRegressor( n_changepoints=best_params['prophet__n_changepoints'], yearly_seasonality=best_params['prophet__yearly_seasonality'] )), ]) # 再構築したパイプラインで学習 pipe_optimized.fit(X_train, y_train) # テストデータで予測 y_pred_optimized = pipe_optimized.predict(X_test) # 予測精度の計算 mse_optimized = mean_squared_error(y_test, y_pred_optimized) r2_optimized = r2_score(y_test, y_pred_optimized) print(f"Optimized Mean Squared Error: {mse_optimized}") print(f"Optimized R^2 Score: {r2_optimized}") # グラフ化 plt.figure(figsize=(10,5)) plt.plot(train['ds'], y_train, label='Train') plt.plot(test['ds'], y_test, label='Test (actual)') plt.plot(test['ds'], y_pred_optimized, label='Optimized Predicted') plt.legend() plt.title('Optimized Prophet Pipeline with Preprocessing') plt.tight_layout() plt.show()
以下、実行結果です。
Optimized Mean Squared Error: 0.037226167764707864 Optimized R^2 Score: 0.8427626399684589
まとめ
パイプライン化により データ準備からモデル評価までを一本のコードで再現可能 になります。
Prophet の強みと sklearn エコシステムの柔軟性を両取りできるため、複雑な時系列プロジェクトの開発体験が大幅に向上します。
以上で、Prophet × scikit-learn Pipeline の基本からチューニングまでを駆け足で解説しました。
ぜひご自身の時系列データで試し、体験してみてください。