Up

ポアッソン分布に基づく回帰分析

 

 

実行時エラーと対応(2024.09.03

 

Ubuntu 22/WSLでの実行時エラー:

cannot import name gaussian from scipy.signal’ 」というエラーが表示された。これは、scipyをバージョン1.12.0のものに置き換えると解決した。

置き換えは、インストールと同じコマンド

 

conda install scipy==1.12.0

 

で行った。

 

Ubuntu 24/WSLでの実行時エラー:

Scipyに関するエラーは、Ubuntu 22の場合と同様、scipy 1.12.0をインストールすることで対応できた。

その後、実行すると

Could not load the Qt platform plugin “xcb”」なるエラーが表示されるようになった。これは、表示デバイスがらみのエラーのようであるので、Ubuntu 24/WSLでは、StanによるMCMCサンプリングまでとして、後の処理はWindowsで行うことにした。例を、このウェブサイトの後半に示す。

 

 

 

Spiegelhalter (2019)EnglandWalesにおける1998年〜2016年の各年ごとの殺人事件の件数を取り上げている。Spiegelhalter(2019), p. xviに記載されているウェブサイトからダウンロードしたファイルを参考にして、リスト1に示すスクリプトを作成した。リスト1のスクリプトを実行すると、図1のグラフが表示される。

図1

 

横軸に年、縦軸に件数をとり、ポアッソン分布に基づく回帰曲線が描かれている。Spiegelhalter (2019)は、件数にポアッソン分布を当てはめている。ポアッソン分布は、次式で表される(岡本、2009岡本、2019)。

このとき、

が成り立つ。

いま、パラメータが、独立変数の多項式で表される場合について考える。

すなわち、

である。ここで、独立変数の平均が0であるようにしておくと、は独立変数0のときのの値を表す。

上のモデルによりベイズ分析を行うStanスクリプトをリスト2のように用意した。

 

回帰モデルにおける多項式(3)の次数を求めるために、次数を1から6まで変化させて、モデルの適合度を調べるプログラムを、リスト3のように用意した。

リスト3のスクリプトを実行すると、1次多項式から6次多項式までの各回帰多項式モデルによるベイズ分析の結果が、図2のように表示される。

図2

 

4次多項式以上のモデルにおいて、回帰曲線はよくデータに当てはまっているように思われる。

モデル適合度の指標としてLOO-CVWAICリスト3のスクリプトにより出力されるが、それらの値を表1とした。

 

表1 モデルの適合度

多項式

LOO-CV

WAIC

1次式モデル

306.54

305.99

2次式モデル

260.67

258.33

3次式モデル

198.03

196.26

4次式モデル

192.25

191.32

5次式モデル

195.16

193.69

6次式モデル

194.24

192.81

 

モデル適合度の値から判断すると、LOO-CVおよびWAICのいずれにおいても4次多項式モデルが最も良いと考えられる。

リスト1のプログラムは、4次多項式による分析を行うものである。実行すると下図(図1再掲)の結果が得られる。

図1(再掲)

 

係数パラメータの事後分布の中央値を係数とする回帰曲線(式(3))のグラフが、黒の曲線で描かれている。データ値を表す棒グラフによく合っていると思われる。回帰曲線上に描かれている両端が+記号のライム色の線分は、パラメータが黒の曲線で表される回帰曲線の値であるポアッソン分布の2.5%値と97.5%値を端点とする線分である。2005年を除いて、棒グラフの示す値はこの[2.5%値、97.5%値]の間にある。2005年の頻度は、回帰モデルで表される全体の傾向から統計的に有意に外れていると解釈される。

黒の破線および1点鎖線の曲線は、MCMCサンプルからランダムに1000組を取り出し、それぞれの組の値で回帰曲線を描いて、それらの2.5%値と97.5%値を描いたものである。1000本の曲線から13を選んで橙色の曲線で描いている。

橙色の曲線群、および2.5%点と97.5%点を繋いだ曲線は、回帰曲線の不確定を表すものであり、ライム色の線分は頻度値(観測値)の不確定を表すものであると言える。

 

 

 

リスト1 ポアッソン分布に基づく回帰分析(p_4.py、圧縮ファイルpoisson_regression.zipに含めた)

 

import matplotlib.pyplot as plt

import numpy as np

import stan

import arviz as az

import scipy.stats as ss

"""

         データは、Spiegelhalter(2019)のウェブサイト

         https://github.com/dspiegel29/ArtofStatistics

         からダウンロードしたファイル09-4-homocide-trends-x.csv

         に含まれているものである。

"""

years = np.array([1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004,

                  2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012,

                  2013, 2014, 2015])

counts = np.array([587, 609, 648, 676, 761, 736, 729, 749, 639,

                   676, 712, 617, 574, 599, 497, 508, 506, 497, 557])

years_c = years - np.mean(years)       #     中心化

 

p=4                                    #    4次の多項式を用いる

#

#                 MCMCサンプリング

#

def init_f():

    return {'v_a': np.mean(counts), 'v_b':[0.0]*p}   #   MCMCの初期値

 

with open('poisson_reg_dp_loo_waic.stan') as f_stan:

    sm = stan.build(f_stan.read(),

                    dict({'p':p, 'n':len(years_c), 'x':years_c, 'y':counts}))

 

fit = sm.sample(num_chains=4, init=[init_f()]*4)

 

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

print(az.summary(i_data))

 

plt.figure(figsize=(9,7))

plt.bar(years, counts)

 

d_fm = fit.to_frame()

#

#         4次の多項式

#

def reg_4(x, va, b1, b2, b3, b4):

    return va + b1*x + b2*(x**2) + b3*(x**3) + b4*(x**4)

 

smpl_a = d_fm['a']

smpl_b1 = d_fm['b.1']

smpl_b2 = d_fm['b.2']

smpl_b3 = d_fm['b.3']

smpl_b4 = d_fm['b.4']

#

#       MCMCサンプルからランダムに1000組のサンプルを選び

#       それぞれのサンプル値による回帰曲線を描く

#

n_samples = len(smpl_a)

rng = np.random.default_rng()

boot_idx = rng.choice(n_samples, 1000)     #    ランダムに1000個採るための添字

regs = []

for i in range(1000):

    idx = boot_idx[i]

    reg_v = reg_4(years_c, smpl_a[idx], smpl_b1[idx], smpl_b2[idx],

                  smpl_b3[idx], smpl_b4[idx])

    regs.append(reg_v)

    if (i % 3) == 0:        #    ランダムな回帰曲線を間引いて描画

        plt.plot(years, reg_v, c='orange', ls=':', alpha=0.1)

#

#     ランダムに選んだ回帰曲線の2.5%値と97.5%値を描く

#

reg025_975 = np.percentile(regs, [2.5, 97.5], axis=0)

plt.plot(years, reg025_975[0], c='k', ls='--', label='2.5% of regs')

plt.plot(years, reg025_975[1], c='k', ls='-.', label='97.5% of regs')

#

#    パラメータの事後分布の中央値を係数値とする

#        回帰曲線を描く

#

reg_v = reg_4(years_c, np.median(smpl_a), np.median(smpl_b1),

              np.median(smpl_b2), np.median(smpl_b3),

              np.median(smpl_b4))

plt.plot(years, reg_v, c='k', label='Reg.by median\ndegree = 4')

#

#         中央値を係数値とする回帰曲線に対応するポアッソン分布の

#         2.5%値と97.5%値を端点する区間を描く

#

pp025 = []

pp975 = []

for v in reg_v:

    pp025.append(ss.poisson.ppf(0.025, v))

    pp975.append(ss.poisson.ppf(0.975, v))   

for i, v in enumerate(years):

    plt.plot([v,v], [pp025[i], pp975[i]], lw=5,

             markersize=20, marker='+', c='lime')

 

plt.plot([], lw=5, c='lime', label='95% interval')

plt.title('Homicides Each Year in England and Wales', fontsize=16)

plt.xticks(years, years, rotation='vertical')

plt.xlabel('Year', fontsize=18)

plt.ylabel('Count of Incidents', fontsize=14)

plt.legend()

plt.tight_layout()

plt.show()

 

 

 

リスト2 ポアッソン分布による回帰分析Stanスクリプト(poisson_reg_dp_loo_waic.stan、圧縮ファイルpoisson_regression.zipに含めた)

 

//

//          ポアッソン回帰モデル

//           Y. Okamoto, 2024.08

//

data {

    int p;            //  多項式の次数

    int n;            //  データ数

    array[n] real x;  //  独立変数

    array[n] int y;   //  従属変数

}

transformed data {

    real mean_y;

    mean_y = mean(y);

 

    array[n] vector[p] Xs;     //    独立変数のj乗の値の配列

    for (i in 1:n)

        for (j in 1:p) {

                Xs[i][j] = x[i] ^ j;

        }

}

parameters {

    //

    //     PyStan 3の要請に合わせたパラメータ

    //     transformed parametersでモデルに合うパラメータに変換

    //

    real<lower=0.0> v_a;

    vector[p] v_b;

}

transformed parameters {

    real a;                //   回帰式のy切片

    vector[p] b;           //   回帰式の偏回帰係数

    array[n] real lmbd;    //   ポアッソン分布のパラメタ

    //

    //       リスケーリング

    //

    a = mean_y * v_a;

    for (j in 1:p)

        b[j] = v_b[j] * mean_y/(10^j);

    //

    //     ポアッソン分布のパラメータlmbdの回帰式

    //

    for (i in 1:n) {

        lmbd[i] = a;

        for (j in 1:p) {

            lmbd[i] += b[j] * Xs[i][j];

            if (lmbd[i] <= 0.0)    //   非正値は強制的に変更

                lmbd[i] = 0.001;

        }

    }

}

model {

    v_a ~ exponential(0.1);

    v_b ~ normal(0.0, 10.0);

    for (i in 1:n)

        y[i] ~ poisson(lmbd[i]);       //    ポアッソン回帰モデル

}

generated quantities {

    array[n] real LL;       //    対数尤度

    for (i in 1:n)

        LL[i] = poisson_lpmf(y[i] | lmbd[i]);

}

 

 

 

リスト3 多項次数を決めるためのプログラム(p_1to6.py、圧縮ファイルpoisson_regression.zipに含めた)

 

import matplotlib.pyplot as plt

import numpy as np

import stan

import arviz as az

"""

         データは、Spiegelhalter(2019)のウェブサイト

         https://github.com/dspiegel29/ArtofStatistics

         からダウンロードしたファイル09-4-homocide-trends-x.csv

         に含まれているものである。

"""

years = np.array([1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004,

                  2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012,

                  2013, 2014, 2015])

counts = np.array([587, 609, 648, 676, 761, 736, 729, 749, 639,

                   676, 712, 617, 574, 599, 497, 508, 506, 497, 557])

 

for v in zip(years, counts):

    print(v)

 

years_c = years - np.mean(years)

years_c = np.array(years_c)

years = np.array(years)

counts = np.array(counts, dtype=int)

 

Loos = np.empty(6)

waics = np.empty(6)

 

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

 

for p in range(1, 7):

    #

    #            MCMCサンプリング

    #

    def init_f():

        return {'v_a': np.mean(counts), 'v_b':[0.0]*p}

 

    with open('poisson_reg_dp_loo_waic.stan') as f_stan:

        sm = stan.build(f_stan.read(),

                        dict({'p':p, 'n':len(years_c), 'x':years_c, 'y':counts}))

 

    fit = sm.sample(num_chains=4, init=[init_f()]*4)

 

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

 

    print(az.summary(i_data))

 

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

    Loos[p-1] = Loo.elpd_loo

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

    waics[p-1] = waic.elpd_waic

 

    plt.subplot(int(f'23{p}'))

   

    plt.bar(years, counts, label=f'p={p}')

   

    d_fm = fit.to_frame()

    med_a = np.median(d_fm['a'])

    med_b = np.empty(p)

    for j in range(p):

        med_b[j] = np.median(d_fm[f'b.{j+1}'])

 

    def reg_p(x):

        v = med_a;

        for i in range(p):

            v += med_b[i] * x**(i+1)

        return v

 

    reg_v = reg_p(years_c)

    plt.plot(years, reg_v)

    plt.legend()

 

plt.tight_layout()

plt.show()

 

for p in range(1,7):

    print('p =', p)

    print('loo =', Loos[p-1])

    print('waic =', waics[p-1])

 

 

 

参考文献

岡本安晴(2009) 統計学を学ぶための数学入門[下].培風館

岡本安晴(2019) いまさら聞けないPythonでデータ分析.丸善出版

Spiegelhalter, D. (2019). The art of statistics: Learning from data. Pelican

 

 

 

Ubuntu 24/WSLにおける例

 

 

Ubuntu 24/WSLQtがらみのエラーが表示されるので(2024.09.03)、以下のようにプログラムをUbuntu 24/WSLで実行する部分(リストUW)と、その結果を受けてPython 12/Windows 10で実行する部分(UW2)に分けて実行した。

リストUW1の実行後、リストUW2を実行すると、図UW1が表示される。

UW1

 

 

 

リストUW1 Ubuntu 24/WSLでの実行スクリプト

 

#import matplotlib.pyplot as plt

import numpy as np

import stan

import arviz as az

import scipy.stats as ss

import pickle

"""

         データは、Spiegelhalter(2019)のウェブサイト

         https://github.com/dspiegel29/ArtofStatistics

         からダウンロードしたファイル09-4-homocide-trends-x.csv

         に含まれているものである。

"""

years = np.array([1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004,

                  2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012,

                  2013, 2014, 2015])

counts = np.array([587, 609, 648, 676, 761, 736, 729, 749, 639,

                   676, 712, 617, 574, 599, 497, 508, 506, 497, 557])

years_c = years - np.mean(years)       #     中心化

 

p=4                                    #    4次の多項式を用いる

#

#                 MCMCサンプリング

#

def init_f():

    return {'v_a': np.mean(counts), 'v_b':[0.0]*p}   #   MCMCの初期値

 

with open('poisson_reg_dp_loo_waic.stan') as f_stan:

    sm = stan.build(f_stan.read(),

                    dict({'p':p, 'n':len(years_c), 'x':years_c, 'y':counts}))

 

fit = sm.sample(num_chains=4, init=[init_f()]*4)

 

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

print(az.summary(i_data))

 

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

    pickle.dump(i_data, f)

 

#plt.figure(figsize=(9,7))

#plt.bar(years, counts)

 

d_fm = fit.to_frame()

 

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

    pickle.dump(d_fm, f)

 

 

 

 

リストUW2 Windowsでの実行スクリプト(リストUW1の実行後に実行)

 

import matplotlib.pyplot as plt

import numpy as np

#import stan

import arviz as az

import scipy.stats as ss

import pickle

"""

         データは、Spiegelhalter(2019)のウェブサイト

         https://github.com/dspiegel29/ArtofStatistics

         からダウンロードしたファイル09-4-homocide-trends-x.csv

         に含まれているものである。

"""

years = np.array([1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004,

                  2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012,

                  2013, 2014, 2015])

counts = np.array([587, 609, 648, 676, 761, 736, 729, 749, 639,

                   676, 712, 617, 574, 599, 497, 508, 506, 497, 557])

years_c = years - np.mean(years)       #     中心化

 

p=4                                    #    4次の多項式を用いる

 

plt.figure(figsize=(9,7))

plt.bar(years, counts)

 

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

    i_data = pickle.load(f)

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

    d_fm = pickle.load(f)

 

#

#         4次の多項式

#

def reg_4(x, va, b1, b2, b3, b4):

    return va + b1*x + b2*(x**2) + b3*(x**3) + b4*(x**4)

 

smpl_a = d_fm['a']

smpl_b1 = d_fm['b.1']

smpl_b2 = d_fm['b.2']

smpl_b3 = d_fm['b.3']

smpl_b4 = d_fm['b.4']

#

#       MCMCサンプルからランダムに1000組のサンプルを選び

#       それぞれのサンプル値による回帰曲線を描く

#

n_samples = len(smpl_a)

rng = np.random.default_rng()

boot_idx = rng.choice(n_samples, 1000)     #    ランダムに1000個採るための添字

regs = []

for i in range(1000):

    idx = boot_idx[i]

    reg_v = reg_4(years_c, smpl_a[idx], smpl_b1[idx], smpl_b2[idx],

                  smpl_b3[idx], smpl_b4[idx])

    regs.append(reg_v)

    if (i % 3) == 0:        #    ランダムな回帰曲線を間引いて描画

        plt.plot(years, reg_v, c='orange', ls=':', alpha=0.1)

#

#     ランダムに選んだ回帰曲線の2.5%値と97.5%値を描く

#

reg025_975 = np.percentile(regs, [2.5, 97.5], axis=0)

plt.plot(years, reg025_975[0], c='k', ls='--', label='2.5% of regs')

plt.plot(years, reg025_975[1], c='k', ls='-.', label='97.5% of regs')

#

#    パラメータの事後分布の中央値を係数値とする

#        回帰曲線を描く

#

reg_v = reg_4(years_c, np.median(smpl_a), np.median(smpl_b1),

              np.median(smpl_b2), np.median(smpl_b3),

              np.median(smpl_b4))

plt.plot(years, reg_v, c='k', label='Reg.by median\ndegree = 4')

#

#         中央値を係数値とする回帰曲線に対応するポアッソン分布の

#         2.5%値と97.5%値を端点する区間を描く

#

pp025 = []

pp975 = []

for v in reg_v:

    pp025.append(ss.poisson.ppf(0.025, v))

    pp975.append(ss.poisson.ppf(0.975, v))   

for i, v in enumerate(years):

    plt.plot([v,v], [pp025[i], pp975[i]], lw=5,

             markersize=20, marker='+', c='lime')

 

plt.plot([], lw=5, c='lime', label='95% interval')

plt.title('Homicides Each Year in England and Wales', fontsize=16)

plt.xticks(years, years, rotation='vertical')

plt.xlabel('Year', fontsize=18)

plt.ylabel('Count of Incidents', fontsize=14)

plt.legend()

plt.tight_layout()

plt.show()

 

 

 

 

Home