LoginSignup
1
3

More than 1 year has passed since last update.

SARIMAモデルでのTeamsレスポンス予測

Last updated at Posted at 2021-09-25

この記事について

Aidemyさんの卒業ブログのネタとして素人が時系列分析したものです。
車の運転でいうと、仮免で公道を走っているようなものなので、何かの拍子に本ページにいらっしゃった方は話半分に見て頂ければと思います。
理論等は置いといて、身近なデータでSARIMAモデルで予測をしてみた、、というものです。

分析環境

Google Colab

分析データについて

ThousandeyesでTeams 'https://teams.microsoft.com' のhttpレスポンスを測定し、当該データでSARIMAモデルを構築。(ログ取得方法
期間:9/9 ~ 9/19の約10日
測定間隔:1分間隔
データ

ログは以下の処理を実施し、分析用データとする。
生データをそのまま利用するとGoogle Colabが非常に重くなり、分析が終わらなかったため、30分単位で集約。(おそらく周期性が1400と大きくなったことが原因かと)

df.index = pd.date_range(start="2021-09-09 02:04:00", freq= '1Min', periods=14885)
df = df.dropna()
df_30T = df_d.resample('30T').mean()

可視化によるパラメーター推測

まずはデータを可視化することで、何となくパラメーターを推測してみます。

周期性(s値)
自己相関から概ね48の周期があることが分かりました。30分間隔に集約しているので、単純に24時間周期ですね。。
p値/q値
偏自己相関からは、AR(p)が2 or 3になりそうな事が想定できます。
MA(q)は自己相関から想定できることもあるようですが、、このデータからはちょっと判断つかないですね。
(MA(q)が想定しやすい自己相関では、qを超える周期で急激なカットオフが見えるようです)

import matplotlib.pyplot as plt
import statsmodels.api as sm

fig = plt.figure(figsize=(12,8))
ax1 = fig.add_subplot(211)
sm.graphics.tsa.plot_acf(df_30T["teams"], lags=100, ax=ax1)
ax2 = fig.add_subplot(212)
sm.graphics.tsa.plot_pacf(df_30T["teams"], lags=100, ax=ax2)
plt.show()

image.png

d値
原系列データは以下の通り非定常性になります。
ここではSARIMAのIに当たる部分のd値想定のため、何回差分をとればデータが定常性をもつか?確認してみます。

fig = sm.tsa.seasonal_decompose(df_30T['teams'], freq=48).plot()
plt.show()

image.png

1回差分を取ると以下のようになります。
ヒストグラムは釣鐘型になり、時系列プロットも平均値を中心に変動しているように見えるので、、正確ではないですが、1回差分を取ることで弱定常性を持つと想定されます。
つまりd値が1と想定できました。

df_30T_diff = df_30T.diff()
df_30T_diff = df_30T_diff.dropna()

image.png

検定/計算によるパラメーター推測

可視化によるパラメーター推測により設定値に当たりがつけられたので、以下では検定や計算による最適値を確認します。
(同じようなパラメータ推測を可視化や計算で実施しているので、冗長に感じると思います。私もまだ素人なので「こうだ」と言い切れませんが、、検定によるパラメータより、可視化やドメイン知識により想定したパラメータでの分析の方が当てはまりが良いことがあります。今回のデータでもそうだったので、パラメータの推測はいろいろな方法で実施し、一番説明力のあるモデルを検討するのが良いかと思います)

ADF検定
トレンド項なし/定数項ありでP値が有意水準(最低値)になりました。
これにより計算上もd値が1と確認できました。
また解析パラメーターとしてトレンドも含めなくてよさそうです。

# 1回差分
df_30T_diff = df_30T.diff()
df_30T_diff = df_30T_diff.dropna()

res_ctt = sm.tsa.stattools.adfuller(df_30T_diff.teams, regression="ctt") # トレンド項あり(2次)、定数項あり
res_ct = sm.tsa.stattools.adfuller(df_30T_diff.teams, regression="ct") # トレンド項あり(1次)、定数項あり
res_c = sm.tsa.stattools.adfuller(df_30T_diff.teams, regression="c") # トレンド項なし、定数項あり
res_nc = sm.tsa.stattools.adfuller(df_30T_diff.teams, regression="nc") # トレンド項なし、定数項なし
print(res_ctt)
print(res_ct)
print(res_c)
print(res_nc)

#結果
(-12.216938254044267, 7.265439656096006e-20, 4, 488, {'1%': -4.395029594877641, '5%': -3.844545309330682, '10%': -3.5607819532881466}, 4588.7577776844255)
(-12.228550456539015, 1.67282250245363e-19, 4, 488, {'1%': -3.9774419619548964, '5%': -3.4195250551746343, '10%': -3.132365034855616}, 4586.806047340068)
(-12.240938773011283, 1.0029394404518871e-22, 4, 488, {'1%': -3.4438213751870337, '5%': -2.867480869596464, '10%': -2.5699342544006987}, 4584.81379925971)
(-12.253299279971273, 2.3712332309428833e-22, 4, 488, {'1%': -2.5703367876578875, '5%': -1.941564271274702, '10%': -1.6162869159188984}, 4582.824475630648)

ARMA自動パラメータ推測
上記でトレンドパラメータが"c"と分かったので、以下でARMAのp,q値を推測します。
p=2、q=1でaicが最小となったので、これが最適値のようです。
理論は、、勉強中です。。

sm.tsa.arma_order_select_ic(df_30T['teams'].diff().dropna(), ic='aic', trend='c')

#結果
{'aic':              0            1            2
 0  4799.765552  4776.893909  4778.729730
 1  4777.887738  4755.262174  4749.763251
 2  4779.560086  4748.691379  4749.500845
 3  4778.929904  4749.548725  4750.901277
 4  4779.645414  4751.548431  4752.577441, 'aic_min_order': (2, 1)}

SARIMAパラメーターの総当たり検証
以下はAidemyさんのコードそのままです。
上で推測したp,d,q,s値以外のsp,sd,sqを総当たり計算。
ここではbicが最小になるパラメーターが最適値になるようです。。が、

あれ?p値が1、d値が0になっちゃいましたね。
SARIMAではARIMAに周期性の要素が回帰されるので得ることかな。。

ARIMAで推測していた際は(2, 1, 1)だったので、数式は以下になってましたが、
$$Y_t = u + a_1*Y_{t-1} + a_2*Y_{t-2} + e_t + \theta_1*e_{t-1}$$

SARIMAでは(1, 0, 1), (0, 1, 1, 48)となり、48周期前のデータが回帰されることになるので、AR項の$Y_{t-2}$を回帰するより、48周期前のMA項を回帰させた方が、計算上は適しているという感じでしょうか。
$$Y_t = u + a_1*Y_{t-1} + e_t + \theta_1*e_{t-1} + \theta_2*e_{t-48}$$

d値に関しては、原系列の1階差より、48周期前の1階差の方が計算上適している、、と言ったところかな。

理屈は何となくわかりますが、、この場合の適切な対処方法はまだわかってないので、今回p値は2で決め打ちしようと思います。

import itertools

def selectparameter(DATA,s):
    p = d = q = range(0, 2)
    pdq = list(itertools.product(p, d, q))
    seasonal_pdq = [(x[0], x[1], x[2], s) for x in list(itertools.product(p, d, q))]
    parameters = []
    BICs = np.array([])
    for param in pdq:
        for param_seasonal in seasonal_pdq:
            try:
                mod = sm.tsa.statespace.SARIMAX(DATA,
                                            order=param,
                                            seasonal_order=param_seasonal)
                results = mod.fit()
                parameters.append([param, param_seasonal, results.bic])
                BICs = np.append(BICs,results.bic)
            except:
                continue
    return parameters[np.argmin(BICs)]

selectparameter(df_30T['teams'],48)

#結果
[(1, 0, 1), (0, 1, 1, 48), 4363.500679956852]

SARIMAモデル構築・モデルの妥当性確認

ひとまずまわりました!
が、results画面の解釈が全くできないのでそのまま妥当性を確認します。

mod = sm.tsa.statespace.SARIMAX(df_30T['teams'], order=(2,0,1), seasonal_order=(0,1,1,48))
res = mod.fit(disp=False)
print(res.summary())

#結果
                                 Statespace Model Results                                 
==========================================================================================
Dep. Variable:                              teams   No. Observations:                  494
Model:             SARIMAX(2, 0, 1)x(0, 1, 1, 48)   Log Likelihood               -2169.419
Date:                            Sat, 25 Sep 2021   AIC                           4348.838
Time:                                    12:09:11   BIC                           4369.339
Sample:                                09-09-2021   HQIC                          4356.921
                                     - 09-19-2021                                         
Covariance Type:                              opg                                         
==============================================================================
                 coef    std err          z      P>|z|      [0.025      0.975]
------------------------------------------------------------------------------
ar.L1          0.9722      0.174      5.598      0.000       0.632       1.313
ar.L2         -0.0961      0.130     -0.738      0.460      -0.351       0.159
ma.L1         -0.4348      0.161     -2.702      0.007      -0.750      -0.119
ma.S.L48      -0.9995     18.808     -0.053      0.958     -37.862      35.862
sigma2       763.7872   1.43e+04      0.053      0.958   -2.74e+04    2.89e+04
===================================================================================
Ljung-Box (Q):                       26.17   Jarque-Bera (JB):                91.31
Prob(Q):                              0.96   Prob(JB):                         0.00
Heteroskedasticity (H):               1.11   Skew:                             0.60
Prob(H) (two-sided):                  0.51   Kurtosis:                         4.86
===================================================================================

妥当性確認
この辺りもまだ勉強中なので正確なところは不明ですが、残差が定常性を持っているか?を確認します。意味合いとしては、分析時点で適切なパラメーター設定ができていれば、残差は定常性を持っているはず。。と言うことになります。

左上はホワイトノイズ??ですかね、、
「時間の経過によらず一定の値を軸に、同程度の幅で振れて変化」しているように見えるので良いかな
右上、、
適切な階差を取ることで、トレンド、周期性を取り除けていれば、KDEが正規分布に従うようです。まぁよいと思われます
左下、、
赤線の沿って青点がプロットされてればよいようです(これはちょっと分かってないです)
右下、、
おかしな相関は見えないのでよいと思われます。ここに相関が残っていると、AR/MA項の設定が適切でないと思われます。

この辺りもAidemyさんの講座では説明されていなかったので、、今後勉強です。。

fig = plt.figure(figsize=(16,9))
res.plot_diagnostics(fig=fig, lags=48)

image.png

推測

それっぽく推測できたと思いますが、同じ傾向の変動が周期的にでているだけなので、こんなので良いのか疑問はあります。
う~ん、、まだまだビジネスには使えないですね。

sarimax_pred = res.predict('2021-09-13', '2021-09-24') 

plt.figure(figsize=(12, 6))
plt.plot(df_30T['teams'], label="train_data")
plt.plot(sarimax_pred, c="r", label="predict")
plt.legend()
plt.show()

image.png

1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3