[Python] StatsForecast で時系列アンサンブル学習による予測

[Python] StatsForecast で時系列アンサンブル学習による予測

StatsForecastは、色々な統計学的な時系列系の予測モデルを構築することのできる、Pythonの時系列予測パッケージです。

以前、StatsForecastのインストール方法から簡単な使い方(予測モデル構築とテストなど)を説明しました。

StatsForecastは、複数の時系列予測モデルを作れるということで、時系列アンサンブル学習に最適です。

アンサンブル学習とは、複数の予測モデルを組み合わせ予測するための、予測モデルを構築する学習アプローチです。

今回は、「StatsForecast で時系列アンサンブル学習による予測」というお話しです。

今回実施するアンサンブル学習

今回は、次の2種類の時系列のアンサンブル学習を実施し、予測モデルを構築していきます。

  • アンサンブル(平均値)
  • アンサンブル(重回帰)

アンサンブル(平均値)は、複数の予測モデルを学習し、その予測結果の平均値を計算しそれを最終的な予測結果にするアプローチです。

アンサンブル(重回帰)は、複数の予測モデルを学習し、その予測値を特徴量(説明変数)とした予測モデルを、重回帰モデルで学習しその予測値を最終的な予測結果とするアプローチです。

ちなみに、アンサンブル学習で高精度な予測モデルが構築できる保証はありません。悪化することもあります。

今回利用するデータ

次の2つのデータセットを利用します。

  • AirPassengers(飛行機乗客数)
  • WineSales(ワイン販売数)

AirPassengers(飛行機乗客数)は、Box&Jenkinsの有名なデータで、1949年から1960年までの国際線航空旅客数の月次集計結果です。

 

StatsForecastが、サンプルデータとして提供しているため、それをそのまま使います。

WineSales(ワイン販売数)は、R forecast パッケージに含まれている1980年1月~1994年8月までのワイン販売数の月次集計結果です。

 

Rで提供されているサンプルデータは、statsmodelsのモジュールを使うことでPythonに読み込むことができます。詳細は以下の記事を参考にしてください。

 

予測精度の評価指標

今回利用する予測精度の評価指標は、RMSE二乗平均平方根誤差、Root Mean Squared Error)とMAE平均絶対誤差、Mean Absolute Error)、MAPE平均絶対パーセント誤差、Mean absolute percentage error)の3つです。

以下の記号を使い精度指標の説明をします。

  • y_i^{actual} ・・・i番目の実測値
  • y_i^{pred} ・・・i番目の予測値
  • n ・・・実測値・予測値の数

 

■ 二乗平均平方根誤差RMSE、Root Mean Squared Error)

\sqrt{\frac{1}{n}\sum_{i=1}^n(y_i^{actual}-{y_i^{pred}})^2}

■ 平均絶対誤差MAE、Mean Absolute Error)

\frac{1}{n}\sum_{i=1}^n|y_i^{actual}-{y_i^{pred}}|

■ 平均絶対パーセント誤差MAPE、Mean absolute percentage error)

\frac{1}{n}\sum_{i=1}^n|\frac{y_i^{actual}-{y_i^{pred}}}{y_i^{actual}}|

 

必要なモジュールの読み込み

先ず、利用するモジュールを読み込みます。

以下、コードです。

#
# 必要なモジュールの読み込み
#

import pandas as pd
import numpy as np
import statsmodels.api as sm

from statsforecast import StatsForecast
from statsforecast.models import (
    AutoARIMA,
    HoltWinters,
    AutoTheta,
    AutoETS,
    DynamicOptimizedTheta as DOT,
    SeasonalNaive
)

from sklearn.metrics import mean_absolute_error
from sklearn.metrics import mean_squared_error
from sklearn.metrics import mean_absolute_percentage_error

from sklearn.linear_model import LinearRegression

import matplotlib.pyplot as plt
plt.style.use('ggplot') #グラフのスタイル
plt.rcParams['figure.figsize'] = [12, 9] # グラフサイズ設定

 

では、データセットごとに実施していきます。

 

AirPassengers(飛行機乗客数)

 データの読み込み

データセットを読み込みます。

以下、コードです。

#
# データの読み込み
#

from statsforecast.utils import AirPassengersDF
df = AirPassengersDF

print(df) #確認

 

以下、実行結果です。

     unique_id         ds      y
0          1.0 1949-01-31  112.0
1          1.0 1949-02-28  118.0
2          1.0 1949-03-31  132.0
3          1.0 1949-04-30  129.0
4          1.0 1949-05-31  121.0
..         ...        ...    ...
139        1.0 1960-08-31  606.0
140        1.0 1960-09-30  508.0
141        1.0 1960-10-31  461.0
142        1.0 1960-11-30  390.0
143        1.0 1960-12-31  432.0

[144 rows x 3 columns]

 

学習データとテストデータ(直近12カ月間)へ分割します。

以下、コードです。

#
# 学習データとテストデータ(直近12ヶ月間)へ分割
#

# 学習データ
df_train = df[:-12]

# テストデータ
df_test = df[-12:]

 

学習データを確認してみます。

以下、コードです。

# 学習データの確認
print(df_train)

 

以下、実行結果です。

     unique_id         ds      y
0          1.0 1949-01-31  112.0
1          1.0 1949-02-28  118.0
2          1.0 1949-03-31  132.0
3          1.0 1949-04-30  129.0
4          1.0 1949-05-31  121.0
..         ...        ...    ...
127        1.0 1959-08-31  559.0
128        1.0 1959-09-30  463.0
129        1.0 1959-10-31  407.0
130        1.0 1959-11-30  362.0
131        1.0 1959-12-31  405.0

[132 rows x 3 columns]

 

テストデータを確認してみます。

以下、コードです。

# テストデータの確認
print(df_test)

 

以下、実行結果です。

     unique_id         ds      y
132        1.0 1960-01-31  417.0
133        1.0 1960-02-29  391.0
134        1.0 1960-03-31  419.0
135        1.0 1960-04-30  461.0
136        1.0 1960-05-31  472.0
137        1.0 1960-06-30  535.0
138        1.0 1960-07-31  622.0
139        1.0 1960-08-31  606.0
140        1.0 1960-09-30  508.0
141        1.0 1960-10-31  461.0
142        1.0 1960-11-30  390.0
143        1.0 1960-12-31  432.0

 

グラフ化し確認してみます。

以下、コードです。

# グラフ化

fig, ax = plt.subplots()

ax.plot(df_train.ds, df_train.y, label="actual(train dataset)")
ax.plot(df_test.ds, df_test.y, label="actual(test dataset)")

ax.set_ylim(ymin=0)
ax.legend()

plt.show()

 

以下、実行結果です。

 

 複数モデルの構築

先ず、複数の予測モデルを構築していきます。

そのために、構築する予測モデルのアルゴリズムのリスト(モデルリスト)を作成します。

以下、コードです。

#
# モデルリストの作成
#

PERIOD = 12

models = [
    AutoARIMA(season_length=PERIOD),
    HoltWinters(season_length=PERIOD),
    SeasonalNaive(season_length=PERIOD),
    AutoTheta(season_length=PERIOD),
    DOT(season_length=PERIOD),
    AutoETS(season_length=PERIOD),
]

 

学習します。

以下、コードです。

 

#
# 時系列予測モデルの学習
#

# インスタンス生成
sf = StatsForecast(
    models = models,
    freq = 'M',
    n_jobs=-1,
)

# 学習(学習データ)
sf.fit(df_train)

 

予測を実施します。

以下、コードです。

#
# 予測の実施
#

# テストデータ期間
test_pred = sf.forecast(len(df_test),fitted=True)

# 学習データ期間
train_pred = sf.forecast_fitted_values()

 

テストデータ期間の予測結果を確認します。

以下、コードです。

# テストデータの予測結果の確認
print(test_pred)

 

以下、実行結果です。

                  ds   AutoARIMA  HoltWinters  SeasonalNaive   AutoTheta  \
unique_id                                                                  
1.0       1960-01-31  424.160156   409.680145          360.0  412.711884   
1.0       1960-02-29  407.081696   390.044067          342.0  404.640472   
1.0       1960-03-31  470.860535   448.929810          406.0  466.810883   
1.0       1960-04-30  460.913605   435.445007          396.0  449.600372   
1.0       1960-05-31  484.900879   454.699951          420.0  454.047760   
1.0       1960-06-30  536.903931   507.904968          472.0  517.857727   
1.0       1960-07-31  612.903198   577.622375          548.0  572.380127   
1.0       1960-08-31  623.903381   581.996765          559.0  571.371643   
1.0       1960-09-30  527.903320   479.946136          463.0  502.108032   
1.0       1960-10-31  471.903320   421.522797          407.0  438.527100   
1.0       1960-11-30  426.903320   373.590912          362.0  382.673859   
1.0       1960-12-31  469.903320   410.594971          405.0  432.116119   

           DynamicOptimizedTheta     AutoETS  
unique_id                                     
1.0                   412.697632  406.651276  
1.0                   404.638550  401.732910  
1.0                   466.829071  456.289642  
1.0                   449.643433  440.870514  
1.0                   454.122589  440.333923  
1.0                   517.984802  496.866058  
1.0                   572.572754  545.839111  
1.0                   571.621704  544.672485  
1.0                   502.383270  477.034485  
1.0                   438.819733  412.423096  
1.0                   382.977936  357.949158  
1.0                   432.517731  402.032745

 

テストデータ期間の予測精度を見てみます。

以下、コードです。

#
# 精度指標(テストデータ)
#

for i in range(1,7) :
    print(test_pred.columns[i])
    print('\t'+'RMSE:'+'\t',
          np.sqrt(mean_squared_error(df_test.y, test_pred.iloc[:,i])))
    print('\t'+'MAE:'+'\t',
          mean_absolute_error(df_test.y, test_pred.iloc[:,i]))
    print('\t'+'MAPE:'+'\t',
          mean_absolute_percentage_error(df_test.y, test_pred.iloc[:,i]))

 

以下、実行結果です。

AutoARIMA
	RMSE:	 23.955328353374384
	MAE:	 18.550587972005207
	MAPE:	 0.04187615687144095
HoltWinters
	RMSE:	 26.228497232379365
	MAE:	 23.490142822265625
	MAPE:	 0.048133214753029695
SeasonalNaive
	RMSE:	 50.708316214732804
	MAE:	 47.833333333333336
	MAPE:	 0.09987532920823484
AutoTheta
	RMSE:	 24.985179187610136
	MAE:	 19.35741424560547
	MAPE:	 0.03919984010285481
DynamicOptimizedTheta
	RMSE:	 24.880112665705433
	MAE:	 19.263458251953125
	MAPE:	 0.03901936142148623
AutoETS
	RMSE:	 40.08362053821043
	MAE:	 35.612475077311196
	MAPE:	 0.07206839292461678

 

グラフ化し見てみます。

以下、コードです。

#
# グラフ化
#

# グラフ用データ(学習データにテストデータ期間の予測結果を結合)
pred = pd.concat([df_train,test_pred]).drop(['y','unique_id','ds'], axis=1)
pred.index = df.ds

# グラフ描写
fig, ax = plt.subplots()

ax.plot(df_train.ds, df_train.y, label="actual(train dataset)")
ax.plot(df_test.ds, df_test.y, label="actual(test dataset)")
pred.plot(ax=ax, linestyle="dotted", lw=2)

ax.set_ylim(ymin=0)
ax.legend()

plt.show()

 

以下、実行結果です。

 

テストデータ期間のみ見てみます。

以下、コードです。

#
# グラフ化
#

# グラフ用データ(テストデータ期間のみ抽出)
pred_test = pred[-12:]

# グラフ描写
fig, ax = plt.subplots()

ax.plot(df_test.ds, df_test.y, label="actual(test dataset)")
pred_test.plot(ax=ax, linestyle="dotted", lw=2)

ax.set_ylim(ymin=0)
ax.legend()

plt.show()

 

以下、実行結果です。

 

 アンサンブル(平均値)

複数の予測モデルから出力された予測値の平均値を計算していきます。それが予測結果です。

以下、コードです。

#
# 予測値の平均(予測結果)
#

y_test_pred = test_pred.iloc[:,1:7].mean(axis=1)

y_train_pred = train_pred.iloc[:,2:8].mean(axis=1)

 

テストデータの予測結果を確認します。

以下、コードです。

# テストデータの予測結果の確認
print(pd.DataFrame(y_test_pred.values,index=test_pred.ds))

 

以下、実行結果です。

                     0
ds                    
1960-01-31  404.316864
1960-02-29  391.689606
1960-03-31  452.619965
1960-04-30  438.745514
1960-05-31  451.350861
1960-06-30  508.252899
1960-07-31  571.552917
1960-08-31  575.427673
1960-09-30  492.062531
1960-10-31  431.699341
1960-11-30  381.015869
1960-12-31  425.360809

 

テストデータ期間の予測精度を見てみます。

以下、コードです。

#
# 精度指標(テストデータ)
#

print('RMSE:')
print(np.sqrt(mean_squared_error(df_test.y, y_test_pred)))
print('MAE:')
print(mean_absolute_error(df_test.y, y_test_pred))
print('MAPE:')
print(mean_absolute_percentage_error(df_test.y, y_test_pred))

 

以下、実行結果です。

RMSE:
25.241494178068326
MAE:
21.543690999348957
MAPE:
0.0432771029010986

 

それほど悪くはありませんが、特別良いとも言えません。

グラフ化し見てみます。

以下、コードです。

#
# グラフ化
#

fig, ax = plt.subplots()

ax.plot(df_train.ds, df_train.y, label="actual(train dataset)")
ax.plot(df_test.ds, df_test.y, label="actual(test dataset)")
ax.plot(df_train.ds, y_train_pred, linestyle="dotted", lw=2,color="m")
ax.plot(df_test.ds, y_test_pred, label="Ensemble", linestyle="dotted", lw=2, color="m") 

ax.set_ylim(ymin=0)
ax.legend()

plt.show()

 

以下、実行結果です。

 

テストデータ期間のみ見てみます。

以下、コードです。

#
# グラフ化
#

fig, ax = plt.subplots()

ax.plot(df_test.ds, df_test.y, label="actual(test dataset)")
ax.plot(df_test.ds, y_test_pred, label="Ensemble", linestyle="dotted", lw=2, color="m") 

ax.set_ylim(ymin=0)
ax.legend()

plt.show()

 

以下、実行結果です。

 

 アンサンブル(重回帰)

複数の予測モデルから出力された予測値を特徴量(説明変数)とした重回帰モデルを学習します。

先ず、重回帰モデル用のデータセットを生成します。

以下、コードです。

#
# 重回帰モデル用のデータセット生成
#

dataset = pd.concat([train_pred,test_pred])
dataset = dataset.set_index('ds')
dataset['y'] = df.set_index('ds').y
dataset = dataset.dropna()

print(dataset)

 

以下、実行結果です。

                y   AutoARIMA  HoltWinters  SeasonalNaive   AutoTheta  \
ds                                                                      
1950-01-31  115.0  115.426987   109.280937          112.0  121.963615   
1950-02-28  126.0  121.146469   118.884819          118.0  115.647797   
1950-03-31  141.0  138.800049   127.466965          132.0  144.786926   
1950-04-30  135.0  137.760010   130.324829          129.0  138.185623   
1950-05-31  125.0  127.719971   137.544403          121.0  138.648422   
...           ...         ...          ...            ...         ...   
1960-08-31  606.0  623.903381   581.996765          559.0  571.371643   
1960-09-30  508.0  527.903320   479.946136          463.0  502.108032   
1960-10-31  461.0  471.903320   421.522797          407.0  438.527100   
1960-11-30  390.0  426.903320   373.590912          362.0  382.673859   
1960-12-31  432.0  469.903320   410.594971          405.0  432.116119   

            DynamicOptimizedTheta     AutoETS  
ds                                             
1950-01-31             119.328331  117.541862  
1950-02-28             112.951286  115.125816  
1950-03-31             142.454269  135.798752  
1950-04-30             135.978622  133.337097  
1950-05-31             136.520020  133.833603  
...                           ...         ...  
1960-08-31             571.621704  544.672485  
1960-09-30             502.383270  477.034485  
1960-10-31             438.819733  412.423096  
1960-11-30             382.977936  357.949158  
1960-12-31             432.517731  402.032745  

[132 rows x 7 columns]

 

目的変数yと説明変数Xに分けます。

以下、コードです。

# 目的変数y
y = dataset.y

# 説明変数X
X = dataset.drop('y',axis=1)

 

目的変数yを確認してみます。

以下、コードです。

# 目的変数yの確認
print(y)

 

以下、実行結果です。

ds
1950-01-31    115.0
1950-02-28    126.0
1950-03-31    141.0
1950-04-30    135.0
1950-05-31    125.0
              ...  
1960-08-31    606.0
1960-09-30    508.0
1960-10-31    461.0
1960-11-30    390.0
1960-12-31    432.0
Name: y, Length: 132, dtype: float64

 

説明変数Xを確認してみます。

以下、コードです。

# 説明変数Xの確認
print(X)

 

以下、実行結果です。

             AutoARIMA  HoltWinters  SeasonalNaive   AutoTheta  \
ds                                                               
1950-01-31  115.426987   109.280937          112.0  121.963615   
1950-02-28  121.146469   118.884819          118.0  115.647797   
1950-03-31  138.800049   127.466965          132.0  144.786926   
1950-04-30  137.760010   130.324829          129.0  138.185623   
1950-05-31  127.719971   137.544403          121.0  138.648422   
...                ...          ...            ...         ...   
1960-08-31  623.903381   581.996765          559.0  571.371643   
1960-09-30  527.903320   479.946136          463.0  502.108032   
1960-10-31  471.903320   421.522797          407.0  438.527100   
1960-11-30  426.903320   373.590912          362.0  382.673859   
1960-12-31  469.903320   410.594971          405.0  432.116119   

            DynamicOptimizedTheta     AutoETS  
ds                                             
1950-01-31             119.328331  117.541862  
1950-02-28             112.951286  115.125816  
1950-03-31             142.454269  135.798752  
1950-04-30             135.978622  133.337097  
1950-05-31             136.520020  133.833603  
...                           ...         ...  
1960-08-31             571.621704  544.672485  
1960-09-30             502.383270  477.034485  
1960-10-31             438.819733  412.423096  
1960-11-30             382.977936  357.949158  
1960-12-31             432.517731  402.032745  

[132 rows x 6 columns]

 

学習データとテストデータ(直近12カ月間)へ分割します。

以下、コードです。

#
# 学習データとテストデータ(直近12ヶ月間)へ分割
#

# 学習データ
y_train = y[:-12]
X_train = X[:-12]

# テストデータ
y_test = y[-12:]
X_test = X[-12:]

 

学習データを使い、重回帰モデルを学習します。

以下、コードです。

#
# 学習
#

model = LinearRegression()
model.fit(X_train, y_train)

 

学習した重回帰モデルを使い、予測します。

以下、コードです。

#
# 予測
#

# テストデータ期間
y_test_pred = model.predict(X_test)

# 学習データ期間
y_train_pred = model.predict(X_train)

 

テストデータの予測結果を確認します。

以下、コードです。

# テストデータの予測結果の確認
print(pd.DataFrame(y_test_pred,index=X_test.index))

 

以下、実行結果です。

                     0
ds                    
1960-01-31  414.696777
1960-02-29  399.045380
1960-03-31  466.506775
1960-04-30  449.620483
1960-05-31  464.420593
1960-06-30  529.268372
1960-07-31  596.430420
1960-08-31  598.346741
1960-09-30  510.280518
1960-10-31  447.608917
1960-11-30  392.959106
1960-12-31  441.324249

 

テストデータ期間の予測精度を見てみます。

以下、コードです。

#
# 精度指標(テストデータ)
#

print('RMSE:')
print(np.sqrt(mean_squared_error(y_test, y_test_pred)))
print('MAE:')
print(mean_absolute_error(y_test, y_test_pred))
print('MAPE:')
print(mean_absolute_percentage_error(y_test, y_test_pred))

 

以下、実行結果です。

RMSE:
17.173901214177544
MAE:
11.976977030436197
MAPE:
0.025615260706653386

 

かなり予測精度が良くなりました。

グラフ化し見てみます。

以下、コードです。

#
# グラフ化
#

fig, ax = plt.subplots()

ax.plot(y_train, label="actual(train dataset)")
ax.plot(y_test, label="actual(test dataset)")
ax.plot(y_train.index, y_train_pred, linestyle="dotted", lw=2,color="m")
ax.plot(y_test.index, y_test_pred, label="Ensemble", linestyle="dotted", lw=2, color="m") 

ax.set_ylim(ymin=0)
ax.legend()

plt.show()

 

以下、実行結果です。

 

テストデータ期間のみ見てみます。

以下、コードです。

#
# グラフ化
#

fig, ax = plt.subplots()

ax.plot(df_test.ds, df_test.y, label="actual(test dataset)")
ax.plot(df_test.ds, y_test_pred, label="Ensemble", linestyle="dotted", lw=2, color="m") 

ax.set_ylim(ymin=0)
ax.legend()

plt.show()

 

以下、実行結果です。

 

WineSales(ワイン販売数)

 データの読み込み

データセットを読み込みます。

以下、コードです。

#
# データの読み込み
#

dataset = sm.datasets.get_rdataset('wineind', 'forecast')

print(dataset.data) #確認

 

以下、実行結果です。

            time  value
0    1980.000000  15136
1    1980.083333  16733
2    1980.166667  20016
3    1980.250000  17708
4    1980.333333  18019
..           ...    ...
171  1994.250000  26323
172  1994.333333  23779
173  1994.416667  27549
174  1994.500000  29660
175  1994.583333  23356

[176 rows x 2 columns]

 

StatsForecastで扱える形式に整えます。

以下、コードです。

#
# StatsForecastで扱える形式に整える
#

df = pd.DataFrame(
    {
        'unique_id':1,
        'ds': pd.date_range(start="1980/1/1",freq="m",periods=176),
        'y': dataset.data.value,
    },
)

print(df) #確認

 

以下、実行結果です。

     unique_id         ds      y
0            1 1980-01-31  15136
1            1 1980-02-29  16733
2            1 1980-03-31  20016
3            1 1980-04-30  17708
4            1 1980-05-31  18019
..         ...        ...    ...
171          1 1994-04-30  26323
172          1 1994-05-31  23779
173          1 1994-06-30  27549
174          1 1994-07-31  29660
175          1 1994-08-31  23356

[176 rows x 3 columns]

 

学習データとテストデータ(直近12カ月間)へ分割します。

以下、コードです。

#
# 学習データとテストデータ(直近12カ月間)へ分割
#

# 学習データ
df_train = df[:-12]

# テストデータ
df_test = df[-12:]

 

学習データを確認してみます。

以下、コードです。

# 学習データの確認
print(df_train)

 

以下、実行結果です。

 

テストデータを確認してみます。

以下、コードです。

# テストデータの確認
print(df_test)

 

以下、実行結果です。

     unique_id         ds      y
164          1 1993-09-30  22724
165          1 1993-10-31  28496
166          1 1993-11-30  32857
167          1 1993-12-31  37198
168          1 1994-01-31  13652
169          1 1994-02-28  22784
170          1 1994-03-31  23565
171          1 1994-04-30  26323
172          1 1994-05-31  23779
173          1 1994-06-30  27549
174          1 1994-07-31  29660
175          1 1994-08-31  23356

 

グラフ化し確認してみます。

以下、コードです。

# グラフ化

fig, ax = plt.subplots()

ax.plot(df_train.ds, df_train.y, label="actual(train dataset)")
ax.plot(df_test.ds, df_test.y, label="actual(test dataset)")

ax.axes.xaxis.set_visible(False)
ax.set_ylim(ymin=0)
ax.legend()

plt.show()

 

以下、実行結果です。

 

 複数モデルの構築

先ず、複数の予測モデルを構築していきます。

そのために、構築する予測モデルのアルゴリズムのリスト(モデルリスト)を作成します。

以下、コードです。

#
# モデルリストの作成
#

PERIOD = 12

models = [
    AutoARIMA(season_length=PERIOD),
    HoltWinters(season_length=PERIOD),
    SeasonalNaive(season_length=PERIOD),
    AutoTheta(season_length=PERIOD),
    DOT(season_length=PERIOD),
    AutoETS(season_length=PERIOD),
]

 

学習します。

以下、コードです。

#
# 時系列予測モデルの学習
#

# インスタンス生成
sf = StatsForecast(
    models = models,
    freq = 'M',
    n_jobs=-1,
)

# 学習(学習データ)
sf.fit(df_train)

 

予測を実施します。

以下、コードです。

#
# 予測の実施
#

# テストデータ期間
test_pred = sf.forecast(len(df_test),fitted=True)

# 学習データ期間
train_pred = sf.forecast_fitted_values()

 

テストデータ期間の予測結果を確認します。

以下、コードです。

# テストデータの予測結果の確認
print(test_pred)

 

以下、実行結果です。

                  ds     AutoARIMA   HoltWinters  SeasonalNaive     AutoTheta  \
unique_id                                                                       
1         1993-09-30  25720.443359  25806.755859        25156.0  25328.417969   
1         1993-10-31  26873.044922  27066.654297        25650.0  26693.591797   
1         1993-11-30  32086.277344  32444.990234        30923.0  31901.193359   
1         1993-12-31  37347.332031  37168.160156        37240.0  36773.554688   
1         1994-01-31  18447.267578  19518.896484        17466.0  18190.744141   
1         1994-02-28  21157.449219  22328.550781        19463.0  21149.164062   
1         1994-03-31  24839.212891  25611.074219        24352.0  24453.267578   
1         1994-04-30  26366.576172  26311.923828        26805.0  25261.683594   
1         1994-05-31  25126.273438  25911.855469        25236.0  24701.000000   
1         1994-06-30  24880.539062  25576.244141        24735.0  24293.765625   
1         1994-07-31  30162.029297  30888.460938        29356.0  29634.562500   
1         1994-08-31  30170.083984  30876.755859        31234.0  29544.162109   

           DynamicOptimizedTheta       AutoETS  
unique_id                                       
1                   25353.738281  25200.136719  
1                   26719.343750  26651.212891  
1                   31930.863281  31660.291016  
1                   36806.496094  36425.386719  
1                   18206.416016  18212.419922  
1                   21166.671875  21052.523438  
1                   24472.685547  24393.921875  
1                   25280.900391  25375.265625  
1                   24718.972656  24501.708984  
1                   24310.640625  24180.208984  
1                   29654.179688  29449.763672  
1                   29562.757812  29938.888672

 

学習データ期間の予測結果を確認します。

以下、コードです。

# 学習データの予測結果の確認
print(train_pred)

 

以下、実行結果です。

                  ds        y     AutoARIMA   HoltWinters  SeasonalNaive  \
unique_id                                                                  
1         1980-01-31  15136.0  15127.260742  12973.968750            NaN   
1         1980-02-29  16733.0  16727.855469  15914.354492            NaN   
1         1980-03-31  20016.0  20009.857422  19257.726562            NaN   
1         1980-04-30  17708.0  17705.457031  20022.988281            NaN   
1         1980-05-31  18019.0  18016.689453  19488.566406            NaN   
...              ...      ...           ...           ...            ...   
1         1993-04-30  26805.0  24846.925781  24965.792969        23757.0   
1         1993-05-31  25236.0  24363.072266  24650.830078        25013.0   
1         1993-06-30  24735.0  23898.470703  24332.656250        24019.0   
1         1993-07-31  29356.0  29871.386719  29655.939453        30345.0   
1         1993-08-31  31234.0  27069.568359  29610.015625        24488.0   

              AutoTheta  DynamicOptimizedTheta       AutoETS  
unique_id                                                     
1          15136.000000           15136.000000  14614.176758  
1          17591.060547           16819.916016  16969.451172  
1          19290.421875           18583.732422  19627.498047  
1          20545.875000           19840.787109  20476.300781  
1          18619.939453           18108.027344  19422.121094  
...                 ...                    ...           ...  
1          24550.865234           24536.003906  24657.693359  
1          24228.806641           24232.750000  24076.099609  
1          23929.789062           23940.388672  23907.376953  
1          29290.353516           29308.763672  29248.238281  
1          29208.222656           29224.103516  29745.482422  

[164 rows x 8 columns]

 

テストデータ期間の予測精度を見てみます。

以下、コードです。

#
# 精度指標(テストデータ)
#

for i in range(1,7) :
    print(test_pred.columns[i])
    print('\t'+'RMSE:'+'\t',
          np.sqrt(mean_squared_error(df_test.y, test_pred.iloc[:,i])))
    print('\t'+'MAE:'+'\t',
          mean_absolute_error(df_test.y, test_pred.iloc[:,i]))
    print('\t'+'MAPE:'+'\t',
          mean_absolute_percentage_error(df_test.y, test_pred.iloc[:,i]))

 

以下、実行結果です。

AutoARIMA
	RMSE:	 2815.3500207871384
	MAE:	 2050.9090169270835
	MAPE:	 0.09640424878808153
HoltWinters
	RMSE:	 3123.0757960277006
	MAE:	 2182.3562825520835
	MAPE:	 0.1050727559379744
SeasonalNaive
	RMSE:	 3114.2218798066824
	MAE:	 2342.5833333333335
	MAPE:	 0.10455805468090233
AutoTheta
	RMSE:	 2677.908377817492
	MAE:	 2025.0896809895833
	MAPE:	 0.0936110047613043
DynamicOptimizedTheta
	RMSE:	 2680.885301527278
	MAE:	 2019.7062174479167
	MAPE:	 0.09354213357886888
AutoETS
	RMSE:	 2771.792032215696
	MAE:	 2103.61865234375
	MAPE:	 0.09614448379415981

 

グラフ化し見てみます。

以下、コードです。

#
# グラフ化
#

# グラフ用データ(学習データにテストデータ期間の予測結果を結合)
pred = pd.concat([df_train,test_pred]).drop(['y','unique_id','ds'], axis=1)
pred.index = df.ds

# グラフ描写
fig, ax = plt.subplots()

ax.plot(df_train.ds, df_train.y, label="actual(train dataset)")
ax.plot(df_test.ds, df_test.y, label="actual(test dataset)")
pred.plot(ax=ax, linestyle="dotted", lw=2)

ax.axes.xaxis.set_visible(False)
ax.set_ylim(ymin=0)
ax.legend()

plt.show()

 

以下、実行結果です。

 

テストデータ期間のみ見てみます。

以下、コードです。

#
# グラフ化
#

# グラフ用データ(テストデータ期間のみ抽出)
pred_test = pred[-12:]

# グラフ描写
fig, ax = plt.subplots()

ax.plot(df_test.ds, df_test.y, label="actual(test dataset)")
pred_test.plot(ax=ax, linestyle="dotted", lw=2)

ax.set_ylim(ymin=0)
ax.legend()

plt.show()

 

以下、実行結果です。

 

 アンサンブル(平均値)

複数の予測モデルから出力された予測値の平均値を計算していきます。それが予測結果です。

以下、コードです。

#
# 予測値の平均を計算
#

y_test_pred = test_pred.iloc[:,1:7].mean(axis=1)

y_train_pred = train_pred.iloc[:,2:8].mean(axis=1)

 

テストデータの予測結果を確認します。

以下、コードです。

# テストデータの予測結果の確認
print(pd.DataFrame(y_test_pred.values,index=test_pred.ds))

 

以下、実行結果です。

                       0
ds                      
1993-09-30  25427.580078
1993-10-31  26608.976562
1993-11-30  31824.437500
1993-12-31  36960.156250
1994-01-31  18340.291016
1994-02-28  21052.892578
1994-03-31  24687.025391
1994-04-30  25900.224609
1994-05-31  25032.632812
1994-06-30  24662.732422
1994-07-31  29857.500000
1994-08-31  30221.109375

 

テストデータ期間の予測精度を見てみます。

以下、コードです。

#
# 精度指標(テストデータ)
#

print('RMSE:')
print(np.sqrt(mean_squared_error(df_test.y, y_test_pred)))
print('MAE:')
print(mean_absolute_error(df_test.y, y_test_pred))
print('MAPE:')
print(mean_absolute_percentage_error(df_test.y, y_test_pred))

 

以下、実行結果です。

RMSE:
2820.7314648500337
MAE:
2085.6432291666665
MAPE:
0.09701371570832255

 

それほど悪くはありませんが、特別良いとも言えません。

グラフ化し見てみます。

以下、コードです。

#
# グラフ化
#

fig, ax = plt.subplots()

ax.plot(df_train.ds, df_train.y, label="actual(train dataset)")
ax.plot(df_test.ds, df_test.y, label="actual(test dataset)")
ax.plot(df_train.ds, y_train_pred, linestyle="dotted", lw=2,color="m")
ax.plot(df_test.ds, y_test_pred, label="Ensemble", linestyle="dotted", lw=2, color="m") 

ax.set_ylim(ymin=0)
ax.legend()

plt.show()

 

以下、実行結果です。

 

テストデータ期間のみ見てみます。

以下、コードです。

#
# グラフ化
#

fig, ax = plt.subplots()

ax.plot(df_test.ds, df_test.y, label="actual(test dataset)")
ax.plot(df_test.ds, y_test_pred, label="Ensemble", linestyle="dotted", lw=2, color="m") 

ax.set_ylim(ymin=0)
ax.legend()

plt.show()

 

以下、実行結果です。

 

 アンサンブル(重回帰)

複数の予測モデルから出力された予測値を特徴量(説明変数)とした重回帰モデルを学習します。

先ず、重回帰モデル用のデータセットを生成します。

以下、コードです。

#
# 重回帰モデル用のデータセット生成
#

dataset = pd.concat([train_pred,test_pred])
dataset = dataset.set_index('ds')
dataset['y'] = df.set_index('ds').y
dataset = dataset.dropna()

print(dataset)

 

以下、実行結果です。

                y     AutoARIMA   HoltWinters  SeasonalNaive     AutoTheta  \
ds                                                                           
1981-09-30  20960  23087.634766  21782.914062        21133.0  22079.109375   
1981-10-31  22254  24215.552734  23026.416016        22591.0  23135.904297   
1981-11-30  27392  28287.804688  28387.013672        26786.0  27539.898438   
1981-12-31  29945  31063.466797  33065.714844        29740.0  31769.185547   
1982-01-31  16933  16742.128906  15225.994141        15028.0  15608.688477   
...           ...           ...           ...            ...           ...   
1994-04-30  26323  26366.576172  26311.923828        26805.0  25261.683594   
1994-05-31  23779  25126.273438  25911.855469        25236.0  24701.000000   
1994-06-30  27549  24880.539062  25576.244141        24735.0  24293.765625   
1994-07-31  29660  30162.029297  30888.460938        29356.0  29634.562500   
1994-08-31  23356  30170.085938  30876.755859        31234.0  29544.162109   

            DynamicOptimizedTheta       AutoETS  
ds                                               
1981-09-30           21948.890625  21448.113281  
1981-10-31           23008.423828  22619.792969  
1981-11-30           27398.462891  26811.078125  
1981-12-31           31619.164062  30927.970703  
1982-01-31           15538.637695  15405.576172  
...                           ...           ...  
1994-04-30           25280.900391  25375.265625  
1994-05-31           24718.972656  24501.708984  
1994-06-30           24310.640625  24180.208984  
1994-07-31           29654.179688  29449.763672  
1994-08-31           29562.757812  29938.888672  

[156 rows x 7 columns]

 

目的変数yと説明変数Xに分けます。

以下、コードです。

# 目的変数y
y = dataset.y

# 説明変数X
X = dataset.drop('y',axis=1)

 

目的変数yを確認してみます。

以下、コードです。

# 目的変数yの確認
print(y)

 

以下、実行結果です。

ds
1981-09-30    20960
1981-10-31    22254
1981-11-30    27392
1981-12-31    29945
1982-01-31    16933
              ...  
1994-04-30    26323
1994-05-31    23779
1994-06-30    27549
1994-07-31    29660
1994-08-31    23356
Name: y, Length: 156, dtype: int64

 

説明変数Xを確認してみます。

以下、コードです。

# 説明変数Xの確認
print(X)

 

以下、実行結果です。

               AutoARIMA   HoltWinters  SeasonalNaive     AutoTheta  \
ds                                                                    
1981-09-30  23087.634766  21782.914062        21133.0  22079.109375   
1981-10-31  24215.552734  23026.416016        22591.0  23135.904297   
1981-11-30  28287.804688  28387.013672        26786.0  27539.898438   
1981-12-31  31063.466797  33065.714844        29740.0  31769.185547   
1982-01-31  16742.128906  15225.994141        15028.0  15608.688477   
...                  ...           ...            ...           ...   
1994-04-30  26366.576172  26311.923828        26805.0  25261.683594   
1994-05-31  25126.273438  25911.855469        25236.0  24701.000000   
1994-06-30  24880.539062  25576.244141        24735.0  24293.765625   
1994-07-31  30162.029297  30888.460938        29356.0  29634.562500   
1994-08-31  30170.085938  30876.755859        31234.0  29544.162109   

            DynamicOptimizedTheta       AutoETS  
ds                                               
1981-09-30           21948.890625  21448.113281  
1981-10-31           23008.423828  22619.792969  
1981-11-30           27398.462891  26811.078125  
1981-12-31           31619.164062  30927.970703  
1982-01-31           15538.637695  15405.576172  
...                           ...           ...  
1994-04-30           25280.900391  25375.265625  
1994-05-31           24718.972656  24501.708984  
1994-06-30           24310.640625  24180.208984  
1994-07-31           29654.179688  29449.763672  
1994-08-31           29562.757812  29938.888672  

[156 rows x 6 columns]

 

学習データとテストデータ(直近12カ月間)へ分割します。

以下、コードです。

#
# 学習データとテストデータ(直近12ヶ月間)へ分割
#

# 学習データ
y_train = y[:-12]
X_train = X[:-12]

# テストデータ
y_test = y[-12:]
X_test = X[-12:]

 

学習データを使い、重回帰モデルを学習します。

以下、コードです。

#
# 学習
#

model = LinearRegression()
model.fit(X_train, y_train)

 

学習した重回帰モデルを使い、予測します。

以下、コードです。

#
# 予測
#

# テストデータ期間
y_test_pred = model.predict(X_test)

# 学習データ期間
y_train_pred = model.predict(X_train)

 

テストデータの予測結果を確認します。

以下、コードです。

# テストデータの予測結果の確認
print(pd.DataFrame(y_test_pred,index=X_test.index))

 

以下、実行結果です。

                       0
ds                      
1993-09-30  25662.535156
1993-10-31  26629.332031
1993-11-30  31795.601562
1993-12-31  36871.882812
1994-01-31  19152.087891
1994-02-28  21589.574219
1994-03-31  25312.744141
1994-04-30  26453.144531
1994-05-31  25838.560547
1994-06-30  25389.544922
1994-07-31  30244.296875
1994-08-31  31085.597656

 

テストデータ期間の予測精度を見てみます。

以下、コードです。

#
# 精度指標(テストデータ)
#

print('RMSE:')
print(np.sqrt(mean_squared_error(y_test, y_test_pred)))
print('MAE:')
print(mean_absolute_error(y_test, y_test_pred))
print('MAPE:')
print(mean_absolute_percentage_error(y_test, y_test_pred))

 

以下、実行結果です。

RMSE:
3123.761658790124
MAE:
2274.8359375
MAPE:
0.10716243803860785

 

悪くはないですが、それほど良い感じでもありません。

グラフ化し見てみます。

以下、コードです。

#
# グラフ化
#

fig, ax = plt.subplots()

ax.plot(y_train, label="actual(train dataset)")
ax.plot(y_test, label="actual(test dataset)")
ax.plot(y_train.index, y_train_pred, linestyle="dotted", lw=2,color="m")
ax.plot(y_test.index, y_test_pred, label="Ensemble", linestyle="dotted", lw=2, color="m") 

ax.set_ylim(ymin=0)
ax.legend()

plt.show()

 

以下、実行結果です。

 

テストデータ期間のみ見てみます。

以下、コードです。

#
# グラフ化
#

fig, ax = plt.subplots()

ax.plot(df_test.ds, df_test.y, label="actual(test dataset)")
ax.plot(df_test.ds, y_test_pred, label="Ensemble", linestyle="dotted", lw=2, color="m") 

ax.set_ylim(ymin=0)
ax.legend()

plt.show()

 

以下、実行結果です。

 

まとめ

今回は、「StatsForecast で時系列アンサンブル学習による予測」というお話しをしました。

StatsForecastは簡単に多くの種類の統計学系の時系列予測モデルをさくっと作れ、それを使いアンサンブルな予測モデルもさくっと作れます。

興味のある方は、試してみてください。