Up

Ordered Categorical Rating Scale

Psychophysical Point of View

Yasuharu Okamoto, 2022.01

 

In the method of ordered categorical scaling, an observer chooses the appropriate category from the ordered categories corresponding to the strength of the sensation caused by the presented stimulus. The method has been discussed by many researchers (e.g., Gescheider, 1997; Torgerson, 1958; Thurstone, 1927).

Suppose the stimulus  invokes sensation , which has a normal distribution of mean  and variance . That is,

Suppose that there are  boundaries s of categories, and observers rating  on the stimulus  is category  when the following condition holds:

 when

where

Then we have

where  is the cumulative distribution function of the standard normal distribution.

Set the origin and unit by the following constraint:

, 

This condition corresponds to the dynamic range, which is supposed to be approximately the same for all modalities (Teghtsoonian, 1971).

As to standard deviation  of sensation , the following three cases are considered.

 

Condition F:

No constraints on , that is, free condition.

 

Condition L:

Linear relation between the standard deviation  and the mean  of sensation  of stimulus  is assumed. That is, the following relation

is set.

This is generalization of what Stevens (1975, p. 235) calls Ekmans law, from which Stevens’ power law can be derived..

 

Condition C:

All s are the same, that is constant,

This condition corresponds to Fechners thinking, and leads to Fechner’s logarithmic law.

 

For the three conditions, three Python scripts are developed, which are included in the file CategoryScalesFiles.zip.

The archived file can be downloaded and used freely on users responsibility, although all rights are reserved.

 

The Python script CategoryScaleFree.py in the folder SigmaFree is for the condition F.

The Python script CategoryScaleL.py in the folder SigmaLinear is for the condition L.

The Python script CategoryScaleC.py in the folder SigmaConst is for the condition C.

How to use the scripts is explained bellow.

All the program accept an input data file of the same format, which is shown in Figure 1.

Figure 1. File name DataL.csv

 

In the first row, variable names are set. The values of the variables are set in rows under the first row.

The first column contains values of the stimuli. Values of the weakest to the strongest stimuli are set from the second to the last (bottom) row.

The second to the last (K+1st) columns correspond to the first to the Kth categories, values in which represent the number of rating responses for combinations of stimuli and rating categories, respectively.

The data file should be saved in CSV file format. That is, the file should be saved with the file name, which has file extension .csv.

 

How to use the programs are explained in the order of Condition F, Condition L, and Condition C.

 

Condition F

Listing F1 and Listing F2 show the Stan script and Python script for Condition F.

Run the Python script in Listing F2, which uses the Stan script in Listing F1, an input file name is asked as follows:

 

(py39) PS D:\SigmaFree> python .\CategoryScaleFree.py

INFO:numexpr.utils:NumExpr defaulting to 8 threads.

Input file (*.csv) = DataL.csv

 

When the data file name (in the above example, DataL.csv) is set and the key ‘Enter’ is pressed down, calculation starts.

After MCMC sampling, graphs of posterior distributions of parametrs are displayed (Figure F1).

Figure F1

 

Close the window of Figure F1, the next window (Figure F2) appears.

Figure F2

 

Close the window of Figure F2, calculation of WAIC starts, and the result is displayed as follows.

 

WAIC2 = 175.73879512540347

(py39) PS D:\SigmaFree>

 

Graphs in Figure F1 and Figure F2 were automatically saved in files, file extension of which is png. Some results of computation were saved in the file Results.txt.

Content of Results.txt is as follows:

 

Input Data File = DataL.csv

 

Data:

         1   63   37    0    0    0    0    0

     1.668   33   65    2    0    0    0    0

     2.783   18   67   15    0    0    0    0

     4.642    4   53   40    3    0    0    0

     7.743    3   28   56   13    0    0    0

    12.915    1   13   45   30   11    0    0

    21.544    0    3   21   34   33    8    1

    35.938    0    0    8   20   36   25   11

    59.948    0    0    1   15   21   32   31

       100    0    0    0    2    7   26   65

 

       Stimulus       Mode Psi     Mode Sigma

           1.00          -0.02           0.08

           1.67           0.04           0.09

           2.78           0.10           0.11

           4.64           0.20           0.12

           7.74           0.29           0.14

          12.91           0.41           0.17

          21.54           0.59           0.18

          35.94           0.75           0.21

          59.95           0.89           0.23

         100.00           1.09           0.21

 

 

WAIC = 175.739

 

 

Condition L

Listing L1 and Listing L2 show the Stan script and Python script for Condition L.

Run the Python script in Listing L2, which uses the Stan script in Listing L1, an input file name is asked as follows:

 

(py39) PS D:\SigmaLinear> python .\CategoryScaleL.py

INFO:numexpr.utils:NumExpr defaulting to 8 threads.

Input file (*.csv) = DataL.csv

 

When the data file name (in the above example, DataL.csv) is set and the key ‘Enter’ is pressed down, calculation starts.

After MCMC sampling, graphs of posterior distributions of parametrs are displayed (Figure L1).

Figure L1

 

Close the window of Figure L1, the next window (Figure L2) appears.

Figure L2

 

Close the window of Figure L2, calculation of WAIC starts, and the result is displayed as follows.

 

WAIC2 = 165.93447882890743

(py39) PS D:\SigmaLinear>

 

Graphs in Figure L1 and Figure L2 were automatically saved in files, file extension of which is png. Some results of computation were saved in the file Results.txt.

Content of Results.txt is as follows:

 

Input Data File = DataL.csv

 

Data:

         1   63   37    0    0    0    0    0

     1.668   33   65    2    0    0    0    0

     2.783   18   67   15    0    0    0    0

     4.642    4   53   40    3    0    0    0

     7.743    3   28   56   13    0    0    0

    12.915    1   13   45   30   11    0    0

    21.544    0    3   21   34   33    8    1

    35.938    0    0    8   20   36   25   11

    59.948    0    0    1   15   21   32   31

       100    0    0    0    2    7   26   65

 

       Stimulus       Mode Psi     Mode Sigma

           1.00          -0.03           0.09

           1.67           0.04           0.10

           2.78           0.11           0.11

           4.64           0.20           0.12

           7.74           0.28           0.14

          12.91           0.41           0.16

          21.54           0.58           0.18

          35.94           0.75           0.21

          59.95           0.88           0.23

         100.00           1.11           0.26

 

 

WAIC = 165.934

 

 

Condition C

Listing C1 and Listing C2 show the Stan script and Python script for Condition C.

Run the Python script in Listing C2, which uses the Stan script in Listing C1, an input file name is asked as follows:

 

(py39) PS D:\SigmaConst> python .\CategoryScaleC.py

INFO:numexpr.utils:NumExpr defaulting to 8 threads.

Input file (*.csv) = DataC.csv

 

When the data file name (in the above example, DataC.csv) is set and the key ‘Enter’ is pressed down, calculation starts.

After MCMC sampling, graphs of posterior distributions of parametrs are displayed (Figure C1).

Figure C1

 

Close the window of Figure C1, the next window (Figure C2) appears.

Figure C2

 

Close the window of Figure C2, calculation of WAIC starts, and the result is displayed as follows.

 

WAIC2 = 162.4332857781438

(py39) PS D:\SigmaConst>

 

Graphs in Figure C1 and Figure C2 were automatically saved in files, file extension of which is png. Some results of computation were saved in the file Results.txt.

Content of Results.txt is as follows:

 

Input Data File = DataC.csv

 

Data:

         1   51   36   13    0    0    0    0

     1.668   43   46   10    1    0    0    0

     2.783   23   48   27    2    0    0    0

     4.642   10   44   36   10    0    0    0

     7.743    1   21   54   22    2    0    0

    12.915    0   11   37   42    9    1    0

    21.544    0    0   23   49   25    3    0

    35.938    0    0    0   19   49   28    4

    59.948    0    0    0    1   32   37   30

       100    0    0    0    0    3   16   81

 

       Stimulus       Mode Psi

           1.00           0.00

           1.67           0.03

           2.78           0.11

           4.64           0.19

           7.74           0.31

          12.91           0.42

          21.54           0.53

          35.94           0.76

          59.95           0.92

         100.00           1.13

 

Sigma: mode =            0.15

 

 

WAIC = 162.433

 

 

 

References

Gescheider, G. A. (1997). Psychophysics: The fundamentals, Third edition. Mahwah: Lawrence Erlbaum Associations, Publishers.

Stevens, S. S. (1975). Psychophysics: Introduction to its perceptual, neural, and social prospects. New York: Wiley.

Tesghtsoonian, R. (1971). On the exponents in Stevens law and the constant in Ekmans law. Psychological Review, 78, 71-80.

Thurstone, L. L. (1927). A law of comparative judgment. Psychological Review, 34, 273-286.

Torgerson, W. S. (1958). Theory and methods of scaling. New York: John Wiley & Sons, Inc.

 

 

Listing F1. Stan script for the case of no constraints on standard deviations s.

data {

    int NSt;

    int K;

    real Sts[NSt];

    int Rs[NSt, K];

}

transformed data {

    vector[NSt+1] a_Psis;

    vector[K-2] a_C;

   

    for (i in 1:(NSt+1)) {

        a_Psis[i] = 1.0;

    }

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

        a_C[i] = 1.0;

    }

}

parameters {

    real<lower = 0.0> sgms[NSt];

    simplex[NSt+1] prePsis;

    simplex[K-2] preC;

}

transformed parameters {

    real Psis[NSt];

    real C[K-1];

    simplex[K] theta[NSt];

    real v;

 

    v = 0.0;

    for (s in 1:NSt) {

        v = v + prePsis[s];

        Psis[s] = logit(v);

    }

   

    C[1] = 0.0;

    C[K-1] = 1.0;

    v = 0.0;

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

        v = v + preC[k-1];

        C[k] = C[1] + v;

    }

    for (s in 1:NSt) {

        theta[s][1] = normal_cdf(C[1], Psis[s], sgms[s]);

        theta[s][K] = 1.0 - normal_cdf(C[K-1], Psis[s], sgms[s]);

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

            theta[s][k] = normal_cdf(C[k], Psis[s], sgms[s]) -

                            normal_cdf(C[k-1], Psis[s], sgms[s]);

        }

    }

}

model {

    for (s in 1:NSt) {

        sgms[s] ~ exponential(0.001);

    }

    prePsis ~ dirichlet(a_Psis);

    preC ~ dirichlet(a_C);

    //a ~ exponential(0.001);

    //b ~ exponential(0.001);

    for (s in 1:NSt) {

        Rs[s] ~ multinomial(theta[s]);

    }

}

 

 

Listing F2. Python script for the case of no constraints on standard deviations s.

import csv

import numpy as np

import pystan

import matplotlib.pyplot as plt

import seaborn as sb

import scipy.stats as ss

import estmode as em

import CalcWAIC2 as cw

 

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

 

fin_nm = input('Input file (*.csv) = ')

with open(fin_nm, 'r') as f:

    Data_in = [v for v in csv.reader(f)]

 

fout.write('Input Data File = {}\n'.format(fin_nm))

 

NSt = len(Data_in) - 1

K = len(Data_in[0]) - 1

Sts = np.empty(NSt)

Rs = np.empty((NSt, K), dtype = 'int')

for j in range(NSt):

    Sts[j] = float(Data_in[j+1][0])

    for k in range(K):

        Rs[j][k] = int(Data_in[j+1][k+1])

 

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

for j in range(NSt):

    print(f'{Sts[j]:>10g}', end = '')

    fout.write(f'{Sts[j]:>10g}')

    for k in range(K):

        print(f'{Rs[j][k]:>5d}', end = '')

        fout.write(f'{Rs[j][k]:>5d}')

    print()

    fout.write('\n')

 

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

 

Data = {'NSt':NSt, 'K':K, 'Sts':Sts, 'Rs':Rs}

 

fit = sm.sampling(data = Data, n_jobs = 1)

 

print(fit)

 

Psis = fit['Psis']

sgms = fit['sgms']

Cs = fit['C']

 

for i in range(NSt):

    sb.kdeplot(Psis.T[i], label = '$\psi_{}$'.format(i+1))

plt.title('Posterior Ditributions of $\psi_s$', fontsize = 20)

plt.yticks([])

plt.legend()

plt.tight_layout()

plt.savefig('FigPsiPost.png')

plt.show()

 

for i in range(NSt):

    sb.kdeplot(sgms.T[i], label = r'$\sigma_{}$'.format(i))

plt.title('Posterior Distributions of $\sigma_s$', fontsize = 20)

plt.legend()

plt.savefig('FigSgmPost.png')

plt.show()

 

plt.plot([0, 0], [0, 5], label = r'$C_0$')

for i in range(1, K-2):

    sb.kdeplot(Cs.T[i], label = r'$C_{}$'.format(i+1))

plt.plot([1,1], [0, 5], label = r'$C_{}$'.format(K-1))

plt.title('Posterior Distributions of Cs', fontsize = 20)

plt.yticks([])

plt.legend()

plt.savefig('FigCsPost.png')

plt.show()

 

v_psis = np.empty(NSt)

v_sgms = np.empty(NSt)

for i in range(NSt):

    v_psis[i] = em.EstMode(Psis.T[i])

    v_sgms[i] = em.EstMode(sgms.T[i])

 

fout.write('\n{0:>15s}{1:>15s}{2:>15s}\n'.format('Stimulus', 'Mode Psi', 'Mode Sigma'))

for vst, vpsi, vsgm in zip(Sts, v_psis, v_sgms):

    fout.write('{0:>15.2f}{1:>15.2f}{2:>15.2f}\n'.format(vst, vpsi, vsgm))

   

v_Cs = np.empty(K-1)

v_Cs[0] = 0.0

v_Cs[K-2] = 1.0

for k in range(1, K-2):

    v_Cs[k] = em.EstMode(Cs.T[k])

for k in range(K-1):

    plt.plot([Sts[0], Sts[-1]], [v_Cs[k], v_Cs[k]], color = 'y')

plt.plot([], 'y-', label = 'C')

plt.plot(Sts, v_psis, color = 'b', lw = 3, label = '$\psi$')

plt.plot(Sts, v_psis - v_sgms, color = 'g', linestyle = '--', lw = 1,label = '$\psi - \sigma$')

plt.plot(Sts, v_psis + v_sgms, color = 'g', linestyle = '--', lw = 1, label = '$\psi+ \sigma$')

   

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

plt.ylabel('$\psi$', fontsize = 16)

plt.legend(loc = 'upper left')

plt.title('Relation of Stimulus and $\psi$', fontsize = 18)

plt.tight_layout()

plt.savefig('PsiSgmC.png')

plt.show()

 

Probs = np.empty((NSt, K))

for s in range(NSt):

    Probs[s][0] = ss.norm(loc = v_psis[s], scale = v_sgms[s]).cdf(v_Cs[0])

    Probs[s][K-1] = 1.0 - ss.norm(loc = v_psis[s], scale = v_sgms[s]).cdf(v_Cs[K-2])

    for k in range(1, K-1):

        Probs[s][k] = ss.norm(loc = v_psis[s], scale = v_sgms[s]).cdf(v_Cs[k]) -\

                        ss.norm(loc = v_psis[s], scale = v_sgms[s]).cdf(v_Cs[k-1])

           

print('Predicted Prob(Rating|Stimulus):')

for s in range(NSt):

    for k in range(K):

        print(f'  {Probs[s][k]:7.3f}', end = '')

    print()

   

Pred_Rs = np.empty((NSt, K))

N_Rs = np.sum(Rs, axis = 1)

for i in range(NSt):

    Pred_Rs[i] = N_Rs[i] * Probs[i]

   

print('Predicted Frequencies of Rating for Stimulus:')

for s in range(NSt):

    for k in range(K):

        print(f'  {Pred_Rs[s][k]:7.1f}', end = '')

    print()

 

cum_data_Rs = np.cumsum(Rs, axis = 1)

cum_pred_Rs = np.cumsum(Pred_Rs, axis = 1)

 

print(cum_data_Rs)

 

for s in range(NSt):

    if s == 0:

        plt.plot(range(K), cum_data_Rs[s]/cum_data_Rs[s][K-1], 'g:', lw = 3, label = 'Data')

    else:

        plt.plot(range(K), cum_data_Rs[s]/cum_data_Rs[s][K-1], 'g:', lw = 3 )

    if s == 0:

        plt.plot(range(K), cum_pred_Rs[s]/cum_pred_Rs[s][K-1], 'b-', label = 'Model')

    else:

        plt.plot(range(K), cum_pred_Rs[s]/cum_pred_Rs[s][K-1], 'b-')

 

plt.xticks(range(K), range(1, K+1))

plt.xlabel('Rating', fontsize = 14)

plt.ylabel('Cum. Prop.', fontsize = 14)

plt.legend()

plt.title('Data and Model Prediction', fontsize = 18)

plt.tight_layout()

plt.savefig('FigDataModel.png')

plt.show()

 

waic2 = cw.calcwaic2(Rs, fit['theta'])

print('WAIC2 =', waic2)

fout.write('\n\nWAIC = {0:.3f}\n'.format(waic2))

 

fout.close()

 

 

Listing L1. Stan script for the case of linear constraint on standard deviations s.

data {

    int NSt;

    int K;

    real Sts[NSt];

    int Rs[NSt, K];

}

transformed data {

    vector[NSt+1] a_Psis;

    vector[K-2] a_C;

   

    for (i in 1:(NSt+1)) {

        a_Psis[i] = 1.0;

    }

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

        a_C[i] = 1.0;

    }

}

parameters {

    simplex[NSt+1] prePsis;

    simplex[K-2] preC;

    real<lower = 0.0> a;

    real<lower = 0.0> b;

}

transformed parameters {

    real Psis[NSt];

    real sgms[NSt];

    real C[K-1];

    simplex[K] theta[NSt];

    real v;

 

    v = 0.0;

    for (s in 1:NSt) {

        v = v + prePsis[s];

        Psis[s] = logit(v);

    }

   

    for (s in 1:NSt) {

        sgms[s] = a + b * (Psis[s] - Psis[1]);

    }

    C[1] = 0.0;

    C[K-1] = 1.0;

    v = 0.0;

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

        v = v + preC[k-1];

        C[k] = C[1] + v;

    }

    for (s in 1:NSt) {

        theta[s][1] = normal_cdf(C[1], Psis[s], sgms[s]);

        theta[s][K] = 1.0 - normal_cdf(C[K-1], Psis[s], sgms[s]);

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

            theta[s][k] = normal_cdf(C[k], Psis[s], sgms[s]) -

                            normal_cdf(C[k-1], Psis[s], sgms[s]);

        }

    }

}

model {

    prePsis ~ dirichlet(a_Psis);

    preC ~ dirichlet(a_C);

    a ~ exponential(0.001);

    b ~ exponential(0.001);

    for (s in 1:NSt) {

        Rs[s] ~ multinomial(theta[s]);

    }

}

 

 

Listing L2. Python script for the case of linear constraint on standard deviations s.

import csv

import numpy as np

import pystan

import matplotlib.pyplot as plt

import seaborn as sb

import scipy.stats as ss

import estmode as em

import CalcWAIC2 as cw

 

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

 

fin_nm = input('Input file (*.csv) = ')

with open(fin_nm, 'r') as f:

    Data_in = [v for v in csv.reader(f)]

 

fout.write('Input Data File = {}\n'.format(fin_nm))

 

NSt = len(Data_in) - 1

K = len(Data_in[0]) - 1

Sts = np.empty(NSt)

Rs = np.empty((NSt, K), dtype = 'int')

for j in range(NSt):

    Sts[j] = float(Data_in[j+1][0])

    for k in range(K):

        Rs[j][k] = int(Data_in[j+1][k+1])

 

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

for j in range(NSt):

    print(f'{Sts[j]:>10g}', end = '')

    fout.write(f'{Sts[j]:>10g}')

    for k in range(K):

        print(f'{Rs[j][k]:>5d}', end = '')

        fout.write(f'{Rs[j][k]:>5d}')

    print()

    fout.write('\n')

 

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

 

Data = {'NSt':NSt, 'K':K, 'Sts':Sts, 'Rs':Rs}

 

fit = sm.sampling(data = Data, n_jobs = 1)

 

print(fit)

 

Psis = fit['Psis']

sgms = fit['sgms']

Cs = fit['C']

a = fit['a']

b = fit['b']

 

for i in range(NSt):

    sb.kdeplot(Psis.T[i], label = '$\psi_{}$'.format(i+1))

plt.title('Posterior Ditributions of $\psi_s$', fontsize = 20)

plt.yticks([])

plt.legend()

plt.tight_layout()

plt.savefig('FigPsiPost.png')

plt.show()

 

for i in range(NSt):

    sb.kdeplot(sgms.T[i], label = r'$\sigma_{}$'.format(i))

plt.title('Posterior Distributions of $\sigma_s$', fontsize = 20)

plt.legend()

plt.savefig('FigSgmPost.png')

plt.show()

 

plt.plot([0, 0], [0, 5], label = r'$C_0$')

for i in range(1, K-2):

    sb.kdeplot(Cs.T[i], label = r'$C_{}$'.format(i+1))

plt.plot([1,1], [0, 5], label = r'$C_{}$'.format(K-1))

plt.title('Posterior Distributions of Cs', fontsize = 20)

plt.yticks([])

plt.legend()

plt.savefig('FigCsPost.png')

plt.show()

 

mode_a = em.EstMode(a)

mode_b = em.EstMode(b)

 

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

plt.subplot(1,2,1)

sb.kdeplot(a)

plt.yticks([])

plt.xlabel('a', fontsize = 14)

plt.title('Posterior Distribution of a' +

          '\nmode = {0:.2f}'.format(mode_a),

          fontsize = 14)

 

plt.subplot(1,2,2)

sb.kdeplot(b)

plt.title('Posterior Distribtuion of b' +

          '\nmode = {0:.2f}'.format(mode_b), fontsize = 14)

plt.yticks([])

plt.xlabel('b', fontsize = 14)

plt.tight_layout()

plt.savefig('Fig_a_b_Post.png')

plt.show()

 

v_psis = np.empty(NSt)

v_sgms = np.empty(NSt)

for i in range(NSt):

    v_psis[i] = em.EstMode(Psis.T[i])

    v_sgms[i] = em.EstMode(sgms.T[i])

 

fout.write('\n{0:>15s}{1:>15s}{2:>15s}\n'.format('Stimulus', 'Mode Psi', 'Mode Sigma'))

for vst, vpsi, vsgm in zip(Sts, v_psis, v_sgms):

    fout.write('{0:>15.2f}{1:>15.2f}{2:>15.2f}\n'.format(vst, vpsi, vsgm))

   

v_Cs = np.empty(K-1)

v_Cs[0] = 0.0

v_Cs[K-2] = 1.0

for k in range(1, K-2):

    v_Cs[k] = em.EstMode(Cs.T[k])

for k in range(K-1):

    plt.plot([Sts[0], Sts[-1]], [v_Cs[k], v_Cs[k]], color = 'y')

plt.plot([], 'y-', label = 'C')

plt.plot(Sts, v_psis, color = 'b', lw = 3, label = '$\psi$')

plt.plot(Sts, v_psis - v_sgms, color = 'g', linestyle = '--', lw = 1,label = '$\psi - \sigma$')

plt.plot(Sts, v_psis + v_sgms, color = 'g', linestyle = '--', lw = 1, label = '$\psi+ \sigma$')

    

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

plt.ylabel('$\psi$', fontsize = 16)

plt.legend(loc = 'upper left')

plt.title('Relation of Stimulus and $\psi$', fontsize = 18)

plt.tight_layout()

plt.savefig('PsiSgmC.png')

plt.show()

 

Probs = np.empty((NSt, K))

for s in range(NSt):

    Probs[s][0] = ss.norm(loc = v_psis[s], scale = v_sgms[s]).cdf(v_Cs[0])

    Probs[s][K-1] = 1.0 - ss.norm(loc = v_psis[s], scale = v_sgms[s]).cdf(v_Cs[K-2])

    for k in range(1, K-1):

        Probs[s][k] = ss.norm(loc = v_psis[s], scale = v_sgms[s]).cdf(v_Cs[k]) -\

                        ss.norm(loc = v_psis[s], scale = v_sgms[s]).cdf(v_Cs[k-1])

           

print('Predicted Prob(Rating|Stimulus):')

for s in range(NSt):

    for k in range(K):

        print(f'  {Probs[s][k]:7.3f}', end = '')

    print()

   

Pred_Rs = np.empty((NSt, K))

N_Rs = np.sum(Rs, axis = 1)

for i in range(NSt):

    Pred_Rs[i] = N_Rs[i] * Probs[i]

   

print('Predicted Frequencies of Rating for Stimulus:')

for s in range(NSt):

    for k in range(K):

        print(f'  {Pred_Rs[s][k]:7.1f}', end = '')

    print()

 

cum_data_Rs = np.cumsum(Rs, axis = 1)

cum_pred_Rs = np.cumsum(Pred_Rs, axis = 1)

 

print(cum_data_Rs)

 

for s in range(NSt):

    if s == 0:

        plt.plot(range(K), cum_data_Rs[s]/cum_data_Rs[s][K-1], 'g:', lw = 3, label = 'Data')

    else:

        plt.plot(range(K), cum_data_Rs[s]/cum_data_Rs[s][K-1], 'g:', lw = 3 )

    if s == 0:

        plt.plot(range(K), cum_pred_Rs[s]/cum_pred_Rs[s][K-1], 'b-', label = 'Model')

    else:

        plt.plot(range(K), cum_pred_Rs[s]/cum_pred_Rs[s][K-1], 'b-')

 

plt.xticks(range(K), range(1, K+1))

plt.xlabel('Rating', fontsize = 14)

plt.ylabel('Cum. Prop.', fontsize = 14)

plt.legend()

plt.title('Data and Model Prediction', fontsize = 18)

plt.tight_layout()

plt.savefig('FigDataModel.png')

plt.show()

 

waic2 = cw.calcwaic2(Rs, fit['theta'])

print('WAIC2 =', waic2)

fout.write('\n\nWAIC = {0:.3f}\n'.format(waic2))

 

fout.close()

 

 

Listing C1. Stan script for the case of constant standard deviations s.

data {

    int NSt;

    int K;

    real Sts[NSt];

    int Rs[NSt, K];

}

transformed data {

    vector[NSt+1] a_Psis;

    vector[K-2] a_C;

   

    for (i in 1:(NSt+1)) {

        a_Psis[i] = 1.0;

    }

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

        a_C[i] = 1.0;

    }

}

parameters {

    simplex[NSt+1] prePsis;

    simplex[K-2] preC;

    real<lower = 0.0> sgm;

}

transformed parameters {

    real Psis[NSt];

    real C[K-1];

    simplex[K] theta[NSt];

    real v;

 

    v = 0.0;

    for (s in 1:NSt) {

        v = v + prePsis[s];

        Psis[s] = logit(v);

    }

   

    C[1] = 0.0;

    C[K-1] = 1.0;

    v = 0.0;

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

        v = v + preC[k-1];

        C[k] = C[1] + v;

    }

    for (s in 1:NSt) {

        theta[s][1] = normal_cdf(C[1], Psis[s], sgm);

        theta[s][K] = 1.0 - normal_cdf(C[K-1], Psis[s], sgm);

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

            theta[s][k] = normal_cdf(C[k], Psis[s], sgm) -

                            normal_cdf(C[k-1], Psis[s], sgm);

        }

    }

}

model {

    prePsis ~ dirichlet(a_Psis);

    preC ~ dirichlet(a_C);

    sgm ~ exponential(0.001);

    for (s in 1:NSt) {

        Rs[s] ~ multinomial(theta[s]);

    }

}

 

 

Listing C2. Python script for the case of constant standard deviations s.

import csv

import numpy as np

import pystan

import matplotlib.pyplot as plt

import seaborn as sb

import scipy.stats as ss

import estmode as em

import CalcWAIC2 as cw

 

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

 

fin_nm = input('Input file (*.csv) = ')

with open(fin_nm, 'r') as f:

    Data_in = [v for v in csv.reader(f)]

 

fout.write('Input Data File = {}\n'.format(fin_nm))

 

NSt = len(Data_in) - 1

K = len(Data_in[0]) - 1

Sts = np.empty(NSt)

Rs = np.empty((NSt, K), dtype = 'int')

for j in range(NSt):

    Sts[j] = float(Data_in[j+1][0])

    for k in range(K):

        Rs[j][k] = int(Data_in[j+1][k+1])

 

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

for j in range(NSt):

    print(f'{Sts[j]:>10g}', end = '')

    fout.write(f'{Sts[j]:>10g}')

    for k in range(K):

        print(f'{Rs[j][k]:>5d}', end = '')

        fout.write(f'{Rs[j][k]:>5d}')

    print()

    fout.write('\n')

 

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

 

Data = {'NSt':NSt, 'K':K, 'Sts':Sts, 'Rs':Rs}

 

fit = sm.sampling(data = Data, n_jobs = 1)

 

print(fit)

 

Psis = fit['Psis']

sgm = fit['sgm']

Cs = fit['C']

 

for i in range(NSt):

    sb.kdeplot(Psis.T[i], label = '$\psi_{}$'.format(i+1))

plt.title('Posterior Ditributions of $\psi_s$', fontsize = 20)

plt.yticks([])

plt.legend()

plt.tight_layout()

plt.savefig('FigPsiPost.png')

plt.show()

 

for i in range(NSt):

    sb.kdeplot(sgm, label = r'$\sigma_{}$'.format(i))

plt.title('Posterior Distribution of $\sigma$', fontsize = 20)

plt.legend()

plt.savefig('FigSgmPost.png')

plt.show()

 

plt.plot([0, 0], [0, 5], label = r'$C_0$')

for i in range(1, K-2):

    sb.kdeplot(Cs.T[i], label = r'$C_{}$'.format(i+1))

plt.plot([1,1], [0, 5], label = r'$C_{}$'.format(K-1))

plt.title('Posterior Distributions of Cs', fontsize = 20)

plt.yticks([])

plt.legend()

plt.savefig('FigCsPost.png')

plt.show()

 

v_psis = np.empty(NSt)

for i in range(NSt):

    v_psis[i] = em.EstMode(Psis.T[i])

v_sgm = em.EstMode(sgm)

 

fout.write('\n{0:>15s}{1:>15s}\n'.format('Stimulus', 'Mode Psi'))

for vst, vpsi in zip(Sts, v_psis):

    fout.write('{0:>15.2f}{1:>15.2f}\n'.format(vst, vpsi))

fout.write('\nSigma: mode = {0:>15.2f}\n'.format(v_sgm))

   

v_Cs = np.empty(K-1)

v_Cs[0] = 0.0

v_Cs[K-2] = 1.0

for k in range(1, K-2):

    v_Cs[k] = em.EstMode(Cs.T[k])

for k in range(K-1):

    plt.plot([Sts[0], Sts[-1]], [v_Cs[k], v_Cs[k]], color = 'y')

plt.plot([], 'y-', label = 'C')

plt.plot(Sts, v_psis, color = 'b', lw = 3, label = '$\psi$')

plt.plot(Sts, v_psis - v_sgm, color = 'g', linestyle = '--', lw = 1,label = '$\psi - \sigma$')

plt.plot(Sts, v_psis + v_sgm, color = 'g', linestyle = '--', lw = 1, label = '$\psi+ \sigma$')

   

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

plt.ylabel('$\psi$', fontsize = 16)

plt.legend(loc = 'upper left')

plt.title('Relation of Stimulus and $\psi$', fontsize = 18)

plt.tight_layout()

plt.savefig('PsiSgmC.png')

plt.show()

 

Probs = np.empty((NSt, K))

for s in range(NSt):

    Probs[s][0] = ss.norm(loc = v_psis[s], scale = v_sgm).cdf(v_Cs[0])

    Probs[s][K-1] = 1.0 - ss.norm(loc = v_psis[s], scale = v_sgm).cdf(v_Cs[K-2])

    for k in range(1, K-1):

        Probs[s][k] = ss.norm(loc = v_psis[s], scale = v_sgm).cdf(v_Cs[k]) -\

                        ss.norm(loc = v_psis[s], scale = v_sgm).cdf(v_Cs[k-1])

           

print('Predicted Prob(Rating|Stimulus):')

for s in range(NSt):

    for k in range(K):

        print(f'  {Probs[s][k]:7.3f}', end = '')

    print()

   

Pred_Rs = np.empty((NSt, K))

N_Rs = np.sum(Rs, axis = 1)

for i in range(NSt):

    Pred_Rs[i] = N_Rs[i] * Probs[i]

   

print('Predicted Frequencies of Rating for Stimulus:')

for s in range(NSt):

    for k in range(K):

        print(f'  {Pred_Rs[s][k]:7.1f}', end = '')

    print()

 

cum_data_Rs = np.cumsum(Rs, axis = 1)

cum_pred_Rs = np.cumsum(Pred_Rs, axis = 1)

 

print(cum_data_Rs)

 

for s in range(NSt):

    if s == 0:

        plt.plot(range(K), cum_data_Rs[s]/cum_data_Rs[s][K-1], 'g:', lw = 3, label = 'Data')

    else:

        plt.plot(range(K), cum_data_Rs[s]/cum_data_Rs[s][K-1], 'g:', lw = 3 )

    if s == 0:

        plt.plot(range(K), cum_pred_Rs[s]/cum_pred_Rs[s][K-1], 'b-', label = 'Model')

    else:

        plt.plot(range(K), cum_pred_Rs[s]/cum_pred_Rs[s][K-1], 'b-')

 

plt.xticks(range(K), range(1, K+1))

plt.xlabel('Rating', fontsize = 14)

plt.ylabel('Cum. Prop.', fontsize = 14)

plt.legend()

plt.title('Data and Model Prediction', fontsize = 18)

plt.tight_layout()

plt.savefig('FigDataModel.png')

plt.show()

 

waic2 = cw.calcwaic2(Rs, fit['theta'])

print('WAIC2 =', waic2)

fout.write('\n\nWAIC = {0:.3f}\n'.format(waic2))

 

fout.close()

 

 

Up