I’m helping a colleague on a project where they used a likert scale questionnaire that has already been well-researched and consensus seems to support a 4-factor latent structure. So I coded up a model that could estimate the loadings of each factor on each question, and it’s working seemingly fine, but my colleague is wondering if the loading weights (outBetas
in the model below) can be transformed to a 0-1 scale reflecting what % of variability in the response is explained by the latent factor. This would be relatively straightforward with a continuous outcome that is modeled with a noise parameter, but I’m unsure if it even has meaning in the ordinal scenario. Any thoughts?
data{
// nCat: number of ordered categories in outcomes
int nCat ;
// nSubj: number of subjects
int nSubj ;
// nOut: number of outcomes
int nOut ;
// outFac: list of which factor is associated with each outcome
int outFac[nOut] ;
// out: integer matrix of outcomes
int out[nSubj,nOut] ;
}
transformed data{
// nFac: number of latent factors
int nFac = max(outFac) ;
// zeroes: a vector of zeroes for latent factor means
vector[nFac] zeroes = rep_vector(0,nFac) ;
}
parameters{
// cutpoints: cutpoints for modelling outcomes as ordered logistic
ordered[nCat-1] cutpoints[nOut] ;
// outMeans: *latent* means for each outcome
vector[nOut] outMeans ;
// outBetas: how each factor loads on to each outcome.
// Must be postive for identifiability.
vector<lower=0>[nOut] outBetas ;
// subjFacs: matrix of values for each factor associated with each subject
vector[nFac] subjFacs[nSubj] ;
// L_Omega: related to correlation amongst the latent factors
cholesky_factor_corr[nFac] L_Omega ;
}
model{
// standard normal prior on all the cut points
for(i in 1:nOut){
cutpoints[i] ~ normal(0,1) ;
}
// standard normal prior on outMeans
outMeans ~ normal(0,1) ;
// implies flat prior on facCor
L_Omega ~ lkj_corr_cholesky(1) ;
// implies subjFacs distributed as multi_normal with zero means, unit SDs, and facCor correlation ;
subjFacs ~ multi_normal_cholesky(zeroes, L_Omega) ;
// outcomes as ordered logistic
for(i in 1:nOut){
for(j in 1:nSubj){
out[j,i] ~ ordered_logistic( outMeans[i] + subjFacs[j,outFac[i]] * outBetas[i], cutpoints[i] ) ;
}
}
}
generated quantities{
// log_lik: storage variable for later use of loo in R
vector[nOut*nSubj] log_lik ;
// facCor: correlations between latent factors
corr_matrix[nFac] facCor ;
// out_rep: matrix of outcomes
matrix[nSubj,nOut] out_rep ;
// compute facCor from L_Omega
facCor = multiply_lower_tri_self_transpose(L_Omega);
// compute log_lik for loo-based model comparison
for(i in 1:nOut){
for(j in 1:nSubj){
log_lik[(i-1)*nSubj+j] = ordered_logistic_lpmf( out[j,i] | outMeans[i] + subjFacs[j,outFac[i]] * outBetas[i], cutpoints[i] ) ;
}
}
// compute simulated outcomes
for(i in 1:nOut){
for(j in 1:nSubj){
out_rep[j,i] = ordered_logistic_rng( outMeans[i] + subjFacs[j,outFac[i]] * outBetas[i], cutpoints[i] ) ;
}
}
}
****