Beginner's Q : Comparison between brms and rstanemax & Data suitability

Hello

I have a biomarker data that has lots of below limit of quantifications value. To understand/interpret this data into % inhibition of this biomarker - if i treat the values below limit of quantification as 0 then it will give me 100% inhibition; if i treat the values as lloq - it will give me inhibition of 80% ; if i treat the values as lloq as half of lloq then it will give me inhibition of 90%.

How can i use brm into evaluating this data using nlm.
Question 2: Currently i am using rstanemax package into fitting this emax model. And use the fitting manually (i.e run with scenario 1 - assuming lloq values as 0, then scenario 2 - assuming lloq as lloq, scenario 3 - assuming lloq as half of lloq).
Can anyone help me to understand :

  1. Can my data be used in brm and what exactly can brm do here
  2. What is the difference between rtanemax and brms - what are the gap

Thank you
Jesmin

I can’t speak to rstanemax, but what you’re describing sounds like a challenge with left-censored data (informative missingness: you only know it’s below a LOQ). You could try using the cens() operator in brms to set this up (read up at ?brms::cens), after creating a new column that indicates when data are left-censored or fully observed.
The beauty with a model that accounts for censoring is that you probably won’t need to consider those 3 scenarios you described (don’t make any further assumption than you need).
Try out some test data here – you’ll be impressed! (To me, getting a model to work in spite of missing data is a real pleasure, and it’s all the better with what brms handles for you.)
Here’s a simple example:

library(brms)
library(tidyverse)

set.seed(20240419)
dat <- tibble(x = 1:30) %>% 
  mutate(y_true = 5 + 1 * x + rnorm(n= n(), 0, sd = 7),
         # indicate which values are left-censored or not
         y_cens = if_else(y_true < 10, 'left', 'none'),
         # set values below detection to detection limit
         y_obs = if_else(y_true < 10, 10, y_true) 
         )

dat %>% 
  ggplot(aes(x, y_obs, color = y_cens))+geom_point()

# model with left-censoring
fit <- brm(y_obs | cens(y_cens) ~ x,
           family = gaussian(),
           data = dat,
           cores = 4, chains = 4)
summary(fit)
conditional_effects(fit) 
# examine the Stan code:
brms::stancode(fit)
# note especially this bit for the likelihood:
# for (n in 1:N) {
#   // special treatment of censored data
#   if (cens[n] == 0) {
#     target += normal_lpdf(Y[n] | mu[n], sigma);
#   } else if (cens[n] == 1) {
#     target += normal_lccdf(Y[n] | mu[n], sigma);
#   } else if (cens[n] == -1) {
#     target += normal_lcdf(Y[n] | mu[n], sigma);
#   }
# }
## contrast with more naive model
  # assume data is at limit
dat %>% lm(y_obs ~ x, data = .) %>% summary()
  # both parameters are biased

Hope that helps.

2 Likes