mrc-ide / drjacoby

Flexible Markov chain monte carlo via reparameterization
https://mrc-ide.github.io/drjacoby/
Other
12 stars 5 forks source link

A proposal for fitting a model with multiple random effects #75

Open JDChallenger opened 3 years ago

JDChallenger commented 3 years ago

Simple GLMM. Based on mosquito death in a experimental hut trial

Here random effects are crossed, rather than nested.

But the overall method should apply more generally

Can provide stan model comparison, if useful

library(ggplot2) library(drjacoby)

In this trial, we are evaluating two mosquito nets, one of which is an untreated (control) net

There are four huts: each night a volunteer sleeps in each hut, under the net.

The sleepers & the nets are moved around the different huts over the course of 32 nights

Here's the trial structure. For 'net', 0=untreated control, 1=insecticide-treated net (ITN)

dfm <- data.frame('hut'=rep(c(1,2,3,4),32), 'sleeper' = c(rep(c(1,2,3,4),8), rep(c(4,1,2,3),8), rep(c(3,4,1,2),8), rep(c(2,3,4,1),8)), 'net' = rep(c(0,0,1,1,1,1,0,0), 16) )

How many mosquitoes enter each hut each night? Draw from a Poisson distribution

dfm$n <- rpois(128, lambda = 11) dfm$dead <- NA

To simulate how many mosquitoes die each night (binomial sampling), we include an effect for the ITN

We also include a weaker effect, depending on the hut & sleeper

for(i in 1:128){ rho <- -1 + 0.1dfm$hut[i] - 0.2dfm$sleeper[i] + 1.5 * dfm$net[i]#prob on the logistic scale

whack a bit o' noise on it

rho2 <- rnorm(1,mean = rho, sd = 0.1)

convert to probability scale

prb <- exp(rho2)/(1+exp(rho2)) dfm$dead[i] <- rbinom(1, size = dfm$n[i], prob = prb) }

Now prepare what we need for drjacoby. There are 4 random effects for hut and 4 for sleeper.

In total, there are 16 combinations of these

M <- 16 # no. of combinations of the two random effects? (4 values each)

df_params <- define_params(name = "hut1", min = -Inf, max = Inf, init = 0, block = c(1,2,3,4,M+1), name = "hut2", min = -Inf, max = Inf, init = 0, block = c(5,6,7,8,M+1), name = "hut3", min = -Inf, max = Inf, init = 0, block = c(9,10,11,12,M+1), name = "hut4", min = -Inf, max = Inf, init = 0, block = c(13,14,15,16,M+1), name = "sleeper1", min = -Inf, max = Inf, init = 0, block = c(1,5,9,13,M+1), name = "sleeper2", min = -Inf, max = Inf, init = 0, block = c(2,6,10,14,M+1), name = "sleeper3", min = -Inf, max = Inf, init = 0, block = c(3,7,11,15,M+1), name = "sleeper4", min = -Inf, max = Inf, init = 0, block = c(4,8,12,16,M+1), name = "sigma_hut", min = 0, max = Inf, init = 1, block = M+1, name = "sigma_sleeper", min = 0, max = Inf, init = 1, block = M+1, name = "a1", min = -Inf, max = Inf, init = 0, block = 1:M, name = "a0", min = -Inf, max = Inf, init = 0, block = 1:M)

Now we have to work out which data points need to go with each block

empty list for the data

lst <- list()

empty dataframe

dfa <- data.frame('blck'=rep(0,16), 'blckH'=rep(0,16), 'blckS'=rep(0,16)) count <- 1 for(j in 1:4){ for(k in 1:4){ aux <- dfm[dfm$hut == j & dfm$sleeper == k,]

preare data for a block, store as a list

lst[[as.character(count)]] <- list(total = aux$n, dead = aux$dead, net = aux$net)
dfa$blck[count] <- count
dfa$blckH[count] <- j
dfa$blckS[count] <- k
count <- count + 1

} }

Look at dfa. For each block, which random effect do we need for hut (H) and sleeper (S)?

head(dfa)

define log-likelihood function

r_loglike <- function(params, data, misc) {

unpack parameters

Patient-specific first

hut <- rep(0,4) sleeper <- rep(0,4) for (i in 1:4) { hut[i] <- params[i] sleeper[i] <- params[4 + i] } a1 <- params["a1"] a0 <- params["a0"] sigma_hut <- params["sigma_hut"] sigma_sleeper <- params["sigma_sleeper"]

get current update block

block = misc$block

distinct method for first M blocks, vs (M+1)

ret = 0.0; if (block == (M+1)) { # // likelihood for the global parameters for(i in 1:4){ ret <- ret + dnorm(hut[i], mean=0, sd=sigma_hut, log=T) + dnorm(sleeper[i], mean=0, sd=sigma_sleeper, log=T) } }else{ tot <- data[[block]]$total ded <- data[[block]]$dead nt <- data[[block]]$net
nnn <- length(tot) for(j in 1:nnn){

use dfa to index the random effects?

  lprob <- a0 + a1*nt[j] + hut[dfa$blckH[block]] + sleeper[dfa$blckS[block]] # on logistic scale
  #convert to probability scale
  prb <- exp(lprob)/(1+exp(lprob))
  ret <- ret + dbinom(ded[j], tot[j], prb, log = T)
}

}
return(ret) }

define log-prior function

r_logprior <- function(params, misc) { sigma_hut = params["sigma_hut"] sigma_sleeper = params["sigma_sleeper"] a0 = params["a0"] a1 = params["a1"] l1 <- dnorm(x=a0, mean = 0, sd = 2.5, log = T) l2 <- dnorm(x=a1, mean = 0, sd = 1, log = T) l3 <- dexp(x = sigma_sleeper, rate = 1, log = T) l4 <- dexp(x = sigma_hut, rate = 1, log = T) ret <- l1 + l2 + l3 + l4 return(ret) }

date() mcmc <- run_mcmc(data = lst, df_params = df_params, loglike = r_loglike, logprior = r_logprior, burnin = 2e3, samples = 6e3, chains = 3,

cluster = cl,

             silent = F)

date() plot_par(mcmc, show = c('a0','a1','sigma_hut','sigma_sleeper'), phase = "sampling") plot_credible(mcmc, show = c('a0','a1','sigma_hut','sigma_sleeper'))

random effect for sleeper should be (more-or-less) descending (see line 19)

plot_credible(mcmc, show = paste0('sleeper',seq(1,4)))

random effect for hut should be (more-or-less) asscending (see line 19)

plot_credible(mcmc, show = paste0('hut',seq(1,4)))