Up

正規分布と対数正規分布

LOO-CVWAICによる比較

 

正規分布は統計分析において広く用いられており、分布の平均値近傍の値の観測される頻度が高いと期待される。これに対して、対数正規分布では、モード(最頻値)は平均値より小さいので、平均値より小さい値の観測される頻度が高いという印象があるかもしれない。得られたデータの分布が、正規分布からのものと考えられるのか、あるいは対数正規分布からのものと考えられるのかを、ベイズ分析におけるLOO-CVWAICにより比較した。心理学においては、正規分布は特別な扱いを受けているので、正規分布との比較を取り上げた。

まず、モデルの簡単な説明を行い、その後、スクリプトの実行例を、正規分布が適合する場合と対数正規分布が適合する場合について示す。スクリプトは、リスト1リスト2として示す。

 

 

モデル

 

正規分布は次式で与えられるもので、

 

 

よく知られている。

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

 

 

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

 

 

正規分布のときは、平均値とモードは等しいが、対数正規分布のときは

 

 

である。

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

式(2)、式(3)より

 

 

とおけば、

 

 

であり、

 

 

である。

 

上のモデルに従ってベイズ分析を行うスクリプトを、リスト1およびリスト2として作成した。リスト1とリスト2、およびデータファイルは同じフォルダ内に置かれているものとする。リスト1のスクリプトをPython3.12/MiniConda/Ubuntu22/WSL/Windowsで実行した後、リスト2のスクリプトをPython3.13/Windowsで実行した。

 

 

 

実行例

 

正規分布の方が適合した場合

 

図1に示すデータを分析した。

 

図1 仮想データファイルdata_norm.csv

 

入力データファイルは、CSV形式で用意する。1行目に変数名を書き、1列目はデータの識別番号を置く。分析では用いないので、任意の数値でよい。分析のデータ値は、2列目に2行目以降に書く。

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

 

(py312) *****/normlognormfiles$ python mcmcsampling.py

Input Data File (*.csv) = data_norm.csv

 

上の例では、図1のファイル名data_norm.csvを設定している。

入力データファイル名を設定して、Enterキーを押すと、ファイルが読み込まれて計算が始まる。

計算が終了すると、以下のメッセージが端末に出力されて、スクリプトの実行終了となる。

 

 

fit_dframe_N.pkl was saved.

 

fit_dframe_LN.pkl was saved.

 

(py312) *****/normlognormfiles$

 

 

MCMCサンプリングのサンプルは、fit_dframe_N.pklfit_dframe_LN.pklに保存されている。これらは、次のリスト2の実行において読み込まれ、MCMCサンプルの分析が行われる。

リスト1の実行後、リスト2をPython3.13/Windowsで実行した。リスト1の実行で保存されたMCMCサンプルが読み込まれ、分析結果が図2のように表示される。

 

図2

 

左側のグラフが、正規分布モデルによる分析結果であり、右側のグラフが3パラメータ対数正規分布による分析結果である。

モデルとデータの適合度の指標であるLOO-CVWAICの値は、いずれも正規分布の方が小さいので、正規分布の方がデータに適合していると言える。

 

図2のWindowを右上角のX印をクリックして閉じると、次のメッセージが端末に出力されて実行終了となる。

 

FigNormLogNorm.png was saved.

 

ファイルFigNormLogNorm.pngには、図2のグラフが保存されている。

 

 

 

 

対数正規分布の方が適合した場合

 

図3に示すデータを分析した。

 

図3 仮想データファイルdata_lognorm.csv

 

入力データファイルは、CSV形式で用意する。1行目に変数名を書き、1列目はデータの識別番号を置く。分析では用いないので、任意の数値でよい。分析のデータ値は、2列目に2行目以降に書く。

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

 

(py312) *****/normlognormfiles$ python mcmcsampling.py

Input Data File (*.csv) = data_lognorm.csv

 

上の例では、図3のファイル名data_lognorm.csvを設定している。

入力データファイル名を設定して、Enterキーを押すと、ファイルが読み込まれて計算が始まる。

計算が終了すると、以下のメッセージが端末に出力されて、スクリプトの実行終了となる。

 

 

fit_dframe_N.pkl was saved.

 

fit_dframe_LN.pkl was saved.

 

(py312) *****/normlognormfiles$

 

 

MCMCサンプリングのサンプルは、fit_dframe_N.pklfit_dframe_LN.pklに保存されている。これらは、次のリスト2の実行において読み込まれ、MCMCサンプルの分析が行われる。

リスト1の実行後、リスト2をPython3.13/Windowsで実行した。リスト1の実行で保存されたMCMCサンプルが読み込まれ、分析結果が図4のように表示される。

 

図4

 

左側のグラフが、正規分布モデルによる分析結果であり、右側のグラフが3パラメータ対数正規分布による分析結果である。

モデルとデータの適合度の指標であるLOO-CVWAICの値は、いずれも正規分布の方より対数正規分布の方が小さいので、対数正規分布の方がデータに適合していると言える。

図4のWindowを右上角のX印をクリックして閉じると、次のメッセージが端末に出力されて実行終了となる。

 

FigNormLogNorm.png was saved.

 

ファイルFigNormLogNorm.pngには、図4のグラフが保存されている。

 

 

 

 

参考文献

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リスト2に分けられているが、これらはデータファイルとともに同じフォルダ内と置かれているものとする。本サイトで用いたスクリプトファイルとデータファイルは、まとめて圧縮ファイルnormlognormfiles.zipとした。

リスト1のスクリプトを実行したのち、リスト2のスクリプトを実行する。リスト1のスクリプトは、Python3.12/MiniConda/Ubuntu22/WSL/Windowsで実行した。リスト1の実行後、リスト2のスクリプトを、Python3.13/Windowsで実行した。

 

 

 

リスト1

 

#

#       Yasuharu Okamoto, 2025.08

#

import stan

import pandas as pd

import arviz as az

import pickle

 

stan_model = """

    //

    //      Normal Distribution

    //

    data {

        int n;

        array[n] real x;

    }

    transformed data {

        real xmin;

        real xmax;

        xmin = min(x);

        xmax = max(x);

    }

    parameters {

        real<lower=0, upper=1> vmu;

        real<lower=0, upper=1> vsgm;

    }

    transformed parameters {

        real mu;

        real sgm;

        mu = xmin + (xmax - xmin) * vmu;

        sgm = 0.0001 + (xmax - xmin) * vsgm;

    }

    model {

        vmu ~ beta(1, 1);

        vsgm ~ beta(1, 1);

        x ~ normal(mu, sgm);

    }

    generated quantities {

        array[n] real log_lik;  //   for arviz.waic and loo

        for (i in 1:n) {

            log_lik[i] = normal_lpdf(x[i]|mu, sgm);

        }

    }

"""

 

pd_df = pd.read_csv(input('Input Data File (*.csv) = ')) # pd.read_csv('data_norm.csv')

print(pd_df)

x = pd_df[pd_df.keys()[1]].values

print(x)

 

Data = {'n':len(x), 'x':x}

sm = stan.build(stan_model, data=Data)

fit = sm.sample(num_chains=4, init=[{'vmu':0.5, 'vsgm':0.5}]*4)

print(fit)

 

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

print('Summary...\n', az.summary(i_data, var_names=['mu', 'sgm']))

 

fit_dframe = fit.to_frame()

   

loo_results = az.loo(i_data, scale='deviance')

print('\n', loo_results)

 

waic_results = az.waic(i_data, scale='deviance')

print('\n', waic_results)

 

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

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

 

stan_lognormal_model = """

    //

    //     LogNormal Distribution

    //

    data {

        int n;

        array[n] real x;

    }

    transformed data {

        real xmin;

        real xmax;

        xmin = min(x);

        xmax = max(x);

    }

    parameters {

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

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

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

    }

    transformed parameters {

        real mn;

        real sd;

        real mu;

        real sgm;

        real theta;

        /*

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

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

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

        */

 

        mn = 0.0001 + vmn * xmax;

        sd = 0.0001 + vsd * xmax;

        sgm = (log((sd/mn)^2 + 1))^0.5;

        mu = log(mn) - 0.5 * (sgm^2);

        theta = vtheta * xmin;

    }

    model {

        vmn ~ beta(1, 1);

        vsd ~ beta(1, 1);

        vtheta ~ beta(1, 1);

        for (i in 1:n) {

            x[i] - theta ~ lognormal(mu, sgm);

        }

    }

    generated quantities {

        real mean;

        real mode;

        array[n] real log_lik;  //   for arviz.waic and loo

        mean = theta + mn;

        mode = theta + exp(mu - sgm^2);

        for (i in 1:n) {

            log_lik[i] = lognormal_lpdf(x[i]-theta|mu, sgm);

        }

    }

"""

 

Data = {'n':len(x), 'x':x}

sm = stan.build(stan_lognormal_model, data=Data)

fit = sm.sample(num_chains=4, init=[{'vmu':0.5, 'vsgm':0.5, 'vtheta':0.5}]*4)

print(fit)

 

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

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

 

fit_dframe = fit.to_frame()

 

loo_results = az.loo(i_data, scale='deviance')

print('\n', loo_results)

 

waic_results = az.waic(i_data, scale='deviance')

print('\n', waic_results)

 

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

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

 

print('\nfit_dframe_N.pkl was saved.')

print('\nfit_dframe_LN.pkl was saved.\n')

 

 

 

リスト2

 

import matplotlib.pyplot as plt

import pandas as pd

import numpy as np

import scipy.stats as ss

import pickle

 

"""

         Using a normal distribution model

"""

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

    (pd_df, fit_dframe, loo_results, waic_results) = pickle.load(f)           #   MCMCサンプルの保存

 

print(pd_df)

x = pd_df[pd_df.keys()[1]].values

print(x)

 

print()

print('\nmu...\n', fit_dframe.describe()['mu'].T)

print('\nsgm...\n', fit_dframe.describe()['sgm'].T)

   

print('\nloo\n', loo_results)

 

print('\nwaic\n', waic_results)

 

x = pd_df[pd_df.keys()[1]].values

print(x)

 

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

 

plt.subplot(121)     #  A graph based on a normal distribution model

 

plt.hist(x, label='Data', density=True)

 

v_mu = np.median(fit_dframe['mu'])

v_sgm = np.median(fit_dframe['sgm'])

 

xcoord = np.linspace(np.min(x), np.max(x), 1000)

ycoord = ss.norm.pdf(xcoord, v_mu, v_sgm)

plt.plot(xcoord, ycoord,

         label=fr'$\mu$={v_mu:.1f}' + '\n' +

         fr'$\sigma$={v_sgm:.1f}', lw=3)

plt.xlabel(pd_df.keys()[1], fontsize=14)

plt.title('Normal Distribution\n' + \

          fr'LOO-CV={loo_results.elpd_loo:.1f},' + \

          fr'  WAIC={waic_results.elpd_waic:.1f}',

          fontsize=16)

plt.legend(fontsize=14)

plt.tight_layout()

 

 

"""

        Using a lognormal distribution model

"""

 

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('fit_dframe_LN.pkl', 'rb') as f:

    (pd_df, fit_dframe, loo_results, waic_results) = pickle.load(f)           #   MCMCサンプルの保存

 

print(pd_df)

x = pd_df[pd_df.keys()[1]].values

print(x)

 

print()

print('\nmean...\n', fit_dframe.describe()['mean'].T)

print('\nmode...\n', fit_dframe.describe()['mode'].T)

print('\nsd...\n', fit_dframe.describe()['sd'].T)

   

print('\nloo\n', loo_results)

 

print('\nwaic\n', waic_results)

 

x = pd_df[pd_df.keys()[1]].values

print(x)

 

plt.subplot(122)     #   A graph based on a lognormal distribution model

 

plt.hist(x, density=True, label='Data')

 

v_mu = np.median(fit_dframe['mu'])

v_sgm = np.median(fit_dframe['sgm'])

v_theta = np.median(fit_dframe['theta'])

 

xcoord = np.linspace(np.min(x), np.max(x), 1000)

ycoord = []

for v in xcoord:

    ycoord.append(lognormal_pdf(v, v_mu, v_sgm, v_theta))

v_mean = np.median(fit_dframe['mean'])

v_mode = np.median(fit_dframe['mode'])

v_sd = np.median(fit_dframe['sd'])

v_theta = np.median(fit_dframe['theta'])

plt.plot(xcoord, ycoord, label=fr'mean={v_mean:.1f}' + '\n' +\

                                 fr'mode={v_mode:.1f}' + '\n' +\

                                 fr'sd={v_sd:.1f}' + '\n' +\

                                 fr'$\theta$={v_theta:.1f}', lw=3)

 

plt.xlabel(pd_df.keys()[1], fontsize=16)

 

plt.title(fr'Log Normal Distribution' + '\n'

          fr'LOO-CV={loo_results.elpd_loo:.1f},' + \

          fr'  WAIC={waic_results.elpd_waic:.1f}',

          fontsize=16)

plt.legend(fontsize=14)

plt.tight_layout()

 

plt.savefig('FigNormLogNorm.png')

 

plt.show()

 

print('\nFigNormLogNorm.png was saved.\n')

 

 

 

 

Home