Home

Bayesian Analysis of Reliability of Ordered Categorical Items Scales

with Correction for Shrinkage

Yasuharu Okamoto, 2023.06, 2023.08

 

Reliability coefficient of a test score is given by the precision of the test score measurement (McDonald, 1999, p. 63), which is given by the following equation

Reliability coefficient  is defined based on the following model:

where  is examinee fs score on the -th item (),  is the mean of ,  is the factor loading of item ,  is examinee fs factor score whose distribution is the standard normal one , and  is a residual whose distribution is a normal one. The summed score of examinee  is given by the following equation.

where

is a true score and

is an error.

In the case of categorical items, the continuous variable  is discretized to categorical variables  (Okamoto, 2013). That is, for category boundaries , we have

where

and K is the number of categories.

Hence, we have

where  is the cumulative distribution function for the standard normal random variable with the convention  and .

The likelihood function for data  is given by

and the posterior distribution by

with a prior distribution .

 

For the estimates of parameters  and , reliability coefficient  is calculated by eq. (1).

It is well known that shrinkage may occur in Bayesian analysis. Correction for shrinkage is done in the scripts shown in the website.

To investigate relation between the observed score

and the true latent value

,

correlation coefficient R and coefficient of determination  are calculated (Okamoto, 2016, 2017).

To show relation between observed values  and estimates of the factor  visually, a scatter plot is displayed.

Reliability coefficient  is also calculated.

 

Actual installation of a model depends on the number of categories K (Okamoto, 2013). Hence, the scripts are developed for K=2, K=3, and K>3, respectively.

A simple way of installing Python with Anaconda is explained on this Website.

Scripts in the website were developed in PyStan 3. For information about how to install and use PyStan 3, check the following websites

http://y-okamoto-psy1949.la.coocan.jp/Python/en2/wsl_ubuntu/

and

http://y-okamoto-psy1949.la.coocan.jp/Python/en2/TryingPyStan3/

 

Files used at the website are archived in the files files8_G3Cat.zip for K>3, files8_3Cat.zip for K=3, and files8_2Cat.zip for K=2. They can be freely downloaded and used.

 

 

In the case of K>3

In this case of K>3, the Stan script for sampling parameter values from posterior distribution is as follows (see Okamoto, 2013, p. 156):

 

 

data {

    int N;

    int M;

    int K;

    array[N, M] int<lower = 1, upper = K> X;

    vector[K-2] a;

}

transformed data {

    real C1;

    real CK_1;

    //

    //     These values specify the origin and unit of the scale

    //     cf. Okamoto (2013). Behaviormetrika, 40, 149-168.

    //

    C1 = -1;

    CK_1 = 1;

}

parameters {

    array[M] real mu;

    array[M] real<lower = 0.0> Lmd;

    array[M] real<lower = 0.0> psy;

    simplex[K-2] Ck;

    array[N] real F;

}

transformed parameters {

    array[N, M] vector[K] p;

    array[N, M] vector[K-1] cumP;

    array[K-1] real C;

 

    C[1] = C1;

    C[K-1] = CK_1;

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

        C[k+1] = C[k] + Ck[k] * 2.0;

    }

    for (i in 1:N)

        for (j in 1:M){

            cumP[i][j][K-1] = normal_cdf((C[K-1] - (mu[j] + Lmd[j] * F[i])) / psy[j] | 0.0, 1.0);

            cumP[i][j][1] = normal_cdf((C[1] - (mu[j] + Lmd[j] * F[i])) / psy[j] | 0.0, 1.0);

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

                cumP[i][j][k+1] = normal_cdf((C[k+1] - (mu[j] + Lmd[j] * F[i])) / psy[j] | 0.0, 1.0);

            }

            p[i][j][1] = cumP[i][j][1];

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

                p[i][j][k+1] = cumP[i][j][k+1] - cumP[i][j][k];

            }

            p[i][j][K] = 1.0 - cumP[i][j][K-1];

        }

   

}

model {

    for (j in 1:M) {

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

        psy[j] ~ exponential(0.1);

        Lmd[j] ~ exponential(0.1);

    }

    Ck ~ dirichlet(a);

    F ~ normal(0.0, 1.0);

    for (i in 1:N) {

        for (j in 1:M) {

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

        }

    }

}

generated quantities {

    real sdF;

    real mnF;

    real sumF;

    real ssF;

    array[N] real cF;

    array[M] real cLmd;

    sumF = 0.0;

    ssF = 0.0;

    for (i in 1:N) {

        sumF += F[i];

        ssF += pow(F[i], 2);

    }

    mnF = sumF / N;

    sdF = pow((ssF - N * pow(mnF, 2)) / (N - 1), 0.5);

    for (i in 1:N) {

        cF[i] = (F[i] - mnF) / sdF;

    }

    for (j in 1:M) {

        cLmd[j] = Lmd[j] * sdF;

    }

}

 

 

The Python script, which uses this Stan script to analyze the data, is shown in Listing 1.

When you run the script on Windows, you must install Ubuntu on Windows, and run the script on Ubuntu on Windows. That is, you run the script in a Python 3.11 virtual environment of Anaconda on Ubuntu (WSL: Windows Subsystem for Linux) on Windows. For more information, check this website, and this website.

 

Run the script Reli_G3Cat.py in Listing 1, then the name of the input data file is asked. Set the input file name as follows.

 

(py311) c /files8_G3Cat$ python Reli_G3Cat.py

Data File Name (*.xlsx) = DataN5.xlsx

 

The input data file is expected to be an Excel data file (*.xlsx) (Figure 1.1).

Figure 1.1

 

In the first row, the names of the variables (items) are set. The first column is used for case identification, and any value (string) is OK. In the second row, set the number of categories K in the second column, i.e., the cell B2. In Figure 1.1, g5h is set. From the third row, responses on the categorical items are set. Values on the items should be integer values from 1 to K, where K is the number of categories. The data in Figure 1.1 is for the case of K=5.

After the input data file name is set, sampling by Stan starts. When the sampling by Stan ends, posterior distributions of s are displayed (Figure 1.2).

Figure 1.2

 

Click the X icon at the upper right corner of the windows to close the window. The next window shows posterior distributions of s (Figure 1.3).

Figure 1.3

 

Close the window in Figure 1.3, then the window in Figure 1.4 appears and the posterior distributions of s are presented.

Figure 1.4

 

Close the window in Figure 1.4, then the next window appears and the posterior distribution of  is presented (Figure 1,5).

Figure 1.5

 

Close the window in Figure 1.5, then the window, where the posterior distributions of R and  are drawn, appears (Figure 1.6).  is the correlation coefficient of the observed values s and the true values s, and  is the square of  and represents the coefficient of determination of s and s (Okamoto, 2016, 2017).

Figure 1.6

 

When the window in Figure 1.6 is closed, a scatter gram of observed scores  and estimates of  is displayed (Figure 1.7).

Figure 1.7

 

Close the window of Figure 1.7, the script ends.

After the script ends, the following message is shown on the terminal.

 

Results.txt was saved.

(py311) c /files8_G3Cat$

 

The contents of the file Results.txt are as follows.

 

 

 

 

Data file = DataN5.xlsx

 

K = 5,  N = 100,  M = 10

    1: [1 1 2 1 2 2 2 2 2 3]

    2: [1 1 1 1 1 1 1 1 2 2]

    3: [2 2 1 2 3 2 3 2 3 3]

        .

        .

        .

   98: [2 3 3 4 3 5 4 3 4 4]

   99: [1 1 2 2 2 3 1 2 3 3]

  100: [1 1 1 1 1 1 3 2 2 3]

 

 

Summary...

          mean     sd  hdi_3%  hdi_97%  mcse_mean  mcse_sd  ess_bulk  ess_tail  r_hat

mu[0]   -0.611  0.077  -0.758   -0.470      0.005    0.003     247.0     525.0   1.01

mu[1]   -0.542  0.086  -0.702   -0.384      0.005    0.004     247.0     543.0   1.02

mu[2]   -0.380  0.086  -0.548   -0.227      0.006    0.004     238.0     467.0   1.02

       .

       .

       .

cF[98]  -0.850  0.181  -1.175   -0.494      0.002    0.001    8479.0    3107.0   1.00

cF[99]  -1.266  0.186  -1.627   -0.938      0.002    0.001    8960.0    2995.0   1.00

sdF      0.948  0.070   0.827    1.083      0.004    0.003     348.0     677.0   1.01

mnF      0.010  0.101  -0.174    0.197      0.008    0.005     175.0     345.0   1.02

 

mu[1] = -0.6118

mu[2] = -0.54057

mu[3] = -0.37889

mu[4] = -0.2674

mu[5] = -0.14473

mu[6] = 0.0005217

mu[7] = 0.090897

mu[8] = 0.28648

mu[9] = 0.48015

mu[10] = 0.44828

 

Lambda[1] = 0.59608

Lambda[2] = 0.67004

Lambda[3] = 0.67247

Lambda[4] = 0.76804

Lambda[5] = 0.72232

Lambda[6] = 0.75365

Lambda[7] = 0.65455

Lambda[8] = 0.78646

Lambda[9] = 0.74405

Lambda[10] = 0.67585

 

Psy[1] = 0.30844

Psy[2] = 0.34215

Psy[3] = 0.32863

Psy[4] = 0.35737

Psy[5] = 0.317

Psy[6] = 0.34649

Psy[7] = 0.30951

Psy[8] = 0.43274

Psy[9] = 0.33779

Psy[10] = 0.27708

 

 

omega(mean) = 0.97692

omega(Med.) = 0.97699

omega(Q1) = 0.97572     omega(Q3) = 0.97822

 

 

R^2(Med.) = 0.9605

R^2(Q1) = 0.95609     R^2(Q3) = 0.96455

R(Med.) = 0.98005

R(Q1) = 0.9778     R(Q3) = 0.98212

 

alpha = 0.96631

standardized alpha = 0.96836

 

 

 

In the case of K=3

When K=3, no parameters between  and , which are set to be -1 and 1, respectively (see, Okamoto, 2013, p. 156), are present. The Stan script is as follows:

 

 

data {

    int N;

    int M;

    array[N, M] int<lower = 1, upper = 3> X;

}

transformed data {

    real C1;

    real C2;

    //

    //     These values specify the origin and unit of the scale

    //     cf. Okamoto (2013). Behaviormetrika, 40, 149-168.

    //

    C1 = -1;

    C2 = 1;

}

parameters {

    array[M] real mu;

    array[M] real<lower = 0.0> Lmd;

    array[M] real<lower = 0.0> vpsy;

    array[N] real F;

}

transformed parameters {

    array[M] real psy;

    array[N, M] vector[3] p;

    array[N, M] real cumP;

    for (j in 1:M) {

        psy[j] = 0.01 + vpsy[j];

    }

    for (i in 1:N)

        for (j in 1:M){

            cumP[i][j] = normal_cdf((C2 - (mu[j] + Lmd[j] * F[i])) / psy[j] | 0.0, 1.0);

            p[i][j][1] = normal_cdf((C1 - (mu[j] + Lmd[j] * F[i])) / psy[j] | 0.0, 1.0);

            p[i][j][2] = cumP[i][j] - p[i][j][1];

            p[i][j][3] = 1.0 - cumP[i][j];

        }

   

}

model {

    for (j in 1:M) {

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

        vpsy[j] ~ exponential(0.1);

        Lmd[j] ~ exponential(0.1);

    }

    F ~ normal(0.0, 1.0);

    for (i in 1:N) {

        for (j in 1:M) {

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

        }

    }

}

generated quantities {

    real sdF;

    real mnF;

    real sumF;

    real ssF;

    array[N] real cF;

    array[M] real cLmd;

    sumF = 0.0;

    ssF = 0.0;

    for (i in 1:N) {

        sumF += F[i];

        ssF += pow(F[i], 2);

    }

    mnF = sumF / N;

    sdF = pow((ssF - N * pow(mnF, 2)) / (N - 1), 0.5);

    for (i in 1:N) {

        cF[i] = (F[i] - mnF) / sdF;

    }

    for (j in 1:M) {

        cLmd[j] = Lmd[j] * sdF;

    }

}

 

 

The Python script, which uses this Stan script to analyze the data, is shown in Listing 2.

When you run the script on Windows, you must install Ubuntu on Windows, and run the script on Ubuntu on Windows. That is, you run the script in a Python 3.11 virtual environment of Anaconda on Ubuntu (WSL: Windows Subsystem for Linux) on Windows. For more information, check this website, and this website.

 

Run the script Reli_3Cat.py in Listing 2, the input file name is asked as follows.

 

(py311) c /files8_3Cat$ python Reli_3Cat.py

Data File Name (*.xlsx) = Data.xlsx

 

The input data file is expected to be an Excel data file (*.xlsx) (Figure 2.1).

E

E

E

Figure 2.1

 

In the first row, the names of the variables (Items) are set. The first column is used for case identification, and any value (string) is OK. From the second column, responses on the categorical items are set. Values on the items should be integer values from 1 to K, where K is the number of categories, that is, K=3. Hence, category values are 1, 2, or 3. After the input file name is set, sampling by Stan starts. When the sampling by Stan ends, posterior distributions of s are displayed (Figure 2.2).

Figure 2.2

 

Click the X icon at the upper right corner of the windows to close the window. The next window shows posterior distributions of s are displayed (Figure 2.3).

Figure 2.3

 

Close the window in Figure 2.3, then the window in Figure 2.4 appears and the posterior distributions of s are presented.

Figure 2.4

 

Close the window in Figure 2.4, then the next window appears and the posterior distribution of  is presented (Figure 2.5).

Figure 2.5

 

Close the window in Figure 2.5, then the window, where the posterior distributions of R and  are drawn, appears (Figure 2.6).  is the correlation coefficient of the observed values s and the true values s, and  is the square of  and represents the coefficient of determination of s and s (Okamoto, 2016, 2017).

Figure 2.6

 

When the window in Figure 2.6 is closed, a scatter gram of observed scores s and estimates of s is shown (Figure 2.7).

Figure 2.7

 

Close the window of Figure 2.7, then the program ends. After the program ends, the terminal display shows the following messages.

 

Results.txt was saved.

(py311) c /files8_3Cat$

 

The contents of the file Results.txt are as follows.

 

 

Data file = Data.xlsx

VarLabels =

['X1' 'X2' 'X3' 'X4' 'X5' 'X6' 'X7' 'X8' 'X9' 'X10']

 

N = 100,   M = 10

 

 

Data...

    1:  [1 1 1 2 3 2 2 2 3 2]

    2:  [1 1 2 2 2 2 2 3 3 1]

    3:  [1 1 2 2 2 2 2 3 3 3]

       .

       .

       .

   98:  [2 3 2 3 3 3 3 3 3 3]

   99:  [1 2 2 1 2 2 2 2 3 3]

  100:  [1 1 1 1 2 1 2 2 2 2]

 

         mean     sd  hdi_3%  hdi_97%  mcse_mean  mcse_sd  ess_bulk  ess_tail  r_hat

mu[0]  -1.783  0.317  -2.358   -1.195      0.016    0.012     341.0     892.0   1.01

mu[1]  -1.588  0.358  -2.258   -0.930      0.020    0.014     316.0     724.0   1.01

mu[2]  -0.623  0.240  -1.065   -0.177      0.016    0.011     220.0     534.0   1.02

       .

       .

       .

cF[97]  1.104  0.290   0.578    1.649      0.004    0.003    5397.0    3141.0   1.00

cF[98] -0.089  0.182  -0.438    0.241      0.002    0.003    6161.0    2973.0   1.00

cF[99] -0.610  0.181  -0.954   -0.275      0.002    0.002    6409.0    2626.0   1.00

 

mu[1] = -1.7546

mu[2] = -1.5558

mu[3] = -0.61515

mu[4] = -0.50779

mu[5] = -0.085098

mu[6] = 0.19538

mu[7] = 0.77405

mu[8] = 1.1675

mu[9] = 2.5391

mu[10] = 2.1659

 

Lambda[1] = 1.8078

Lambda[2] = 2.0748

Lambda[3] = 1.8036

Lambda[4] = 2.0777

Lambda[5] = 2.2282

Lambda[6] = 2.4247

Lambda[7] = 2.502

Lambda[8] = 2.5311

Lambda[9] = 3.5708

Lambda[10] = 2.605

 

Psy[1] = 0.85983

Psy[2] = 1.2953

Psy[3] = 1.079

Psy[4] = 1.2426

Psy[5] = 0.95208

Psy[6] = 0.73502

Psy[7] = 0.88215

Psy[8] = 1.1486

Psy[9] = 1.631

Psy[10] = 1.1614

 

 

omega(mean) = 0.97659

omega(Med.) = 0.97688

omega(Q1) = 0.9743     omega(Q3) = 0.97915

 

 

R^2(Med.) = 0.89435

R^2(Q1) = 0.88037     R^2(Q3) = 0.9065

R(Med.) = 0.9457

R(Q1) = 0.93828     R(Q3) = 0.9521

 

alpha = 0.93625

standardized alpha = 0.93771

 

 

 

 

In the case of K=2

When K=2, only one parameter  for the category boundary is present and set to be 0. Parameters s and s are set as follows by technical reasons (see Okamoto, 2013, p. 158).

 and .

The Stan script is as follows:

 

 

data {

    int N;

    int M;

    array[N, M] int<lower = 1, upper = 2> X;

}

parameters {

    array[M] real mu;

    real<lower = 0.0> psy;

    array[N] real F;

}

transformed parameters {

    array[N, M] real p;

    real sdF;

    real mnF;

    real sumF;

    real ssF;

    array[N] real cF;

    sumF = 0.0;

    ssF = 0.0;

    for (i in 1:N) {

        sumF += F[i];

        ssF += pow(F[i], 2);

    }

    mnF = sumF / N;

    sdF = pow((ssF - N * pow(mnF, 2)) / (N - 1), 0.5);

    for (i in 1:N) {

        cF[i] = (F[i] - mnF) / sdF;

    }

 

    for (i in 1:N)

        for (j in 1:M){

            p[i][j] = normal_cdf((mu[j] + cF[i]) / psy | 0.0, 1.0);

        }

   

}

model {

    psy ~ exponential(0.1);

    mu ~ normal(0.0, 10.0);

    F ~ normal(0.0, 1.0);

    for (i in 1:N) {

        for (j in 1:M) {

            (X[i][j] - 1) ~ bernoulli(p[i][j]);

        }

    }

}

 

 

The Python script, which uses this Stan script to analyze the data, is shown in Listing 3 in the latter half of this website.

When you run the script on Windows, you must install Ubuntu on Windows, and run the script on Ubuntu on Windows. That is, you run the script in a Python 3.11 virtual environment of Anaconda on Ubuntu (WSL: Windows Subsystem for Linux) on Windows. For more information, check this website, and this website.

 

Run the script Reli_2Cat.py in Listing 3, the input file name is asked as follows.

 

(py311) c /files8_2Cat$ python Reli_2Cat.py

Data File Name (*.xlsx) = Data.xlsx

 

The input data file is expected to be an Excel data file (*.xlsx) (Figure 3.2).

E

E

E

Figure 3.1

 

In the first row, the names of the variables (Items) are set. The first column is used for case identification, and any value (string) is OK. From the second column, responses on the categorical items are set. Values on the items should be integer values from 1 to K, where K is the number of categories. In this case of K=2, category values are 1 or 2. After the input file name is set, sampling by Stan starts. When the sampling by Stan ends, posterior distributions of s are displayed (Figure 3.2).

Figure 3.2

 

Click the X icon at the upper right corner of the windows to close. The window which displays the posterior distribution of  appears (Figure 3.3).

Figure 3.3

 

Close the windows of Figure 3.3, The window in Figure 3.4 appears and the posterior distributions of  is presented .

Figure 3.4

 

Close the window in Figure 3.4, then the window, where the posterior distributions of R and  are drawn, appears (Figure 3.5).  is the correlation coefficient of the observed values s and the true values s, and  is the square of  and represents the coefficient of determination of s and s (Okamoto, 2016, 2017).

Figure 3.5

 

Close the window in Figure 3.5, then a scatter plot of observed scores  and estimates of  is displayed (Figure 3.6).

Figure 3.6

 

Close the window of Figure 3.6, then the program ends.

After the program ends, the terminal display shows the following messages.

 

Results.txt was saved.

(py311) c /files8_2Cat$

 

The contents of the file Results.txt are as follows.

 

 

Data file = Data.xlsx

 

VarLables =

{VarLables}

N = 100,   M = 10

 

X =

    1  [1 1 2 1 1 1 2 2 2 2]

    2  [1 2 1 2 1 1 1 2 2 2]

    3  [1 1 1 1 1 1 2 1 2 1]

     .

     .

     .

   98  [2 2 1 1 1 2 2 1 2 2]

   99  [1 1 1 1 1 1 1 1 2 1]

  100  [1 1 1 1 1 1 1 1 1 2]

 

Summary...

         mean     sd  hdi_3%  hdi_97%  mcse_mean  mcse_sd  ess_bulk  ess_tail  r_hat

mu[0]  -1.192  0.137  -1.450   -0.929      0.002    0.001    6084.0    3189.0   1.00

mu[1]  -1.059  0.134  -1.307   -0.811      0.002    0.001    6591.0    3170.0   1.00

mu[2]  -0.736  0.121  -0.959   -0.513      0.001    0.001    6776.0    3268.0   1.00

      .

      .

      .

cF[99] -0.928  0.375  -1.700   -0.287      0.004    0.004    8655.0    2869.0   1.00

mnF    -0.002  0.097  -0.177    0.186      0.003    0.002    1132.0    1856.0   1.00

sdF     0.999  0.070   0.870    1.128      0.002    0.001    1788.0    2445.0   1.00

 

mu[1] = -1.1871

mu[2] = -1.0553

mu[3] = -0.73387

mu[4] = -0.26835

mu[5] = -0.23873

mu[6] = 0.028156

mu[7] = 0.15127

mu[8] = 0.32501

mu[9] = 0.48954

mu[10] = 0.84849

 

Psy = 0.63552

 

omega(mean) = 0.96078

omega(Med.) = 0.96118

omega(Q1) = 0.95694     omega(Q3) = 0.96507

 

 

R^2(Med.) = 0.85445

R^2(Q1) = 0.83922     R^2(Q3) = 0.8688

R(Med.) = 0.92436

R(Q1) = 0.91609     R(Q3) = 0.9321

 

alpha = 0.88414

standardized alpha = 0.88536

 

 

 

Listing 1  Python script in the case of K>3 (Reli_G3Cat.py). The files are contained in the archived file files8_G3Cat.zip , which can be freely downloaded and used.

 

import pandas as pd

import numpy as np

import matplotlib.pyplot as plt

import scipy.stats as ss

import stan

import seaborn as sb

import arviz as az

 

pd.options.display.max_rows = 10000

 

flnm = input('Data File Name (*.xlsx) = ')

xlsx = pd.ExcelFile(flnm)              

data = pd.read_excel(xlsx)     

 

fout = open('Results.txt', 'w')

fout.write(f'Data file = {flnm}\n')

 

VarLabels = data.columns[1:].values;

print(VarLabels)

 

print('data.vlaues...\n', data.values[:5])

 

K = int(data.values[0,1])

print('K =', K)

X = np.array(data.values[1:, 1:], dtype='int')

N = len(X)

M = len(X[0])

print('N = ', N)

print('M = ', M)

print('X =\n', X)

fout.write('\n')

fout.write(f'K = {K},  N = {N},  M = {M}\n')

for i, v in enumerate(X):

    fout.write(f'{i+1:>5d}: {v}\n')

 

Data = {'N': N, 'M': M, 'X': X, 'K': K, 'a': [1.0]*(K-2)}

 

def f_init():

    return dict(mu = [0.0]*M, psy = [1.0]*M, F = [0.0]*N)

 

with open('Reli_G3Cat.stan', 'r') as fstan:

    sm = stan.build(fstan.read(), data = Data)

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

 

print(fit)

 

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

smry = az.summary(i_data, var_names = ['mu', 'cLmd', 'psy', 'Ck', 'cF',

                                       'sdF', 'mnF'] )

print('Summary =\n',smry)

fout.write('\n\n')

fout.write('Summary...\n')

fout.write(smry.__str__())

fout.write('\n')

 

d_fm = fit.to_frame()

 

Mus = []

for j in range(M):

    Mus.append(d_fm[f'mu.{j+1}'])

Mus = np.array(Mus).T

              

for i in range(M):

    sb.kdeplot(Mus.T[i])

plt.title('$\mu$', fontsize = 16)

plt.show()

 

print()

fout.write('\n')

for j in range(M):

    print('mu[{0:}] = {1:.5}'.format(j+1, np.percentile(Mus.T[j], 50)))

    fout.write('mu[{0:}] = {1:.5}\n'.format(j+1, np.percentile(Mus.T[j], 50)))

 

Lmds = []

for j in range(M):

    Lmds.append(d_fm[f'cLmd.{j+1}'])

Lmds = np.array(Lmds).T

for i in range(M):

    sb.kdeplot(Lmds.T[i])

plt.title('$\lambda$', fontsize = 24)

plt.show()

 

print()

fout.write('\n')

for j in range(M):

    print('Lambda[{0:}] = {1:.5}'.format(j+1, np.percentile(Lmds.T[j], 50)))

    fout.write('Lambda[{0:}] = {1:.5}\n'.format(j+1, np.percentile(Lmds.T[j], 50)))

 

Psys = []

for j in range(M):

    Psys.append(d_fm[f'psy.{j+1}'])

Psys = np.array(Psys).T

for j in range(M):

    sb.kdeplot(Psys.T[j])

plt.title('$\psi$', fontsize = 24)

plt.show()

 

print()

fout.write('\n')

for j in range(M):

    print('Psy[{0:}] = {1:.5}'.format(j+1, np.percentile(Psys.T[j], 50)))

    fout.write('Psy[{0:}] = {1:.5}\n'.format(j+1, np.percentile(Psys.T[j], 50)))

 

rho = []      #  omega

for i in range(len(Lmds)):

    ss_Lmd = Lmds[i].sum() ** 2

    s_Psys2 = (Psys[i] ** 2).sum()

    rho.append(ss_Lmd / (ss_Lmd + s_Psys2))

rho_Q1, rho_Med, rho_Q3 = np.percentile(rho, [25, 50, 75])

rho_Mean = np.array(rho).mean()

 

fout.write('\n')

print('\nomega(mean) = {0:.5}'.format(rho_Mean))

fout.write('\nomega(mean) = {0:.5}\n'.format(rho_Mean))

print('omega(Med.) = {0:.5}'.format(rho_Med))

fout.write('omega(Med.) = {0:.5}\n'.format(rho_Med))

print('omega(Q1) = {0:.5}     omega(Q3) = {1:.5}'.format(rho_Q1, rho_Q3))

fout.write('omega(Q1) = {0:.5}     omega(Q3) = {1:.5}\n'.format(rho_Q1, rho_Q3))

 

sb.kdeplot(rho)

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

          format(rho_Mean, rho_Q1, rho_Med, rho_Q3), fontsize = 14)

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

plt.show()

 

Fs = []

for i in range(N):

    Fs.append(d_fm[f'cF.{i+1}'])

Fs = np.array(Fs).T

 

Xtot = X.sum(axis = 1)

 

Rs = []

R2s = []

for t in range(len(Fs)):

    r = np.corrcoef(Xtot, Fs[t])[0][1]

    Rs.append(r)

    R2s.append(r**2)

   

R_Q1, R_med, R_Q3 = np.percentile(Rs, [25, 50, 75])

R2_Q1, R2_med, R2_Q3 = np.percentile(R2s, [25, 50, 75])   

sb.kdeplot(Rs, label = 'R')

sb.kdeplot(R2s, label = '$R^2$')

plt.title('R(Med.) = {0:.3}   $R^2$(Med.) = {1:.3}'.format(R_med, R2_med), fontsize = 16)

plt.legend()

plt.show()

 

FsMeds = np.median(Fs, axis=0)

plt.plot(FsMeds, Xtot, 'o')

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

plt.ylabel('X', fontsize = 16)

plt.show()

 

fout.write('\n')

print('\nR^2(Med.) = {0:.5}'.format(R2_med))

fout.write('\nR^2(Med.) = {0:.5}\n'.format(R2_med))

print('R^2(Q1) = {0:.5}     R^2(Q3) = {1:.5}'.format(R2_Q1, R2_Q3))

fout.write('R^2(Q1) = {0:.5}     R^2(Q3) = {1:.5}\n'.format(R2_Q1, R2_Q3))

print('R(Med.) = {0:.5}'.format(R_med))

fout.write('R(Med.) = {0:.5}\n'.format(R_med))

print('R(Q1) = {0:.5}     R(Q3) = {1:.5}'.format(R_Q1, R_Q3))

fout.write('R(Q1) = {0:.5}     R(Q3) = {1:.5}'.format(R_Q1, R_Q3))

 

S = np.cov(X, rowvar = False)

VarX = np.var(X.sum(axis = 1))

alpha_coef = (M/(M-1)) * (1 - (np.diag(S).sum() / VarX))

print('\nalpha = {0:.5}'.format(alpha_coef))

fout.write('\n')

fout.write('\nalpha = {0:.5}\n'.format(alpha_coef))

 

Cor = np.corrcoef(X, rowvar = False)

r_bar = (Cor.sum() - np.diag(Cor).sum()) / (M * (M - 1))

std_alpha = M * r_bar / (1.0 + (M - 1) * r_bar)

print('standardized alpha = {0:.5}'.format(std_alpha))

fout.write('standardized alpha = {0:.5}'.format(std_alpha))

 

fout.close()

print('Results.txt was saved.')

 

 

Listing 2  Python script in the case of K=3. The files are contained in the archived file files8_3Cat.zip , which can be freely downloaded and used.

 

import pandas as pd

import numpy as np

import matplotlib.pyplot as plt

import scipy.stats as ss

import stan

import seaborn as sb

import arviz as az

 

pd.options.display.max_rows = 10000

 

fout = open('Results.txt', 'w')

 

flnm = input('Data File Name (*.xlsx) = ')

xlsx = pd.ExcelFile(flnm)      

data = pd.read_excel(xlsx)     

fout.write(f'Data file = {flnm}\n')

 

VarLabels = data.columns[1:].values;

print(VarLabels)

fout.write(f'VarLabels = \n{VarLabels}\n')

 

X = data.values[:, 1:]

N = len(X)

M = len(X[0])

print('N = ', N)

print('M = ', M)

fout.write(f'\nN = {N},   M = {M}\n')

 

fout.write('\n\nData...\n')

for i, v in enumerate(X):

    fout.write(f'{i+1:>5d}:  {v}\n')

 

Data = {'N': N, 'M': M, 'X': X}

 

def f_init():

    return dict(mu = [0.0]*M, vpsy = [1.0]*M, F = [0.0]*N)

 

with open('Reli_3Cat.stan', 'r') as fstan:

    sm = stan.build(fstan.read(), data = Data)

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

 

 

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

smry = az.summary(i_data, var_names = ['mu', 'Lmd', 'psy', 'sdF', 'mnF', 'cF'])

print('Summary =\n',smry)

fout.write('\n')

fout.write(f'{smry}\n')

 

d_fm = fit.to_frame()

 

print(fit)

 

Mus = []

for j in range(M):

    Mus.append(d_fm[f'mu.{j+1}'])

Mus = np.array(Mus).T

for i in range(M):

    sb.kdeplot(Mus.T[i])

plt.title('$\mu$', fontsize = 16)

plt.show()

 

 

print()

fout.write('\n')

for j in range(M):

    print('mu[{0:}] = {1:.5}'.format(j+1, np.percentile(Mus.T[j], 50)))

    fout.write('mu[{0:}] = {1:.5}\n'.format(j+1, np.percentile(Mus.T[j], 50)))

 

Lmds = []

for j in range(M):

    Lmds.append(d_fm[f'cLmd.{j+1}'])

Lmds = np.array(Lmds).T

for i in range(M):

    sb.kdeplot(Lmds.T[i],)

plt.title('$\lambda$', fontsize = 24)

plt.show()

 

print()

fout.write('\n')

for j in range(M):

    print('Lambda[{0:}] = {1:.5}'.format(j+1, np.percentile(Lmds.T[j], 50)))

    fout.write('Lambda[{0:}] = {1:.5}\n'.format(j+1, np.percentile(Lmds.T[j], 50)))

 

Psys = []

for j in range(M):

    Psys.append(d_fm[f'psy.{j+1}'])

Psys = np.array(Psys).T

for j in range(M):

    sb.kdeplot(Psys.T[j])

plt.title('$\psi$', fontsize = 24)

plt.show()

 

print()

fout.write('\n')

for j in range(M):

    print('Psy[{0:}] = {1:.5}'.format(j+1, np.percentile(Psys.T[j], 50)))

    fout.write('Psy[{0:}] = {1:.5}\n'.format(j+1, np.percentile(Psys.T[j], 50)))

 

rho = []

for i in range(len(Lmds)):

    ss_Lmd = Lmds[i].sum() ** 2

    s_Psys2 = (Psys[i] ** 2).sum()

    rho.append(ss_Lmd / (ss_Lmd + s_Psys2))

rho_Q1, rho_Med, rho_Q3 = np.percentile(rho, [25, 50, 75])

rho_Mean = np.array(rho).mean()

 

fout.write('\n')

print('\nomega(mean) = {0:.5}'.format(rho_Mean))

fout.write('\nomega(mean) = {0:.5}\n'.format(rho_Mean))

print('omega(Med.) = {0:.5}'.format(rho_Med))

fout.write('omega(Med.) = {0:.5}\n'.format(rho_Med))

print('omega(Q1) = {0:.5}     omega(Q3) = {1:.5}'.format(rho_Q1, rho_Q3))

fout.write('omega(Q1) = {0:.5}     omega(Q3) = {1:.5}\n'.format(rho_Q1, rho_Q3))

 

sb.kdeplot(rho)

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

          format(rho_Mean, rho_Q1, rho_Med, rho_Q3), fontsize = 14)

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

plt.show()

 

Xtot = X.sum(axis = 1)

Fs = []

for i in range(N):

    Fs.append(d_fm[f'cF.{i+1}'])

Fs = np.array(Fs).T

Rs = []

R2s = []

for t in range(len(Fs)):

    r = np.corrcoef(Xtot, Fs[t])[0][1]

    Rs.append(r)

    R2s.append(r**2)

 

R_Q1, R_med, R_Q3 = np.percentile(Rs, [25, 50, 75])

R2_Q1, R2_med, R2_Q3 = np.percentile(R2s, [25, 50, 75])   

sb.kdeplot(Rs, label = 'R')

sb.kdeplot(R2s, label = '$R^2$')

plt.title('R(Med.) = {0:.3}   $R^2$(Med.) = {1:.3}'.format(R_med, R2_med), fontsize = 16)

plt.show()

 

FsMeds = np.median(Fs, axis=0)

plt.plot(FsMeds, Xtot, 'o')

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

plt.ylabel('X', fontsize = 16)

plt.show()

 

fout.write('\n')

print('\nR^2(Med.) = {0:.5}'.format(R2_med))

fout.write('\nR^2(Med.) = {0:.5}\n'.format(R2_med))

print('R^2(Q1) = {0:.5}     R^2(Q3) = {1:.5}'.format(R2_Q1, R2_Q3))

fout.write('R^2(Q1) = {0:.5}     R^2(Q3) = {1:.5}\n'.format(R2_Q1, R2_Q3))

print('R(Med.) = {0:.5}'.format(R_med))

fout.write('R(Med.) = {0:.5}\n'.format(R_med))

print('R(Q1) = {0:.5}     R(Q3) = {1:.5}'.format(R_Q1, R_Q3))

fout.write('R(Q1) = {0:.5}     R(Q3) = {1:.5}'.format(R_Q1, R_Q3))

 

S = np.cov(X, rowvar = False)

VarX = np.var(X.sum(axis = 1))

alpha_coef = (M/(M-1)) * (1 - (np.diag(S).sum() / VarX))

print('\nalpha = {0:.5}'.format(alpha_coef))

fout.write('\n')

fout.write('\nalpha = {0:.5}\n'.format(alpha_coef))

 

Cor = np.corrcoef(X, rowvar = False)

r_bar = (Cor.sum() - np.diag(Cor).sum()) / (M * (M - 1))

std_alpha = M * r_bar / (1.0 + (M - 1) * r_bar)

print('standardized alpha = {0:.5}'.format(std_alpha))

fout.write('standardized alpha = {0:.5}'.format(std_alpha))

 

fout.close()

print('Results.txt was saved.')

 

 

 

Listing 3  Python script in the case of K=2. The files are contained in the archived file files8_2Cat.zip , which can be freely downloaded and used.

 

import pandas as pd

import numpy as np

import matplotlib.pyplot as plt

import scipy.stats as ss

import stan

import seaborn as sb

import arviz as az

 

pd.options.display.max_rows = 10000

 

fout = open('Results.txt', 'w')

 

flnm = input('Data File Name (*.xlsx) = ')

xlsx = pd.ExcelFile(flnm)          

data = pd.read_excel(xlsx)

fout.write(f'Data file = {flnm}\n')

 

VarLabels = data.columns[1:].values;

print(VarLabels)

 

X = data.values[:, 1:]

print(X[:10])

N = len(X)

M = len(X[0])

print('N = ', N)

print('M = ', M)

fout.write('\n')

fout.write('VarLables =\n{VarLables}\n')

fout.write(f'N = {N},   M = {M}\n')

fout.write('\nX =\n')

for i, v in enumerate(X):

    fout.write(f'{i+1:>5d}  {v}\n')

 

Data = {'N': N, 'M': M, 'X': X}

 

def f_init():

    return dict(mu = [0.0]*M, psy = 1.0,

                F = np.random.normal(size = N))

 

with open('Reli_2Cat.stan', 'r') as fstan:

    sm = stan.build(fstan.read(), data = Data)

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

 

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

smry = az.summary(i_data, var_names = ['mu', 'psy', 'cF', 'mnF', 'sdF'])

print('Summary =\n',smry)

fout.write('\nSummary...\n')

fout.write(f'{smry}\n')

 

d_fm = fit.to_frame()

 

print('d_fm...keys...\n', d_fm.keys())

 

print(fit)

 

Mus = []

for j in range(M):

    Mus.append(d_fm[f'mu.{j+1}'])

Mus = np.array(Mus).T

 

for i in range(M):

    sb.kdeplot(Mus.T[i])

plt.title('$\mu$', fontsize = 16)

plt.show()

print()

fout.write('\n')

for j in range(M):

    print('mu[{0:}] = {1:.5}'.format(j+1, np.percentile(Mus.T[j], 50)))

    fout.write('mu[{0:}] = {1:.5}\n'.format(j+1, np.percentile(Mus.T[j], 50)))

 

Psys = np.array(d_fm['psy'])

sb.kdeplot(Psys)

psy_Q1, psy_Med, psy_Q3 = np.percentile(Psys, [25, 50, 75])

plt.title(f'Mean = {np.mean(Psys):.3f}\nQ1 = {psy_Q1:.3f},  Med.= {psy_Med:.3f}' +

          f',  Q3 = {psy_Q3:.3f}' , fontsize = 16)

plt.xlabel('$\psi$', fontsize = 20)

plt.tight_layout()

plt.show()

 

print()

fout.write('\n')

print('Psys =\n', Psys)

print('Psy = {0:.5f}\n'.format(np.percentile(Psys, 50)))

fout.write('Psy = {0:.5f}\n'.format(np.percentile(Psys, 50)))

 

rho = []

for i in range(len(Psys)):

    rho.append(M**2 / (M**2 + M*(Psys[i]**2)))

rho_Q1, rho_Med, rho_Q3 = np.percentile(rho, [25, 50, 75])

rho_Mean = np.array(rho).mean()

 

print('\nomega(mean) = {0:.5}'.format(rho_Mean))

fout.write('\nomega(mean) = {0:.5}\n'.format(rho_Mean))

print('omega(Med.) = {0:.5}'.format(rho_Med))

fout.write('omega(Med.) = {0:.5}\n'.format(rho_Med))

print('omega(Q1) = {0:.5}     omega(Q3) = {1:.5}'.format(rho_Q1, rho_Q3))

fout.write('omega(Q1) = {0:.5}     omega(Q3) = {1:.5}\n'.format(rho_Q1, rho_Q3))

 

sb.kdeplot(rho)

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

          format(rho_Mean, rho_Q1, rho_Med, rho_Q3), fontsize = 14)

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

plt.show()

 

Xtot = X.sum(axis = 1)

Fs = []

for i in range(N):

    Fs.append(d_fm[f'cF.{i+1}'])

Fs = np.array(Fs).T

Rs = []

R2s = []

for t in range(len(Fs)):

    r = np.corrcoef(Xtot, Fs[t])[0][1]

    Rs.append(r)

    R2s.append(r**2)

   

R_Q1, R_med, R_Q3 = np.percentile(Rs, [25, 50, 75])

R2_Q1, R2_med, R2_Q3 = np.percentile(R2s, [25, 50, 75])

sb.kdeplot(Rs, label = 'R')

sb.kdeplot(R2s, label = '$R^2$')

plt.title('R(Med.) = {0:.3}   $R^2$(Med.) = {1:.3}'.format(R_med, R2_med), fontsize = 16)

plt.legend(fontsize = 16)

plt.show()

 

FsMeds = np.median(Fs, axis=0)

plt.plot(FsMeds, Xtot, 'o')

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

plt.ylabel('X', fontsize = 16)

plt.show()

 

fout.write('\n')

print('\nR^2(Med.) = {0:.5}'.format(R2_med))

fout.write('\nR^2(Med.) = {0:.5}\n'.format(R2_med))

print('R^2(Q1) = {0:.5}     R^2(Q3) = {1:.5}'.format(R2_Q1, R2_Q3))

fout.write('R^2(Q1) = {0:.5}     R^2(Q3) = {1:.5}\n'.format(R2_Q1, R2_Q3))

print('R(Med.) = {0:.5}'.format(R_med))

fout.write('R(Med.) = {0:.5}\n'.format(R_med))

print('R(Q1) = {0:.5}     R(Q3) = {1:.5}'.format(R_Q1, R_Q3))

fout.write('R(Q1) = {0:.5}     R(Q3) = {1:.5}'.format(R_Q1, R_Q3))

 

S = np.cov(X, rowvar = False)

VarX = np.var(X.sum(axis = 1))

alpha_coef = (M/(M-1)) * (1 - (np.diag(S).sum() / VarX))

print('\nalpha = {0:.5}'.format(alpha_coef))

fout.write('\n')

fout.write('\nalpha = {0:.5}\n'.format(alpha_coef))

 

Cor = np.corrcoef(X, rowvar = False)

r_bar = (Cor.sum() - np.diag(Cor).sum()) / (M * (M - 1))

std_alpha = M * r_bar / (1.0 + (M - 1) * r_bar)

print('standardized alpha = {0:.5}'.format(std_alpha))

fout.write('standardized alpha = {0:.5}'.format(std_alpha))

 

fout.close()

print('Results.txt was saved.')

 

 

 

References

McDonald, R. P. (1999). Test theory: A unified treatment. London: Lawrence Erlbaum Associates, Publishers.

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

Okamoto, Y. (2016). Reliability Coefficients for Ordinal Categorical Items: Actual Relation of Observed and True Scores. Japan Womenfs University Journal: Faculty of Integrated Arts and Social Sciences, 2016, 27, 113-122.

Okamoto, Y. (2017). Bayesian Estimation of Ordinal Categorical Itemsf Reliability Coefficients: Relationship Between True Values and Observed Scores. Japan Womenfs University Journal: Faculty of Integrated Arts and Social Sciences, 2017, 28, 59-78.

 

 

Home