Up

独立な2つのサンプルの平均値と分散のベイズ分析

 

2つのデータセットがそれぞれ独立に2つの母集団からサンプリングされたとする。それぞれの母集団の確率分布は、平均、標準偏差の正規分布であるとする。すなわち、

である。

 このモデルのPyStan 3対応Stanスクリプトをリストのように用意した。なお、スクリプトファイルなどはまとめて圧縮ファイルDiffIndMeansFiles.zipとした。ダウンロード展開すれば自由に利用できる。岡本安晴いまさら聞けないPythonデータ分析ではPyStan 2について説明している。PyStan 3PyStan 2の違いについては、このウェブサイトなどで説明している。

 

 

リスト1 Stanスクリプト(diff_model.stan

 

data {

    int n1;

    array[n1] real x1;

    int n2;

    array[n2] real x2;

    //

    //     Values for rescaling

    //

    real dmean1;

    real dsgm1;

    real dmean2;

    real dsgm2;

}

parameters {

    real vmu1;

    real vmu2;

    real<lower=0.0> vsgm1;

    real<lower=0.0> vsgm2;

}

transformed parameters {

    real mu1;

    real mu2;

    real sgm1;

    real sgm2;

    //

    //      Rescaling of parameters

    //

    mu1 = vmu1 * dsgm1 + dmean1;

    mu2 = vmu2 * dsgm2 + dmean2;

    sgm1 = vsgm1 * dsgm1 + 0.001;

    sgm2 = vsgm2 * dsgm2 + 0.001;

}

model {

    vmu1 ~ normal(0.0, 10.0);

    vmu2 ~ normal(0.0, 10.0);

    vsgm1 ~ exponential(0.1);

    vsgm2 ~ exponential(0.1);

    x1 ~ normal(mu1, sgm1);

    x2 ~ normal(mu2, sgm2);

}

generated quantities {

    real diff;

    real dsgm;

    diff = mu1 - mu2;

    dsgm = sgm1 - sgm2;

}

 

 

PyStan 3では、事前分布の分散が大き過ぎると警告メッセージが出される。これを避けるために、事前分布を

 

    vmu1 ~ normal(0.0, 10.0);

    vmu2 ~ normal(0.0, 10.0);

    vsgm1 ~ exponential(0.1);

    vsgm2 ~ exponential(0.1);

 

と設定してから、母集団のパラメータの変動範囲をデータに合わせて

 

    mu1 = vmu1 * dsgm1 + dmean1;

    mu2 = vmu2 * dsgm2 + dmean2;

    sgm1 = vsgm1 * dsgm1 + 0.001;

    sgm2 = vsgm2 * dsgm2 + 0.001;

 

とスケーリングしている。

リストStanスクリプトを用いてベイズ分析を行うスクリプトを、リストのスクリプトのように用意した。リストのスクリプトをWindowsで実行する場合は、WindowsUbuntuをインストールして、UbuntuにインストールされたAnacondaPython仮想環境を用意して実行できる。以下の実行例は、Python 3.11仮想環境でのものである(2024.02.04)。WindowsへのUbuntuのインストールなどは、このウェブサイトなどで説明している。

ベイズ分析については、岡本安晴(2019岡本安晴(2014などで説明している。

リストのスクリプトを実行すると、まず、入力データファイル名と出力ファイル名を聞いてくる。入力データファイルはExcel形式のファイルを用意し、出力ファイルは適当なテキストファイル名を設定する。

 

(py311) *****/DiffIndMeansFiles$ python AnalIndMeans.py

入力データファイル(*.xlsx= data.xlsx

出力ファイル(*.txt= temp.txt

 

入力データファイルの例として図1のものを考える。

図1 Excelデータ(data.xlsx

 

A市とB市からランダムにそれぞれ15人と20人を選び、血圧を測ったものである(仮想データ)。1行目に変数名を書く。1列目はデータの識別値であるが、分析には用いられない。2列目と3列目にデータの値を書く。

出力ファイル名を設定すると、データの読み込みが始まり、ヒストグラムが表示される(図2)。

図2

 

図2のウィンドウを閉じると、Stanスクリプト(リスト)のビルドが始まり、MCMCサンプリングが行われる。サンプリング後、トレース図が表示される(図3)。

図3

 

図3のウィンドウを閉じると、パラメータの事後分布のグラフが表示れる(図4)。

図4

 

2つの事後分布は、明瞭に分離されている。タイトルの上段にの事後分布の四分位数、下段にの事後分布の四分位数が表示されている。分布の代表値としてGelman(2021は、平均値は不安定であるとして、中央値を勧めている。

図4のウィンドウを閉じると、の事後分布のグラフが表示される(図5)。

図5

 

の事後分布の95HDIが[-11.54, -6.04]となっていて、0より負の領域にあることが明瞭である。すなわち、母集団の平均値はよりの方が大きいことが明白であると言える(図4参照)。

図5のウィンドウを閉じると、の事後分布のグラフが表示される(図6)。

図6

 

2つの事後分布のグラフはほとんど重なっている。

図6のウィンドウを閉じると、パラメータの差の事後分布のグラフが表示される(図7)。

図7

 

95HDIの中ほどに原点0が位置している。すなわち、2つのパラメータの差は0と考えてもよさそうである。

図7のウィンドウを閉じると、プログラムの実行終了である。端末画面には、出力ファイルtemp.txtが保存されたことが表示されている。

 

temp.txt was saved.

 

出力ファイルtemp.txtを開くと、以下のような内容である。

 

入力データファイル: data.xlsx

 

A

[126, 122, 120, 132, 129, 124, 124, 125, 124, 124, 121, 121, 130, 125, 129]

 

B

[126, 139, 137, 130, 139, 132, 128, 137, 134, 137, 131, 133, 136, 131, 130, 134, 141, 135, 136, 133]

 

A

平均値 = 125.07  標準偏差 = 3.43

 

B

平均値 = 133.95  標準偏差 = 3.81

 

Summary...

          mean     sd   hdi_3%  hdi_97%  mcse_mean  mcse_sd  ess_bulk  ess_tail  r_hat

vmu1    -0.003  0.302   -0.573    0.560      0.005    0.005    3340.0    2265.0    1.0

vmu2     0.000  0.245   -0.483    0.449      0.004    0.004    3469.0    2492.0    1.0

vsgm1    1.140  0.248    0.745    1.606      0.005    0.004    3188.0    2139.0    1.0

vsgm2    1.098  0.191    0.783    1.464      0.003    0.003    3551.0    2576.0    1.0

mu1    125.058  1.036  123.098  126.989      0.018    0.013    3340.0    2265.0    1.0

mu2    133.951  0.935  132.106  135.664      0.016    0.011    3469.0    2492.0    1.0

sgm1     3.918  0.850    2.561    5.518      0.016    0.012    3188.0    2139.0    1.0

sgm2     4.187  0.728    2.986    5.584      0.013    0.010    3551.0    2576.0    1.0

diff    -8.894  1.389  -11.524   -6.283      0.025    0.018    3251.0    2507.0    1.0

dsgm    -0.270  1.127   -2.362    1.860      0.020    0.017    3224.0    2712.0    1.0

 

Posterior distributions of mu1 and mu2

A:  Q1 = 124.41, Q2 = 125.06, Q3 = 125.71

B:  Q1 = 133.33, Q2 = 133.94, Q3 = 134.55

 

Posterior distribution of mu1 - mu2...

mu1-m2: Q1 = -9.77, Q2 = -8.89, Q3 = -7.97

95% HDI = [-11.54, -6.04]

 

Posterior distributions of sgm1 and sgm2...

A:  Q1 = 3.33, Q2 = 3.77, Q3 = 4.38

B:  Q1 = 3.68, Q2 = 4.10, Q3 = 4.58

 

Posterior distribution of sgm1 - sgm2...

sgm1-sgm: Q1 = -0.99, Q2 = -0.31, Q3 = 0.43

95% HDI = [-2.35, 2.11]

 

 

プログラムの実行中に表示されるグラフは、グラフのウィンドウの保存ボタンをクリックすれば保存される。

 

 

リスト2 独立なサンプルのベイズ分析(AnalIndMeans.py

 

import pandas as pd

import numpy as np

import matplotlib.pyplot as plt

import japanize_matplotlib

import scipy.stats as ss

import stan

import arviz as az

import seaborn as sb

 

finnm = input('入力データファイル(*.xlsx= ')

txtoutnm = input('出力ファイル(*.txt= ')

txtout = open(txtoutnm, 'w')

#

#      Excelファイルの読み込み

#

xlsx_fl = pd.ExcelFile(finnm)                       #   ExcelファイルからExcelFile型オブジェクトを生成

data_xlsx = pd.read_excel(xlsx_fl,  header = None)  #   ExcelFile型オブジェクトからDataFrame型オブジェクトを生成

data = data_xlsx.values                             #   Dataframe型オブジェクトからnumpyの配列オブジェクトを生成

print('\n', data)

 

txtout.write('\n入力データファイル: {}\n'.format(finnm))

 

VarNameA = data[0][1]

VarNameB = data[0][2]

 

dataA = []

dataB = []

for i in range(1,len(data)):

    if not pd.isnull(data[i][1]):

        dataA.append(data[i][1])

    if not pd.isnull(data[i][2]):

        dataB.append(data[i][2])

print('dataA =\n', dataA)

print('dataB =\n', dataB)

 

txtout.write('\n{0}\n'.format(VarNameA))

txtout.write('{0}\n'.format(dataA))

txtout.write('\n{0}\n'.format(VarNameB))

txtout.write('{0}\n'.format(dataB))

 

meanA = np.mean(dataA)

meanB = np.mean(dataB)

sdA = np.std(dataA)

sdB = np.std(dataB)

print(VarNameA)

print('平均値 = {0:.2f}  標準偏差 = {1:.2f}'.format(meanA, sdA))

print(VarNameB)

print('平均値 = {0:.2f}  標準偏差 = {1:.2f}'.format(meanB, sdB))

txtout.write('\n' + VarNameA + '\n')

txtout.write('平均値 = {0:.2f}  標準偏差 = {1:.2f}\n'.format(meanA, sdA))

txtout.write('\n' + VarNameB + '\n')

txtout.write('平均値 = {0:.2f}  標準偏差 = {1:.2f}\n'.format(meanB, sdB))

 

plt.title(('{0} mean = {1:.2f}  sd = {2:.2f}'.format(VarNameA, meanA, sdA)) + \

          '\n{0} mean = {1:.2f}  sd = {2:.2f}'.format(VarNameB, meanB, sdB),

          fontsize = 16)

plt.hist(dataA, alpha = 0.5, label = VarNameA)

plt.hist(dataB, alpha = 0.5, label = VarNameB)

plt.xlabel('', fontsize = 14)

plt.ylabel('度数', fontsize = 14)

plt.legend()

plt.tight_layout()

plt.show()

 

Data = {'n1':len(dataA), 'x1':dataA, 'n2':len(dataB), 'x2':dataB,

        'dmean1':meanA, 'dsgm1':sdA, 'dmean2':meanB, 'dsgm2':sdB}

with open('diff_model.stan') as f_stan:

    sm = stan.build(f_stan.read(), data=Data)

fit = sm.sample()

 

i_data = az.from_pystan(posterior=fit, posterior_model=sm)

 

smry = az.summary(i_data)      #     Summary of statistics of MCMC sampling

print('Summary...\n', smry)

txtout.write(f'\nSummary...\n{smry}\n')

#

#         Trace graph of MCMC sampling

#

az.plot_trace(i_data, var_names=['mu1', 'mu2', 'sgm1', 'sgm2'],

              figsize=(10,7))

plt.tight_layout()

plt.show()

#

#         Posterior distributions of parameters mu1 and mu2

#

d_frame = fit.to_frame()

mu1_para = d_frame['mu1']

mu2_para = d_frame['mu2']

q1A, q2A, q3A = np.percentile(mu1_para, [25, 50, 75])

q1B, q2B, q3B = np.percentile(mu2_para, [25, 50, 75])

strA = f'{VarNameA}:  Q1 = {q1A:.2f}, Q2 = {q2A:.2f}, Q3 = {q3A:.2f}\n' + \

       f'{VarNameB}:  Q1 = {q1B:.2f}, Q2 = {q2B:.2f}, Q3 = {q3B:.2f}'

sb.kdeplot(mu1_para, color='g', label=VarNameA)

sb.kdeplot(mu2_para, color='b', label=VarNameB)

plt.xlabel('mu1 or mu2', fontsize=16)

plt.title(strA)

plt.legend()

plt.show()

txtout.write('\nPosterior distributions of mu1 and mu2\n' + strA + '\n')

#

#           Posterior distribution of difference mu1 - mu2

#

diff_para = d_frame['diff']

print(diff_para)

diff_para = np.array(diff_para)

print(diff_para)

q1D, q2D, q3D = np.percentile(diff_para, [25, 50, 75])

LvUv = az.hdi(diff_para, hdi_prob=0.95)

print('LvUv =', LvUv)

strD = f'mu1-m2: Q1 = {q1D:.2f}, Q2 = {q2D:.2f}, Q3 = {q3D:.2f}\n' + \

      f'95% HDI = [{LvUv[0]:.2f}, {LvUv[1]:.2f}]'

txtout.write('\nPosterior distribution of mu1 - mu2...\n' + strD + '\n')

sb.kdeplot(diff_para)

plt.title(strD)

plt.xlabel('mu1-m2', fontsize = 16)

plt.tight_layout()

plt.show()

#

#           Posterior distributions of parameters sgm1 and sgm2

#

sgm1_para = d_frame['sgm1']

sgm2_para = d_frame['sgm2']

q1DA, q2DA, q3DA = np.percentile(sgm1_para, [25, 50, 75])

q1DB, q2DB, q3DB = np.percentile(sgm2_para, [25, 50, 75])

strSgm = f'{VarNameA}:  Q1 = {q1DA:.2f}, Q2 = {q2DA:.2f}, Q3 = {q3DA:.2f}\n' + \

       f'{VarNameB}:  Q1 = {q1DB:.2f}, Q2 = {q2DB:.2f}, Q3 = {q3DB:.2f}'

txtout.write('\nPosterior distributions of sgm1 and sgm2...\n' + strSgm + '\n')

sb.kdeplot(sgm1_para, label=VarNameA)

sb.kdeplot(sgm2_para, label=VarNameB)

plt.xlabel('sgm1 or sgm2', fontsize=16)

plt.title(strSgm)

plt.legend()

plt.tight_layout()

plt.show()

#

#          Posterior distribution of difference sgm1 - sgm2

#

dsgm_para = d_frame['dsgm']

dsgm_para = np.array(dsgm_para)

q1DS, q2DS, q3DS = np.percentile(dsgm_para, [25, 50, 75])

LvUvS = az.hdi(dsgm_para, hdi_prob=0.95)

print('LvUvS =', LvUvS)

strD = f'sgm1-sgm: Q1 = {q1DS:.2f}, Q2 = {q2DS:.2f}, Q3 = {q3DS:.2f}\n' + \

      f'95% HDI = [{LvUvS[0]:.2f}, {LvUvS[1]:.2f}]'

txtout.write('\nPosterior distribution of sgm1 - sgm2...\n' + strD + '\n')

sb.kdeplot(dsgm_para)

plt.title(strD)

plt.xlabel('sgm1 - sgm2', fontsize=16)

plt.legend()

plt.tight_layout()

plt.show()

 

txtout.close()

print(f'\n{txtoutnm} was saved.\n')

 

 

 

Home