![見出し画像](https://assets.st-note.com/production/uploads/images/122547803/rectangle_large_type_2_e49e2e927f4c7266afefd7d693429b2b.png?width=1200)
時系列分析入門【第4章 その4】観測値が二項分布/ポアソン分布に従う状態空間モデルをPythonとPyMC Ver.5 で実践する
この記事は、テキスト「RとStanではじめる 心理学のための時系列分析入門」の第4章「RStanによる状態空間モデル」のRスクリプト・Stanコードをお借りして、PythonとPyMC Ver.5 で「実験的」に実装する様子を描いた統計ドキュメンタリーです。
取り扱いテーマは「観測値が離散量」である状態空間モデルです。
・観測値が二項分布に従うモデル
・観測値がポアソン分布に従うモデル
今回はPyMCでモデリングします。
まぁまぁ、諸々はさておいて、時系列分析とベイズモデリングを楽しんでいきましょう!
テキストの紹介、引用表記、シリーズまえがきは、このリンクの記事をご参照ください。
テキストで使用するデータは、R・Stan等のサンプルスクリプトで指定されています。
サンプルスクリプトは著者GitHubサイトからダウンロードして取得できます。
第4章 RStanによる状態空間モデル
この記事は「4.3 観測値が離散量の場合」を実践いたします。
インポート(+環境準備)
この記事で用いるライブラリをインポートします。
### インポート
# 基本
import numpy as np
import pandas as pd
# PyMC
import pymc as pm
import pytensor.tensor as pt
import arviz as az
# グラフ描画
import matplotlib.pyplot as plt
plt.rcParams['font.family'] = 'Meiryo'
# ワーニング表示の抑制
import warnings
warnings.simplefilter('ignore')
【補足】PyMC環境の構築
AnacondaでPyMC環境を構築する方法を次のリンクの「pymc環境の構築」の箇所に記載しています。
ご参考まで。
![](https://assets.st-note.com/img/1700557576317-FOlK6oYA2i.png)
観測値が二項分布に従うモデル
観測値が0以上の整数=カウントデータであり、上限が予め決まっている場合、二項分布を選択します。
二項分布に従うモデルで重要となる「標準ロジスティック関数」を描画しましょう。
【標準ロジスティック関数】(ロジット関数の逆関数)
$${p=\cfrac{1}{1+e^{-\mu}}}$$
### 標準ロジスティック関数
def logistic(x):
return 1 / (1 + np.exp(-x))
x = np.linspace(-10, 10, 1001)
p = logistic(x)
fig, ax = plt.subplots(1, 1, figsize=(7, 3))
ax.plot(x, p)
ax.axvline(0, color='black', lw=0.5, ls='--')
ax.axhline(0.5, color='black', lw=0.5, ls='--')
ax.set(title='標準ロジスティック関数', xlabel=r'$\mu$', ylabel='p', xlim=(-5, 5));
【実行結果】
$${p}$$は0から1の間の値をとります。
標準ロジスティック関数を通すことで、$${\mu}$$の値を0~1の確率$${p}$$に収める、こんな感じです。
$${\mu=0}$$のとき$${p=0.5}$$になります。
![](https://assets.st-note.com/img/1700552223930-IhT8RsyQ0W.png)
テキストでは「授業の出席に関するデータ」を分析します。
ある学生が履修する授業に関して、先生が出席確認を行った回数を$${N_t}$$、その学生の実際の出席回数を目的変数$${Y_t}$$とし、その学生の出席意欲$${\mu_t}$$が関心のあるパラメータです。
モデルの数式です。
$$
\begin{align*}
\mu_t &\sim \text{Normal}\ (\mu_{t-1},\ \sigma^2) \quad \cdots システムモデル \\
\theta_t &= \text{Logit}^{-1}\ (\mu_t) \quad \cdots ロジットリンク関数の逆関数 \\
Y_t &\sim \text{Binomial}\ (N_t,\ \theta_t) \quad \cdots 観測モデル:二項分布
\end{align*}
$$
添字$${t}$$は時間(単位:日)です。
$${\theta_t}$$は出席確率であり、出席意欲$${\mu_t}$$を引数にするロジスティック関数で求めます。
目的変数である出席回数$${Y_t}$$は、試行回数=出席確認回数$${N_t}$$、成功確率=出席確率$${\theta_t}$$をパラメータにする二項分布に従います。
① データの作成・前処理
テキストのRコードに記載されたデータをお借りします。
### データの準備 # Rコードより引用
#日毎の出席回数
attend = (0,0,0,0,0,0,0,0,0,3,3,2,2,3,0,0,3,3,3,2,3,0,0,3,3,3,2,3,0,0,0,2,0,0,
0,0,0,3,3,3,2,3,0,0,3,3,3,2,4,0,0,3,2,3,2,3,0,0,3,3,2,2,2,0,0,0,1,2,
2,2,0,0,2,1,2,2,2,0,0,3,2,2,2,2,0,0,3,2,2,2,2,0,0,0,2,2,2,2,0,0,2,2,
2,2,2,0,0,0,2,2,1,1,1,0,3,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
1,1,1,2,3,0,0,1,1,2,2,3,0,0,1,1,2,2,3,0,0,1,1,0,0,0,0,0,1,1,2,2,3,0,
0,0,1,1,2,3,0,0,1,1,1,0,0,0,0,0,2,2,1,3,0,0,1,2,2,2,3,0,0,1,1,2,2,3,
0,0,1,1,2,2,3,0,0,1,1,1,1,3,0,0,1,1,2,2,3,0,0,0,1,0,2,3,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,2,2,3,0,0,1,2,2,2,3,0,0,1,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)
#日毎の出席確認回数
check = (0,0,0,0,0,0,0,0,0,3,3,3,2,3,0,0,4,4,4,2,3,0,0,4,4,4,2,3,0,0,4,4,0,0,0,
0,0,4,4,4,2,3,0,0,4,4,4,2,4,0,0,4,4,4,2,3,0,0,4,4,3,2,3,0,0,3,3,2,2,2,
0,0,3,3,2,2,2,0,0,3,3,2,2,2,0,0,3,3,2,2,2,0,0,3,3,2,2,2,0,0,2,3,2,2,2,
0,0,0,3,2,1,1,1,0,3,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,2,2,3,
0,0,1,2,2,2,3,0,0,1,2,2,2,3,0,0,1,1,2,2,3,0,0,1,2,2,2,3,0,0,1,2,2,2,3,
0,0,1,2,2,0,0,0,0,0,2,2,2,3,0,0,1,2,2,2,3,0,0,1,2,2,2,3,0,0,1,2,2,2,3,
0,0,1,2,2,2,3,0,0,1,2,2,2,3,0,0,1,1,0,2,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,2,2,2,3,0,0,1,2,2,2,3,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)
# データフレーム化
df_attend = pd.DataFrame({'Attend': attend, 'Check': check})
display(df_attend)
【実行結果】
365日分のデータです。
Attendは学生の出席回数、Checkは先生の出席確認回数です。
![](https://assets.st-note.com/img/1700553089972-j717B21HDQ.png)
テキストにならって、データを描画します。
テキストの図4.15に相当します。
### 出席確認回数と出席数の描画 ※図4.15に対応
plt.figure(figsize=(10, 4))
plt.plot(df_attend['Check'], label='出席確認')
plt.plot(df_attend['Attend'], color='tomato', lw=2, ls=':', label='出席')
plt.xlabel('日')
plt.ylabel('回数')
plt.legend(['出席', '出席確認'])
plt.show()
【実行結果】
回数=0は夏休み等の長期休暇だそうです。
![](https://assets.st-note.com/img/1700553172349-7Tx2tmjY7m.png?width=1200)
② モデルの定義
システムモデルの記述には「pm.GaussianRandomWalk()」を利用しました。
時系列分析感が出てきますね!
ロジスティック関数には、ロジット逆関数に相当する「pm.invlogit()」を利用しました。
尤度関数の二項分布は「pm.Binomial()」です。
### モデルの定義
with pm.Model() as model_bin:
### データ関連定義
# coordの定義
model_bin.add_coord('data', values=df_attend.index, mutable=True)
# dataの定義
y = pm.ConstantData('y', value=df_attend['Attend'].values, dims='data')
x = pm.ConstantData('x', value=df_attend['Check'].values, dims='data')
### 事前分布
sigma = pm.HalfCauchy('sigma', beta=5)
# mu:システムモデル
initDist = pm.Normal.dist(mu=0, sigma=sigma)
mu = pm.GaussianRandomWalk('mu', mu=0, sigma=sigma, init_dist=initDist,
dims='data')
# 出席確率:ロジットリンク関数の逆関数
theta = pm.Deterministic('theta', pm.invlogit(mu), dims='data')
### 尤度:観測モデル
likelihood = pm.Binomial('likelihood', n=x, p=theta, observed=y,
dims='data')
【実行結果】出力なし
③ モデルの表示・可視化
モデルの数式を表示します。
# モデルの表示
model_bin
【実行結果】
![](https://assets.st-note.com/img/1700553367912-gvNj8hiZBS.png?width=1200)
モデルを図にして可視化しましょう。
# モデルの可視化
pm.model_to_graphviz(model_bin)
【実行結果】
シンプルなモデルです。
![](https://assets.st-note.com/img/1700553394951-Mne1IPK0QK.png)
④ 事後分布からのサンプリング
MCMCを実行します。
NUTSサンプラーにnumpyroを利用します。
実行時間はおよそ3分30秒でした。
### 事後分布からのサンプリング ※NUTSサンプラーにnumpyroを使用 3分30秒
with model_bin:
idata_bin = pm.sample(draws=2000, tune=5000, chains=4, target_accept=0.95,
nuts_sampler='numpyro', random_seed=1234)
【実行結果(一部)】
![](https://assets.st-note.com/img/1700553558152-mDxgEsxKni.png?width=1200)
⑤ サンプリングデータの確認
$${\hat{R}}$$、事後分布の要約統計量、トレースプロットを確認します。
事後分布の収束確認はテキストにならって$${\hat{R} \leq 1.1}$$としています。
### r_hat>1.1の確認
# 設定
idata_in = idata_bin # idata名
threshold = 1.1 # しきい値
# しきい値を超えるR_hatの個数を表示
print((az.rhat(idata_in).to_dataframe() > threshold).sum())
【実行結果】
$${\hat{R}>1.1}$$のパラメータは「0」件です。
全てのパラメータが$${\hat{R} \leq 1.1}$$であることを確認できました。
![](https://assets.st-note.com/img/1700553622727-wsQBACEFAf.png)
パラメータ等の事後分布の要約統計量です。
### 推論データの要約統計情報の表示
pm.summary(idata_bin, hdi_prob=0.95)
【実行結果】
パラメータ数が多いので省略表示となりました。
![](https://assets.st-note.com/img/1700553695895-wfP3AV4oNy.png?width=1200)
トレースプロットを確認しましょう。
### トレースプロットの表示
pm.plot_trace(idata_bin, combined=True, figsize=(10, 8))
plt.tight_layout();
【実行結果】
収束している感じがします。
![](https://assets.st-note.com/img/1700553728326-2Ntx8Z43kr.png?width=1200)
⑥ 出席意欲と出席確率の描画
テキストの図4.16に相当する図を描画します。
### 出席意欲と出席確率の描画 ※図4.16に相当
# 出席意欲の平均値、出席確率の中央値を算出
mu_mean = idata_bin.posterior.mu.stack(sample=('chain', 'draw')).mean(axis=1)
theta_median = (idata_bin.posterior.theta.stack(sample=('chain', 'draw'))
.median(axis=1))
## 描画
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 5))
# 出席意欲
# 平均値の描画
ax1.plot(df_attend.index, mu_mean, label='mean')
# 95%HDIの描画
pm.plot_hdi(df_attend.index, idata_bin.posterior.mu, hdi_prob=0.95, ax=ax1,
fill_kwargs={'color': 'tomato', 'alpha': 0.2, 'label': '95%HDI'})
# 505HDIの描画
pm.plot_hdi(df_attend.index, idata_bin.posterior.mu, hdi_prob=0.50, ax=ax1,
fill_kwargs={'color': 'tomato', 'alpha': 0.5, 'label': '50%HDI'})
# 修飾
ax1.set(title=r'出席意欲$\mu$の事後期待値', xlabel='DAY', ylabel=r'\mu')
ax1.legend()
ax1.grid(lw=0.2)
# 出席確率
# 中央値の描画
ax2.plot(df_attend.index, theta_median, label='median')
# 95%HDIの描画
pm.plot_hdi(df_attend.index, idata_bin.posterior.theta, hdi_prob=0.95, ax=ax2,
fill_kwargs={'color': 'tomato', 'alpha': 0.2, 'label': '95%HDI'})
# 50%HDIの描画
pm.plot_hdi(df_attend.index, idata_bin.posterior.theta, hdi_prob=0.50, ax=ax2,
fill_kwargs={'color': 'tomato', 'alpha': 0.5, 'label': '50%HDI'})
# 修飾
ax2.set(title=r'出席確率$\theta$の事後中央値', xlabel='DAY', ylabel=r'$\theta$')
ax2.legend()
ax2.grid(lw=0.2)
plt.tight_layout()
plt.show()
【実行結果】
左の出席意欲$${\mu}$$はテキストと見た目は近いです。
ただ、HDI区間(テキストは確信区間・信用区間)の幅はテキストよりも大きい感じがします。
右の出席確率$${\theta}$$はテキストよりも変動が大きく(特に200日あたり)、区間の幅も大きい結果になりました。
![](https://assets.st-note.com/img/1700554238776-Tj81abvXiV.png?width=1200)
「y」の事後予測サンプリングを行って描画してみましょう。
### 事後予測のサンプリングの実施
with model_bin:
idata_bin.extend(pm.sample_posterior_predictive(idata_bin,
random_seed=1234))
【実行結果】
![](https://assets.st-note.com/img/1700555424881-apTTnuweEf.png?width=1200)
描画します。
### 出席回数yの事後予測プロット
## 各種データの算出
# yの事後予測配列の取得 shape=(365, 8000)
y_pred = (idata_bin.posterior_predictive.likelihood
.stack(sample=('chain', 'draw')).data)
# yの事後予測の平均値
y_mean = y_pred.mean(axis=1)
# yの事後予測の95%HDI区間
y_hdi95 = az.hdi(y_pred.T, hdi_prob=0.95)
# yの事後予測の50%HDI区間
y_hdi50 = az.hdi(y_pred.T, hdi_prob=0.50)
## 描画
plt.figure(figsize=(15, 5))
# 観測値の描画
plt.plot(df_attend.index, df_attend['Attend'], 'o', markersize=1, color='blue',
label='観測値')
# yの事後予測の平均値の描画
plt.plot(df_attend.index, y_mean, color='tab:blue', lw=1, label='事後平均')
# yの事後予測の95%HDI区間の描画
plt.fill_between(df_attend.index, y_hdi95[:, 0], y_hdi95[:, 1], color='tomato',
alpha=0.2, label='95%HDI')
# yの事後予測の50%HDI区間の描画
plt.fill_between(df_attend.index, y_hdi50[:, 0], y_hdi50[:, 1], color='tomato',
alpha=0.5, label='50%HDI')
# 修飾
plt.xlabel('DAY')
plt.ylabel('出席回数')
plt.yticks([0, 1, 2, 3, 4])
plt.legend()
plt.show()
【実行結果】
50日ごろまで95%HDI区間は広めな感じです。4回出席まで伸びています。
![](https://assets.st-note.com/img/1700555446670-lnm6MWNN1A.png?width=1200)
![](https://assets.st-note.com/img/1700555646132-JXeY3XayvC.png)
観測値がポアソン分布に従うモデル
観測値が0以上の整数=カウントデータであり、上限がない場合、ポアソン分布を選択します。
ポアソン分布は、一定期間のイベント平均発生回数$${\lambda}$$をパラメータにもつ離散型分布です。
ポアソン分布の確率質量関数の数式です。
$$
p(y \mid \lambda) = \cfrac{\lambda^y e^{-\lambda}}{y!}
$$
この節で利用するデータは、60日間の毎日の行動生起回数$${\boldsymbol{Y_t}}$$をカウントしたデータであり、後半30日間に「介入」が入り、周期12の季節成分を含んでいます。
モデルの数式です。
$$
\begin{align*}
\mu_t &\sim \text{Normal}\ (\mu_{t-1},\ \sigma^2) \quad \cdots システムモデル \\
\lambda_t &= \text{exp}\ (\mu_t + 介入効果k + 季節成分season)\\
&\quad \cdots 対数リンク関数の逆関数 \\
Y_t &\sim \text{Poisson}\ (\lambda_t) \quad \cdots 観測モデル \\
\end{align*}
$$
① データの作成・前処理
テキストのRコードを参考にしてコード化しました。
【おことわり】
生成する乱数やフーリエ級数項の計算の相違などから、この記事で利用するデータはテキストと異なります。
したがいまして、推論結果もテキストと相違することを予めお断りいたします。
### データの準備 # Rコードより
# 初期値設定
np.random.seed(5)
N = 60 # 期間
mu_0 = 1 # muの状態初期値
sigma_S = 0.1 # muの標準偏差
mu_K = 1 # 介入効果の平均値
sigma_K = 0.1 # 介入効果の標準偏差
# 季節成分の生成
# 12か月周期のフーリエ級数の第2調和まで考慮、5年分作成
p = np.zeros(N)
t = np.arange(1, 13)
for i in range(1, 3):
p = np.tile((np.random.random()*2-1) * np.cos(i*t) \
+ (np.random.random()*2-1) * np.sin(i*t), N//len(t)+1)[:N] + p
# 介入系列の生成
IV = np.concatenate([np.zeros(30), np.ones(30)])
# 介入効果kの生成
k = np.random.normal(loc=mu_K, scale=sigma_K, size=N)
# 状態μの生成
mu = np.random.normal(loc=mu_0, scale=sigma_S)
# 観測値の生成
observation = np.random.poisson(lam=np.exp(mu + k * IV + p), size=N)
# データフレーム化
df_poi = pd.DataFrame({'obs': observation, 'IV': IV, 'mu': mu, 'k': k,
'season': p})
print('df_poi.shape:', df_poi.shape)
display(df_poi.head())
【実行結果】
60日分のデータです。
以下は項目内容です。
・obs:日々の行動生起回数$${Y_t}$$
・IV:介入フラグ(0:介入なし、1:介入あり、30日目以降に1を設定)
・mu:データ生成時に計算した状態$${\mu}$$
・season:データ生成時に計算した季節成分
![](https://assets.st-note.com/img/1700557738739-vp9vdsg6fa.png)
テキストにならって、データを描画します。
テキストの図4.17に相当します。
### 描画 ★図4.17に相当
plt.figure(figsize=(12, 4))
plt.plot(df_poi.index, df_poi['obs'])
plt.hlines(-1, 30, 60, lw=8, alpha=0.5)
plt.xlabel('Time')
plt.ylabel('observation');
【実行結果】
介入が入った30日以降で変動が大きくなっています。
![](https://assets.st-note.com/img/1700558014390-14uzPsMLYI.png?width=1200)
② モデルの定義
レベル成分の記述には「pm.GaussianRandomWalk()」を、季節成分の記述には「pm.AR()」をそれぞれ利用しました。
PyMC時系列分析オールスターズです!
![](https://assets.st-note.com/img/1700559732911-2i5D9lveJQ.png)
対数リンク関数の逆関数=自然対数の底$${e}$$のべき乗関数には、pytensorの関数「pt.exp()」を利用しました。
関数内で線形予測子の計算をしています。
尤度関数のポアソン分布は「pm.Poisson()」です。
### モデルの定義
# 季節成分を実装するARパラメータの設定
period = 12 # 季節成分の周期
order = period - 1 # ARの次数
rhos = np.ones(order) * -1 # ARのρパラメータ
with pm.Model() as model_poi:
### データ関連定義
# coordの定義
model_poi.add_coord('data', values=df_poi.index, mutable=True)
# dataの定義
y = pm.ConstantData('y', value=df_poi['obs'].values, dims='data')
IV = pm.ConstantData('IV', value=df_poi['IV'].values, dims='data')
### 事前分布
sigmaS = pm.Uniform('sigmaS', lower=0, upper=100)
sigmaSeason = pm.Uniform('sigmaSeason', lower=0, upper=100)
k = pm.Uniform('k', lower=-100, upper=100)
# mu:レベル成分
init_dist_mu = pm.Normal.dist(mu=0, sigma=sigmaS)
mu = pm.GaussianRandomWalk('mu', mu=0, sigma=sigmaS, init_dist=init_dist_mu,
dims='data')
# season:季節成分
init_dist_season = pm.Normal.dist(mu=0, sigma=sigmaSeason)
season = pm.AR('season', rho=rhos, sigma=sigmaSeason, constant=False,
init_dist=init_dist_season, ar_order=order, dims='data')
# 対数リンク関数の逆関数
lam = pm.Deterministic('lam', pt.exp(mu + k * IV + season), dims='data')
### 尤度:観測モデル
likelihood = pm.Poisson('likelihood', mu=lam, observed=y, dims='data')
【実行結果】出力なし
③ モデルの表示・可視化
モデルの数式を表示します。
### モデルの表示
model_poi
【実行結果】
![](https://assets.st-note.com/img/1700558278481-cXDi75OMF6.png?width=1200)
モデルを図にして可視化しましょう。
# モデルの可視化
pm.model_to_graphviz(model_poi)
【実行結果】
シンプルなモデルです。
![](https://assets.st-note.com/img/1700558300941-Yjd0OGxXDQ.png?width=1200)
④ 事後分布からのサンプリング
MCMCを実行します。
NUTSサンプラーにnumpyroを利用します。
実行時間はおよそ30秒でした。
### 事後分布からのサンプリング ※NUTSサンプラーにnumpyroを使用 30秒
with model_poi:
idata_poi= pm.sample(draws=6000, tune=2000, chains=4, target_accept=0.9,
nuts_sampler='numpyro', random_seed=1234)
【実行結果(一部)】
![](https://assets.st-note.com/img/1700558366909-y4hBwyYAdr.png?width=1200)
⑤ サンプリングデータの確認
$${\hat{R}}$$、事後分布の要約統計量、トレースプロットを確認します。
事後分布の収束確認はテキストにならって$${\hat{R} \leq 1.1}$$としています。
### r_hat>1.1の確認
# 設定
idata_in = idata_poi # idata名
threshold = 1.1 # しきい値
# しきい値を超えるR_hatの個数を表示
print((az.rhat(idata_in).to_dataframe() > threshold).sum())
【実行結果】
$${\hat{R}>1.1}$$のパラメータは「0」件です。
全てのパラメータが$${\hat{R} \leq 1.1}$$であることを確認できました。
![](https://assets.st-note.com/img/1700558407928-kW8kOErGvb.png)
パラメータ等の事後分布の要約統計量です。
日別ではないパラメータに限定して出力します。
### 推論データの要約統計情報の表示
var_names = ['k', 'sigmaS', 'sigmaSeason']
pm.summary(idata_poi, hdi_prob=0.95, var_names=var_names)
【実行結果】
介入の効果の平均値は$${1.366}$$です。
データ生成時の各パラメータの値とはあまり近くない結果になりました(汗)
![](https://assets.st-note.com/img/1700558429679-OX6UEatSis.png?width=1200)
(参考:データ生成時のパラメータ値)
![](https://assets.st-note.com/img/1700558752201-ST3Zqao0kZ.png)
トレースプロットを確認しましょう。
こちらは全パラメータを表示しましょう。
### トレースプロットの表示
pm.plot_trace(idata_poi, combined=True, figsize=(10, 10))
plt.tight_layout();
【実行結果】
収束している感じがします。
![](https://assets.st-note.com/img/1700558528336-BHRqGA5iy7.png?width=1200)
⑥ 介入効果$${\boldsymbol{k}}$$の描画
テキストの図4.18に相当する図を描画します。
### 介入効果kの事後分布 ※図4.18に相当
pm.plot_posterior(idata_poi, var_names='k', hdi_prob=0.95, figsize=(5, 3));
【実行結果】
95%HDIは$${[0.11,\ 2.6]}$$であり正の値です。
介入には正の効果があると考えられます。
![](https://assets.st-note.com/img/1700558578907-uttClL5gSA.png)
テキストのように介入効果$${k}$$の事後分布の中央値、95%確信区間を求めてみます。
# 介入効果kの事後中央値、95%CIの計算
tmp = idata_poi.posterior.k
k_median = tmp.median().data
k_ci0025 = tmp.quantile(q=0.025).data
k_ci0975 = tmp.quantile(q=0.975).data
print(f'k: {k_median:.3f} (95%CI= ({k_ci0025:.3f}, {k_ci0975:.3f}])')
【実行結果】
![](https://assets.st-note.com/img/1700558966604-rrwjk7a0ox.png)
⑦ 事後分布サンプリングデータにて目的変数「y」を予測
テキストの図4.19に相当する目的変数「y」の予測値を計算して、描画しましょう。
事後分布サンプリングデータの$${\mu, k}$$と介入実績$${IV}$$を用いて、「y」を予測します。
季節成分が除外されていることに留意しましょう。
### 季節調整済行動生起回数の事後分布の描画 ※図4.19に相当
## データの準備
# サンプルデータでμ+IVxkを計算 shape=(60, 24000)
y_pred_tmp = (np.exp(idata_poi.posterior.mu.stack(sample=('chain', 'draw')).data
+ df_poi['IV'].values.reshape(-1, 1)
* idata_poi.posterior.k.stack(sample=('chain', 'draw')).data))
# y_predの平均値の計算 shape=(60)
y_pred_mean = y_pred_tmp.mean(axis=1)
# y_predのHDIの計算 shape=(60, 2)
y_pred_hdi = az.hdi(y_pred_tmp.T)
## 描画
plt.figure(figsize=(12, 4))
plt.plot(df_poi.index, y_pred_mean)
plt.fill_between(df_poi.index, y_pred_hdi[:, 0], y_pred_hdi[:, 1], alpha=0.2)
plt.xlabel('Time')
plt.ylabel(r'Pred: $\mu \times IV$');
【実行結果】
30日以降、生起回数は増えている様子が分かります。
薄い青色の95%HDIは30日以降の方が幅が広くなっています。
![](https://assets.st-note.com/img/1700559307378-1FMp6FxKEH.png?width=1200)
以上で終了です。
お疲れ様でした。
![](https://assets.st-note.com/img/1700559469329-Hx6civIioR.png)
まとめ
観測値が離散量のケースをモデリングして、二項分布・ポアソン分布といった離散型分布を利用しました。
モデリングの精度はテキストよりも劣る結果になりました(汗)
(参考)推論データ(idata)の保存コード
pickle形式で保存します。
### idataの保存 pickle
# ユーティリティ
import pickle
file = r'ch04_04_idata_bin.pkl'
with open(file, 'wb') as f:
pickle.dump(idata_bin, f)
file = r'ch04_04_idata_poi.pkl'
with open(file, 'wb') as f:
pickle.dump(idata_poi, f)
読み込みコードは次のとおりです。
「file」のパス&ファイル名と「idata_bin_load」を適宜変更してお使い下さい。
### idataの読み込み(例)
file = r'ch04_04_idata_bin.pkl'
with open(file, 'rb') as f:
idata_bin_load = pickle.load(f)
第4章 その5 へ続く
■次回の取り組みテーマ
引き続き、状態空間モデルを学びます。
推測するパラメータが離散値のケースです。
・変化点モデル
・隠れマルコフモデル
次の記事
前の記事
目次
ブログの紹介
note で4つのシリーズ記事を書いています。
ぜひ覗いていってくださいね!
1.のんびり統計
統計検定2級の問題集を手がかりにして、確率・統計をざっくり掘り下げるブログです。
雑談感覚で大丈夫です。ぜひ覗いていってくださいね。
統計検定2級公式問題集CBT対応版に対応しています。
2.実験!たのしいベイズモデリングをPyMC Ver.5で
書籍「たのしいベイズモデリング」の心理学研究に用いられたベイズモデルを PyMC Ver.5で描いて分析します。
この書籍をはじめ、多くのベイズモデルは R言語+Stanで書かれています。
PyMCの可能性を探り出し、手軽にベイズモデリングを実践できるように努めます。
身近なテーマ、イメージしやすいテーマですので、ぜひぜひPyMCで動かして、一緒に楽しみましょう!
3.Python機械学習プログラミング実践記
書籍「Python機械学習プログラミング PyTorch & scikit-learn編」を学んだときのさまざまな思いを記事にしました。
この書籍は、scikit-learn と PyTorch の教科書です。
よかったらぜひ、お試しくださいませ。
4.データサイエンスっぽいことを綴る
統計、データ分析、AI、機械学習、Python のコラムを不定期に綴っています。
「統計」「Python」「数学とPython」「R」のシリーズが生まれています。
最後までお読みいただきまして、ありがとうございました。