In this post a few days ago I attempted to get some advice on the procedure for discretizing a hurdle model and a gaussian model of the same outcome data so as to compare the two (recommended by @avehtari in his reply here). Professor Vehtari recommended using the process outlined in Section 4 of his Nabiximols case study.
I attempted to conduct the procedure on the data included in the case study, however this was not such a great candidate for a hurdle model as the outcome variable was an integer count variable, which, as described in the Section 4 of the case study, would be better modelled as a betabinomial. One of the great properties of the binomial family, when used to model non-negative integer data is that, because the width of each interval on the continuous scale is always 1, the probability value from the binomial model is the same as the density value from the gaussian model, and thus you can compare the betabinomial model to the gaussian directly without any need for extra discretisation steps. However this is not true of a hurdle model on continuous data (more below).
I have some new data from a clinical trial I am analysing and this time the outcome variable is an ideal candidate for a hurdle model. It is continuous, highly right-skewed, non-negative and includes many zeros. I include the data here
d.RData (5.7 KB)
The outcome (named outcome) is measured a maximum of six times through the trial at weeks 0 (baseline), 3, 6, 9, 12, and at a follow-up interview 12 weeks after the medication has been stopped (some participants drop out early and hence have less than six measures. The measurement occasion information is contained in a factorial variable called weekFac. There are two types of medication (contained in variable group): placebo and 'exp. Participant ID is contained in the id` variable. I want to examine group differences at each measurement occasion
Code to prepare environment, load the dataset, and remove missing values.
# load packages
library(tidyr)
library(matrixStats)
library(dplyr)
library(tibble)
library(modelr)
library(loo)
library(brms)
library(rstan)
library(posterior)
options(posterior.num_args = list(digits = 2))
library(pillar)
options(pillar.negative = FALSE)
library(ggplot2)
library(bayesplot)
theme_set(bayesplot::theme_default(base_family = "sans", base_size = 14))
library(tidybayes)
library(patchwork)
library(tinytable)
options(
tinytable_format_num_fmt = "significant_cell",
tinytable_format_digits = 2,
tinytable_tt_digits = 2
)
# load data
load(file = "d.RData")
# remove nas
d <- d |>
drop_na(outcome)
The outcome is on a range from 0-13 and looks like this
Now I run the two models, hurdle lognormal and gaussian, and run each through loo.
# gaussian model
fit_normal <- brm(formula = outcome ~ group*weekFac + (1 | id),
data = d,
family = gaussian(),
save_pars = save_pars(all = TRUE),
seed = 1234,
refresh = 0)
fit_normal <- add_criterion(fit_normal,
criterion = "loo",
save_psis = TRUE,
moment_match = TRUE)
loo_normal <- loo(fit_normal)
# hurdle model
fit_hurdle <- brm(formula = bf(outcome ~ group*weekFac + (1 | id),
hu ~ group*weekFac,
decomp = "QR"),
data = d,
family = hurdle_lognormal(),
save_pars = save_pars(all = TRUE),
seed = 1234,
refresh = 0)
fit_hurdle <- add_criterion(fit_hurdle,
criterion = "loo",
save_psis = TRUE,
moment_match = TRUE)
loo_hurdle <- loo(fit_hurdle)
Now I am going to ignore all the model refinement and diagnostics for the purposes of this post. What I need advice on is the process of discretisation.
And when I compare them
loo_compare(loo_normal, loo_hurdle)
# output
# model elpd_diff se_diff p_worse diag_diff diag_elpd
# fit_hurdle 0.0 0.0 NA 10 k_psis > 0.7
# fit_normal -426.3 149.8 1.00 12 k_psis > 0.7
The hurdle is superior. HOWEVER, from what I gather, as-is, this loo comparison is not valid because the hurdle lognormal contains both a discrete logit model for ‘0 vs everything else’ and a continuous portion modelling the non-zero values. So Professor Vehtari advised doing “the model comparison in discrete space by completely discretizing both models” and suggested using the Nabiximols case study as a guide.
Now, I have gone through Section 4 of the case study, which I think is the relevant section. And show my attempts at the discretization below.
The closest I can get is to use the log_lik() and expand_grid() functions.
llNormal <- log_lik(object = fit_normal,
newdata = d[i, ] |>
select(!outcome) |>
expand_grid(outcome = seq(from = 0,
to = 13,
by = 1)))
llHurdle <- log_lik(object = fit_hurdle,
newdata = d[i, ] |>
select(!outcome) |>
expand_grid(outcome = seq(from = 0,
to = 13,
by = 1)))
It turns out you can pass the output of the log_lik function directly into the loo() function
loo_hurdle_ll <- loo(llHurdle)
loo_normal_ll <- loo(llNormal)
And compare the two via loo_compare()
loo_compare(loo_normal_ll, loo_hurdle_ll)
# output
# model elpd_diff se_diff p_worse diag_diff diag_elpd
# model2 0.0 0.0 NA 1 k_psis > 0.7
# model1 -1071.9 303.1 1.00 N < 100 11 k_psis > 0.7
Now it looks like once again the now-discretized (?) hurdle model is superior to the gaussian. But I am fairly sure I am doing something wrong. In the case study Professor Vehtari uses the colLogSumExps() function from the matrixStats package to integrate across intervals, but I can’t figure out how to then use that output to compare models.
So I am flying blind basically, with little idea how to proceed. I have an output, which is better than an error message I suppose, but I have no idea if the output can be trusted. I need advice from people who know (much) more than me about whether what I did is right and, if not, how to do it properly.
