見出し画像

「ベイズ統計モデリングによるデータ分析入門」をPythonとStanで写経 ~ Vol.12 ダミー変数と分散分析モデル

書籍の著者 馬場真哉 先生


この記事は、書籍「RとStanではじめるベイズ統計モデリングによるデータ分析入門」第3部第6章「ダミー変数と分散分析モデル」Python 写経活動記録です。

書籍の第3部は「一般化線形モデル」のベイズ統計モデリングです。
今回は説明変数がダミー変数の「分散分析モデル」を2つのツール・手法で取り組みます。

  • 回帰分析による分散分析モデル

  • Stanのベイズ分散分析モデル

では書籍を開いてベイズ統計モデリングの旅に出かけましょう🚀


はじめに


このブログシリーズは書籍「RとStanではじめるベイズ統計モデリングによるデータ分析入門」(講談社、「テキスト」と呼びます)の Python 写経です。

テキストの紹介と引用表記はリンク先の記事に掲載しています。

準備


■ 記事の範囲
この記事はテキスト第3部第6章の以下の節を取り扱います。

6.3 分析の準備
6.4 データの読み込みと可視化
6.5 brmsによる分散分析モデルの推定
6.6 補足:分散分析モデルのデザイン行列
6.7 補足:brmsを使わない分散分析モデルの推定

ベイズ統計モデリングの理論面が気になった場合には、ぜひテキストの第1部、第3部第1章をご覧いただき、本記事との繋がりをご確認下さいませ。

■ コード記述法
Jupyter Notebook 形式でコードを記述します。

■ 利用データ
テキスト・サポートサイトのデータファイルを引用しています。
Jupyter Notebook ファイルと同一フォルダ内の「data」フォルダにデータファイルを格納しています。

■ ライブラリのインポート
この記事で用いるライブラリをインポートします。

# インポート

# 数値計算
import pandas as pd
import numpy as np

# ベイズ統計モデリング
from cmdstanpy import CmdStanModel     # stan
import arviz as az                     # 分析・可視化

# 統計モデリング
import statsmodels.formula.api as smf

# デザイン行列
from patsy import dmatrices

# ユーティリティ
import os

# 可視化
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_theme()                        # ggplot風のスタイル
plt.rcParams['font.family'] = 'Meiryo'

第6章 ダミー変数と分散分析モデル


データの読み込みと外観の確認

テキスト 6.4 節に相当します。
テキストの仮想のビールの売り上げデータを引用いたします。
csv ファイルを pandas のデータフレーム形式で変数 sales_weather に読み込みます。

# p.202 分析対象データの読み込み

# ファイルの読み込み
sales_weather = pd.read_csv('./data/3-6-1-beer-sales-3.csv')

# 結果の表示
print('sales_weather.shape: ', sales_weather.shape)
sales_weather.head(3)

【実行結果】
標本サイズ 150、変数 sales は 売り上げ(単位:万円)、weather は天気の種類(晴れ、曇り、雨)です。
天気と売り上げの関係を分散分析モデルで分析します。

天気ごとのデータ件数をカウントします。
pandas の value_counts メソッドを利用します。

# 天気ごとのデータ件数
sales_weather['weather'].value_counts().to_frame()

【実行結果】
曇り、晴れ、雨それぞれ 50 件のデータがあります。

データの要約統計量を確認します。
まずは全体です。

# データの要約統計量
sales_weather.describe().T.round(2)

【実行結果】
売り上げは平均 70、最小値 25、最大値 116 です。範囲が広い感じ。

次は天気別売り上げの要約統計量です。

# 天気別の要約統計量
sales_weather.groupby(['weather'])['sales'].describe().round(2)

【実行結果】
平均 mean を見ると、曇りと雨の売り上げは約 63 万円、晴れのときは+20 万円の約 83 万円です。

データを可視化しましょう。
テキスト 図 3.6.1 に相当するバイオリンプロットです。
seaborn ライブラリを利用します。

# p.203 図3.6.1 ビールの売上と天気のバイオリンプロット

# バイオリンプロット
sns.violinplot(data=sales_weather, x='weather', y='sales', hue='weather',
               fill=False)
# 散布図(ストリッププロット)
sns.stripplot(data=sales_weather, x='weather', y='sales', hue='weather')
# 修飾
plt.title('観測データ:天気別のバイオリンプロット');

【実行結果】
晴れの売り上げが大きい感じを掴めると思います。

天気と売り上げの相関を見てみましょう。
質的変数 vs 量的変数なので「相関比」を使ってみます。
相関比算出関数を定義します。

# 相関比算出関数の定義
# ※引数: x=量的変数(numpy配列), gp=質的変数(numpy配列)

def correlation_ratio(x, gp):
    # 全変動: 全データの偏差平方和を計算
    ST = ((x - x.mean())**2).sum()
    # 群内変動: 群gごとに偏差平方和を計算して総合計する
    SW = sum(((x[gp==g] - x[gp==g].mean())**2).sum() for g in np.unique(gp))
    # 相関比 = 1 - 群内変動 / 全変動 = 群間変動 / 全変動
    eta2 = 1 - SW / ST
    # 戻り値  相関比:eta2, 総変動:ST, 群内変動:SW, 群間変動:SB
    return {'eta2': eta2, 'ST': ST, 'SW': SW, 'SB': ST-SW}

データの相関比を計算しましょう。

# 相関比の算出 ※決定係数 R² と一致する
result = correlation_ratio(sales_weather['sales'], sales_weather['weather'])
print(f'相関比η² = {result["eta2"]:.3f}')

【実行結果】
相関比は 0.247。
天気と売り上げとの間には弱い正の相関があるようです。

🔵🔵🔵

回帰分析による分散分析モデル

① モデルの概要
まず分散分析モデルを回帰分析で確認しておき、あとでベイズ統計モデルの結果と比べてみましょう。
Python の統計ライブラリ statsmodels を利用して、次のモデルを実装します!

$$
sales = 切片 + 係数1 \times 雨ダミー + 係数2 \times 晴れダミー + \varepsilon
$$

$${\varepsilon}$$ は誤差です。
曇りを基準要素とし、ダミー変数から除外します。

この分散分析モデルを statsmodels の最小二乗法 olsにあたえる formula 構文に変換します。
$${\sim}$$ を挟んで、左辺は目的変数、右辺は説明変数です。

$$
\mathtt{sales} \sim \mathtt{weather}
$$

質的変数(Categorical variable)の天気 weather を C() で括る書き方もあります。

$$
\mathtt{sales} \sim \mathtt{C(weather)}
$$

② 回帰分析の実行
では回帰分析を実行します!

# 回帰分析 by statsmodels
formula = 'sales ~ weather'
res_sm = smf.ols(formula=formula, data=sales_weather).fit()
res_sm.summary()

【実行結果】
こちらは(お馴染みの?)回帰分析のサマリーです。
決定係数(R-squared)の 0.247 は相関比と一致しています。
一元配置分散分析モデルの場合、データの相関比と回帰モデル(切片付き)の決定係数は一致するようですね!

③ 推定値の確認
係数の推定値にフォーカスしてみます。

# 要約表から係数の推定値の部分を取り出し
res_sm.summary().tables[1]

【実行結果】

Intercept:切片の推定値(coef)の 63.0 は基準要素「曇り(cloudy)」の平均売り上げです。
雨の平均売り上げは、「weather[T.rainy]」の推定値 - 0.3 と曇りの推定値を合算した 62.7 です。
晴れの平均売り上げは、「weather[T.sunny]」の推定値 20.0 と曇りの推定値を合算した 83.0 です。

誤差 $${\varepsilon}$$ の標準偏差を確認しましょう。
回帰分析の結果 res_sm の scale 属性で誤差分散を取り出して正の平方根をとります。

# 誤差の標準偏差
print('誤差の標準偏差:', np.sqrt(res_sm.scale))

【実行結果】
誤差の標準偏差は 16.78 です。
ベイズ統計モデルの観測値 sales の標準偏差と照らし合わせましょう。

④ 天気別平均売上の可視化
最後に天気別平均売上の95%信頼区間を可視化しましょう。
回帰分析の結果 res_sm に対して get_prediction メソッドを適用するなどして、予測値を得ます。

# 天気別平均売上の95%信頼区間をエラーバーで描画

# 予測用データの作成(各グループ1つずつ)
test_data = pd.DataFrame({'weather': ['cloudy', 'rainy', 'sunny']})

# 予測値を取得
prediction = res_sm.get_prediction(test_data)
pred_summary = prediction.summary_frame(alpha=0.05) # alpha=0.05で95%信頼区間

# 95%信頼区間付きエラーバーの描画
fig, ax = plt.subplots()
ax.errorbar(test_data['weather'], pred_summary['mean'], 
            yerr=[pred_summary['mean'] - pred_summary['mean_ci_lower'], 
                  pred_summary['mean_ci_upper'] - pred_summary['mean']],
            fmt='o', capsize=5)
plt.ylabel('sales')
plt.title('天気別平均売上の95%信頼区間')
ax.margins(x=0.3)
plt.show()

【実行結果】
「曇り・雨」と「晴れ」の信頼区間に重なりはなく、大きな差があります。
晴れると売り上げが上がる感覚をイメージできます。
ではベイズ分散分析モデルではどうなるでしょう?

ベイズ流の分散分析モデルへ進みます。

🔵🔵🔵

ベイズモデリング

テキスト 6.6、6.7 節に相当します。
デザイン行列を作成して CmdStanPy でモデリングします。

① モデルの概要
デザイン行列と係数ベクトルを用いて、次のモデルを実装します。

$$
\begin{align*}
\bm Y &\sim \text{Normal}(\bm \mu,\ \sigma^2) \\
\bm \mu &= \bm{X \beta}
\end{align*}
$$

目的変数 $${\bm Y}$$(sales)は正規分布に従うと仮定しています。
正規分布の平均パラメータ $${\bm \mu}$$ は、デザイン行列 $${\bm X}$$ と係数ベクトル $${\bm \beta}$$ の積です。
係数ベクトル $${\bm \beta}$$、標準偏差パラメータ $${\sigma}$$ には事前分布を明示的に設定しません。

③ Stan のモデル設定
Stan ファイル(Stan コード)を作成します。
テキストの Stan コードを引用いたします。
📑ファイル名:3-6-1-lm-design-matrix.stan

data {
    int N;                  // 標本サイズ
    int K;                  // デザイン行列の列数(説明変数の数+1)
    vector[N] Y;            // 目的変数
    matrix[N, K] X;         // 説明変数
}

parameters {
    vector[K] b;            // 切片を含む係数ベクトル
    real<lower=0> sigma;    // 標準偏差
}

model {
    vector[N] mu = X * b;
    Y ~ normal(mu, sigma);
}

【実行結果】なし

③ データセットの作成
patsy ライブラリを用いてデザイン行列等を作成します。

# p.205 デザイン行列の作成

# formula構文の設定
formula_lm = 'sales ~ weather'
# 目的変数Y, デザイン行列(説明変数)Xの作成
Y, X = formula_lm = dmatrices(formula_lm, sales_weather, return_type='dataframe')
# デザイン行列の先頭5行の表示
X.head()

【実行結果】
デザイン行列は定数項と天気のダミー変数(雨と晴れ)で構成されます。

# 目的変数の先頭5行の表示
Y.head()

【実行結果】
目的変数も作ってくれました。

標本サイズ・説明変数の数を算出して、Stan に渡すデータセットを辞書にまとめます。

# データセットの準備

# サンプルサイズ、デザイン行列の列数(説明変数の数+1)
N, K = X.shape

# 辞書にまとめる ※Y:pd.Series, X:pd.DataFrame
data_dict_design = dict(N=N, K=K, Y=Y['sales'], X=X)

【実行結果】なし

④ モデルのコンパイル
モデルのコンパイルを実行します。

%%time
# モデルのコンパイル

# stanプログラムファイルのパス指定
stan_file = '3-6-1-lm-design-matrix.stan'
current_dir = os.path.abspath(os.getcwd())
stan_path = os.path.join(current_dir, 'stan', stan_file)

# モデルオブジェクトの作成(exeの作成)
model = CmdStanModel(stan_file=stan_path)  # stanのpathを設定

【実行結果】

MCMC の準備が整いました!

⑤ MCMC の実行
MCMCを実行しましょう。

%%time
# p.206 MCMCの実行
fit = model.sample(
    data=data_dict_design,   # 対象データ
    seed=1,                  # 乱数の種
    sig_figs=18,             # 出力CSV等に適用する数値精度
)

【実行結果】

⑥ 収束確認
収束の確認をします。
診断メソッド diagnose を利用します。

# 事後分布の診断
print(fit.diagnose())

【実行結果】(1行目のファイルパスは記載省略)
問題は検出されませんでした(no problems detected.)。

CmdStanPy の 出力結果 fit から MCMCサンプルの要約統計量を把握しましょう。

# p.206 結果
fit.summary(percentiles=[2.5, 50, 97.5]).round(2)

【実行結果】
$${\widehat{R}}$$(R_hat)、有効サンプル数(N_Eff)に問題はなさそうです。
b[1] は切片、b[2] は雨、b[3] は晴れの係数に対応しています。

データの要約統計量と回帰分析の推定値の表を並べましょう。
(参考:データの要約統計量)

(参考:回帰分析の推定値と誤差の標準偏差)

3つの手法間で平均値・係数推定値は近い値になっています。

トレースプロットを描画します。
fit を arviz の idata に変換します。

# arvizのidataに変換
idata = az.from_cmdstanpy(posterior=fit, log_likelihood='lp__')
idata

【実行結果】

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

# トレースプロットの描画
az.plot_trace(idata, compact=False, backend_kwargs={'tight_layout': True});

【実行結果】
左側のチャートの4本の Chain はほぼ重なっており、かつ1峰です。
右側のチャートがゲジゲジしています。

以上のチェックに基づいて、収束していると考えましょう。

⑦ 天気別の平均売上の可視化
テキストの図 3.6.2 に相当します。
MCMCサンプルを足し算して天気別平均売上データを作成し、エラーバープロットを描画します。

# p.204 図3.6.3 天気別平均売上のエラーバープロット

## データの作成
# MCMCサンプルからβ0, β1, β2, σを取り出し
beta0s, beta1s, beta2s, sigmas = fit.draws_pd().iloc[:, -4:].values.T
# 曇り、雨、晴れの平均売上データの作成
sales_weather_result = pd.DataFrame({
    'cloudy': beta0s, 'rainy': beta0s + beta1s, 'sunny': beta0s + beta2s
})

## 描画
# 天気別平均売上のエラーバープロットの描画
sns.pointplot(data=sales_weather_result, errorbar=('pi', 95), capsize=0.2,
              linestyle='none')
# 修飾
plt.title('事後分布サンプル:天気別平均売上の95%信用区間')
plt.xlabel('天気', fontsize=12)
plt.ylabel('売上金額', fontsize=12);

【実行結果】
晴れの平均売上は他の天気と比べて 20 万円くらい大きいですね!

⑦ 平均売上の事後分布プロットの描画
天気別平均売上の分布を可視化しましょう。

# 平均売上の事後分布プロット

# 天気ごとのデータを辞書にまとめる
plot_dict = {
    'cloudy': sales_weather_result['cloudy'].values,
    'rainy': sales_weather_result['rainy'].values,
    'sunny': sales_weather_result['sunny'].values
}

# 事後予測のプロット
az.plot_posterior(plot_dict, hdi_prob=0.95);

【実行結果】

🔵🔵🔵

今回の記事は以上です。
楽しかったですね!

シリーズの記事


次の記事

前の記事

PyMC版

目次

ブログの紹介


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

1.のんびり統計

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

2.統計・データ分析とつながる

シリーズ「統計・データ分析とつながる」は、統計・データ分析との「つながり」を発掘して、コラム風に仕立てたブログシリーズです。
生成 AI の力を借りながら、統計・データ分析の入り口をイメージして、自由気ままに書きました。
たとえば…
・日常生活と統計のつながり
・統計検定2級からその先へのつながり
気楽にお読みいただけたら嬉しいです🍀

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

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

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

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

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

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

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

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

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

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

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

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

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

いいなと思ったら応援しよう!

ネイピア DS 応援ありがとうございます。これからもがんばって記事を作成します!

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