Home

順序カテゴリ項目尺度の信頼性係数ω(オメガ)

ベイズ分析による直接推定

 

順序カテゴリ項目から構成される尺度の信頼性係数を求める。各項目は、K個のカテゴリ値をとるものとし、1からKまでの整数値でカテゴリ値を表す。

項目の回答者の潜在変数を考え、この値によってカテゴリ値が決まるとする。

McDonald (1999)に従い、1因子モデル

を設定する。は項目に対する定数、は回答者の因子得点であり、であるとする。は、いわゆる誤差項であり、と独立な確率変数で、とする。このとき、テスト得点の和

の信頼性係数は次式(McDonald (1999))で与えられる(岡本、2014)。

カテゴリ境界

と設定し、の値ととの関係より、回答選択肢(カテゴリ値)が次式

により決まるものとする。このとき、の値に対して

であり、

となる。ここで、は累積標準正規分布関数で、

とおく。また、の次元の単位と原点を次式の制約で決める。

ここで、

という制約を置いている。すなわち、カテゴリ数が3以上の場合について考える。

以上の仮定の下で、データに対する尤度関数は次式で与えられる。

したがって、事後確率は

で与えられる。は事前確率分布で、十分に平らな分布を用いる(Gelman et al., 2021)。

上記の確率モデルのStanスクリプトをリスト1に示す。

リスト1のStanスクリプトを用いて、上記の確率モデルのベイズ分析を行い、式(2)のを求めるPythonスクリプトをリスト2に示す。

リスト1とリスト2のスクリプトファイル、およびサンプルデータファイルは、圧縮ファイルomega_files.zipとしてまとめた。ダウンロード展開して、自由に利用されたい。

PythonによるStanの利用は、岡本(2019で説明している。

Pythonの準備については、このウェブサイトで説明している。

 

以下のリスト2の実行例では、Anaconda/Windows 10上のPython 3.7仮想環境においてPyStan 2.17.1.0を利用した。

 

リスト2のスクリプトは次のコマンドで実行できる。

 

(py37) PS D:\omega_files> python CalcOmegaMain.py

 

上のコマンドで実行すると、入力データファイルの設定が求められる。

 

(py37) PS D:\omega_files> python CalcOmegaMain.py

Input file =

 

入力データファイルは、図1に示す形式でExcelファイルとして用意する。

図1

 

各項目の回答値(カテゴリ値)かは、2列目から設定する。1列目は、各ケースの名前で、数値でも文字列でもよい。

1行目は、1列目に適当な文字列を設定し、2列目からは各項目のカテゴリ数Kを設定する。

2行目は、各列の変数名である。

3行目は、項目の選択・非選択の指示であり、1が設定されている項目が分析の対象になる。図1では、すべて1が設定されているので、すべての項目が取り込まれて分析されてる。

4行目から、各ケースのデータを1行ずつ設定していく。

以下のように、入力データファイル名を設定して、Enterキーを押すと、計算が始まる。

下の例では、図1のExcelファイル名が設定されている。

 

(py37) PS D:\omega_files> python CalcOmegaMain.py

Input file = DataCat5.xlsx

 

MCMCサンプリングが終了すると、以下のように統計量が表示される。

 

Elapsed Time: 75.129 seconds (Warm-up)

               31.615 seconds (Sampling)

               106.744 seconds (Total)

 

Inference for Stan model: anon_model_4de137d01c5c96aab1aeacdbb8a86c8c.

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

mu[0]     0.53  7.5e-4   0.04   0.45    0.5   0.53   0.56    0.6   2571    1.0

mu[1]     0.48  7.5e-4   0.04   0.41   0.46   0.48    0.5   0.55   2236    1.0

mu[2]     0.51  6.1e-4   0.04   0.43   0.48   0.51   0.53   0.58   4000    1.0

mu[3]      0.5  7.9e-4   0.04   0.42   0.47    0.5   0.52   0.57   2299    1.0

mu[4]      0.5  7.7e-4   0.04   0.42   0.47    0.5   0.53   0.58   2566    1.0

lmbd[0]   0.33  8.5e-4   0.04   0.25    0.3   0.33   0.36   0.41   2608    1.0

その後、ωの事後分布が表示される(図2)。

 

図2

 

図2のWindowを閉じると、因子と項目和との相関係数rの事後分布のグラフが表示される(図3)。

図3

 

図3のWindowを閉じると、プログラムの実行終了となる。

端末画面には、以下のように出力されている。

 

FigOmega.png was saved.

FigCors,png was saved.

Output.txt was saved.

(py37) PS D:\omega_files>

 

ファイルFigOmega.pngに図2のグラフ、ファイルFigCors,pngに図3のグラフが保存されている。

ファイルOutput.txtには、計算結果の出力が以下のように書き出されている。

Lambda...

    Item-1:  0.299   0.328   0.357

    Item-2:  0.290   0.317   0.347

    Item-3:  0.276   0.306   0.337

    Item-4:  0.296   0.324   0.353

    Item-5:  0.295   0.325   0.356

各項目のLambdaの事後分布の第一四分位数Q1、中央値Median、第三四分位数Q3が出力されている。

項目Item-3Lambdaの値が、他のものより小さいので、この項目を除いて分析するためのファイルを図4のように用意してみる。

図4

 

3行目4列目のItem-3に対する選択支持の値が0に設定されていることに注意。

このファイルを入力データファイルとして、以下のようにプログラムを実行する。

 

(py37) PS D:\omega_files> python CalcOmegaMain.py

Input file = DataCat5del3.xlsx

 

実行結果は、図5のようになる。

図5

 

項目を除いた結果、ωとrの値は少し下がったようである。これは、Lambdaの小さい項を除いて期待される+の効果よりも、項目数が減ることのーの効果の方が大きかったようである。

 

 

参考文献

 

Gelman, A., Hill, J., & Vehtari, A. (2021). Regression and Other Stories. Cambridge University Press.

McDonald, R. P. (1999). Test Theory: A unified treatment. Lawrence Erlbaum Associations, Publishers.

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

岡本安晴(2014)心理学データ分析と測定.勁草書房

Okamoto, Y. (2013). “A Direct Bayesian Estimation of Reliability” Behaviormetrika, 40, 149-168.

 

 

リスト1 Stanスクリプト(calc_omega.stan

 

data {

    int n_items;

    int n_persons;

    int<lower = 3> K;

    int X[n_persons,n_items];

}

transformed data {

    vector[K-2] a_K;

    for (k in 1:(K-2)) {

        a_K[k] = 1.0;

    }

}

parameters {

    real F[n_persons];

    real mu[n_items];

    real<lower = 0.0> lmbd[n_items];

    real<lower = 0.0> sgm[n_items];

    simplex[K-2] theta;

}

transformed parameters {

    real C[K-1];

    simplex[K] p[n_persons,n_items];

    C[1] = 0.0;

    C[K-1] = 1.0;

    if (K > 3) {

        for (j in 2:(K-2)){

            C[j] = C[j-1] + theta[j-1];

        }

    }

    for (i in 1:n_persons) {

        for (j in 1:n_items) {

            p[i][j][1] = normal_cdf(C[1],

                                    mu[j] + lmbd[j] * F[i], sgm[j]);

            p[i][j][K] = 1.0 - normal_cdf(C[K-1],

                                    mu[j] + lmbd[j] * F[i], sgm[j]);

            for (k in 2:(K-1)) {

                p[i][j][k] = normal_cdf(C[k],

                                    mu[j] + lmbd[j] * F[i], sgm[j]) -

                             normal_cdf(C[k-1],

                                    mu[j] + lmbd[j] * F[i], sgm[j]);

            }

        }

    }

}

model {

    theta ~ dirichlet(a_K);

    for (i in 1:n_persons) {

        F[i] ~ normal(0.0, 1.0);

    }

    for (j in 1:n_items) {

        lmbd[j] ~ exponential(0.1);

        mu[j] ~ normal(0.0, 10.0);

        sgm[j] ~ exponential(0.1);

    }

    for (i in 1:n_persons) {

        for (j in 1:n_items) {

            X[i][j] ~ categorical(p[i][j]);

        }

    }

}

 

 

リスト2 Pythonスクリプト(CalcOmegaMain.py

 

import pandas as pd

import numpy as np

import pystan

import platform

import pickle

import scipy.stats as ss

import matplotlib.pyplot as plt

import seaborn as sb

 

in_flnm = input('Input file(*.xlsx) = ')       #   Excel file   

out_flnm = 'Output.txt'

out_f = open(out_flnm, 'w')

out_f.write('Input data file = ' + in_flnm + '\n')

#

#        Read in the Excel file

#

fl_xlsx = pd.ExcelFile(in_flnm)

data = pd.read_excel(fl_xlsx, header = None)

data = data.values

K = data[0][1]

print('K =', K)

print(data[:10])

out_f.write('\ndata...\n')

for v in data:

    out_f.write(v.__str__() + '\n')

item_names = data[1]

print(item_names)

ck_items = data[2]

sel_items = ck_items == 1

print(sel_items)

item_names = item_names[sel_items]

print('Selected items...')

print(item_names)

out_f.write('\n\nThe selected item names...' + item_names.__str__() + '\n')

 

data = np.array(data)

X = data[3:].T[1:].T

X = X[:, sel_items[1:]]

print(X[:3])

#

#        MCMC sampling by Stan

#

sm = pystan.StanModel(file = 'calc_omega.stan')

 

X = X.astype(dtype = int)

fit = sm.sampling(data = {'n_items':len(X[0]), 'n_persons':len(X), 'K':K, 'X': X},

                  pars = ['mu', 'lmbd', 'sgm', 'F'],

                             n_jobs = 1 if platform.system() == 'Windows' else -1)

 

#

#           Results of MCMC sampling

#

print(fit)

out_f.write('\nfit...\n')

out_f.write(fit.__str__())

#

#      Calculation on parameter mu

#

print('mu =\n', fit['mu'[:10]])

out_f.write('\nMu (Q1, Med, Q3)...\n')

for j in range(len(item_names)):

    out_f.write('    ' + item_names[j] + ':  ')

    mu_q1, mu_med, mu_q3 = np.percentile(fit['mu'].T[j], [25, 50, 75])

    out_f.write('{0:.3f}   {1:.3f}   {2:.3f}\n'.format(mu_q1, mu_med, mu_q3))

#

#       Calculation on parameter lambda

#

print('lmbd =\n', fit['lmbd'][:10])

print('Lambda =\n', fit['lmbd'[:10]])

out_f.write('\nLambda (Q1, Med, Q3)...\n')

for j in range(len(item_names)):

    out_f.write('    ' + item_names[j] + ':  ')

    lmbd_q1, lmbd_med, lmbd_q3 = np.percentile(fit['lmbd'].T[j], [25, 50, 75])

    out_f.write('{0:.3f}   {1:.3f}   {2:.3f}\n'.format(lmbd_q1, lmbd_med, lmbd_q3))

#

#         Calculation on parameter sigma

#

print('Sigma =\n', fit['sgm'[:10]])

out_f.write('\nSigma (Q1, Med, Q3)...\n')

for j in range(len(item_names)):

    out_f.write('    ' + item_names[j] + ':  ')

    sgm_q1, sgm_med, sgm_q3 = np.percentile(fit['sgm'].T[j], [25, 50, 75])

    out_f.write('{0:.3f}   {1:.3f}   {2:.3f}\n'.format(sgm_q1, sgm_med, sgm_q3))

#

#         Calculation on omega

#

sum_lmbd = fit['lmbd'].sum(axis=1)

sum_sqr_sgm = (fit['sgm']**2).sum(axis=1)

omegas = sum_lmbd**2 / (sum_lmbd**2 + sum_sqr_sgm)

 

def calcMAPEst(samples, a = 0.05, n_points = 1000):

    """         Estimation of the MAP estimate by kde

         samples:    Input data for which MAP estimate is calculated.

         a:          Each side of a/2 of samples is ignored, Default value is 0.05

         n_points:   number of points used in kde.  Default value is 1000

    """

    import numpy as np

    import scipy.stats as ss

 

    Lp, Up = np.percentile(samples, [100 * a/2, 100 * (1 - a/2)])

    coord = np.linspace(Lp, Up, n_points)

    est_pdf = ss.gaussian_kde(samples).pdf(coord)

    map_idx = np.argmax(est_pdf)

    MAP_Est = coord[map_idx]     

    return MAP_Est, est_pdf[map_idx]    #   MAP estimate,  the value at MAP estimate

 

 

MAP = calcMAPEst(omegas)[0]

print('MAP =', MAP)

Q1, Med, Q3 = np.percentile(omegas, [25, 50, 75])

print(Q1, Med, Q3)

out_f.write('\nomega (MAP / Q1, Med, Q3)...\n')

out_f.write('    {0:.3f}\n'.format(MAP))

out_f.write('    {0:.3f}   {1:.3f}   {2:.3f}\n'.format(Q1, Med, Q3))

 

sb.kdeplot(omegas, color = 'b')

plt.title('MAP = {0:.2f}\nQ1 = {1:.2f}  Med = {2:.2f}  Q3 = {3:.2f}'.format(MAP, Q1, Med, Q3))

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

plt.savefig('FigOmega.png')

plt.show()

#

#          Calculation on Pearson correlation coefficient

#            between factor F and the sum score of Xjs

#

SumX = X.sum(axis=1)

Cors = []

for Fvalues in fit['F']:

    r = ss.pearsonr(SumX, Fvalues)[0]

    Cors.append(r)

   

r_map = calcMAPEst(Cors)[0]

r_q1, r_med, r_q3 = np.percentile(Cors, [25, 50, 75])

 

out_f.write('\nCorrelation between F and X (MAP / Q1, Med, Q3)...\n')

out_f.write('    {0:.3f}\n'.format(r_map))

out_f.write('    {0:.3f}   {1:.3f}   {2:.3f}\n'.format(r_q1, r_med, r_q3))

   

sb.kdeplot(Cors)

plt.title('MAP = {0:.2f}\nQ1 = {1:.2f}  Med = {2:.2f}  Q3 = {3:.2f}'.

         format(r_map, r_q1, r_med, r_q3), fontsize = 14)

plt.xlabel('r', fontsize = 16)

plt.savefig('FigCors.png')

plt.show()

print('FigOmega.png was saved.')

print('FigCors,png was saved.')

out_f.close()

print(out_flnm + ' was saved.')

 

 

 

Home