Pythonで時系列データを検定(Shapiro-Wilk test, runs test, Ljung-Box test)する

pythonstatistics

統計的仮説検定 - sambaiz-net

テストデータ

Dominick’s datasetのビールの週売上データを使う。 UPC(Universal Product Code)に対応する商品データと、店(STORE)で週(WEEK)に売れた数(MOVE)と価格(PRICE)、収益(PROFIT)を含むMovementデータがCSVで提供されている。

これらをカレントディレクトリに置いてJupyter Notebookを立ち上げる。

$ docker run -p 8888:8888 -v `pwd`:/home/jovyan/work jupyter/datascience-notebook start-notebook.sh

ロードしてplotしてみる。

import pandas as pd

df = pd.read_csv('./wber.csv', usecols=['STORE', 'WEEK', 'UPC', 'MOVE', 'PRICE', 'PROFIT'])
agg = df[df['STORE'] == 5].groupby(['WEEK']).sum().loc[:, ['MOVE', 'PROFIT']]
agg.plot()

この内、中央の区間の値や差分に対してα=0.05で検定する。

Shapiro-Wilk test

帰無仮説は"正規分布に従っている"。p>αとなり帰無仮説は棄却されず、正規分布に従うとみなせる。

from scipy import stats
W, p = stats.shapiro(agg['PROFIT'].loc[230:310])
print(f'p={p:.3f}') # p=0.183

runs test

帰無仮説は"2値の数列の値がランダムである"。runというのは数列の連続して増加/減少している部分のことで、帰無仮説が正しい場合数列に含まれるrunの数は次の平均と分散の正規分布に従う。

updown = agg['PROFIT'].loc[230:310].diff().map(lambda x: x > 0)
Np = updown.value_counts()[True]
Nf = updown.value_counts()[False]
N = Np + Nf
mu = (2 * Np * Nf / N) + 1
var = 2 * Np * Nf * (2 * Np * Nf - N) / (N ** 2 * (N - 1))
print(f'mu={mu:.3f}, var={var:.3f}') # mu=38.333, var=18.330

p<αとなり帰無仮説が棄却されるので、値の増減はランダムでなく何か傾向があると考えられる。

import math
runs_num = len(updown[updown != updown.shift(1)])
print(runs_num) # 46
cdf = stats.norm.cdf(x=runs_num, loc=mu, scale=math.sqrt(var))
print(f'p={min(1-cdf, cdf):.3f}') # p=0.037

Ljung-Box test

帰無仮説を"任意のlagにおいて自己相関係数が0である(独立している)“とするかばん検定(portmanteau test)の一種。

差分系列を検定したところ、少なくともlag=1(1つ前の値との相関)でp<αとなり帰無仮説が棄却されるので、これは自己相関がありそうだということになる。

import statsmodels.api as sm
res = sm.stats.acorr_ljungbox(agg['PROFIT'].diff().loc[230:310], return_df=True)
print(f'lags={res.index.values}') # lags=[ 1  2  ... 34 35]
maxplag = res['lb_pvalue'].idxmin()
print(f'lag={maxplag} p={res.loc[maxplag]["lb_pvalue"]:.5f}') # lag=1 p=0.00437

自己相関係数を出してみると強くはないが確かに相関がある。グラフを見ても増減を繰り返しているようだし下がった次は上がる傾向にあるというのは正しそうだ。

agg['PROFIT-diff'] = agg['PROFIT'].diff()
agg['PROFIT-diff-lag1'] = agg['PROFIT-diff'].shift(1)
corr = agg['PROFIT-diff'].loc[230:310].corr(agg['PROFIT-diff-lag1'].loc[230:310])
print(f'corr={corr:.3f}') # corr=-0.365
agg.loc[230:310].plot.scatter(x='PROFIT-diff', y='PROFIT-diff-lag1');

横軸にlagを、縦軸に自己相関係数を取ったグラフをコレログラムと呼ぶ。

参考

現場ですぐ使える時系列データ分析~データサイエンティストのための基礎知識〜

1.3.5.13. Runs Test for Detecting Non-randomness

kwcooper/Wald-Wolfowitz_Runs_Test.py