2項分布のベイズ分析
比率の比較
図1のExcelデータは、平成31年度男女別合格者(京都大学ホームページより:合計が200名以上の学部、内数で「認」と「他」を除く)である。
図1
学部ごとの女子学生の比率に対して、2項分布を用いたベイズ分析を行ってみる。すなわち、学部kの男子学生数を、女子学生数をで表し、女子学生の比率をとしたとき
とおく。モデル(1)に基づいてベイズ分析を行うためのStanスクリプトを本ウェブサイト後半に示すリスト1のように用意した。本ウェブサイトのスクリプトファイルなどは圧縮ファイルfilesBinomials.zipにまとめたので、ダウンロード解凍すれば自由に利用できる。
リスト1のStanスクリプトを使って図1の形式のデータを分析するPythonスクリプトを、本ウェブサイトの後半に示すリスト2として用意した。
リスト2のスクリプトを実行すると、入力データファイル名の設定を求めるダイアログウィンドウが表示される(図2)。
図2
入力データファイルは、図1の形式で用意する。1行目にグループ名(図1の場合は、学部名)を設定し、1列目に2項分布のカテゴリ値(図1の場合は、「男性」と「女性」)を書く。2項分布における該当カテゴリ値(「成功」カテゴリあるいは「1」として説明されることが多い)を3行目に、非該当カテゴリ値(「失敗」カテゴリあるいは「0」として説明されることが多い)を2行目に設定する。各グループにおけるカテゴリ値の数はそれぞれの行・列のセルに設定する。
図2では、図1のExcelデータファイル名「Data.xlsx」が設定されている。設定後、「開く」ボタンをクリックすると、画像保存用ファイル名の設定を求めるダイアログウィンドウが表示される(図3)。
図3
ファイル拡張子が「.png」である任意のファイル名を設定する。ファイル名を設定して「保存」ボタンをクリックすると、次に、計算結果の出力用テキストファイル名の設定を求めるダイアログウィンドウが表示される(図4)。
図4
テキストファイル用ファイル名を設定して「保存」ボタンをクリックすると、入力データファイルが読み込まれて処理が始まる。Stanスクリプトのコンパイルとビルドに多少時間が掛かるが、その後、MCMCサンプリングが行われ、サンプリング終了後に事後分布が図5のように表示される。
図5
女子の比率に対応する2項分布の確率の事後分布は、工学部と理学部、法学部、農学部と文学部の3グループに分かれていることが表されている。
図3のウィンドウを閉じると、プログラムの実行終了となる。実行終了後、図4で設定した出力テキストファイルを開くと、以下のようになっている。Python IDLEのエディタで開こうとすると
図6のボックスが表示されることがある。
図6
このときは、「s-jis」を設定して「OK」ボタンをクリックすればよい(図7)。
図7
出力ファイルの内容
入力データファイル =
D:/yasuharu/www/LaCoocan/Python/IntroPyStat/BysBinomials/filesBinomials/Data.xlsx
K = 5
['文学部', '法学部', '理学部', '工学部', '農学部']
男性, 女性
[[130 83]
[236
75]
[281
32]
[858
79]
[189 103]]
Inference for Stan model:
anon_model_9bbe48d3e7a751424b61594e434755ef.
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
p[1] 0.39 4.5e-4 0.03 0.33 0.37 0.39 0.41 0.46 5181 1.0
p[2] 0.24 3.4e-4 0.02 0.2 0.23 0.24 0.26 0.29 5038 1.0
p[3] 0.1 2.4e-4 0.02 0.07 0.09 0.1 0.12 0.14 5061 1.0
p[4] 0.09 1.3e-4 9.1e-3 0.07 0.08 0.08 0.09 0.1 4812 1.0
p[5] 0.35 4.2e-4 0.03 0.3 0.33 0.35 0.37 0.41 4680 1.0
lp__ -890.0 0.03 1.58 -894.0 -890.8 -889.7 -888.8
-887.9 2251 1.0
Samples were drawn using NUTS at Sun
Jul 19 10:37:11 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).
P(女性)
文学部:
平均 = 0.39105
中央値 = 0.39101
Q1 = 0.36830 Q3 = 0.41270
95%確信区間 = [0.32880, 0.45646]
法学部:
平均 = 0.24387
中央値 = 0.24333
Q1 = 0.22752 Q3 = 0.26007
95%確信区間 = [0.19880, 0.29129]
理学部:
平均 = 0.10436
中央値 = 0.10323
Q1 = 0.09233 Q3 = 0.11567
95%確信区間 = [0.07353, 0.14061]
工学部:
平均 = 0.08510
中央値 = 0.08487
Q1 = 0.07867 Q3 = 0.09071
95%確信区間 = [0.06829, 0.10378]
農学部:
平均 = 0.35361
中央値 = 0.35323
Q1 = 0.33429 Q3 = 0.37283
95%確信区間 = [0.29953, 0.41217]
リスト1 2項分による分析のStanスクリプト(BinModel.stan)
data {
int K;
int Data[K, 2];
}
parameters {
real<lower = 0.0, upper =
1.0> p[K];
}
model {
for (i in 1:K)
p[i]
~ uniform(0.0, 1.0);
for (i in 1:K)
Data[i][2] ~ binomial(Data[i][1] + Data[i][2], p[i]);
}
リスト2 リスト1のStanスクリプトによるベイズ分析(MainBinomial.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
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')])
foutnm = askoutfilename(msg = '画像保存ファイル', types = [('画像 (*.png)', '*.png')],
deftype = '.png')
ftxtoutnm = askoutfilename(msg = 'テキスト出力ファイル', types = [('Text
(*.txt)', '*.txt')],
deftype = '.txt')
fout = open(ftxtoutnm, 'w')
fout.write('入力データファイル = {}\n'.format(finnm))
xlsx_fl = pd.ExcelFile(finnm)
data_xlsx = pd.read_excel(xlsx_fl, header = None)
data = data_xlsx.values
K = len(data[0]) - 1
print('K = ', K)
fout.write('\nK = {}\n'.format(K))
VarGrp = []
for i in range(1, K + 1):
VarGrp.append(data[0][i])
print(VarGrp)
fout.write('\n{}\n'.format(VarGrp))
VarCnd1 = data[1][0]
VarCnd2 = data[2][0]
print(VarCnd1, ', ', VarCnd2)
fout.write('\n{0:}, {1:}\n'.format(VarCnd1, VarCnd2))
Data = np.empty((K, 2), dtype = int)
for k in range(K):
for i in range(2):
Data[k][i] = data[i + 1][k + 1]
print(Data)
fout.write('\n{}\n'.format(Data))
StanData = {'K': K, 'Data': Data}
sm = pystan.StanModel(file =
'BinModel.stan')
fit = sm.sampling(data = StanData,
n_jobs = 1)
print(fit)
fout.write('\n{}\n'.format(fit))
# 日本語表記の設定
plt.rcParams['font.family'] = ['Yu
Gothic', 'Noto Sans CJK JP', 'Hiragino Maru Gothic Pro']
plt.figure(figsize = (9, 5))
plt.title('pの事後分布', fontsize = 18)
for k in range(K):
sb.kdeplot(fit['p'][:, k],
label = VarGrp[k])
plt.yticks([])
plt.xlabel('p', fontsize = 14)
plt.legend(loc = 'upper right',
bbox_to_anchor = (1.25, 1.0), fontsize = 12)
plt.tight_layout()
plt.savefig(foutnm)
plt.show()
print('\n\nP({})\n'.format(VarCnd2))
fout.write('\n\n\nP({})\n\n'.format(VarCnd2))
for k in range(K):
print('\n{}:'.format(VarGrp[k]))
fout.write('\n{}:\n'.format(VarGrp[k]))
p_mean = fit['p'][:,
k].mean()
p_025, Q1, Med, Q3, p_975 =
np.percentile(fit['p'][:, k], [2.5, 25, 50, 75, 97.5])
print('平均 = {0:.5f}'.format(p_mean))
fout.write('平均 = {0:.5f}\n'.format(p_mean))
print('中央値 = {0:.5f}'.format(Med))
fout.write('中央値 = {0:.5f}\n'.format(Med))
print('Q1 = {0:.5f} Q3 =
{1:.5f}'.format(Q1, Q3))
fout.write('Q1 =
{0:.5f} Q3 =
{1:.5f}\n'.format(Q1, Q3))
print('95%確信区間 = [{0:.5f}, {1:.5f}]'.format(p_025,
p_975))
fout.write('95%確信区間 = [{0:.5f},
{1:.5f}]\n'.format(p_025, p_975))
fout.close()