Представление параметрической модели выживания в форме "Процесс подсчета" в JAGS

Эта проблема

Я пытаюсь построить модель выживания в JAGS, которая учитывает изменяющиеся во времени ковариаты. Я хотел бы, чтобы это была параметрическая модель - например, предполагая, что выживание следует распределению Вейбулла (но я бы хотел, чтобы опасность изменялась, поэтому экспонента слишком проста). Так что это, по сути, байесовская версия того, что можно сделать в пакете flexsurv, который допускает изменяющиеся во времени ковариаты в параметрических моделях.

Поэтому я хочу иметь возможность вводить данные в форме "процесса подсчета", где у каждого субъекта есть несколько строк, каждая из которых соответствует временному интервалу, в котором их ковариаты оставались постоянными (как описано в этом PDF-документе или здесь. Это (start, stop] формулировка, flexurv позволяют пакеты survival или flexurv.

К сожалению, каждое объяснение того, как проводить анализ выживаемости в JAGS, похоже, предполагает один ряд на субъекта.

Я попытался использовать этот более простой подход и распространить его на формат процесса подсчета, но модель неправильно оценивает распределение.

Неудачная попытка:

Вот пример. Сначала мы генерируем некоторые данные:

library('dplyr')
library('survival')

## Make the Data: -----
set.seed(3)
n_sub <- 1000
current_date <- 365*2

true_shape <- 2
true_scale <- 365

dat <- data_frame(person = 1:n_sub,
                  true_duration = rweibull(n = n_sub, shape = true_shape, scale = true_scale),
                  person_start_time = runif(n_sub, min= 0, max= true_scale*2),
                  person_censored = (person_start_time + true_duration) > current_date,
                  person_duration = ifelse(person_censored, current_date - person_start_time, true_duration)
)

  person person_start_time person_censored person_duration
   (int)             (dbl)           (lgl)           (dbl)
1      1          11.81416           FALSE        487.4553
2      2         114.20900           FALSE        168.7674
3      3          75.34220           FALSE        356.6298
4      4         339.98225           FALSE        385.5119
5      5         389.23357           FALSE        259.9791
6      6         253.71067           FALSE        259.0032
7      7         419.52305            TRUE        310.4770

Затем мы разбиваем данные на 2 наблюдения по каждому предмету. Я просто делю каждый предмет на время = 300 (если они не успели на время = 300, в котором они получают только одно наблюдение).

## Split into multiple observations per person: --------
cens_point <- 300 # <----- try changing to 0 for no split; if so, model correctly estimates
dat_split <- dat %>%
  group_by(person) %>%
  do(data_frame(
    split = ifelse(.$person_duration > cens_point, cens_point, .$person_duration),
    START = c(0, split[1]),
    END = c(split[1], .$person_duration),
    TINTERVAL = c(split[1], .$person_duration - split[1]), 
    CENS = c(ifelse(.$person_duration > cens_point, 1, .$person_censored), .$person_censored), # <— edited original post here due to bug; but problem still present when fixing bug
    TINTERVAL_CENS = ifelse(CENS, NA, TINTERVAL),
    END_CENS = ifelse(CENS, NA, END)
  )) %>%
  filter(TINTERVAL != 0)

  person    split START      END TINTERVAL CENS TINTERVAL_CENS
   (int)    (dbl) (dbl)    (dbl)     (dbl) (dbl)        (dbl)
1      1 300.0000     0 300.0000 300.00000     1           NA
2      1 300.0000   300 487.4553 187.45530     0    187.45530
3      2 168.7674     0 168.7674 168.76738     1           NA
4      3 300.0000     0 300.0000 300.00000     1           NA
5      3 300.0000   300 356.6298  56.62979     0     56.62979
6      4 300.0000     0 300.0000 300.00000     1           NA

Теперь мы можем настроить модель JAGS.

## Set-Up JAGS Model -------
dat_jags <- as.list(dat_split)
dat_jags$N <- length(dat_jags$TINTERVAL)
inits <- replicate(n = 2, simplify = FALSE, expr = {
       list(TINTERVAL_CENS = with(dat_jags, ifelse(CENS, TINTERVAL + 1, NA)),
            END_CENS = with(dat_jags, ifelse(CENS, END + 1, NA)) )
})

model_string <- 
"
  model {
    # set priors on reparameterized version, as suggested
    # here: https://sourceforge.net/p/mcmc-jags/discussion/610036/thread/d5249e71/?limit=25#8c3b
    log_a ~ dnorm(0, .001) 
    log(a) <- log_a
    log_b ~ dnorm(0, .001)
    log(b) <- log_b
    nu <- a
    lambda <- (1/b)^a

    for (i in 1:N) {
      # Estimate Subject-Durations:
      CENS[i] ~ dinterval(TINTERVAL_CENS[i], TINTERVAL[i])
      TINTERVAL_CENS[i] ~ dweibull( nu, lambda )
    }
  }
"

library('runjags')
param_monitors <- c('a', 'b', 'nu', 'lambda') 
fit_jags <- run.jags(model = model_string,
                     burnin = 1000, sample = 1000, 
                     monitor = param_monitors,
                     n.chains = 2, data = dat_jags, inits = inits)
# estimates:
fit_jags
# actual:
c(a=true_shape, b=true_scale)

В зависимости от того, где находится точка разделения, модель оценивает очень разные параметры для базового распределения. Он получает правильные параметры, только если данные не разбиты на форму подсчета. Кажется, что это не способ форматирования данных для такого рода проблемы.

Если я пропускаю предположение, и моя проблема в меньшей степени связана с JAGS и больше связана с тем, как я ее формулирую, предложения приветствуются. Я мог бы отчаиваться, что изменяющиеся во времени ковариаты не могут использоваться в параметрических моделях выживания (и могут использоваться только в моделях, таких как модель Кокса, которая предполагает постоянные опасности и которая фактически не оценивает лежащее в основе распределение) - однако, как Как я упоминал выше, пакет flexsurvreg в R действительно учитывает формулировку (start, stop] в параметрических моделях.

И если кто-нибудь знает, как построить такую модель на другом языке (например, STAN вместо JAGS), это тоже будет оценено.

В любом случае, я был озадачен этим более недели, поэтому любая помощь или предложения очень ценятся.

РЕДАКТИРОВАТЬ:

Крис Джексон дает несколько полезных советов по электронной почте:

Я думаю, что конструкция T() для усечения в JAGS необходима здесь. По существу, для каждого периода (t [i], t [i + 1]), где человек жив, но ковариата постоянна, время выживания сокращается влево в начале периода и, возможно, также подвергается цензуре вправо в конец. Так что вы бы написали что-то вроде y[i] ~ dweib(shape, scale[i])T(t[i], )

Я попытался реализовать это предложение следующим образом:

model {
  # same as before
  log_a ~ dnorm(0, .01) 
  log(a) <- log_a
  log_b ~ dnorm(0, .01)
  log(b) <- log_b
  nu <- a
  lambda <- (1/b)^a

  for (i in 1:N) {
    # modified to include left-truncation
    CENS[i] ~ dinterval(END_CENS[i], END[i])
    END_CENS[i] ~ dweibull( nu, lambda )T(START[i],)
  }
}

К сожалению, это не совсем подходит. Со старым кодом модель в основном правильно понимала параметр масштаба, но очень плохо справлялась с параметром формы. С этим новым кодом он становится очень близким к правильному параметру формы, но постоянно переоценивает параметр масштаба. Я заметил, что степень переоценки коррелирует с тем, насколько поздно наступает точка разделения. Если точка разделения ранняя (cens_point = 50), переоценка на самом деле отсутствует; если уже поздно (cens_point = 350), их много.

Я подумал, что, возможно, проблема может быть связана с "двойным подсчетом" наблюдений: если мы видим цензурированное наблюдение в момент времени t = 300, то от этого же человека, наблюдение без цензуры в момент времени t = 400, мне кажется интуитивным, что этот человек вносит две точки данных в наш вывод о параметрах Вейбулла, тогда как на самом деле они должны просто вносить одну точку. Поэтому я попытался включить случайный эффект для каждого человека; однако, это полностью провалилось, с огромными оценками (в диапазоне 50-90) для параметра nu. Я не уверен, почему это так, но, возможно, это вопрос для отдельного поста. Поскольку я не знаю, связаны ли проблемы, вы можете найти код для всего этого поста, включая код JAGS для этой модели, здесь.

Ответы