Up

3パラメータ対数正規分布による独立な2つのデータの平均値のベイズ分析

 

確率変数の対数が平均、分散の正規分布に従うとき、確率変数は3パラメータの対数正規分布に従うという(Johnson et al., 1994)。確率密度関数は次式(1)のように書くことができる。

 

 

このとき、平均値、分散、モードは次式で与えられる。

 

 

対数正規分布のパラメータをではなく、対数正規分布の平均値と標準偏差をパラメータとすると、パラメータと分布との関係が分かり易くなる。

式(2)、式(3)より

 

 

とおけば、

 

 

であり、

 

 

である。

いま、2つの独立なデータがそれぞれ対数正規分布の母集団から与えられたとき、事後分布を式(7)のようにおく。

 

 

事前分布を一様分布としたときのStanによるMCMCサンプリングのスクリプトをリスト1に示す。

リスト1によるMCMCサンプルを用いてデータ分析を行うスクリプト例を、リスト2に示す。

これらのスクリプトファイルなどは圧縮ファイルbalognormal3pfiles.zipとしてまとめた。

次節において、これらのスクリプトによる分析例を示す。

 

 

分析例

 

Windows上でも実行できるように、メインのスクリプトファイルをリスト1とリスト2に分割した。リスト1のスクリプトにおいてStanスクリプトによるMCMCサンプリングを行い、MCMCサンプリングの結果をファイルに出力している。そのファイルをリスト2のスクリプトで読み込んで、グラフ描画を行っている。リスト1のスクリプトはWindowsでは直接実行できないので、WindowsWSLWindows Subsystem for Linux)をインストールして、WSL上にインストールされたUbuntu上でリスト1のスクリプトを実行した。Ubuntu上での実行は、miniconda上に用意したPython3.12仮想環境で行った。すなわち、リスト1のスクリプトは、Python3.12/miniconda/Ubuntu22/WSL/Windows 11で実行した。リスト2は、Python3.13/Windows 11で実行した。

リスト1のスクリプトを実行すると、入力データファイル名の設定が求められる。

 

 

(py312) *****/balognormal3pfiles$ python ba_means_lognormal3p.py

Input data file(*.csv) =

 

入力データファイルは、CSV形式のファイルとして、図1のように用意する。

図1 sampleN47N50.csv

 

1行目に変数名を設定する。1列目は空欄でもよい。データは2行目から設定する。1列目はデータの識別用である。2列目と3列目にデータ値を設定する。データ数が2つのグループで異なるときは、少ない方のデータは空欄にしておけばよい。空欄はスクリプトの実行時に無視される。

作成したデータファイルは、CSVファイルとして保存する。データファイルとリスト1およびリスト2のファイルは、同じフォルダ内に置いておく。

次の例では、入力データファイルとして図1のファイルsampledata.csvを設定している。ファイルampledata.csvは圧縮ファイルbalognormal3pfiles.zipに含まれている。

 

(py312) *****/balognormal3pfiles$ python ba_means_lognormal3p.py

Input data file(*.csv) = sampledata.csv

 

入力データファイル名を設定すると、計算が始まり、終了すると以下のようなメッセージが表示されてリスト1の実行が終了する。

 

 

theta1       93.358  18.027   59.080  119.618  ...    0.550    1221.0    1194.0    1.0

theta2       83.577  39.052   12.247  142.386  ...    0.512    1173.0    1672.0    1.0

mean1       197.778   5.214  188.082  207.643  ...    0.086    2443.0    2124.0    1.0

mean2       170.033   1.070  168.139  172.175  ...    0.016    3428.0    2983.0    1.0

diff_means   27.745   5.325   18.277   38.206  ...    0.087    2462.0    2251.0    1.0

diff_sds     41.147   6.871   29.460   53.817  ...    0.178    1609.0    1530.0    1.0

diff_mus      0.179   0.531   -0.721    1.135  ...    0.007    1184.0    1624.0    1.0

diff_sgms     0.325   0.126    0.082    0.547  ...    0.002    1148.0    1348.0    1.0

mode1       168.462   6.015  157.702  180.008  ...    0.095    1764.0    1982.0    1.0

mode2       167.657   1.543  164.459  170.226  ...    0.024    1903.0    2243.0    1.0

diff_modes    0.805   6.184  -10.194   12.457  ...    0.099    1758.0    1969.0    1.0

 

[25 rows x 9 columns]

(py312) *****/balognormal3pfiles$

 

 

リスト1の実行において読み込まれたデータとMCMCサンプルはファイルに保存されている。

保存されたファイルは、リスト2のスクリプトで読み込まれ、描画などの処理が行われる。リスト2はWindows上で直接実行した。

リスト1の実行後、リスト2を実行すると図2のグラフが表示される。

 

図2

 

パラメータの事後分布の中央値を推定値とする対数正規分布のグラフが描かれている。2つの分布のモード(mode)はほぼ等しいが、平均値は異なっている。

図2のWindowを閉じると、図3のグラフが表示される。

 

図3

 

パラメータの事後分布である。

図3のWindowを閉じると、図4のグラフが表示される。

 

図4

 

平均値の事後分布である。2つの事後分布は、明瞭に分離されている。

図4のWindowを閉じると、図5のグラフが表示される。

 

図5

 

平均値の差の事後分布と、事後確率が示されている。平均値は異なると考えられる。

図5のWindowを閉じると、図6のグラフが表示される。

 

図6

 

パラメータの事後分布である。

図6のWindowを閉じると、図7のグラフが表示される。

 

図7

 

パラメータの差事後分布と確率が表示されている。

図7のWindowを閉じると、図8のグラフが表示される。

 

図8

 

パラメータの事後分布である。

図8のWindowを閉じると、図9のグラフが表示される。

 

図9

 

パラメータの差の事後分布と事後確率が表示されている。2つのパラメータには、明瞭な差は認められない。

図9のWindowが閉じられると、図10のグラフが表示される。

 

10

 

パラメータの差の事後分布である。明瞭に分離されている。

10Windowを閉じると、図11のグラフが表示される。

 

11

 

パラメータの差の事後分布と、事後確率が表示されている。2つのパラメータの差は、ほぼ確実にあるといえそうである。

11Windowを閉じると、図12のグラフが表示される。

 

12

 

最頻値(mode)の事後分布である。

12Windowを閉じると、図13のグラフが表示される。

 

13

 

モードの差事後分布と事後確率が表示されている。最頻値に差はないと考えてよさそうである。

 

 

 

 

参考文献

Gelman, A., Carlin, J. B., Stern, H. S., Dunson, D. B., Vehtari, A., & Rubin, D. B. (2014). Bayesian data analysis, 3rd. Ed. CRC Press.

Gelman, A., Hill, J., & Vehtari, A. (2021). Regression and other stories. Cambridge University

Johnson, N. L., Kotz, S., & Barakrishnan, N. (1994) Continuous univariate distributions, Vol.1, 2nd Ed. John Wiley & Sons, Inc.

 

 

 

リスト1 MCMCサンプリングスクリプト(ba_means_lognormal3p.py

 

"""

       Yasuharu Okamoto, 2025.07

      The script can be used freely

"""

import numpy as np

import pandas as pd

import stan

import arviz as az

import pickle

 

stan_code = """

    data {

        int n1;

        array[n1] real x1;

        int n2;

        array[n2] real x2;

        real max_v;        //  パラメータの変域設定用

        real min_v1;       //  パラメータの変域設定用

        real min_v2;       //  パラメータの変域設定用

    }

    parameters {

        real<lower=0.0, upper=1.0> vmn1;

        real<lower=0.0, upper=1.0> vmn2;

        real<lower=0.0, upper=1.0> vsd1;

        real<lower=0.0, upper=1.0> vsd2;

        real<lower=0.0, upper=1.0> vtheta1;

        real<lower=0.0, upper=1.0> vtheta2;

    }

    transformed parameters {

        real mn1;

        real mn2;

        real sd1;

        real sd2;

        real mu1;

        real mu2;

        real sgm1;

        real sgm2;

        real theta1;

        real theta2;

        /*

                パラメータのスケーリング

                model宣言部で変域の広いパラメータに

                出される警告を、スケーリングで避ける

        */

        mn1 = 0.0001 + vmn1 * max_v;

        mn2 = 0.0001 + vmn2 * max_v;

        sd1 = 0.0001 + vsd1 * max_v;

        sd2 = 0.0001 + vsd2 * max_v;

        sgm1 = (log((sd1/mn1)^2 + 1))^0.5;

        mu1 = log(mn1) - 0.5 * (sgm1^2);

        sgm2 = log((sd2/mn2)^2 + 1)^0.5;

        mu2 = log(mn2) - 0.5 * (sgm2^2);

        theta1 = vtheta1 * min_v1;

        theta2 = vtheta2 * min_v2;

    }

    model {

        vmn1 ~ uniform(0, 1);

        vmn2 ~ uniform(0, 1);

        vsd1 ~ uniform(0, 1); 

        vsd2 ~ uniform(0, 1);

        vtheta1 ~ uniform(0, 1);

        vtheta2 ~ uniform(0, 1);

        for (i in 1:n1) { 

            x1[i] - theta1 ~ lognormal(mu1, sgm1);

        }

        for (i in 1:n2) {

            x2[i] - theta2 ~ lognormal(mu2, sgm2);

        }

    }

    generated quantities {

        real mean1;

        real mean2;

        real diff_means;

        real diff_sds;

        real diff_mus;

        real diff_sgms;

        real mode1;

        real mode2;

        real diff_modes;

        mean1 = theta1 + mn1;

        mean2 = theta2 + mn2;

        diff_means = mean1 - mean2;

        diff_sds = sd1 - sd2;

        diff_mus = mu1 - mu2;

        diff_sgms = sgm1 - sgm2;

        mode1 = theta1 + exp(mu1 - sgm1^2);

        mode2 = theta2 + exp(mu2 - sgm2^2);

        diff_modes = mode1 - mode2;

    }

"""

 

inf_nm = input('Input data file(*.csv) = ')

dframe = pd.read_csv(inf_nm)                #  データの読み込み

 

with open('in_dframe.pkl', 'wb') as f:

    pickle.dump(dframe, f)                  #   読み込みデータの保存

 

df_keys = dframe.keys()

x_name = df_keys[1]

y_name = df_keys[2]

print(x_name, y_name)

x = dframe[x_name].values

x = x[np.isnan(x)==False]       #  空セルデータの削除

print('X =\n', x)

y = dframe[y_name].values

y = y[np.isnan(y)==False]       #  空セルデータの削除

print('Y =\n', y)

 

mean1 = np.mean(x)

sd1 = np.std(x)

print('mean1 =', mean1, '   sd1 =', sd1)

mean2 = np.mean(y)

sd2 = np.std(y)

print('mean2 =', mean2, '   sd2 =', sd2)

 

max_v = np.max(x) if np.max(x) > np.max(y) else np.max(y);

min_v1 = np.min(x)

min_v2 = np.min(y)

 

sm = stan.build(stan_code, data = {'n1':len(x), 'x1':x,

                                   'n2':len(y), 'x2':y,

                                   'max_v':max_v,

                                   'min_v1':min_v1, 'min_v2':min_v2})

fit = sm.sample()

 

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

print('Summary...\n', az.summary(i_data))

 

fit_dframe = fit.to_frame()

with open('fit_dframe.pkl', 'wb') as f:

    pickle.dump(fit_dframe, f)           #   MCMCサンプルの保存

 

 

 

リスト2 リスト1の実行で得られたMCMCサンプルの分析(draw_ba_lognormal3p.py

 

"""

        Yasuharu Okamoto, 2025.07

      The script can be used freely

"""

import numpy as np

import scipy.stats as ss

import pickle

import matplotlib.pyplot as plt

import seaborn as sb

 

 

def lognormal_pdf(x, mu, sgm, theta):

    x -= theta

    if x <= 0.0:

        return 0.0

    else:

        v1 = sgm * x * ((2*np.pi)**0.5)

        v2 = np.exp(-((np.log(x) - mu)**2) / (2 * (sgm**2)))

    return v2 / v1

 

with open('in_dframe.pkl', 'rb') as f:

    in_dframe = pickle.load(f)          #   データファイルの読み込み

 

 

with open('fit_dframe.pkl', 'rb') as f:

    fit_dframe = pickle.load(f)            #   MCMCサンプルの読み込み

 

print(fit_dframe.keys())

 

df_keys = in_dframe.keys()

print('df_keys =\n', df_keys)

x_name = df_keys[1]

y_name = df_keys[2]

print(x_name, y_name)

x = in_dframe[x_name].values

x = x[np.isnan(x)==False]        #   空白セルを除く

print('X =\n', x)

y = in_dframe[y_name].values

y = y[np.isnan(y)==False]        #   空白セルを除く

print('Y =\n', y)

 

"""

      パラメータの点推定値として中央値をとる

"""

mu1 = np.median(fit_dframe['mu1'])

sgm1 = np.median(fit_dframe['sgm1'])

theta1 = np.median(fit_dframe['theta1'])

max_x = np.max(x) if np.max(x) > np.max(y) else np.max(y)

min_x = np.min(x) if np.min(x) < np.min(y) else np.min(y)

rng_x = np.linspace(min_x, max_x, 1000)

#  データ1の対数正規分布

d1 = []

for vx in rng_x:

    d1.append(lognormal_pdf(vx, mu1, sgm1, theta1))

plt.plot(rng_x, d1, label=x_name)

mu2 = np.median(fit_dframe['mu2'])

sgm2 = np.median(fit_dframe['sgm2'])

theta2 = np.median(fit_dframe['theta2'])

#  データ2の対数正規分布

d2 = []

for vx in rng_x:

    d2.append(lognormal_pdf(vx, mu2, sgm2, theta2))

plt.plot(rng_x, d2, label=y_name)

"""

       事後分布の代表値として中央値をとる

"""

mean1 = np.median(fit_dframe['mean1'])

sd1 = np.median(fit_dframe['sd1'])

mean2 = np.median(fit_dframe['mean2'])

sd2 = np.median(fit_dframe['sd2'])

mode1 = np.median(fit_dframe['mode1'])

mode2 = np.median(fit_dframe['mode2'])

plt.hist([x, y], density=True, label=[x_name, y_name])

plt.title('Histograms of data and Probability densities\n' +

          fr'{x_name}: $\theta$={theta1:.1f}, $\mu={mu1:.1f}, \sigma={sgm1:.1f}$' +

          fr', $mean$={mean1:.1f}, $sd$={sd1:.1f}, $mode$={mode1:.1f}' + '\n' +

          fr'  {y_name}: $\theta$={theta2:.1f}, $\mu={mu2:.1f}, \sigma={sgm2:.1f}$' +

          fr', $mean$={mean2:.1f}, $sd$={sd2:.1f}, $mode$={mode2:.1f}')

plt.ylabel('Density')

plt.legend()

plt.tight_layout()

plt.show()

 

plt.title(r'Posterior disgtributions of $\theta$', fontsize=16)

sb.kdeplot(fit_dframe['theta1'], label=x_name)

sb.kdeplot(fit_dframe['theta2'], label=y_name)

plt.xlabel(r'$\theta$', fontsize=16)

plt.legend()

plt.show()

 

"""

        平均値パラメータmean1mean2の事後分布

"""

plt_mns = {x_name:fit_dframe['mean1'], y_name:fit_dframe['mean2']}

sb.kdeplot(data=plt_mns)

plt.xlabel('$mean$', fontsize=14)

plt.title('Posterior distrbutions of $mean$s')

plt.show()

 

diff_means = fit_dframe['diff_means']

p_pos = np.mean(diff_means > 0)

sb.kdeplot(diff_means)

plt.xlabel(f'$mean$[{x_name}] - $mean$[{y_name}]', fontsize=14)

plt.title(f'P($mean$[{x_name}] - $mean$[{y_name}] > 0) = {p_pos}' + '\n' +

          fr'Posterior distribution of $mean$[{x_name}] - $mean$[{y_name}]')

plt.show()

"""

       標準偏差パラメータsd1sd2の事後分布

"""

plt_sds = {x_name:fit_dframe['sd1'], y_name:fit_dframe['sd2']}

sb.kdeplot(data=plt_sds)

plt.xlabel('$sd$', fontsize=14)

plt.title('Posterior distrbutions of $sd$s')

plt.show()

 

diff_sds = fit_dframe['diff_sds']

p_pos = np.mean(diff_sds > 0)

sb.kdeplot(diff_sds)

plt.xlabel(fr'$sd$[{x_name}] - $sd$[{y_name}]', fontsize=14)

plt.title(fr'P($sd$[{x_name}] - $sd$[{y_name}] > 0) = {p_pos}' + '\n' +

          fr'Posterior distribution of $sd$[{x_name}] - $sd$[{y_name}]')

plt.show()

"""

             変換パラメータmu1mu2の事後分布

"""

plt_mus = {x_name:fit_dframe['mu1'], y_name:fit_dframe['mu2']}

sb.kdeplot(data=plt_mus)

plt.xlabel(r'$\mu$', fontsize=14)

plt.title(r'Posterior distrbutions of $\mu$s')

plt.show()

 

diff_mus = fit_dframe['diff_mus']

p_pos = np.mean(diff_mus > 0)

sb.kdeplot(diff_mus)

plt.xlabel(fr'$\mu$[{x_name}] - $\mu$[{y_name}]', fontsize=14)

plt.title(fr'P($\mu$[{x_name}] - $\mu$[{y_name}] > 0) = {p_pos}' + '\n' +

          fr'Posterior distribution of $\mu$[{x_name}] - $\mu$[{y_name}]')

plt.show()

"""

             パラメータsgm1sgm2の事後分布

"""

plt_sgms = {x_name:fit_dframe['sgm1'], y_name:fit_dframe['sgm2']}

sb.kdeplot(data=plt_mns)

plt.xlabel(r'$\sigma$', fontsize=14)

plt.title(r'Posterior distrbutions of $\sigma$s')

plt.show()

 

diff_sgms = fit_dframe['diff_sgms']

p_pos = np.mean(diff_sgms > 0)

sb.kdeplot(diff_sgms)

plt.xlabel(fr'$\sigma$[{x_name}] - $\sigma$[{y_name}]', fontsize=14)

plt.title(fr'P($\sigma$[{x_name}] - $\sigma$[{y_name}] > 0) = {p_pos}' + '\n' +

          fr'Posterior distribution of $\sigma$[{x_name}] - $\sigma$[{y_name}]')

plt.show()

"""

           パラメータmode1mode2の事後分布

"""

mode_samples = {x_name:fit_dframe['mode1'], y_name:fit_dframe['mode2']}

sb.kdeplot(data=mode_samples)

plt.xlabel('mode', fontsize=14)

plt.title('Posterior distribution of modes')

plt.show()

"""

              パラメータの差 mode1-mode2 の事後分布

"""

diff_modes = fit_dframe['diff_modes']

p_pos = np.mean(diff_modes > 0)

sb.kdeplot(diff_modes)

plt.xlabel(fr'$mode$[{x_name}] - $mode$[{y_name}]', fontsize=14)

plt.title(fr'P($mode$[{x_name}] - $mode$[{y_name}] > 0) = {p_pos}' + '\n' +

          fr'Posterior distribution of $mode$[{x_name}] - $mode$[{y_name}]')

plt.show()

 

 

 

 

Home