見出し画像

StanとRでベイズ統計モデリングをPyMC Ver.5で写経~第12章「12.2 季節調整項」

第12章「時間や空間を扱うモデル」

書籍の著者 松浦健太郎 先生


この記事は、テキスト第12章「時間や空間を扱うモデル」の 12.2節「季節調整項」の PyMC5写経 を取り扱います。
PyMCの AR() で周期4の季節調整項を表現します。

はじめに


StanとRでベイズ統計モデリングの紹介

この記事は書籍「StanとRでベイズ統計モデリング」(共立出版、「テキスト」と呼びます)のベイズモデルを用いて、PyMC Ver.5で「実験的」に写経する翻訳的ドキュメンタリーです。

テキストは、2016年10月に発売され、ベイズモデリングのモデル式とプログラミングに関する丁寧な解説とモデリングの改善ポイントを網羅するチュートリアル「実践解説書」です。もちろん素晴らしいです!
アヒル本」の愛称で多くのベイジアンに愛されてきた書籍です!

テキストに従ってStanとRで実践する予定でしたが、RのStan環境を整えることができませんでした(泣)
そこでこのシリーズは、テキストのベイズモデルをPyMC Ver.5に書き換えて実践します。

引用表記

この記事は、出典に記載の書籍に掲載された文章及びコードを引用し、適宜、掲載文章とコードを改変して書いています。
【出典】
「StanとRでベイズ統計モデリング」初版第13刷、著者 松浦健太郎、共立出版

記事中のイラストは、「かわいいフリー素材集いらすとや」さんのイラストをお借りしています。
ありがとうございます!

PyMC環境の準備

Anacondaを用いる環境構築とGoogle ColaboratoryでPyMCを動かす方法について、次の記事にまとめています。
「PyMCを動かすまでの準備」章をご覧ください。


12.2 季節調整項


モデリングの準備

インポート

### インポート

# 数値・確率計算
import pandas as pd
import numpy as np

# PyMC
import pymc as pm
import arviz as az

# 描画
import matplotlib.pyplot as plt
plt.rcParams['font.family'] = 'Meiryo'

# ワーニング表示の抑制
import warnings
warnings.simplefilter('ignore')

データの読み込みと確認
サンプルコードのデータを読み込みます。

### データの読み込み ◆data-ss2.txt
# X:日付(四半期), Y:季節ものの販売数[千個]

data = pd.read_csv('./data/data-ss2.txt')
print('data.shape: ', data.shape)
display(data.head())

【実行結果】

テキスト図12.3左の時系列折れ線グラフを描画します。

# 時系列折れ線グラフの描画 ◆図12.3左
fig, ax = plt.subplots(figsize=(8, 4))
ax.plot(data.X, data.Y, '-o');
ax.set(xticks=range(0, len(data)+2, 5), xlabel='Time(Quarter)', ylabel='Y')
ax.grid(lw=0.5);

【実行結果】
4半期(周期4)の周期性がありそうです。

12.2.3 Stanで実装

周期4の季節調整項を含むモデルです。
テキストの数式をお借りします。

$$
\begin{align*}
\mu[t] &\sim \text{Normal}\ (\mu[t-1],\ \sigma_{\mu}) \\
season[t] &\sim \text{Normal}\ \left(\displaystyle -\sum^{L-1}_{l=1} season[t-l],\ \sigma_{season} \right) \\
Y[t] &\sim \text{Normal}\ (\mu[t] + season[t],\ \sigma_Y)
\end{align*}
$$

テキストより引用

PyMCのモデル定義

PyMCでモデル式12-2を実装します。
季節調整項$${season}$$の従う確率分布には AR() を用いました。
自己回帰係数 rhoに [-1, -1, -1]を設定します。
モデル式の$${season}$$の左辺$${-\sum^{L-1}_{l=1} season[t-l]}$$に注目します。
周期$${L=4}$$のとき、総和記号$${\sum}$$を展開すると次のようになります。

$$
-season[t-1]-season[t-2]-season[t-3]
$$

$${-season[t-1]-season[t-2]-season[t-3]}$$の自己回帰係数はすべて$${-1}$$です。
PyMC の AR() の引数 rho(自己回帰係数)に$${-1,\ -1,\ -1}$$を設定します。

モデルの定義です。 

### モデルの定義 ◆モデル式12-6 model12-6.stan

# 季節調整項のARパラメータの設定
period = 4                     # 季節成分の周期
order = period - 1             # ARの次数
rhos = np.ones(order) * -1     # ARのρパラメータ

# モデルの定義
with pm.Model() as model:
    
    ### データ関連定義
    # coordの定義
    model.add_coord('data', values=data.index, mutable=True)
    # dataの定義
    Y = pm.ConstantData('Y', value=data['Y'].values, dims='data')
    
    ### 事前分布
    # 標準偏差
    sigmaMu = pm.Uniform('sigmaMu', lower=0, upper=10)          # トレンド項
    sigmaSeason = pm.Uniform('sigmaSeason', lower=0, upper=10)  # 季節調整項
    sigmaY = pm.Uniform('sigmaY', lower=0, upper=10)            # 観測ノイズ
    # トレンド項
    init_dist_trend = pm.Normal.dist(mu=data['Y'].mean(), sigma=10)
    mu = pm.AR('mu', rho=1, sigma=sigmaMu, constant=False,
                  init_dist=init_dist_trend, ar_order=1, dims='data')
    # 季節調整項
    init_dist_season = pm.Normal.dist(mu=0, sigma=10)
    season = pm.AR('season', rho=rhos, sigma=sigmaSeason, constant=False,
                   init_dist=init_dist_season, ar_order=order, dims='data')
    # yMean
    yMean = pm.Deterministic('yMean', mu + season, dims='data')
    
    ### 尤度
    obs = pm.Normal('obs', mu=yMean, sigma=sigmaY, observed=Y, dims='data')

モデルの定義内容を見ます。

### モデルの表示
model

【実行結果】

### モデルの可視化
pm.model_to_graphviz(model)

【実行結果】

MCMCの実行と収束確認

MCMCを実行します。

### 事後分布からのサンプリング 1分20秒
with model:
    idata = pm.sample(draws=1000, tune=1000, chains=4, target_accept=0.8,
                      nuts_sampler='numpyro', random_seed=1234)

【実行結果】省略

Pythonで事後分布からのサンプリングデータの確認を行います。
Rhatの確認から。
テキストの収束条件は「chainを3以上にして$${\hat{R}<1.1}$$のとき」です。

### r_hat>1.1の確認
# 設定
idata_in = idata          # idata名
threshold = 1.02          # しきい値

# しきい値を超えるR_hatの個数を表示
print((az.rhat(idata_in) > threshold).sum())

【実行結果】
収束条件を満たしています。

事後統計量を表示します。

### 推論データの要約統計情報の表示
var_names = ['sigmaMu', 'sigmaSeason', 'sigmaY', 'mu', 'yMean']
pm.summary(idata, hdi_prob=0.95, var_names=var_names, round_to=3)

【実行結果】

トレースプロットを描画します。

### トレースプロットの表示
pm.plot_trace(idata, compact=True, var_names=var_names)
plt.tight_layout();

【実行結果】

12.2.4 推論結果の解釈

事後統計量

事後分布の要約統計量を算出します。
算出関数を定義します。

### median, 10%, 90%パーセンタイル点をデータフレーム化する関数の定義
def make_stats_df(y):
    probs = [50, 10, 90]
    columns = ['median', '10%CI', '90%CI']
    quantiles = pd.DataFrame(np.percentile(y, probs, axis=0).T, index=y.columns)
    quantiles.columns = columns
    return quantiles

$${\sigma_{\mu},\ \sigma_{season},\ \sigma_Y}$$の要約統計量を算出します。

### 要約統計量の算出・表示
vars = ['sigmaMu', 'sigmaSeason', 'sigmaY']
param_samples = idata.posterior[vars].to_dataframe().reset_index(drop=True)
display(make_stats_df(param_samples).round(3))

【実行結果】
テキストに事後統計量の記載が無いため、PyMCモデルの推論結果の妥当性は分かりません。

モデルの推定結果の可視化

テキスト図12.4を描画します。
まずはトレンド項(図12.4左)。

### μの描画 ◆図12.4左

## 描画用データの作成
# 推論データからμのMCMCサンプルデータを取り出し
mu_samples = idata.posterior.mu.stack(sample=('chain', 'draw')).data
# μの中央値の算出
mu_median = np.median(mu_samples, axis=1)
# μの80%CI, 50%CIの算出
mu_80ci = np.quantile(mu_samples, q=[0.10, 0.90], axis=1)
mu_50ci = np.quantile(mu_samples, q=[0.25, 0.75], axis=1)

## 描画処理
# 描画領域の設定
plt.figure(figsize=(8, 4))
# Yの観測値の描画
plt.plot(data.X, data.Y, '-o', color='tab:blue', label='$Y$ の観測値')
# μの中央値の描画
plt.plot(data.X, mu_median, color='tab:red', label='$\mu$:中央値')
# μの50%CIの描画
plt.fill_between(data.X, mu_50ci[0], mu_50ci[1], color='tomato', alpha=0.5,
                 label='$\mu$:50%CI')
# μの80%CIの描画
plt.fill_between(data.X, mu_80ci[0], mu_80ci[1], color='tomato', alpha=0.2,
                 label='$\mu$:80%CI')
# 修飾
plt.xlabel('Time(Quarter)')
plt.ylabel('Y')
plt.title(r'$Y$ の観測値とトレンド項 $\mu$ の推論値')
plt.legend(bbox_to_anchor=(1, 1))
plt.grid(lw=0.5);

【実行結果】
テキストに近い感じがします。

続いて季節調整項(図12.4右)。

### 季節調整項の描画 ◆図12.4右

## 描画用データの作成
# 推論データからseasonのMCMCサンプルデータを取り出し
season_samples = idata.posterior.season.stack(sample=('chain', 'draw')).data
# seasonの中央値の算出
season_median = np.median(season_samples, axis=1)
# seasonの80%CI, 50%CIの算出
season_80ci = np.quantile(season_samples, q=[0.10, 0.90], axis=1)
season_50ci = np.quantile(season_samples, q=[0.25, 0.75], axis=1)

## 描画処理
# 描画領域の設定
plt.figure(figsize=(8, 4))
# seasonの中央値の描画
plt.plot(data.X, season_median, color='tab:red', label='中央値')
# seasonの50%CIの描画
plt.fill_between(data.X, season_50ci[0], season_50ci[1], color='tomato',
                 alpha=0.5, label='50%CI')
# seasonの80%CIの描画
plt.fill_between(data.X, season_80ci[0], season_80ci[1], color='tomato',
                 alpha=0.2, label='80%CI')
# 修飾
plt.xlabel('Time(Quarter)')
plt.ylabel('Y')
plt.title(r'季節調整項の推論値')
plt.legend(bbox_to_anchor=(1, 1))
plt.grid(lw=0.5);

【実行結果】
こちらもテキストに近い感じがします。

$${yMean}$$の推定結果を可視化します。

### yMeanの描画

## 描画用データの作成
# 推論データからyMeanのMCMCサンプルデータを取り出し
y_pred_samples = idata.posterior.yMean.stack(sample=('chain', 'draw')).data
# μの中央値の算出
y_pred_median = np.median(y_pred_samples, axis=1)
# μの80%CI, 50%CIの算出
y_pred_80ci = np.quantile(y_pred_samples, q=[0.10, 0.90], axis=1)
y_pred_50ci = np.quantile(y_pred_samples, q=[0.25, 0.75], axis=1)

## 描画処理
# 描画領域の設定
plt.figure(figsize=(8, 4))
# Yの観測値の描画
plt.plot(data.X, data.Y, 'o', color='tab:blue', label='$Y$ の観測値')
# yMeanの中央値の描画
plt.plot(data.X, y_pred_median, color='tab:red', label='中央値')
# yMeanの50%CIの描画
plt.fill_between(data.X, y_pred_50ci[0], y_pred_50ci[1], color='tomato', alpha=0.5,
                 label='50%CI')
# yMeanの80%CIの描画
plt.fill_between(data.X, y_pred_80ci[0], y_pred_80ci[1], color='tomato', alpha=0.2,
                 label='80%CI')
# 修飾
plt.xlabel('Time(Quarter)')
plt.ylabel('Y')
plt.title(r'$Y$ の観測値と平均の推論値')
plt.legend(bbox_to_anchor=(1, 1))
plt.grid(lw=0.5);

【実行結果】
過学習並みに推定できている感じがします。

12.2 節は以上です。


シリーズの記事

次の記事

前の記事

目次


ブログの紹介


note で7つのシリーズ記事を書いています。
ぜひ覗いていってくださいね!

1.のんびり統計

統計検定2級の問題集を手がかりにして、確率・統計をざっくり掘り下げるブログです。
雑談感覚で大丈夫です。ぜひ覗いていってくださいね。
統計検定2級公式問題集CBT対応版に対応しています。
Python、EXCELのサンプルコードの配布もあります。

2.実験!たのしいベイズモデリング1&2をPyMC Ver.5で

書籍「たのしいベイズモデリング」・「たのしいベイズモデリング2」の心理学研究に用いられたベイズモデルを PyMC Ver.5で描いて分析します。
この書籍をはじめ、多くのベイズモデルは R言語+Stanで書かれています。
PyMCの可能性を探り出し、手軽にベイズモデリングを実践できるように努めます。
身近なテーマ、イメージしやすいテーマですので、ぜひぜひPyMCで動かして、一緒に楽しみましょう!

3.実験!岩波データサイエンス1のベイズモデリングをPyMC Ver.5で

書籍「実験!岩波データサイエンスvol.1」の4人のベイジアンによるベイズモデルを PyMC Ver.5で描いて分析します。
この書籍はベイズプログラミングのイロハをざっくりと学ぶことができる良書です。
楽しくPyMCモデルを動かして、ベイズと仲良しになれた気がします。
みなさんもぜひぜひPyMCで動かして、一緒に遊んで学びましょう!

4.楽しい写経 ベイズ・Python等

ベイズ、Python、その他の「書籍の写経活動」の成果をブログにします。
主にPythonへの翻訳に取り組んでいます。
写経に取り組むお仲間さんのサンプルコードになれば幸いです🍀

5.RとStanではじめる心理学のための時系列分析入門 を PythonとPyMC Ver.5 で

書籍「RとStanではじめる心理学のための時系列分析入門」の時系列分析をPythonとPyMC Ver.5 で実践します。
この書籍には時系列分析のテーマが盛りだくさん!
時系列分析の懐の深さを実感いたしました。
大好きなPythonで楽しく時系列分析を学びます。

6.データサイエンスっぽいことを綴る

統計、データ分析、AI、機械学習、Pythonのコラムを不定期に綴っています。
統計・データサイエンス書籍にまつわる記事が多いです。
「統計」「Python」「数学とPython」「R」のシリーズが生まれています。

7.Python機械学習プログラミング実践記

書籍「Python機械学習プログラミング PyTorch & scikit-learn編」を学んだときのさまざまな思いを記事にしました。
この書籍は、scikit-learnとPyTorchの教科書です。
よかったらぜひ、お試しくださいませ。

最後までお読みいただきまして、ありがとうございました。

この記事が参加している募集

うちの積読を紹介する

この記事が気に入ったらサポートをしてみませんか?