I built a simple model and tested it with a set of known values. I thought I could share the results here to get some feedback and hopefully get it done better and faster.
The model tries to fit a circular Gaussian, also known as von Mises function to a set of data points. Don't get confused, this is not a circular probability function, it is just a circular Gaussian curve with 3 parameters, the precision kappa, the difference between top and bottom, amp, and the offset of the curve i.e. the position on the y-axis where the minimum of the curve is located.
The data organized as clusters, so that each of the M clusters contain T such vM profiles. Thus each cluster has an hyperparameter, which dictates a prior on the profiles it contains.
von Mises function is defined in the functions{} block... wrt performance, could one vectorize this operation, or wouldn't it matter any way?
between line 52-60, where I define the hyperparameters. should these lines be included in the for loop block?
I took the pain and generated some artificial data to compare the fitted parameters to their true values. I used 125 clusters that resulted by combining the following values... For each profile I added some random normal noise independently.
The figure depicts this relationship between fitted cluster parameters and ground truth. The second row shows the joint error matrix after marginalizing for the 3rd parameter.
I see that the offset and amplitude parameters are nicely fit and the values are close to the data The kappa parameter is another story, there are lots of points all away from the identity line. But the joint error matrix shows that these are originating mostly from the case where the amplitude parameter is 0, which makes the kappa value undetermined. Otherwise, I am a bit surprised about the sigma_noise parameter which is always undershot by the fitting procedure even though it has a rather non-informative prior (line 56).
s.
#Function to fit to dependent variables. This is a vonMises function scaled to span [0 1],
#amp is the difference between peak and thorough
#offset is the height of the minimum value.
#kappa is the precision parameter.
#no free parameter for the location of the peak, it is always at 0 degrees.
real vonMises(real x, real amp, real kappa, real offset) {
#how to vectorize this?, cos does only take real values not real[].
y <- amp*((exp(kappa*cos(x))-exp(-kappa))/(exp(kappa)-exp(-kappa)))+offset;
#the data consists of T columns of X data points, which are to be modelled using the vM function
#Columns originates M different groups and within each group there T profiles.
#Each participant is present in each group:
#Nth profile in each group comes from the same participant, but this is ignored here.
int<lower=0> X; #number of independent variables.
vector[X] x; #independent variables in degrees.
int<lower=0> T; #total number of profiles.
real y[X,T]; #dependent variable, each column is one vMises profile.
int<lower=0> M; #number of groups.
int m[T]; #indicator variable for origin of y (specifies the recording modality).
real<lower=0> sigma_y[M]; #noise variance for each group
real mu_amp[M]; #average amplitude for the group
real mu_offset[M]; #average offset for the group
real<lower=0,upper=400> mu_kappa[M]; #average precision for the group, values bigger than 400 leads to NaN output from vonMises function above.
real<lower=0> sigma_amp[M]; #variance for amplitude for group
real<lower=0> sigma_offset[M];#variance for offset for the group
real<lower=0> sigma_kappa[M]; #variance for kappa
#profile level parameters
real amp[T]; #amplitude for single profiles
real offset[T]; #offset for single profiles
real<lower=0,upper=400> kappa[T]; #precision for single profiles
#group level (does this need to be inside the for loop?)
sigma_kappa ~ cauchy(0,1);#what alternative are there for these parameters?
sigma_offset ~ cauchy(0,1);
mu_kappa ~ lognormal(0,1.5);
mu_offset ~ cauchy(0,10);
for (i in 1:T) {#run over profiles
#parameters of single profile are distributed according to group-level parameters.
#could this be vectorized?
offset[i] ~ normal(mu_offset[m[i]] , sigma_offset[m[i]]);
kappa[i] ~ lognormal( log(mu_kappa[m[i]]) , sigma_kappa[m[i]]);
amp[i] ~ normal(mu_amp[m[i]] , sigma_amp[m[i]]);
for (xi in 1:X){#run over the dependent variable
y[xi,i] ~ normal( vonMises(x[xi],amp[i],kappa[i],offset[i]) , sigma_y[m[i]] );