移動平均のコーディングとstatsmodelsを使った回帰モデルの構築と予測

前回は、時系列データにて連番を振っただけの簡単なダミー変数や1日前のデータを元にしたTime Lagで単純な線形回帰を扱ってきました。

今回は主に移動平均を中心に短期的な要因や季節性の要因を排除したりするなどのアプローチを元に機械学習を行っていきます。

前回同様、Kaggle公式のチュートリアルに準拠した形で進めます
[Trend](https://www.kaggle.com/ryanholbrook/trend)
（今回データの自作は厳しいのでKaggleのデータを用います。
そのため、ここではデータの添付などができないので実際に同じことをしたい場合はこちらからダウンロードやKaggleのnotebookで行ってくださいませ。）

今回ちょっと変な意訳をしている可能性があるので、困った時はやはり参照元の確認をおすすめします。

# Trend（トレンド）とは？
トレンドとは、時系列データが持つ永続性や長期間での変化を平均値などから表現する方法みたいなものです。
トレンドは期間の幅が一番大きいため、時間として一番遅く、一番長い時間軸の重要度（カラムと置き換えてもいいと思います）となります。

# 移動平均の持つ意味
前回は簡単な理論に触れましたが、時系列データでは移動平均がわりと定番のアプローチです。
移動平均を求める意図は、
<B>区間に区切ることで短期的な動き（例えば売り上げが1日だけ非常に小さいとか、株価が急に値下げする、など）の要因を抑えて長期的な変動を確認すること</B>です。

投資とかでも移動平均線が使われ、7, 14, 50, 100日などの区間で色々なアプローチがされています。
（つまり7日区間での値動きを気にしたいのか、もっと長期で投資するために100日移動平均線を見るなどなど）
# 移動平均のコーディング
大体の意味合いはこんなもんなので、早速コーディングしましょう。
（自作しないとか言いつつ、一旦しました）

```
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import seaborn as sns

from scipy import stats


rng = np.random.default_rng()

x = np.linspace(100, 1000, 900)
y = np.round(x + rng.integers(-80, 70, size=900) - rng.integers(0, 150, size=900), 0)
time_series = pd.date_range(start='2001-4-1', periods=900, freq='MS')  # MSは月初めで月ごとにデータ作成（4/1, 5/1,...）
data = pd.DataFrame({'Sales': y}, index=time_series, dtype='int')

moving_avg = data.rolling(
    window=12,
    min_periods=6, 
    center=True, 
).mean()


plt.rc("figure", autolayout=True, figsize=(11, 5))
ax = data.plot(style='.')
moving_avg.plot(
    ax=ax, linewidth=3, title='Moving Average 12-month!'
)
```

![スクリーンショット 2021-10-29 12.04.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1865635/66b5ed22-6d44-585f-8611-a1e1fc92e2f8.png)
自作データなので、ゴニョゴニョしちゃってますが、主題は移動平均なのでお気になさらずw

移動平均はDataFrame.rollingで一発です。
公式ドキュメント: [pandas.DataFrame.rolling](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.rolling.html)

window: 求めたい区間
min_periods: 最初のデータなどはwindowで定めた区間分を確保できないため、最低限これだけ集まれば移動平均を取るという指定
center: データを中心に持ってくる（Falseの場合、index=window番目からデータが入力される。多分実際に見た方が早いと思うので割愛w）

# statsmodelsでさらに踏み込む
さて、前回の内容では、時系列データのダミー変数として、（時系列順にsortされている前提で）上から順番に連番を振ってたカラム`Time`を作成しました。

今回はstatsmodelsを用いたもう少し踏み込んだパターンを使います。

## DeterministicProcess
ベースとなるのは数学の「決定論」があります（語弊を恐れずにいえば、あらかじめ何らかの要因で決まっていること。）
前回の連番を振るのと似た動きをする関数が
`statsmodels.tsa.deterministic.DeterministicProcess`となります。

いわゆるconstants（一定の値）とか時系列の順序、さらにはフーリエ成分とかもあります（時系列データにおけるフーリエ変換やフーリエ解析はこのKaggleチュートリアルにあります。ここで書きたいが理解が。。。）

実際にコードを書いてみます
(※簡単なコードはいつもgoogle colabとかで済ましてるんですが、今回の
`from statsmodels.tsa.deterministic import DeterministicProcess`はずっとエラー出たのでローカルでしました。原因は不明（pip installとかもした）なので、ご注意を〜)

```
from statsmodels.tsa.deterministic import DeterministicProcess


df = DeterministicProcess(
    index=data.index, 
    constant=True, # interceptの設定
    order=1, # ダミー変数の設定（trend。いわゆる連番を振る）
    drop=True # 共線性の排除（完全に一直線にあるなど）
)

df.in_sample()
```
![スクリーンショット 2021-10-29 12.47.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1865635/a3d41455-044f-3a1f-9825-1444882e1a09.png)
公式ドキュメント: [statsmodels.tsa.deterministic.DeterministicProcess](https://www.statsmodels.org/dev/generated/statsmodels.tsa.deterministic.DeterministicProcess.html)

前回のように連番を振りつつ、今回はintercept(切片)の設定して線形回帰の変数作成を行なっています
（order=1: 1次, =2: 二次, =3: 3次、・・・）


### 回帰モデル

```
rng = np.random.default_rng()

x = np.linspace(100, 1000, 900)
y = np.round(x + rng.integers(100, 470, size=900) - rng.integers(0, 150, size=900), 0)
time_series = pd.date_range(start='2001-4-1', periods=900) 

data = pd.DataFrame({'Sales': y}, index=time_series, dtype='int')


df = DeterministicProcess(
    index=data.index, 
    constant=True, 
    order=3 # ダミー変数の設定（trend。いわゆる連番を振る）
)

X = df.in_sample()
y = data['Sales']

model = LinearRegression(fit_intercept=False)
model.fit(X, y)

y_pred = pd.Series(model.predict(X), index=X.index)

ax = data.plot(style='.', title='Linear Reg with Pred')
_ = y_pred.plot(ax=ax, linewidth=3, label="Trend")
```
![スクリーンショット 2021-10-29 14.05.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1865635/6cbc549f-fcaf-21a0-26ad-8c6c7e273855.png)


## 予測
ここから実際は未知な（将来的な）時系列データに対しての予測を行います。
DeterministicProcessには指定したインデックス要素を取り出すin_sampleの他に、そこから整数`steps`だけ次の要素を取り出すout_of_sampleがあります。

少しみてみましょう。

```
df = DeterministicProcess(
    index=data.index, 
    constant=True, 
    order=3 # ダミー変数の設定（trend。いわゆる連番を振る）
)

X = df.in_sample()
display(X.tail())

X_fore = df.out_of_sample(steps=90)
display(X_fore)
```
![スクリーンショット 2021-10-29 14.12.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1865635/f20f21de-0f60-cb11-cacb-a0f1ea231fdd.png)

[statsmodels.tsa.deterministic.DeterministicProcess.out_of_sample](https://www.statsmodels.org/dev/generated/statsmodels.tsa.deterministic.DeterministicProcess.out_of_sample.html)

実際に取り込んだデータの次の日から指定した日数だけ次の時系列データを作成してくれます。

### データ予測
ではこの未知データで予測してみましょう。

```
y_pred = pd.Series(model.predict(X), index=X.index)
y_fore = pd.Series(model.predict(X_fore), index=X_fore.index)

plt.rc('figure', figsize=(11, 8))
ax = data.plot(style='.', title='Linear Reg with Pred')
_ = y_pred.plot(ax=ax, linewidth=3, label="Trend")
_ = y_fore.plot(ax=ax, linewidth=3, label="Forecast", color='b')
```
![スクリーンショット 2021-10-29 14.14.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1865635/0f6b7249-9125-1bf9-f67e-d4fedadc3cc7.png)

実際に次の90日間の予測が青線で表現されています。
まぁ、納得感のある予測かなと思います（実際はちゃんと評価しますが。。）

# 今回参考にさせていただいた記事
[pandasの時系列データにおける頻度（引数freq）の指定方法](https://note.nkmk.me/python-pandas-time-series-freq/)
[Statsmodelsによる時系列分析入門](https://qiita.com/innovation1005/items/6c5263d79ccc67263b2c)