I'm trying to create a multilevel ordinal logistic regression model in Stan and the following code would seem to work, in the sense that Stan seems to convergence to sensible answers:
stanmodel <- '
data {
int<lower=2> K; // ordinal response with 4 values, 3 cutpoints
int<lower=0> N; // number of measurements int<lower=1,upper=K> y[N]; // response
int<lower=0> Ntests; // number of groups
int<lower=1,upper=Ntests> tests[N]; // groups
}
parameters { // population cutpoints and associated variance.
ordered[K-1] CutpointsMean;
real<lower=0> CutpointsSigma[K-1];
ordered[K-1] Cutpoints[Ntests]; // ordinal response cutpoints for groups
}
model {
CutpointsSigma ~ exponential(1);
CutpointsMean ~ normal(2, 3);
for (i in 1:Ntests) {
Cutpoints[i][1] ~ normal(CutpointsMean[1] , CutpointsSigma[1]);
Cutpoints[i][2] ~ normal(CutpointsMean[2] , CutpointsSigma[2]);
Cutpoints[i][3] ~ normal(CutpointsMean[3] , CutpointsSigma[3]);
}
for (i in 1:N)
y[i] ~ ordered_logistic(0, Cutpoints[tests[i]]);
}
I have removed the part relating to the covariates for clarity.
'CutpointsMean' and 'CutpointsSigma' define the population global ordinal response while Cutpoints[i][1:3] is the ordinal response for group i.
The idea is that for example the first 'cutpoint' of each group is generated from a normal distribution centered on the first 'cutpoint' of the overall population.
The second 'cutpoint' of each group is generated from a normal distribution centered on the second 'cutpoint' of the overall population and so on.
As Cutpoints[i] is an ordered vector of 3 elements, what happens when I write directly into Cutpoints[i][2] ?
Is the write operation rejected if the constraints are not satisfied or simply the entry is written in the vector and the results is sorted?
Is this the correct way of modelling a multilevel ordinal response in Stan?
Thanks,
Antonio
for (i in 1:Ntests) {
Cutpoints[i][1] ~ normal(CutpointsMean[1] , CutpointsSigma[1]);
Cutpoints[i][2] ~ normal(CutpointsMean[2] , CutpointsSigma[2]);
Cutpoints[i][3] ~ normal(CutpointsMean[3] , CutpointsSigma[3]);
}
parameters {
real Cutpoints1[Ntests]; // cutpoint 1 group i
real<lower=0> Cutpoints2[Ntests]; // offset cutpoint 2 group i
real<lower=0> Cutpoints3[Ntests]; // offset cutpoint 3 group i
real CutpointsMean1; // global cutpoint 1
real<lower=0> CutpointsDMean2; // global cutpoint 2 offset
real<lower=0> CutpointsDMean3; // global cutpoint 3 offset
real<lower=0> CutpointsSigma[3]; // global cutponts sigma values
...
}
model {
CutpointsSigma ~ exponential(1);
CutpointsMean1 ~ normal(1, 1); // these priors are problem specific
CutpointsDMean2 ~ normal(1.5, 1.5);
CutpointsDMean3 ~ normal(2, 2);
for (i in 1:Ntests) {
Cutpoints1[i] ~ normal(CutpointsMean1 , CutpointsSigma[1]); // cutpoint 1 group i
Cutpoints2[i] ~ normal(CutpointsDMean2, CutpointsSigma[2]); // delta cutpoint 2
Cutpoints3[i] ~ normal(CutpointsDMean3, CutpointsSigma[3]); // delta cutpoint 3
}
for (i in 1:N) {
vector[3] cuts;
vector[3] ocuts;
cuts[1] <- Cutpoints1[tests[i]]; // from normal
cuts[2] <- Cutpoints2[tests[i]]; // from normal > 0 (half normal)
cuts[3] <- Cutpoints3[tests[i]]; // from normal > 0 (half normal)
// recover cutpoints from offsets
ocuts[1] <- cuts[1]; // can take any value
ocuts[2] <- cuts[1] + cuts[2]; // ocuts[2] > ocuts[1]
ocuts[3] <- cuts[1] + cuts[2] + cuts[3]; // ocuts[3] > ocuts[2] > ocuts[1]
y[i] ~ ordered_logistic("predictors go here", ocuts);
}
parameters {
vector[K-1] Cutpoints[Ntests]; // no longer ordered
vector[K-1] CutpointsMean;
real<lower=0> CutpointsSigma[K-1];
...
}
model {
CutpointsSigma ~ exponential(1);
CutpointsMean ~ normal(0, 10);
// The vectorized version didn't work for some reason, I will double check later on .
for (i in 1:Ntests) {
Cutpoints[i][1] ~ normal(CutpointsMean[1] , CutpointsSigma[1]);
Cutpoints[i][2] ~ normal(CutpointsMean[2] , CutpointsSigma[2]);
Cutpoints[i][3] ~ normal(CutpointsMean[3] , CutpointsSigma[3]);
}
for (i in 1:N)
{
vector[3] cuts;
// map to ordered sequence by Ordered Inverse Transform
cuts <- Cutpoints[tests[i]];
cuts[2] <- cuts[1] + exp(cuts[2]);
cuts[3] <- cuts[2] + exp(cuts[3]);
y[i] ~ ordered_logistic("predictors go here", cuts);
}
}
'