【SCM分析3】Pythonで出荷データから周期変動と長期トレンドを分離する

2023年10月7日

適正在庫管理は需要予測ができれば簡単です。

極端にいえば、需要予測が100%の精度でできれば安全在庫はゼロになります。

発注してから納品されるまでのリードタイム分だけの在庫を持っておけば良いことになります。

適正在庫にするための計算式【リードタイムや発注サイクルが長い場合】

 

そのため需要予測は重要なのですが、一番簡単な方法は直近データの平均を使うことです。

上記の記事で解説した方法は、これに基づいています。

需要変動は予測不能だけれども、平均を中心に対称的にばらつくでしょうという考え方です。

 

しかし、需要変動には予測可能な要素も含まれています。

一つは周期変動です。

コーラは夏に、カイロは冬に多く売れるという季節変動はその一種です。

より短期的には、週末は出荷が少ないけれども、金曜日や月曜日にその分多くなるといったような曜日変動もあるかもしれません。

 

もう一つは長期トレンドです。

成長期にある商品は毎月少しずつ出荷が増えるでしょうし、衰退期にある商品はその逆かもしれません。

 

このように予測可能な要素を元に需要予測をすれば、平均よりも精度の高い予測ができそうです。

本記事では、生の需要データからこれら予測可能な要素を分離することを、Pythonを使って行ってみました。

 

需要データをデータフレームに読み込む

560アイテムについて取得した6ヶ月間の日々の出荷データをデータフレームraw_dfに読み込みます。

raw_df = pd.read_csv('demand.csv')
raw_df['Date'] = pd.to_datetime(raw_df['Date']) # 日付データを日付型に変換
raw_df

今回はこの中のS00239のアイテムについて分析することにします。

data_df = raw_df[['Date', 'S00239']]
data_df = data_df.set_index('Date')
data_df

グラフにすると次のようになります。

plt.figure(figsize=(10, 5))
plt.plot(data_df)
plt.show()

 

 

自己相関(コレログラム)を見てみる

まずは自己相関を調べてみます。

自己相関とは元データと時間をずらしたデータとの相関のことです。

例えば、カイロの毎月の売上データを2年間とったら、1年目と2年目のグラフは似たような形になることが予想できます。

この場合、元データと12ヶ月ずらしたデータは相関が高くなります。

この相関が自己相関です。

詳しくはこちらのサイトでわかりやすく解説されています。

 

自己相関を調べるにはコレログラムを作るとよくわかります。

まずはこのS00239の需要データでコレログラムを作ってみましょう。

これはStatsmodelsplot_acfを使うことによって簡単にできます。

from statsmodels.graphics.tsaplots import plot_acf

# 自己相関(原系列)
acf = plot_acf(data_df.S00239)

このグラフの横軸はずらす時間を示しています。

そして、縦軸は相関係数です。

グラフを見ると、横軸7に対する縦軸の値が0.5になっています。

これから、7日ずらした時系列のデータと元の時系列データとの相関が0.5と高くなっていることがわかります。

また、14日ずらしたデータと21日ずらしたデータも、元のデータとの相関が高くなっていることがわかります。

つまり1週間ごとに似たような需要データになっていることがこれで示されます。

 

次に偏自己相関を見てみます。

自己相関では7の倍数日で相関が高くなっていましたが、1週周期に意味はありますが、2週とか3週周期に意味はありません。

1週周期が2つ、3つ重なった結果、2週間後や3週間後に似たようなデータが現れているだけです。

このような場合、偏自己相関では本当に意味のある1週周期だけを取り出すことができます。

# 偏自己相関(原系列)
pacf = plot_pacf(data_df.S00239)


 

周期/トレンド/残差に分解する

時系列データは周期変動長期トレンド残差に分解できます。

まず最初に周期変動を取り出します。

先のコレログラムにより1週間の周期変動があることがわかったので、各曜日ごとにデータの平均を取ってみます。

下図の例では、日曜日の需要データの平均は354、月曜日の平均は480、、、となっています。

これを周期変動係数とします。

次に、元データを周期変動係数で割れば、それが周期変動調整後の値になります。

調整前と調整後のデータを曜日別に集計してグラフにすると、次のように曜日の影響が取り除かれていることがわかります。

 

次に、周期変動調整後のデータの移動平均を取ることによってトレンド係数が求まります。

そして周期変動調整後のデータをトレンド係数で割ることにより、トレンド調整後のデータが得られます。

このトレンド調整後のデータを元データで割ると、残りが残差です。

このように周期変動/長期トレンド/残差を分解する方法を移動平均法を利用した乗法モデルによる分解といいます。

元データ=周期変動✕長期トレンド✕残差

のように掛け算の形で分解していることになります。

 

同様に移動平均法を利用した加法モデルによる分解方法もあります。

これは

元データ=周期変動長期トレンド残差

のように足し算の形で分解します。

 

更にSTL分解というノンパラメトリック回帰モデルを利用した分解方法もあります。

 

周期/トレンド/残差を可視化する

これら3つの分解方法はPythonのstatsmodelライブラリーのseasonal_decomposeにすべてカバーされています。

まずは移動平均法を利用した乗法モデルによる分解を実行してみます。

from statsmodels.tsa.seasonal import seasonal_decompose

# 移動平均(乗法モデル)
mul_output = seasonal_decompose(data_df, model='multicative', period=7)
mul_output.plot()
plt.show()

4つのグラフが表示されますが、一番上から元データ長期トレンド周期変動残差になっています。

長期トレンドはギザギザになっていますが、年末にかけて増加、年明けから減少しているトレンドがなんとなく読み取れます。

周期変動は1週間単位での周期が明確に読み取れます。

残差は1を中心にほぼ均等に分散していることがわかります。

 

次に移動平均法を利用した加法モデルによる分解を行ってみます。

# 移動平均(加法モデル)
add_output = seasonal_decompose(data_df, model='additive', period=7)
add_output.plot()
plt.show()

先程の乗法モデルの場合とほぼ同じ結果ですが、残差は0近辺に均等に散らばっている点が異なります。

 

次にSTL分解を実行してみましょう。

# STL分解
stl_output = STL(data_df.S00239, period=7, robust=True).fit()
stl_output.plot()
plt.show()

今度は長期トレンドが滑らかになって、わかりやすくなりました。

周期変動もはっきり現れていて、残差の散らばり具合も0近辺のデータ密度が高くなっていて、一番よく分解されているようです。

最後に、4つのグラフを重ねてSTL分解の結果を表示してみましょう。

data_df.S00239.plot(label = 'Original')
stl_output.trend.plot(label = 'Trend')
stl_output.seasonal.plot(label = 'Seasonal')
stl_output.resid.plot(label = 'Residuals')
plt.legend()
plt.show()

3つの成分に分解される様子を同じスケールで表示することができました。