独立なサンプルの平均値の差のベイズ分析
2つの独立なサンプルの平均値の差についてベイズ分析を行う。
A市から15人、B市から20人をランダムに選び血圧を測ったところ図1に示す結果を得たとする。
図1 data.xlsx
データの形式は、1行目に変数名を書き、2行目以降にデータ値が書かれている。第1列は各データの識別用で数値でも文字列でもよい。2列目に第1グループ(図1の場合はA市)のデータを書き、3列目に第2グループ(図1の場合はB市)のデータを書く。人数が揃わないときは、少ない方のデータは欠損値として空欄にしておく。
図1の形式のデータに対して以下の確率モデルを設定して平均値の差のベイズ分析を行う。
上のモデルに対するStanスクリプト例をリスト1としてこのウェブサイトの後半に示す。なお、サンプルファイルは圧縮ファイルfilesBysDifMuInd.zipとしてまとめた。ダウンロード解凍すれば自由に利用できる。
リスト1のStanスクリプトを利用するPythonスクリプトをリスト2としてこのウェブサイトの後半に示す。
リスト2のスクリプトを実行すると、入力データファイル名の設定を求めるダイアログウィンドウが表示される(図2)。
図2
入力データファイルを設定する。図2では、図1のファイル名data.xlsxが設定されている。データファイル名を設定して「開く」ボタンをクリックすると、計算結果などが書き出される出力ファイル名の設定を求めるダイアログウィンドウが表示される(図3)。
図3
適当なテキストファイル名を設定する。ファイル名を設定して「保存」ボタンをクリックすると、グラフ画像を保存するファイルの名前の設定を求めるダイアログウィンドウが表示される(図4)。
図4
画像は複数枚保存されるので、図4のダイアログウィンドウで設定した名前を基に複数の名前が作成されて保存される。設定した名前が図4のように「MyFig.png」であれば、「MyFig_XXX.png」という形式の名前(XXXの部分は図によって変わる)で画像が保存される。保存ファイル名を設定して「保存」ボタンをクリックすると、Stanスクリプトのコンパイルが始まり、コードのビルド後、MCMCによるサンプリングが始まる。サンプリングが終了すると、まず平均値パラメータの事後分布が表示される(図5)。
図5
図5のウィンドウを閉じると、平均値の差の事後分布が表示される(図6)。
図6
差の事後分布の95%CIが[-11.77, -6.11]と負の領域にあるので、A市とB市の平均値の差は負である、すなわちA市の平均値の方が低いことが分かる。
図7のウィンドウを閉じると、標準偏差の比の事後分布が表示される(図7)。
図7
標準偏差の比の事後分布が、平均値が0.957と1に近く、95%CIも[0.556, 1.584]と1を内部の中央付近に含む位置である。標準偏差は等しいと考えてよいようである。
図7のウィンドウを閉じると、2つの標準偏差それぞれの事後分布が表示される(図8)。2つの事後分布の重なりが明瞭である。
図8
図8のウィンドウを閉じると、図9の事後予測分布のグラフが表示される。
図9
2つの群(A市とB市)からそれぞれランダムにサンプルを採り、その差を求めたものである。2つの群は、パラメータ値が事後分布から取られた値である正規分布に従うものとしてシムレーションによって求めている。A市とB市からランダムに1人ずつ選んで比べたとき、A市の人の方が値が高いのは100回中数回のレベルであるという予測である。
図9のウィンドウを閉じると、プログラムの実行終了である。実行終了後、図3で指定した出力ファイルを開くと以下のようになっている。PythonのIDLEでは開けないことがあるが、そのときはメモ帳やワードパッドなど他のエディタで開けばよい。
入力データファイル:
D:/yasuharu/www/LaCoocan/Python/IntroPyStat/BysDiffMeanInd/filesBysDifMuInd/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]
Inference for Stan model:
anon_model_ccbd0563488bdd44c9dc307138ff4420.
4 chains, each with iter=2000;
warmup=1000; thin=1;
post-warmup draws per chain=1000,
total post-warmup draws=4000.
mean
se_mean sd 2.5% 25% 50% 75% 97.5% n_eff Rhat
muA 125.05 0.02 1.06 122.99 124.38 125.02 125.72
127.19 3238 1.0
muB 133.97 0.02 0.97 132.13 133.32 133.96 134.6 135.94 3642 1.0
sgmA 3.91 0.02 0.83 2.68 3.33 3.78 4.34 5.94 2665 1.0
sgmB 4.21 0.01 0.75 3.03 3.66 4.12 4.64 5.96 2913 1.0
mu_d -8.92 0.02 1.43 -11.77 -9.86 -8.9 -7.98 -6.1 3292 1.0
sgm_r 0.96 4.8e-3 0.26 0.56 0.77 0.92 1.1 1.59 2975 1.0
lp__ -62.36 0.04 1.5 -66.05 -63.09 -62.02
-61.24 -60.48 1639 1.0
Samples were drawn using NUTS at Wed
Jul 8 10:21:37 2020.
For each parameter, n_eff is a crude
measure of effective sample size,
and Rhat is the potential scale
reduction factor on split chains (at
convergence, Rhat=1).
muA:
mean = 125.052
95%CI = [122.998, 127.192]
Q1 = 124.377, Median = 125.022, Q3 = 125.719
muB:
mean = 133.969
95%CI = [132.134, 135.935]
Q1 = 133.315, Median = 133.956, Q3 = 134.595
mu_d:
mean = -8.917
95%CI = [-11.772, -6.111]
Q1 = -9.856, Median = -8.899, Q3 = -7.984
sgm_r:
mean = 0.957
95%CI = [0.556, 1.584]
Q1 = 0.770, Median = 0.917, Q3 = 1.102
sgmA:
mean = 3.911
95%CI = [2.682, 5.938]
Q1 = 3.331, Median = 3.780, Q3 = 4.344
sgmB:
mean = 4.214
95%CI = [3.033, 5.954]
Q1 = 3.663, Median = 4.120, Q3 = 4.640
リスト1 Stanスクリプト(IndDiffMu2Sgm.stan)
data {
int NA;
int NB;
real DataA[NA];
real DataB[NB];
}
parameters {
real muA;
real muB;
real<lower = 0.0>
sgmA;
real<lower = 0.0>
sgmB;
}
transformed parameters {
real mu_d;
real sgm_r;
mu_d = muA - muB;
sgm_r = sgmA / sgmB;
}
model {
muA ~ normal(0.0, 1000.0);
muB ~ normal(0.0, 1000.0);
sgmA ~ exponential(0.001);
sgmB ~ exponential(0.001);
for (i in 1:NA)
DataA[i] ~ normal(muA, sgmA);
for (i in 1:NB)
DataB[i] ~ normal(muB, sgmB);
}
リスト2 Stanスクリプトを用いるPythonスクリプト(AnalIndMeans2SgmBys.py)
import tkinter as tk
import tkinter.filedialog as fd
import pandas as pd
import numpy as np
import pystan
import matplotlib.pyplot as plt
import seaborn as sb
import scipy.stats as ss
def askinfilename(msg = None, types =
[('', '*.*')]):
"""
入力用ファイル名の設定
"""
rt = tk.Tk()
rt.withdraw()
flnm = fd.askopenfilename(title
= msg, filetypes = types)
rt.destroy()
return flnm
def askoutfilename(msg = None, types =
[('', '*.*')], deftype = '.txt'):
"""
出力用ファイル名の設定
"""
rt = tk.Tk()
rt.withdraw()
flnm = fd.asksaveasfilename(title
= msg, filetypes = types,
defaultextension = deftype)
rt.destroy()
return flnm
finnm = askinfilename('入力データExcelファイル', types = [('Excel(*.xlsx)', '*.xlsx')])
txtoutnm = askoutfilename(msg = '出力ファイル', types = [('Text
(*.txt)', '*.txt')])
txtout = open(txtoutnm, 'w')
foutnm = askoutfilename(msg = '画像保存ファイル', types = [('画像 (*.png)', '*.png')],
deftype = '.png')
xlsx_fl = pd.ExcelFile(finnm)
data_xlsx =
pd.read_excel(xlsx_fl, header =
None)
data = data_xlsx.values
print(data)
txtout.write('\n入力データファイル:\n')
txtout.write(' {0}\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])
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))
StanData = {'NA': len(dataA), 'DataA':
dataA,
'NB': len(dataB), 'DataB': dataB}
sm = pystan.StanModel(file =
'IndDiffMu2Sgm.stan')
fit = sm.sampling(data = StanData,
n_jobs = 1)
print(fit)
txtout.write('\n{0}\n'.format(fit))
# 日本語表記の設定
plt.rcParams['font.family'] = ['Yu
Gothic', 'Noto Sans CJK JP', 'Hiragino Maru Gothic Pro']
muA_p025, muA_p25, muA_p50, muA_p75,
muA_p975 = \
np.percentile(fit['muA'],
[2.5, 25, 50, 75, 97.5])
muA_mean = fit['muA'].mean()
muB_p025, muB_p25, muB_p50, muB_p75,
muB_p975 = \
np.percentile(fit['muB'],
[2.5, 25, 50, 75, 97.5])
muB_mean = fit['muB'].mean()
txtout.write('\nmuA:\n')
txtout.write(' mean =
{0:.3f}\n'.format(muA_mean))
txtout.write(' 95%CI = [{0:.3f},
{1:.3f}]\n'.format(muA_p025, muA_p975))
txtout.write(' Q1 = {0:.3f}, Median = {1:.3f}, Q3 = {2:.3f}\n'.format(
muA_p25, muA_p50, muA_p75))
txtout.write('\nmuB:\n')
txtout.write(' mean =
{0:.3f}\n'.format(muB_mean))
txtout.write(' 95%CI = [{0:.3f},
{1:.3f}]\n'.format(muB_p025, muB_p975))
txtout.write(' Q1 = {0:.3f}, Median = {1:.3f}, Q3 = {2:.3f}\n'.format(
muB_p25, muB_p50, muB_p75))
plt.title('平均値の事後分布', fontsize = 20)
sb.kdeplot(fit['muA'], label =
VarNameA)
sb.kdeplot(fit['muB'], label =
VarNameB)
plt.yticks([])
plt.legend(fontsize = 12)
plt.savefig(foutnm[:-4] + '_muAB.png')
plt.show()
mean_mu_d = fit['mu_d'].mean()
mu_d025P, mu_d25P, mu_d50P, mu_d75P,
mu_d975P = \
np.percentile(fit['mu_d'], [2.5, 25, 50, 75, 97.5])
txtout.write('\nmu_d:\n')
txtout.write(' mean =
{0:.3f}\n'.format(mean_mu_d))
txtout.write(' 95%CI = [{0:.3f},
{1:.3f}]\n'.format(mu_d025P, mu_d975P))
txtout.write(' Q1 = {0:.3f}, Median = {1:.3f}, Q3 = {2:.3f}\n'.format(
mu_d25P, mu_d50P, mu_d75P))
sb.kdeplot(fit['mu_d'])
plt.title('平均値の差の事後分布' +
'\n平均 =
{0:.2f} 95%CI = [{1:.2f},
{2:.2f}]'.format(
mean_mu_d, mu_d025P, mu_d975P), fontsize = 16)
plt.yticks([])
plt.xlabel('平均値({0}) - 平均値({1})'.format(VarNameA, VarNameB), fontsize = 14)
plt.savefig(foutnm[:-4] + '_mu_d.png')
plt.show()
mean_sgm_r = fit['sgm_r'].mean()
sgm_r025P, sgm_r25P, sgm_r50P,
sgm_r75P, sgm_r975P = \
np.percentile(fit['sgm_r'], [2.5, 25, 50, 75, 97.5])
txtout.write('\nsgm_r:\n')
txtout.write(' mean =
{0:.3f}\n'.format(mean_sgm_r))
txtout.write(' 95%CI = [{0:.3f},
{1:.3f}]\n'.format(sgm_r025P, sgm_r975P))
txtout.write(' Q1 = {0:.3f}, Median = {1:.3f}, Q3 = {2:.3f}\n'.format(
sgm_r25P, sgm_r50P, sgm_r75P))
plt.title(('標準偏差の比SD({0})/SD({1})の事後分布'.format(VarNameA, VarNameB)) +
'\n平均 =
{0:.3f}, 95%CI = [{1:.3f},
{2:.3f}]'.format(
mean_sgm_r, sgm_r025P, sgm_r975P), fontsize = 18)
sb.kdeplot(fit['sgm_r'])
plt.xlabel('SD({0})/SD({1})'.format(VarNameA,
VarNameB), fontsize = 14)
plt.tight_layout()
plt.savefig(foutnm[:-4] +
'_sgm_r.png')
plt.show()
sgmA_p025, sgmA_p25, sgmA_p50,
sgmA_p75, sgmA_p975 = \
np.percentile(fit['sgmA'],
[2.5, 25, 50, 75, 97.5])
sgmA_mean = fit['sgmA'].mean()
sgmB_p025, sgmB_p25, sgmB_p50,
sgmB_p75, sgmB_p975 = \
np.percentile(fit['sgmB'],
[2.5, 25, 50, 75, 97.5])
sgmB_mean = fit['sgmB'].mean()
txtout.write('\nsgmA:\n')
txtout.write(' mean =
{0:.3f}\n'.format(sgmA_mean))
txtout.write(' 95%CI = [{0:.3f},
{1:.3f}]\n'.format(sgmA_p025, sgmA_p975))
txtout.write(' Q1 = {0:.3f}, Median = {1:.3f}, Q3 = {2:.3f}\n'.format(
sgmA_p25,
sgmA_p50, sgmA_p75))
txtout.write('\nsgmB:\n')
txtout.write(' mean =
{0:.3f}\n'.format(sgmB_mean))
txtout.write(' 95%CI = [{0:.3f},
{1:.3f}]\n'.format(sgmB_p025, sgmB_p975))
txtout.write(' Q1 = {0:.3f}, Median = {1:.3f}, Q3 = {2:.3f}\n'.format(
sgmB_p25, sgmB_p50, sgmB_p75))
plt.title('標準偏差の事後分布', fontsize = 20)
sb.kdeplot(fit['sgmA'], label =
VarNameA)
sb.kdeplot(fit['sgmB'], label =
VarNameB)
plt.xlabel('標準偏差', fontsize = 14)
plt.savefig(foutnm[:-4] + '_sgmAB.png')
plt.show()
#
# サンプル値の差の事後予測
#
diff = []
for ma, mb, sgma, sgmb in
zip(fit['muA'], fit['muB'], fit['sgmA'], fit['sgmB']):
va = ss.norm.rvs(ma, sgma)
vb = ss.norm.rvs(mb, sgmb)
diff.append(va - vb)
diff = np.array(diff)
p_pos_diff = (diff > 0.0).sum() /
len(diff)
sb.kdeplot(diff)
plt.title(('サンプル値の差の事後予測 {0} -
{1}'.format(VarNameA, VarNameB)) +
'\n確率(差>0)= {0:.3f}'.format(p_pos_diff), fontsize = 16)
plt.xlabel('{0}サンプル値 - {1}サンプル値'.format(VarNameA, VarNameB), fontsize = 14)
plt.yticks([])
plt.savefig(foutnm[:-4] +
'_postD.png')
plt.show()
txtout.close()
print('\n出力ファイル{}が保存されました。\n'.format(txtoutnm))