It is too bad there's not an elegant way to do this
within Stan. We could handle this case with a triangular
matrix data type -- we wouldn't even need full ragged arrays.
We'll probably add one in the near future (months, not weeks
or days, that is).
As to some of the other proposed solutions, you don't want
unnecessary "junk" parameters as they'll distort the
posterior geometry and what the U-turn condition in
NUTS does (there are ways to mitigate their effect,
but it's still work by the algorithm).
Here's one solution that stays entirely within
Stan's modeling language and doesn't add any
unnecessary parameters. But it does make you recode things
like ordered vectors from scratch in the model.
Suppose you have K maximum possible outcome categories, which
means anywhere from 1 to (K-1) cut points per instance.
Suppose K=5, then I'm proposing a representation like this:
outcomes num_cuts lowest increments
2 1 first[1]
3 2 first[2] inc[1]
4 3 first[3] inc[2] inc[3]
5 4 first[4] inc[4] inc[5] inc[6]
Then the 3 cutpoints for the case of 4 outcomes
are (lowest[3], lowest[3]+inc[2], lowest[3]+inc[2]+inc[3]).
The rest is just a bunch of by-hand triangular array
indexing, which is majorly ugly.
There's some indexing arithmetic below to compute the
starting indexes 1, 2, and 4 for he increments.
Note that each is the sum of the number of entries in
the previous row, so takes the form of the sum of integers
from 1 to some number (plus 1 because we went with
R-style matrix indexing).
Anyway, here goes.
parameters {
real first_cuts[K-1];
real<lower=0> incr_cuts[((K-1)*(K-2))/ 2];
}
model {
// priors on cutpoints
first_cuts ~ ...;
incr_cuts ~ ...;
for (n in 1:N) {
// build up cutpoint vector
vector[num_cuts[n]] c;
c[1] <- first_cuts[num_cuts[n]];
for (k in 2:num_cuts[n])
c[k] <- c[k-1]
+ incr_cuts[((num_cuts[n]-1)*(num_cuts[n]-2))/2 + k - 1];
// linear predictor (local var or transformed eta[n] if saving)
eta <- ...;
// likelihood
y[n] ~ ordered_logistic(eta,c);
}
I haven't compiled this, so no guarantees on perfect
well-formedness.
The amount of work done to create new vectors (it requires
a malloc then free under the hood by our matrix lib) and all the
arithmetic and copying will probably be dwarfed by the
other operations, which include inverse logits (hence exponentials)
and their derivatives. (The allocations could be reduced
by sorting the data based on number of cut points and then
indexing into it.)
The next step would be to do what Ben suggests and just
rewrite the terms in the ordered logistic, which would require
a bit more fancy indexing footwork, but could be implemented
pretty directly.
An integer binomial coefficient function would make things
cleaner. All of this (n*(n+1))/2 stuff is just (n choose 2),
the sum of the numbers from 1 to n.
- Bob
> --
>
>