Apologies for coming back - I am still struggling a little to understand some of the subtleties of how the NA filling works. I have a more advanced version as shown:
###########################################
###########################################
#points(x4,col='green')
xC_full <- rbind(x1,x2,x3)
#create discrete data
# build cutpoints automatically for K categories
make_cuts <- function(K) {
if (K < 2) stop("Each dimension must have at least 2 categories")
qnorm(seq(0, 1, length.out = K + 1))[-c(1, K + 1)]
}
# convert MVN -> ordinal with varying categories per dimension
to_ordinal_general <- function(x, K_vec) {
D <- ncol(x)
if (length(K_vec) != D) {
stop("Length of K_vec must match number of dimensions")
}
out <- matrix(NA, nrow(x), D)
for (j in 1:D) {
cuts <- make_cuts(K_vec[j])
out[, j] <- as.integer(cut(
x[, j],
breaks = c(-Inf, cuts, Inf),
labels = 1:K_vec[j]
))
}
return(out)
}
K_vec <- c(2, 3, 4) # 3 dimensions: binary, 3-level, 4-level
D <- length(K_vec)
# cluster 1
xD1_lat <- rmvnorm(800, rep(0, D),
sigma = diag(D))
xD1 <- to_ordinal_general(xD1_lat, K_vec)
# cluster 2
xD2_lat <- rmvnorm(400, rep(1, D),
sigma = diag(0.5, D))
xD2 <- to_ordinal_general(xD2_lat, K_vec)
# cluster 3
xD3_lat <- rmvnorm(300, rep(-1, D),
sigma = diag(1, D))
xD3 <- to_ordinal_general(xD3_lat, K_vec)
# combine
xD_full <- rbind(xD1, xD2, xD3)
# apply missingness
indicatorC <- array(c(0,1,1,0,0,1,0),dim(xC_full))
indicatorD <- array(c(0,1,1),dim(xD_full))
xC_full[indicatorC == 0] <- NA
xD_full[indicatorD == 0] <- NA
# rows with at least one NA
impute_idx <- which(apply(xC_full, 1, function(row) any(
is.na(row))))
# rows with no NA
complete_idx <- which(apply(xC_full, 1, function(row) all(!
is.na(row))))
constants <- list(
I = nrow(xC_full),#no. individuals/points in the data set
JC = ncol(xC_full),#no. continuous variables
K = 10,#cap on no. Dirichlet groupings
L = K_vec,#no. categories in each discrete variables
D = ncol(xC_full)#,#no. discrete variables,
)
data <- list(
xC = as.matrix(xC_full),#continuous variables IxJC
xD = as.matrix(xD_full)#discrete variables IxD
)
mons <- c('alpha1','alpha2','xC','xD','theta','mu','sigma','w','v','xD_new','z','xC_new','U')
inits <- list(
v = rep(0.6,constants$K-1),
z = rep(1,constants$I),
mu = array(0,c(constants$K,constants$J)),
sigma = array(1,c(constants$K,constants$J)),
xC <- array(0,dim=dim(data$xC)),
xD <- array(1,dim=dim(data$xD))
)
uppertri_mult_diag <- nimbleFunction(
run = function(mat = double(2), vec = double(1)) {
returnType(double(2))
p <- length(vec)
out <- matrix(nrow = p, ncol = p, init = FALSE)
for(i in 1:p)
out[ , i] <- mat[ , i] * vec[i]
return(out)
})
code <- nimbleCode({
alpha1 ~ T(dnorm(0, sd=100),0,)
alpha2 ~ T(dnorm(0, sd=100),0,)
for(i in 1:(K-1)) { # stick-breaking variables
v[i] ~ dbeta(alpha1, alpha2)
}
w[1:K] <- stick_breaking(v[1:(K-1)]) # stick-breaking weights
#dirichlet prior for discrete variables
for (k in 1:D) {
for (l in 1:L[k]){
alpha_theta[k,l] <- 1 / L[k]
}
}
# Priors for each group
for(k in 1:K) {
#dirichlet prior for discrete
for (j in 1:D){
theta[k,j,1:L[j]] ~ ddirch(alpha_theta[j,1:L[j]])
}
for (j in 1:JC){
mu[k,j] ~ dnorm(0, var = 1)
sigma[k,j] ~ dinvgamma(2, 1)
}
Ustar[k,1:JC,1:JC] ~ dlkj_corr_cholesky(1, JC)
U[k,1:JC,1:JC] <- uppertri_mult_diag(Ustar[k,1:JC, 1:JC], sigma[k,1:JC])
}
# Likelihood
for(i in 1:I) {
z[i] ~ dcat(w[1:K])
#continuous
muTilde[i,1:JC] <- mu[z[i],1:JC]
UTilde[i,1:JC, 1:JC] <- U[z[i],1:JC, 1:JC]
xC[i, 1:JC] ~ dmnorm(muTilde[i,1:JC], cholesky = UTilde[i,1:JC, 1:JC], prec_param = 0)
#discrete
for (j in 1:D){
xD[i,j] ~ dcat( theta[z[i],j,1:L[j]])
}
}
#simulate a new individual
z_new ~ dcat(w[1:K])
#continuous
xC_new[1:JC] ~ dmnorm(mu[z_new,1:JC], cholesky = U[z_new,1:JC, 1:JC], prec_param = 0)
#discrete
for (j in 1:D){
xD_new[j] ~ dcat(theta[z_new,j,1:L[j]])
}
})
myModel <- nimbleModel(code = code,
data = data,
constants = constants,
inits =inits)
CmyModel <- compileNimble(myModel)
config <- configureMCMC(CmyModel,
monitors = mons)
built <- buildMCMC(config)
# compile model
CmyMCMC <- compileNimble(built)
samps <- runMCMC(CmyMCMC,
samplesAsCodaMCMC = TRUE,
niter = 1500,
nburnin = 1000,
thin=1,
nchains = 3
)
########################################### ###########################################
These nodes correspond to rows which are partially observed (as opposed to fully NA or no NA) - suggesting that it is these partially observed nodes are causing an issue. Changing the indicatorC line, which creates missing points, to
indicatorC <- array(c(0,1,1),dim(xC_full)) allows the model to successfully run, which as far as I can tell confirms that this is the issue.
However, in the version I am using: 1.4.1, I understand that a special sampler specifically for partially observed mvn was released, and looking at the configureMCMC prints would appear to confirm it is being used: