Up

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

CmdStanPy対応

2026.03改訂

 

 

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

 

 

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

 

 

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

式(2)、式(3)より

 

 

とおけば、

 

 

であり、

 

 

である。

 

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

 

 

事前分布を一様分布としたときのStanによるMCMCサンプリングのスクリプトをリスト1に示す。ファイルはlognormal3pcspfiles.zipにまとめた。

 

 

 

リスト1 MCMCサンプリングのStanスクリプト(lognormal3p.stan

 

data {

    int n1;

    array[n1] real x1;

    int n2;

    array[n2] real x2;

}

transformed data {

    real min_x1;

    real min_x2;

    min_x1 = min(x1);

    min_x2 = min(x2);

}

parameters {

    real mu1;

    real mu2;

    real<lower=0.0001, upper=10000.0> sgm1;

    real<lower=0.0001, upper=10000.0> sgm2;

    real<lower=0.0, upper=min_x1> theta1;

    real<lower=0.0, upper=min_x2> theta2;

}

model {

    mu1 ~ uniform(-1000, 1000);

    mu2 ~ uniform(-1000, 1000);

    sgm1 ~ uniform(0.0001, 10000);

    sgm2 ~ uniform(0.0001, 10000);

    theta1 ~ uniform(0, min_x1);

    theta2 ~ uniform(0, min_x2);

   

    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 mode1;

    real sd1;

    real mean2;

    real mode2;

    real sd2;

    real diff_means;

    real diff_sds;

    real diff_mus;

    real diff_sgms;

    real diff_modes;

    mean1 = theta1 + exp(mu1 + 0.5*(sgm1^2));

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

    sd1 = (exp(2*mu1 + (sgm1^2)) * (exp(sgm1^2) - 1.0))^0.5;

    mean2 = theta2 + exp(mu2 + 0.5*(sgm2^2));

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

    sd2 = (exp(2*mu2 + (sgm2^2)) * (exp(sgm2^2) - 1.0))^0.5;

    diff_means = mean1 - mean2;

    diff_sds = sd1 - sd2;

    diff_mus = mu1 - mu2;

    diff_sgms = sgm1 - sgm2;

    diff_modes = mode1 - mode2;

}

 

 

Generated quantities部において、式(2)、(3)、(4)により、平均値、標準偏差(分散の平方根)、モードを算出している。

 

リスト1のStanスクリプトを用いて、ベイズ分析を行うPythonスクリプトをリスト2のように用意した。ファイルはlognormal3pcspfiles.zipにまとめている。

 

 

 

リスト2 リスト1のStanスクリプトを用いてベイズ分析を行うPythonスクリプト(ba_lognormal3p_csp.py

 

from cmdstanpy import CmdStanModel

import numpy as np

import pandas as pd

import matplotlib.pyplot as plt

import seaborn as sb

import arviz as az

 

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

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

 

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)

 

#          Stanスクリプトのコンパイル

model = CmdStanModel(stan_file="lognormal3p.stan")

 

#            MCMCサンプリング

fit = model.sample(data={'n1':len(x), 'x1':x, 'n2':len(y), 'x2':y},

                   adapt_delta=0.99)

                         #'max_v':max_v, 'min_v1':min_v1, 'min_v2':min_v2},)

 

print(fit.diagnose())

print(fit.summary())                        #   MCMCサンプリングの統計

 

inf_data = az.from_cmdstanpy(fit)           #   Arviz(az)用のデータに変換

 

#   inf_dataを用いてのトレースプロット

az.plot_trace(inf_data, var_names=['mean1','sd1','mean2','sd2',

                                   'theta1', 'theta2'], figsize=(12,8))

plt.tight_layout()

plt.show()

 

fit_dframe = fit.draws_pd()      #   PandasDataFrame型に変換

 

print(fit_dframe.keys())

 

 

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

 

 

 

"""

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

"""

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

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

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

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

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

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

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'])

"""

        thetaの事後分布

"""

plt.figure(figsize=(10,5))

plt.subplot(1,2,1)

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

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

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

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

plt.legend()

plt.tight_layout()

 

plt.subplot(1,2,2)

p_pos = np.mean(fit_dframe['theta1'] - fit_dframe['theta2'] > 0)

plt.title(r'Posterior disgtribution of $\theta1-\theta2$' + '\n' +

          fr'$P(\theta1-\theta2>0)$={p_pos:.3f}', fontsize=16)

sb.kdeplot(fit_dframe['theta1'] - fit_dframe['theta2'])

plt.xlabel(r'$\theta1-\theta2$', fontsize=16)

plt.legend()

plt.tight_layout()

plt.show()

 

"""

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

"""

plt.figure(figsize=(10,5))

plt.subplot(1,2,1)

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.subplot(1,2,2)

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()

 

"""

        モードmode1mode2の事後分布

"""

plt.figure(figsize=(10,5))

plt.subplot(1,2,1)

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

sb.kdeplot(data=plt_mds)

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

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

 

plt.subplot(1,2,2)

diff_modes = fit_dframe['diff_modes']

p_pos = np.mean(diff_modes > 0)

sb.kdeplot(diff_modes)

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

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

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

plt.show()

 

"""

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

"""

plt.figure(figsize=(10,5))

plt.subplot(1,2,1)

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.subplot(1,2,2)

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()

 

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, c='g')

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

d2 = []

for vx in rng_x:

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

plt.plot(rng_x, d2, label=y_name, c='r')

 

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

plt.hist(x, density=True, label=x_name, color='g', alpha=0.5)

plt.hist(y, density=True, label=y_name, color='r', alpha=0.5)

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()

 

 

 

リスト1のファイル、リスト2のファイル、および入力データファイルを同じフォルダにおいて、CmdStanPyのインストールされた環境においてリスト2のスクリプトを実行する。CmdStanPyのインストールの簡単な説明を、このウェブサイトなどに用意した。

リスト2のスクリプトを実行すると、入力データファイル名を聞いてくる。

 

(stan) ****/lognormal3pcspfiles$ python ba_lognormal3p_csp.py

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

 

上の例では、sampledata.xlsxが設定されている。

入力データファイルは、Excelファイルとして用意する(図1A、図1B)。

 

図1A

図1B

 

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

 

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

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

 

 

入力データファイル名を設定して、Enterキーを押すと、入力データファイルが読み込まれて、リスト1のStanスクリプトのコンパイル・ビルドが始まる。このコンパイル・ビルドには多少の時間が掛かる。コンパイル後、MCMCサンプリングが始まり、サンプリング後、トレース図が表示される(図2)。

 

図2

 

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

 

図3

 

パラメータの事後分布と、差の事後分布、および差が正である事後確率が表示されている。正である事後確率がであるので2つのパラメータに差があるとは言い難い。なお、本ウェブサイトにおいては、変数xの添字はAあるいは1、変数yの添字はBあるいは2が用いられている。

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

 

図4

 

平均値パラメータmeanの事後分布、および差の事後分布と差が正である事後確率が表示されている。であるので、パラメータmean[VarA]はパラメータmean[VarB]より確かに大きい値であると言える。事後確率が、1.0と小数点1位までの表記であるのは、スクリプト言語の仕様である。

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

 

図5

 

モードmodeの事後分布と、モードの差の事後分布、および差が正である事後確率が表示されている。差が正である事後確率はであるので、差はないと考えられる。平均値には明瞭な差が認められたが(図4)、モードには差が認められない(図5)。

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

 

図6

 

標準偏差の事後分布と、標準偏差の差の事後分布、および差が正である事後確率が表示されている。差が正である事後確率は、であるので、VarAの標準偏差の方が確かに大きいと言えそうである。確率が1.0と小数点以下1位で切れているのは、スクリプト言語の仕様である。

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

 

図7

 

データのヒストグラムと、それぞれのデータに対する3パラメータ対数正規分布の確率密度関数が表示されている。確率密度関数のパラメータ値は、事後分布の中央値を点推定値としている。Gelmanら(2021は、平均値より安定しているとして、中央値を代表値とすることを勧めている。

3パラメータ対数正規分布がデータに良く当てはまっていること、2つの3パラメータ対数正規分布のモードがほぼ同じであること、平均値と標準偏差が異なることがよく表されていると言える。

 

 

 

参考文献

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.

 

 

 

 

Home