diff --git a/index.qmd b/index.qmd index ac19a0e..ae6617e 100644 --- a/index.qmd +++ b/index.qmd @@ -13,6 +13,17 @@ development problems. In order to highlight the versatility of that package, we have developed a collection of case studies covering a diverse set of clinical questions that were addressed via Bayesian modelling with the `brms` package. +::: {.content-visible when-profile="public"} +::: {.callout-tip} +## Short course at JSM -- Portland, OR -- August 5th 2024 + +We will offer a short course based on this material at the 2024 Joint Statistical Meetings in Portland on August 5. The materials are available here: + +- [Slides](workshops/jsm2024/slides/bamdd_jsm2024.pdf) & [additional material (see readme)](https://github.com/Novartis/bamdd/tree/main/workshops/jsm2024) + +::: +::: + ## Navigating the site The material is organized as follows: @@ -26,6 +37,7 @@ studies. You can find a [listing with descriptions on this page](src/02_case_stu as exposing and injecting custom stan code to `brms` models, and efficient parallel computation to support sampling in `brms`. + ## Updates This web-site is intended as a live document with updates as @@ -34,6 +46,7 @@ appropiate. Key changes to the web-site are tracked here: ::: {.content-visible when-profile="public"} +- 31st July 2024: Linked materials from JSM Portland 2024 short course - 5th June 2024: Added @sec-pos on assessing probability of success of in Phase-III based on a single-arm trial in Phase-II - 29th April 2024: Third edition course from Paul Bürkner with the new diff --git a/src/01b_basic_workflow.qmd b/src/01b_basic_workflow.qmd index 32b69cc..d58c430 100644 --- a/src/01b_basic_workflow.qmd +++ b/src/01b_basic_workflow.qmd @@ -91,7 +91,8 @@ set.seed(5886935) #| eval: true #| include: false # invisible to the reader additional setup steps, which are optional -{{< include setup.R >}} +# {{< include setup.R >}} +source("setup.R") ``` As `brms` models are translated to Stan model files, which must be diff --git a/src/01c_priors.qmd b/src/01c_priors.qmd index f4f9fb0..a174baa 100644 --- a/src/01c_priors.qmd +++ b/src/01c_priors.qmd @@ -9,7 +9,10 @@ bibliography: references.bib # Model setup & priors {#sec-model-priors} -```{r, include=FALSE} +```{r, eval=TRUE,echo=TRUE,message=FALSE,warning=FALSE} +#| code-fold: true +#| code-summary: "Show R setup" + library(ggplot2) library(dplyr) library(knitr) @@ -31,7 +34,8 @@ theme_set(theme_bw(12)) ```{r, include=FALSE, echo=FALSE, eval=TRUE} # invisible to the reader additional setup steps, which are optional -{{< include setup.R >}} +# {{< include setup.R >}} +source("setup.R") ``` Within the Bayesian regression modeling in Stan framework `brms` diff --git a/src/02a_meta_analysis.qmd b/src/02a_meta_analysis.qmd index 2542b5f..2ab8bc8 100644 --- a/src/02a_meta_analysis.qmd +++ b/src/02a_meta_analysis.qmd @@ -46,7 +46,8 @@ set.seed(593467) ```{r, include=FALSE, echo=FALSE, eval=TRUE} # invisible to the reader additional setup steps, which are optional -{{< include setup.R >}} +# {{< include setup.R >}} +source("setup.R") ``` ## Background diff --git a/src/02ab_meta_analysis_trtdiff.qmd b/src/02ab_meta_analysis_trtdiff.qmd index d7ad071..dcb4851 100644 --- a/src/02ab_meta_analysis_trtdiff.qmd +++ b/src/02ab_meta_analysis_trtdiff.qmd @@ -24,7 +24,8 @@ set.seed(979356) ```{r, include=FALSE, echo=FALSE, eval=TRUE} # invisible to the reader additional setup steps, which are optional -{{< include setup.R >}} +# {{< include setup.R >}} +source("setup.R") ``` ## Background diff --git a/src/02ac_meta_analysis_strata.qmd b/src/02ac_meta_analysis_strata.qmd index d937060..66f15c1 100644 --- a/src/02ac_meta_analysis_strata.qmd +++ b/src/02ac_meta_analysis_strata.qmd @@ -56,7 +56,8 @@ options(width=120, digits=2) ```{r, include=FALSE, echo=FALSE, eval=TRUE} # invisible to the reader additional setup steps, which are optional -{{< include setup.R >}} +# {{< include setup.R >}} +source("setup.R") ``` ## Background diff --git a/src/02b_dose_finding.qmd b/src/02b_dose_finding.qmd index 029708b..3594fc7 100644 --- a/src/02b_dose_finding.qmd +++ b/src/02b_dose_finding.qmd @@ -52,7 +52,8 @@ set.seed(8979476) ```{r, include=FALSE, echo=FALSE, eval=TRUE} # invisible to the reader additional setup steps, which are optional -{{< include setup.R >}} +# {{< include setup.R >}} +source("setup.R") ``` ```{r get_annualized_rates} diff --git a/src/02c_dose_escalation.qmd b/src/02c_dose_escalation.qmd index 7d9b518..0103dc0 100644 --- a/src/02c_dose_escalation.qmd +++ b/src/02c_dose_escalation.qmd @@ -72,7 +72,8 @@ set.seed(8794567) ```{r, include=FALSE, echo=FALSE, eval=TRUE} # invisible to the reader additional setup steps, which are optional -{{< include setup.R >}} +# {{< include setup.R >}} +source("setup.R") ``` Such a model is straightforward to implement in `brms`. Below, we use a nonlinear formula specification, in order to allow the prior for the intercept to be specified on the log scale. diff --git a/src/02cb_tte_dose_escalation.qmd b/src/02cb_tte_dose_escalation.qmd index 9c0680a..478bc41 100644 --- a/src/02cb_tte_dose_escalation.qmd +++ b/src/02cb_tte_dose_escalation.qmd @@ -144,7 +144,8 @@ gt_format <- function(x) { ```{r, include=FALSE, echo=FALSE, eval=TRUE} # invisible to the reader additional setup steps, which are optional -{{< include setup.R >}} +# {{< include setup.R >}} +source("setup.R") ``` ## Example trial diff --git a/src/02e_multiple_imputation.qmd b/src/02e_multiple_imputation.qmd index a11ac5e..0f9e98e 100644 --- a/src/02e_multiple_imputation.qmd +++ b/src/02e_multiple_imputation.qmd @@ -76,7 +76,8 @@ options(brms.normalize=FALSE) ```{r, include=FALSE, echo=FALSE, eval=TRUE} # invisible to the reader additional setup steps, which are optional -{{< include setup.R >}} +# {{< include setup.R >}} +source("setup.R") ``` Let us consider some simulated data for a trial in chronic obstructive diff --git a/src/02g_longitudinal.qmd b/src/02g_longitudinal.qmd index 67c3bf1..cf5ae00 100644 --- a/src/02g_longitudinal.qmd +++ b/src/02g_longitudinal.qmd @@ -45,7 +45,8 @@ control_args <- list(adapt_delta = 0.95) ```{r, include=FALSE, echo=FALSE, eval=TRUE} # invisible to the reader additional setup steps, which are optional -{{< include setup.R >}} +# {{< include setup.R >}} +source("setup.R") ``` ```{r get_data, echo = TRUE} diff --git a/src/02h_mmrm.qmd b/src/02h_mmrm.qmd index 213ed58..f8d1ffb 100644 --- a/src/02h_mmrm.qmd +++ b/src/02h_mmrm.qmd @@ -51,7 +51,8 @@ options(width=120) ```{r, include=FALSE, echo=FALSE, eval=TRUE} # invisible to the reader additional setup steps, which are optional -{{< include setup.R >}} +# {{< include setup.R >}} +source("setup.R") ``` ```{r include=FALSE} diff --git a/src/02i_time_to_event.qmd b/src/02i_time_to_event.qmd index 735ac89..bfc0869 100644 --- a/src/02i_time_to_event.qmd +++ b/src/02i_time_to_event.qmd @@ -58,7 +58,8 @@ options(width=120) ```{r, include=FALSE, echo=FALSE, eval=TRUE} # invisible to the reader additional setup steps, which are optional -{{< include setup.R >}} +# {{< include setup.R >}} +source("setup.R") ``` diff --git a/src/02j_network_meta_analysis.qmd b/src/02j_network_meta_analysis.qmd index de01414..796b37c 100644 --- a/src/02j_network_meta_analysis.qmd +++ b/src/02j_network_meta_analysis.qmd @@ -38,7 +38,8 @@ control_args <- list(adapt_delta=0.95) ```{r, include=FALSE, echo=FALSE, eval=TRUE} # invisible to the reader additional setup steps, which are optional -{{< include setup.R >}} +# {{< include setup.R >}} +source("setup.R") ``` diff --git a/src/02l_single_arm_pos.qmd b/src/02l_single_arm_pos.qmd index f0f317b..5044318 100644 --- a/src/02l_single_arm_pos.qmd +++ b/src/02l_single_arm_pos.qmd @@ -47,7 +47,8 @@ theme_set(theme_bw(base_size=18)) ```{r, include=FALSE, echo=FALSE, eval=TRUE} # invisible to the reader additional setup steps, which are optional -{{< include setup.R >}} +# {{< include setup.R >}} +source("setup.R") ``` diff --git a/workshops/jsm2024/README.md b/workshops/jsm2024/README.md new file mode 100644 index 0000000..7cfa042 --- /dev/null +++ b/workshops/jsm2024/README.md @@ -0,0 +1,28 @@ +# Course materials + +These are the materials accompanying the short course "Applied Modelling in Drug Development using brms" at the Joint Statistical Meetings in Portland, OR on August 5, 2024. + +Instructors: +- David Ohlssen +- Andrew Bean +- Bjoern Holzhauer + +Additional contributors: +- Paul Buerkner +- Sebastian Weber +- Lukas Widmer +- Cong Zhang + +## Folder structure + +- `schedule.png` has the course schedule +- `slides/bamdd_jsm2024.pdf` is the main slide deck for the course +- `slides/R/` is a directory containing the R code from the case studies presented in the course +- `exercises/` contains two sets of hands-on exercises in the form of Quarto files +- `exercises/solutions` has solutions from the organizers + +## Exercise instructions + +Course participants can find the exercises in the shared Posit Cloud space for the course (link provided during the course). See slides 38-44 in the main slides for instructions. + + diff --git a/workshops/jsm2024/exercises/ex1_historical_control.qmd b/workshops/jsm2024/exercises/ex1_historical_control.qmd new file mode 100644 index 0000000..2a2f69d --- /dev/null +++ b/workshops/jsm2024/exercises/ex1_historical_control.qmd @@ -0,0 +1,154 @@ +--- +title: 'brms exercises: historical controls' +author: 'your-name-here' +date: today +output: + html_document: + toc: true + embed-resources: true +--- + +```{r setup, include=FALSE} +knitr::opts_chunk$set(echo = TRUE) +``` + +## Background + +These exercises accompany the case study [Use of Historical Control +Data](TODO:link) as part of the training course "Workshop +on applied modeling in drug development using brms." + +## Preliminary code + +First load the required packages and set some options as described in the case study. + +```{r, warning = FALSE, message = FALSE} +here::i_am("exercises/ex1_historical_control.qmd") +library(here) + +library(ggplot2) +library(dplyr) +library(knitr) +library(brms) +library(posterior) +library(bayesplot) +library(RBesT) +theme_set(theme_bw()) + +options( + # how many processor cores would you like to use? + mc.cores = 4, + # how would you like to access Stan? + brms.backend = "cmdstanr", + # cache model binaries + cmdstanr_write_stan_file_dir=here::here("_brms-cache"), + # no need to normalize likelihoods + brms.normalize = FALSE, + # when you are storing your model to file, + # how shall it be updated? + brms.file_refit = "on_change" + # alternatives: "never", "always" + # use "never" for production +) + +set.seed(254335) +``` + +Then create the dataset used in the example: + +```{r} +AS_region <- bind_cols(RBesT::AS, region=sample(c("asia", "europe", "north_america"), 8, TRUE)) +kable(AS_region) +``` + +And fit the model as shown in the case study: + +```{r} +model <- bf(r | trials(n) ~ 1 + (1 | study), family=binomial) + +model_prior <- prior(normal(0, 2), class="Intercept") + + prior(normal(0, 1), class="sd", coef="Intercept", group="study") + +map_mc_brms <- brm(model, AS_region, prior = model_prior, + seed = 4767, + silent = 2, refresh = 0) + +``` + +## Exercise 1 + +Create a posterior predictive check based on the predictive +distribution for the response rate. +Steps: +* Use `posterior_predict` to create samples from the predictive + distribution of outcomes per trial. +* Use `sweep(predictive, 2, AS_region$n, "/")` to convert these + samples from the predictive distribution of the outcome counts to + samples from the predictive distribution for responder rates. +* Use `ppc_intervals` from `bayesplot` to create a plot showing your + results. + +```{r} +# Your solution here ----------------------------------------------------------- + + +``` + + +## Exercise 2 + +Redo the analysis with region, but treat region as a fixed + effect. Evaluate the informativeness of the obtained MAP priors. + The model formula for `brms` should look like `region_model_fixed + <- bf(r | trials(n) ~ 1 + region + (1 | study), family=binomial)`. +Steps: +* Consider the prior for the region fixed effect first. The reference + region is included in the intercept. The reference region is + implicitly defined by the first level of the variable region when + defined as `factor`. + - Define `asia` to be the reference region in the example. Also + include a level `other` in the set of levels. + - Assume that an odds-ratio of $2$ between regions can be seen as + very large such that a prior of $\mbox{N}(0, (\log(2)/1.96)^2)$ + for the region main effect is adequate. +* Obtain the MAP prior for each region by using the + `AS_region_all` data frame defined below and apply + `posterior_linpred` as shown above. +* Convert the MCMC samples from the MAP prior distribution into + mixture distributions with the same code as above. +* Calculate the ESS for each prior distribution with the `ess` + function from `RBesT`. + +```{r} + +AS_region_all <- data.frame(region=c("asia", "europe", "north_america", "other")) %>% + mutate(study=paste("new_study", region, sep="_"), r=0, n=6) + +# Your solution here ----------------------------------------------------------- + + +``` + + +## Exercise 3 + +Run the analysis for the normal endpoint in the `crohn` data set of +`RBesT`. Refer to the `RBesT` vignette for a [normal endpoint](https://cran.r-project.org/web/packages/RBesT/vignettes/introduction.html) + on more details and context. +Steps: +* Use as `family=gaussian` and use the `se` response modifier in place + of `trials` to specify a known standard error. +* Use the same priors as proposed in the vignette. +* Compare the obtained MAP prior (in MCMC sample form) from `RBesT` + and `brms`. + +```{r} +crohn <- RBesT::crohn + +# Your solution here ----------------------------------------------------------- + + +``` + + + diff --git a/workshops/jsm2024/exercises/ex2_dose_finding.qmd b/workshops/jsm2024/exercises/ex2_dose_finding.qmd new file mode 100644 index 0000000..58aaf51 --- /dev/null +++ b/workshops/jsm2024/exercises/ex2_dose_finding.qmd @@ -0,0 +1,342 @@ +--- +title: 'brms exercises: dose finding' +author: 'your-name-here' +date: today +output: + html_document: + toc: true + embed-resources: true +--- + +```{r setup, include=FALSE} +knitr::opts_chunk$set(echo = TRUE) +``` + +## Background + +This exercise accompanies the [Dose Finding](https://opensource.nibr.com/bamdd/src/02b_dose_finding.html) case study as part of the training course "Workshop on applied modeling in drug development using brms." + +## Preliminary code + +First load the required packages and set some options as described in the case study. + +```{r, warning = FALSE, message = FALSE} +here::i_am("exercises/ex2_dose_finding.qmd") +library(here) + +library(tidyverse) +library(brms) +library(tidybayes) +library(ggrepel) +library(patchwork) +library(ggplot2) +library(ggdist) + +# Set defaults for ggplot2 ---- +theme_set(theme_bw(base_size=18) + + theme(legend.position = "none")) + +scale_colour_discrete <- function(...) { + # Alternative: ggsci::scale_color_nejm(...) + # scale_colour_brewer(..., palette="Set1") + ggthemes::scale_colour_colorblind(...) +} +scale_fill_discrete <- function(...) { + # Alternative: ggsci::scale_fill_nejm(...) + #scale_fill_brewer(..., palette="Set1") + ggthemes::scale_fill_colorblind(...) +} +scale_colour_continuous <- function(...) { + scale_colour_viridis_c(..., option="turbo") +} +update_geom_defaults("point", list(size=2)) +update_geom_defaults("line", list(size=1.5)) + +options( + # how many processor cores would you like to use? + mc.cores = 4, + # how would you like to access Stan? + brms.backend = "cmdstanr", + # cache model binaries + cmdstanr_write_stan_file_dir=here::here("_brms-cache"), + # no need to normalize likelihoods + brms.normalize = FALSE, + # when you are storing your model to file, + # how shall it be updated? + brms.file_refit = "on_change" + # alternatives: "never", "always" + # use "never" for production +) + +set.seed(254335) +``` + +## Excercise: + +### Exercise 1: Peanut allergy + +In a hypothetical phase 2b trial children and adolescents with peanut allergy +were randomly assigned to placebo or 5 doses (5 to 300 mg) of a new drug. +After 26 weeks the patients underwent a double-blind placebo-controlled +food challenge and the primary endpoint was the proportion of patients that +could ingest 600 mg or more of peanut protein without experiencing dose-limiting +symptoms. The plots below show an overview of the data. Note that no placebo +patients were considered "responders" per the primary endpoint definition. + +```{r peanutdata, class.source = 'fold-hide'} +peanut <- tibble( + TRT01P = structure(c( + 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, + 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 5L, 5L, 5L, 5L, 5L, 5L, + 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, + 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, + 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, + 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, + 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, + 3L, 3L, 3L, 3L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, + 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L + ),levels = c("PBO", "5 mg", "15 mg", "50 mg", "150 mg", "300 mg"), class = "factor"), + dose = c( + 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, + 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, + 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, + 150L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, + 150L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, + 150L, 150L, 150L, 150L, 150L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, + 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, + 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, + 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, + 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, + 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, + 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, + 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, + 0L, 0L), + AVAL = c( + 1000L, 100L, 0L, 600L, 600L, 300L, 300L, 10L, 10L, 100L, 1000L, 300L, 10L, 1000L, 1000L, 1000L, 3L, 3L, 600L, 300L, + 1000L, 1000L, 100L, 1000L, 600L, 600L, 1000L, 1000L, 1000L, 300L, 600L, 1000L, 1000L, 1000L, 1000L, 1000L, 300L, + 1000L, 600L, 1000L, 300L, 1000L, 600L, 600L, 1000L, 1000L, 600L, 1000L, 1000L, 1000L, 1000L, 600L, 1000L, 1000L, + 1000L, 0L, 600L, 300L, 1000L, 1000L, 1000L, 100L, 1000L, 1000L, 600L, 1000L, 1000L, 1000L, 600L, 300L, 600L, 600L, + 100L, 600L, 1000L, 300L, 10L, 3L, 1000L, 300L, 300L, 1000L, 300L, 300L, 10L, 1000L, 1000L, 10L, 10L, 600L, 3L, 10L, + 600L, 600L, 600L, 1000L, 100L, 1000L, 1000L, 10L, 1000L, 3L, 10L, 1000L, 100L, 1000L, 100L, 10L, 300L, 10L, 100L, + 1000L, 0L, 1000L, 100L, 3L, 10L, 100L, 100L, 300L, 1000L, 1000L, 1000L, 10L, 1000L, 3L, 3L, 600L, 600L, 10L, 3L, + 600L, 600L, 300L, 300L, 1000L, 3L, 3L, 1000L, 10L, 1000L, 1000L, 600L, 100L, 300L, 600L, 10L, 100L, 0L, 100L, 3L, + 0L, 10L, 3L, 600L, 300L, 300L, 300L, 600L, 300L, 100L, 3L, 0L, 10L, 600L, 300L, 10L, 300L, 600L, 1000L, 600L, 600L, + 0L, 0L, 600L, 600L, 600L, 0L, 0L, 300L, 100L, 0L, 10L, 300L, 1000L, 300L, 600L, 600L, 300L, 10L, 600L, 100L, 100L, + 300L, 3L, 3L, 300L, 1000L, 10L, 3L, 100L, 3L, 100L, 100L, 300L, 3L, 3L, 600L, 300L, 3L, 3L, 3L, 300L, 3L, 0L, 10L, + 3L, 300L, 10L, 10L, 600L, 0L, 300L, 600L, 0L, 0L, 100L, 100L, 10L, 100L, 10L, 100L, 600L, 0L, 600L, 0L, 10L, 100L, + 0L, 0L, 0L, 0L, 3L, 10L, 3L, 300L, 600L, 0L, 0L, 300L, 10L, 10L, 100L, 300L, 0L, 0L, 0L, 3L, 0L, 0L, 10L, 0L, 300L, + 3L, 0L, 0L, 0L, 0L, 100L, 3L, 0L, 3L, 10L, 10L, 3L, 0L, 3L, 10L, 3L, 0L, 0L, 3L, 100L, 0L, 0L, 300L, 0L, 0L, 0L, + 0L, 0L, 0L, 0L, 3L, 0L, 3L, 0L, 0L, 0L, 0L), + PARAM = structure( + c( + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L), + levels = "Tolerated amount of peanut protein (mg)", class = "factor"), + USUBJID = c( + 129L, 103L, 104L, 185L, 34L, 15L, 140L, 151L, 7L, 144L, 200L, 23L, 138L, 115L, 255L, 147L, 224L, 101L, 156L, 284L, + 136L, 248L, 179L, 298L, 168L, 295L, 289L, 241L, 36L, 27L, 123L, 266L, 11L, 291L, 236L, 130L, 173L, 195L, 203L, 160L, + 274L, 167L, 290L, 94L, 9L, 269L, 122L, 135L, 64L, 26L, 95L, 10L, 5L, 234L, 161L, 299L, 88L, 69L, 35L, 233L, 286L, + 85L, 91L, 189L, 80L, 152L, 223L, 287L, 244L, 57L, 108L, 18L, 62L, 157L, 300L, 283L, 164L, 243L, 89L, 220L, 24L, 271L, + 166L, 118L, 201L, 127L, 121L, 41L, 267L, 213L, 49L, 73L, 202L, 134L, 112L, 25L, 227L, 29L, 251L, 273L, 119L, 132L, + 74L, 270L, 83L, 37L, 181L, 258L, 253L, 48L, 120L, 54L, 277L, 176L, 65L, 264L, 107L, 171L, 262L, 162L, 187L, 272L, + 288L, 294L, 245L, 109L, 172L, 204L, 275L, 22L, 66L, 186L, 247L, 17L, 149L, 141L, 177L, 280L, 216L, 40L, 75L, 263L, + 246L, 14L, 81L, 260L, 153L, 45L, 237L, 8L, 117L, 86L, 296L, 146L, 154L, 116L, 38L, 100L, 191L, 175L, 92L, 158L, 192L, + 180L, 256L, 254L, 125L, 222L, 145L, 261L, 155L, 159L, 3L, 206L, 278L, 19L, 31L, 63L, 208L, 55L, 259L, 218L, 111L, + 226L, 33L, 2L, 44L, 297L, 53L, 87L, 16L, 28L, 90L, 207L, 56L, 137L, 128L, 178L, 142L, 143L, 148L, 193L, 229L, 265L, + 97L, 252L, 205L, 150L, 165L, 188L, 52L, 99L, 93L, 1L, 221L, 124L, 210L, 6L, 232L, 21L, 211L, 163L, 96L, 60L, 183L, + 190L, 242L, 42L, 46L, 67L, 126L, 209L, 72L, 194L, 238L, 184L, 39L, 105L, 249L, 61L, 113L, 30L, 77L, 12L, 4L, 51L, + 139L, 20L, 268L, 215L, 292L, 217L, 199L, 32L, 276L, 47L, 225L, 230L, 79L, 71L, 98L, 50L, 13L, 76L, 231L, 250L, 58L, + 68L, 239L, 198L, 293L, 212L, 110L, 59L, 182L, 133L, 170L, 43L, 282L, 281L, 131L, 114L, 196L, 214L, 285L, 70L, 102L, + 279L, 106L, 197L, 257L, 228L, 84L, 235L, 78L, 169L, 240L, 219L, 174L, 82L), + CRIT1 = structure(c( + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L), + levels = "Tolerating >=600 mg of peanut protein without dose-limiting symptoms", class = "factor"), + CRIT1FL = structure(c( + 2L, 1L, 1L, 2L, 2L, 1L, 1L, 1L, 1L, 1L, 2L, 1L, 1L, 2L, 2L, 2L, 1L, 1L, 2L, 1L, 2L, 2L, 1L, 2L, 2L, 2L, 2L, 2L, + 2L, 1L, 2L, 2L, 2L, 2L, 2L, 2L, 1L, 2L, 2L, 2L, 1L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 1L, + 2L, 1L, 2L, 2L, 2L, 1L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 1L, 2L, 2L, 1L, 2L, 2L, 1L, 1L, 1L, 2L, 1L, 1L, 2L, 1L, 1L, + 1L, 2L, 2L, 1L, 1L, 2L, 1L, 1L, 2L, 2L, 2L, 2L, 1L, 2L, 2L, 1L, 2L, 1L, 1L, 2L, 1L, 2L, 1L, 1L, 1L, 1L, 1L, 2L, + 1L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 2L, 1L, 2L, 1L, 1L, 2L, 2L, 1L, 1L, 2L, 2L, 1L, 1L, 2L, 1L, 1L, 2L, 1L, + 2L, 2L, 2L, 1L, 1L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 1L, 1L, 1L, 2L, 1L, 1L, 1L, 1L, 1L, 2L, 1L, 1L, 1L, + 2L, 2L, 2L, 2L, 1L, 1L, 2L, 2L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 1L, 2L, 2L, 1L, 1L, 2L, 1L, 1L, 1L, 1L, 1L, + 1L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 1L, 1L, 2L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 1L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L), + levels = c("N", "Y"), class = "factor"), + CRIT2 = structure(c( + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L), + levels = "Tolerating >=1000 mg of peanut protein without dose-limiting symptoms", class = "factor"), + CRIT2FL = structure(c( + 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 1L, 1L, 2L, 2L, 2L, 1L, 1L, 1L, 1L, 2L, 2L, 1L, 2L, 1L, 1L, 2L, 2L, + 2L, 1L, 1L, 2L, 2L, 2L, 2L, 2L, 1L, 2L, 1L, 2L, 1L, 2L, 1L, 1L, 2L, 2L, 1L, 2L, 2L, 2L, 2L, 1L, 2L, 2L, 2L, 1L, + 1L, 1L, 2L, 2L, 2L, 1L, 2L, 2L, 1L, 2L, 2L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 1L, 1L, 1L, 2L, 1L, 1L, 2L, 1L, 1L, + 1L, 2L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 1L, 2L, 2L, 1L, 2L, 1L, 1L, 2L, 1L, 2L, 1L, 1L, 1L, 1L, 1L, 2L, + 1L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 2L, 1L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 1L, 1L, 2L, 1L, + 2L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L), + levels = c("N", "Y"), class = "factor" ) ) + +peanut %>% + ggplot(aes(x=factor(AVAL), fill=TRT01P)) + + geom_bar( position=position_dodge(preserve = "single")) + + scale_fill_manual(values=c("royalblue", "#fb6a4a", "#ef3b2c", "#cb181d", "#a50f15", "#67000d")) + + xlab("Peanut protein tolerated without dose-limiting symptoms (mg)") + + ylab("Patients") + + theme(legend.position="right") + +peanut %>% + group_by(TRT01P) %>% + summarize(proportion = sum(CRIT1FL=="Y") / n()) %>% + ggplot(aes(x=TRT01P, y=proportion, fill=TRT01P)) + + geom_bar(stat="identity", position=position_dodge()) + + geom_text(aes(label=scales::percent(proportion)), size=7, vjust=c(0, rep(1.5, 5))) + + scale_fill_manual(values=c("royalblue", "#fb6a4a", "#ef3b2c", "#cb181d", "#a50f15", "#67000d")) + + scale_y_continuous(breaks=seq(0, 0.7, 0.1), labels=scales::percent) + + xlab("Tolerating >=600 mg of peanut protein without dose-limiting symptoms") + + ylab("Percentage of patients") +``` + +#### Excercise 1A + +Fit a sigmoid Emax logistic regression model +(i.e. `family=binomial(link="logit")`). +To accelerate likelihood evaluations, first summarize data as "responders" `y` +out of `n` patients per dose. We tell `brms` to use this information using +`y | trials(n) ~ ...`. +We expect the true placebo proportion to be around 0.05 (-2.944 on the logit +scale) with much more than 0.1 or much less than 0.02 considered unlikely. +It is a-priori at least possible that there is a huge treatment effect such +as 95\% versus 5\% responders (difference on the log-scale close to 6), +but we are at least mildly skeptical. +The dose response is a-priori somewhat likely to follow a Emax curve (with Hill +parameter near 1), but we wish to allow for the possibility of a steeper or +shallower curve. The dose with half the effect (ED50) might be near 15 mg, but +have considerable uncertainty around that. + +```{r peanutmodel1} +model1 <- bf( #---- INSERT YOUR CODE HERE --- # ~ E0 + Emax * dose^h/(dose^h + ED50^h), + family= #---- INSERT YOUR CODE HERE --- # , + nlf(h ~ exp(logh)), nlf(ED50 ~ exp(logED50)), + E0 ~ 1, logED50 ~ 1, logh ~ 1, Emax ~ 1, + nl=TRUE) + +priors1 <- prior(normal(-2.944, 0.38), nlpar="E0") + + prior(normal(0, 1), nlpar="logh") + + prior(normal(0, 6), nlpar="Emax") + + prior(normal(2.7, 2.5), nlpar="logED50") +``` + +Now fit the model +```{r fitpeanutmodel1} +brmfit1 <- brm( + formula = model1, + prior = priors1, + data = peanut %>% group_by(TRT01P, dose) %>% summarize(y=sum(CRIT1FL=="Y"), n=n(), .groups="drop") +) +``` + +Now use +`tibble(dose = seq(0, 300, 1), n=1) %>% tidybayes::add_epred_rvars(object=brmfit1, newdata=.)` +to obtain predicted proportions for every dose from 0 to 300 mg. +Then, plot the curve of predicted proportions for each of these doses using +`ggplot2` and `ggdist` (using `ydist=.epred` in the aesthetics and the +`stat_lineribbon()` geom from the `ggdist` package). + +```{r plotmodel1preds} +tibble(dose = seq(0, 300, 1), n=1) %>% + tidybayes::add_epred_rvars(object=brmfit1, newdata=.) + # ---- INSERT YOUR CODE HERE ---- # +``` + +If you prefer, replace the default fill colors e.g. with shades of blue +using `+ scale_fill_brewer(palette="Blues") + +Next obtain the posterior distribution for the difference in proportions for +each of these dose levels compared with placebo. + +```{r model1diffs} +# ---- INSERT YOUR CODE HERE ---- # +``` + +#### Excercise 1B* (optional) + +Try to perform a more efficient statistical analysis by either using an ordinal +outcome or treating the data as interval censored. + +Try treating the data about the logarithm of the amount of protein +tolerated as interval censored +($-\infty$ to = log(3) to % + mutate(study=paste("new_study", region, sep="_"), r=0, n=6) + +# Your solution here ----------------------------------------------------------- + +## to get brms to include the other factor level in the model, we have +## to add a fake row with region "other" and n=0 +AS_region_2 <- mutate(bind_rows(AS_region, mutate(AS_region_all, n=0)[4,]), + region=factor(region, levels=c("asia", "europe", "north_america", "other"))) + +str(AS_region_2) + +model_fixed <- bf(r | trials(n) ~ 1 + region + (1|study), family=binomial) + +get_prior(model_fixed, AS_region_2) + +model_fixed_prior <- prior(normal(0, 2), class="Intercept") + + prior(normal(0, 1), class="sd", coef="Intercept", group="study") + + prior(normal(0, log(2)/1.96), class="b") + +fixed_mc_brms <- brm(model_fixed, AS_region_2, prior=model_fixed_prior, + seed=4767, + silent = 2, refresh = 0, control=list(adapt_delta=0.99)) + +fixed_mc_brms + +post_regions <- posterior_linpred(fixed_mc_brms, + newdata=AS_region_all, + transform=TRUE, + allow_new_levels=TRUE, + sample_new_levels="gaussian") + +head(post_regions) + +colnames(post_regions) <- AS_region_all$region +map_region <- list() +for(r in AS_region_all$region) { + map_region[[r]] <- mixfit(post_regions[,r], type="beta", Nc=3, constrain_gt1=TRUE) +} + +kable(bind_rows(lapply(map_region, summary), .id="MAP"), digits=3) + +sapply(map_region, ess) + +``` + + +## Exercise 3 + +Run the analysis for the normal endpoint in the `crohn` data set of +`RBesT`. Refer to the `RBesT` vignette for a [normal endpoint](https://cran.r-project.org/web/packages/RBesT/vignettes/introduction.html) + on more details and context. +Steps: +* Use as `family=gaussian` and use the `se` response modifier in place + of `trials` to specify a known standard error. +* Use the same priors as proposed in the vignette. +* Compare the obtained MAP prior (in MCMC sample form) from `RBesT` + and `brms`. + +```{r} +crohn <- RBesT::crohn + +# Your solution here ----------------------------------------------------------- + +crohn_sigma <- 88 +crohn$y.se <- crohn_sigma/sqrt(crohn$n) + +library(RBesT) +set.seed(1234) +rbest_normal_map_mcmc <- gMAP(cbind(y, y.se) ~ 1 | study, + weights=n, data=crohn, + family=gaussian, + beta.prior=cbind(0, crohn_sigma), + tau.dist="HalfNormal",tau.prior=cbind(0,crohn_sigma/2)) + +model_normal <- bf(y | se(y.se) ~ 1 + (1 | study), family=gaussian()) + +prior_normal <- prior(normal(0, 88), class="Intercept") + + prior(normal(0, 88/2), class="sd", coef="Intercept", group="study") + +brms_normal_map_mcmc <- brm(model_normal, crohn, prior=prior_normal, + seed=4767, + silent = 2, refresh = 0, control=list(adapt_delta=0.99)) + +## comparing the outputs we see that the random effect posterior +## matches... +rbest_normal_map_mcmc +brms_normal_map_mcmc + +brms_normal_map <- posterior_epred(brms_normal_map_mcmc, + newdata=data.frame(study="new", n=1, y=0, y.se=88), + allow_new_levels=TRUE, + sample_new_levels="gaussian") + +## ... and the MAP prior is also the same +summarise_draws(brms_normal_map, mean, sd, ~quantile(., probs = c(0.025, 0.5, 0.975))) + +``` + + + diff --git a/workshops/jsm2024/exercises/solutions/ex2_dose_finding.qmd b/workshops/jsm2024/exercises/solutions/ex2_dose_finding.qmd new file mode 100644 index 0000000..c836043 --- /dev/null +++ b/workshops/jsm2024/exercises/solutions/ex2_dose_finding.qmd @@ -0,0 +1,410 @@ +--- +title: 'brms exercises: dose finding' +author: 'Björn Holzhauer' +date: today +output: + html_document: + toc: true + embed-resources: true +--- + +```{r setup, include=FALSE} +knitr::opts_chunk$set(echo = TRUE) +``` + +## Background + +This exercises accompany the case study [Dose Finding](https://opensource.nibr.com/bamdd/src/02b_dose_finding.html) as part of the training course "Workshop on applied modeling in drug development using brms." + +## Preliminary code + +First load the required packages and set some options as described in the case study. + +```{r, warning = FALSE, message = FALSE} +here::i_am("exercises/solutions/ex2_dose_finding.qmd") +library(here) +library(tidyverse) +library(brms) +library(tidybayes) +library(ggrepel) +library(patchwork) +library(ggplot2) +library(ggdist) + +# Set defaults for ggplot2 ---- +theme_set(theme_bw(base_size=18) + + theme(legend.position = "bottom")) + +scale_colour_discrete <- function(...) { + # Alternative: ggsci::scale_color_nejm(...) + # scale_colour_brewer(..., palette="Set1") + ggthemes::scale_colour_colorblind(...) +} +scale_fill_discrete <- function(...) { + # Alternative: ggsci::scale_fill_nejm(...) + #scale_fill_brewer(..., palette="Set1") + ggthemes::scale_fill_colorblind(...) +} +scale_colour_continuous <- function(...) { + scale_colour_viridis_c(..., option="turbo") +} +update_geom_defaults("point", list(size=2)) +update_geom_defaults("line", list(size=1.5)) + +options( + # how many processor cores would you like to use? + mc.cores = 4, + # how would you like to access Stan? + brms.backend = "cmdstanr", + # cache model binaries + cmdstanr_write_stan_file_dir=here::here("_brms-cache"), + # no need to normalize likelihoods + brms.normalize = FALSE, + # when you are storing your model to file, + # how shall it be updated? + brms.file_refit = "on_change" + # alternatives: "never", "always" + # use "never" for production +) + +set.seed(254335) +``` + +## Excercise: + +### Exercise 1: Peanut allergy + +In a hypothetical phase 2b trial children and adolescents with peanut allergy +were randomly assigned to placebo or 5 doses (5 to 300 mg) of a new drug. +After 26 weeks the patients underwent a double-blind placebo-controlled +food challenge and the primary endpoint was the proportion of patients that +could ingest 600 mg or more of peanut protein without experiencing dose-limiting +symptoms. The plots below show an overview of the data. Note that no placebo +patients were considered "responders" per the primary endpoint definition. + +```{r peanutdata, class.source = 'fold-hide'} +peanut <- tibble( + TRT01P = structure(c( + 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, + 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 5L, 5L, 5L, 5L, 5L, 5L, + 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, + 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, + 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, + 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, + 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, + 3L, 3L, 3L, 3L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, + 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L + ),levels = c("PBO", "5 mg", "15 mg", "50 mg", "150 mg", "300 mg"), class = "factor"), + dose = c( + 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, + 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, + 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 300L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, + 150L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, + 150L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, 150L, + 150L, 150L, 150L, 150L, 150L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, + 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, + 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 50L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, + 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, + 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 15L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, + 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, + 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, + 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, + 0L, 0L), + AVAL = c( + 1000L, 100L, 0L, 600L, 600L, 300L, 300L, 10L, 10L, 100L, 1000L, 300L, 10L, 1000L, 1000L, 1000L, 3L, 3L, 600L, 300L, + 1000L, 1000L, 100L, 1000L, 600L, 600L, 1000L, 1000L, 1000L, 300L, 600L, 1000L, 1000L, 1000L, 1000L, 1000L, 300L, + 1000L, 600L, 1000L, 300L, 1000L, 600L, 600L, 1000L, 1000L, 600L, 1000L, 1000L, 1000L, 1000L, 600L, 1000L, 1000L, + 1000L, 0L, 600L, 300L, 1000L, 1000L, 1000L, 100L, 1000L, 1000L, 600L, 1000L, 1000L, 1000L, 600L, 300L, 600L, 600L, + 100L, 600L, 1000L, 300L, 10L, 3L, 1000L, 300L, 300L, 1000L, 300L, 300L, 10L, 1000L, 1000L, 10L, 10L, 600L, 3L, 10L, + 600L, 600L, 600L, 1000L, 100L, 1000L, 1000L, 10L, 1000L, 3L, 10L, 1000L, 100L, 1000L, 100L, 10L, 300L, 10L, 100L, + 1000L, 0L, 1000L, 100L, 3L, 10L, 100L, 100L, 300L, 1000L, 1000L, 1000L, 10L, 1000L, 3L, 3L, 600L, 600L, 10L, 3L, + 600L, 600L, 300L, 300L, 1000L, 3L, 3L, 1000L, 10L, 1000L, 1000L, 600L, 100L, 300L, 600L, 10L, 100L, 0L, 100L, 3L, + 0L, 10L, 3L, 600L, 300L, 300L, 300L, 600L, 300L, 100L, 3L, 0L, 10L, 600L, 300L, 10L, 300L, 600L, 1000L, 600L, 600L, + 0L, 0L, 600L, 600L, 600L, 0L, 0L, 300L, 100L, 0L, 10L, 300L, 1000L, 300L, 600L, 600L, 300L, 10L, 600L, 100L, 100L, + 300L, 3L, 3L, 300L, 1000L, 10L, 3L, 100L, 3L, 100L, 100L, 300L, 3L, 3L, 600L, 300L, 3L, 3L, 3L, 300L, 3L, 0L, 10L, + 3L, 300L, 10L, 10L, 600L, 0L, 300L, 600L, 0L, 0L, 100L, 100L, 10L, 100L, 10L, 100L, 600L, 0L, 600L, 0L, 10L, 100L, + 0L, 0L, 0L, 0L, 3L, 10L, 3L, 300L, 600L, 0L, 0L, 300L, 10L, 10L, 100L, 300L, 0L, 0L, 0L, 3L, 0L, 0L, 10L, 0L, 300L, + 3L, 0L, 0L, 0L, 0L, 100L, 3L, 0L, 3L, 10L, 10L, 3L, 0L, 3L, 10L, 3L, 0L, 0L, 3L, 100L, 0L, 0L, 300L, 0L, 0L, 0L, + 0L, 0L, 0L, 0L, 3L, 0L, 3L, 0L, 0L, 0L, 0L), + PARAM = structure( + c( + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L), + levels = "Tolerated amount of peanut protein (mg)", class = "factor"), + USUBJID = c( + 129L, 103L, 104L, 185L, 34L, 15L, 140L, 151L, 7L, 144L, 200L, 23L, 138L, 115L, 255L, 147L, 224L, 101L, 156L, 284L, + 136L, 248L, 179L, 298L, 168L, 295L, 289L, 241L, 36L, 27L, 123L, 266L, 11L, 291L, 236L, 130L, 173L, 195L, 203L, 160L, + 274L, 167L, 290L, 94L, 9L, 269L, 122L, 135L, 64L, 26L, 95L, 10L, 5L, 234L, 161L, 299L, 88L, 69L, 35L, 233L, 286L, + 85L, 91L, 189L, 80L, 152L, 223L, 287L, 244L, 57L, 108L, 18L, 62L, 157L, 300L, 283L, 164L, 243L, 89L, 220L, 24L, 271L, + 166L, 118L, 201L, 127L, 121L, 41L, 267L, 213L, 49L, 73L, 202L, 134L, 112L, 25L, 227L, 29L, 251L, 273L, 119L, 132L, + 74L, 270L, 83L, 37L, 181L, 258L, 253L, 48L, 120L, 54L, 277L, 176L, 65L, 264L, 107L, 171L, 262L, 162L, 187L, 272L, + 288L, 294L, 245L, 109L, 172L, 204L, 275L, 22L, 66L, 186L, 247L, 17L, 149L, 141L, 177L, 280L, 216L, 40L, 75L, 263L, + 246L, 14L, 81L, 260L, 153L, 45L, 237L, 8L, 117L, 86L, 296L, 146L, 154L, 116L, 38L, 100L, 191L, 175L, 92L, 158L, 192L, + 180L, 256L, 254L, 125L, 222L, 145L, 261L, 155L, 159L, 3L, 206L, 278L, 19L, 31L, 63L, 208L, 55L, 259L, 218L, 111L, + 226L, 33L, 2L, 44L, 297L, 53L, 87L, 16L, 28L, 90L, 207L, 56L, 137L, 128L, 178L, 142L, 143L, 148L, 193L, 229L, 265L, + 97L, 252L, 205L, 150L, 165L, 188L, 52L, 99L, 93L, 1L, 221L, 124L, 210L, 6L, 232L, 21L, 211L, 163L, 96L, 60L, 183L, + 190L, 242L, 42L, 46L, 67L, 126L, 209L, 72L, 194L, 238L, 184L, 39L, 105L, 249L, 61L, 113L, 30L, 77L, 12L, 4L, 51L, + 139L, 20L, 268L, 215L, 292L, 217L, 199L, 32L, 276L, 47L, 225L, 230L, 79L, 71L, 98L, 50L, 13L, 76L, 231L, 250L, 58L, + 68L, 239L, 198L, 293L, 212L, 110L, 59L, 182L, 133L, 170L, 43L, 282L, 281L, 131L, 114L, 196L, 214L, 285L, 70L, 102L, + 279L, 106L, 197L, 257L, 228L, 84L, 235L, 78L, 169L, 240L, 219L, 174L, 82L), + CRIT1 = structure(c( + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L), + levels = "Tolerating >=600 mg of peanut protein without dose-limiting symptoms", class = "factor"), + CRIT1FL = structure(c( + 2L, 1L, 1L, 2L, 2L, 1L, 1L, 1L, 1L, 1L, 2L, 1L, 1L, 2L, 2L, 2L, 1L, 1L, 2L, 1L, 2L, 2L, 1L, 2L, 2L, 2L, 2L, 2L, + 2L, 1L, 2L, 2L, 2L, 2L, 2L, 2L, 1L, 2L, 2L, 2L, 1L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 1L, + 2L, 1L, 2L, 2L, 2L, 1L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 1L, 2L, 2L, 1L, 2L, 2L, 1L, 1L, 1L, 2L, 1L, 1L, 2L, 1L, 1L, + 1L, 2L, 2L, 1L, 1L, 2L, 1L, 1L, 2L, 2L, 2L, 2L, 1L, 2L, 2L, 1L, 2L, 1L, 1L, 2L, 1L, 2L, 1L, 1L, 1L, 1L, 1L, 2L, + 1L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 2L, 1L, 2L, 1L, 1L, 2L, 2L, 1L, 1L, 2L, 2L, 1L, 1L, 2L, 1L, 1L, 2L, 1L, + 2L, 2L, 2L, 1L, 1L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 1L, 1L, 1L, 2L, 1L, 1L, 1L, 1L, 1L, 2L, 1L, 1L, 1L, + 2L, 2L, 2L, 2L, 1L, 1L, 2L, 2L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 1L, 2L, 2L, 1L, 1L, 2L, 1L, 1L, 1L, 1L, 1L, + 1L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 1L, 1L, 2L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 1L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L), + levels = c("N", "Y"), class = "factor"), + CRIT2 = structure(c( + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L), + levels = "Tolerating >=1000 mg of peanut protein without dose-limiting symptoms", class = "factor"), + CRIT2FL = structure(c( + 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 1L, 1L, 2L, 2L, 2L, 1L, 1L, 1L, 1L, 2L, 2L, 1L, 2L, 1L, 1L, 2L, 2L, + 2L, 1L, 1L, 2L, 2L, 2L, 2L, 2L, 1L, 2L, 1L, 2L, 1L, 2L, 1L, 1L, 2L, 2L, 1L, 2L, 2L, 2L, 2L, 1L, 2L, 2L, 2L, 1L, + 1L, 1L, 2L, 2L, 2L, 1L, 2L, 2L, 1L, 2L, 2L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 1L, 1L, 1L, 2L, 1L, 1L, 2L, 1L, 1L, + 1L, 2L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 1L, 2L, 2L, 1L, 2L, 1L, 1L, 2L, 1L, 2L, 1L, 1L, 1L, 1L, 1L, 2L, + 1L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 2L, 1L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 1L, 1L, 2L, 1L, + 2L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, + 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L), + levels = c("N", "Y"), class = "factor" ) ) + +peanut %>% + ggplot(aes(x=factor(AVAL), fill=TRT01P)) + + geom_bar( position=position_dodge(preserve = "single")) + + scale_fill_manual(values=c("royalblue", "#fb6a4a", "#ef3b2c", "#cb181d", "#a50f15", "#67000d")) + + xlab("Peanut protein tolerated without\ndose-limiting symptoms (mg)") + + ylab("Patients") + + theme(legend.position="right") + +peanut %>% + group_by(TRT01P) %>% + summarize(proportion = sum(CRIT1FL=="Y") / n()) %>% + ggplot(aes(x=TRT01P, y=proportion, fill=TRT01P)) + + geom_bar(stat="identity", position=position_dodge()) + + geom_text(aes(label=scales::percent(proportion)), size=7, vjust=c(0, rep(1.5, 5))) + + scale_fill_manual(values=c("royalblue", "#fb6a4a", "#ef3b2c", "#cb181d", "#a50f15", "#67000d")) + + scale_y_continuous(breaks=seq(0, 0.7, 0.1), labels=scales::percent) + + xlab("Tolerating >=600 mg of peanut protein\nwithout dose-limiting symptoms") + + ylab("Percentage of patients") +``` + +#### Excercise 1A + +Fit a sigmoid Emax logistic regression model +(i.e. `family=binomial(link="logit")`). +To accelerate likelihood evaluations, first summarize data as "responders" `y` +out of `n` patients per dose. We tell `brms` to use this information using +`y | trials(n) ~ ...`. +We expect the true placebo proportion to be around 0.05 (-2.944 on the logit +scale) with much more than 0.1 or much less than 0.02 considered unlikely. +It is a-priori at least possible that there is a huge treatment effect such +as 95\% versus 5\% responders (difference on the log-scale close to 6), +but we are at least mildly skeptical. +The dose response is a-priori somewhat likely to follow a Emax curve (with Hill +parameter near 1), but we wish to allow for the possibility of a steeper or +shallower curve. The dose with half the effect (ED50) might be near 15 mg, but +have considerable uncertainty around that. + +```{r peanutmodel1} +model1 <- bf( y | trials(n) ~ E0 + Emax * dose^h/(dose^h + ED50^h), + family=binomial(link="logit"), + nlf(h ~ exp(logh)), nlf(ED50 ~ exp(logED50)), + E0 ~ 1, logED50 ~ 1, logh ~ 1, Emax ~ 1, + nl=TRUE) + +priors1 <- prior(normal(-2.944, 0.38), nlpar="E0") + + prior(normal(0, 1), nlpar="logh") + + prior(normal(0, 6), nlpar="Emax") + + prior(normal(2.7, 2.5), nlpar="logED50") +``` + +Now fit the model +```{r fitpeanutmodel1} +brmfit1 <- brm( + formula = model1, + prior = priors1, + data = peanut %>% group_by(TRT01P, dose) %>% summarize(y=sum(CRIT1FL=="Y"), n=n(), .groups="drop") +) + +summary(brmfit1) +``` + +Now use +`tibble(dose = seq(0, 300, 1), n=1) %>% tidybayes::add_epred_rvars(object=brmfit1, newdata=.)` +to obtain predicted proportions for every dose from 0 to 300 mg. +Then, plot the curve of predicted proportions for each of these doses using +`ggplot2` and `ggdist` (using `ydist=.epred` in the aesthetics and the +`stat_lineribbon()` geom from the `ggdist` package). + +```{r plotmodel1preds} +tibble(dose = seq(0, 300, 1), n=1) %>% + tidybayes::add_epred_rvars(object=brmfit1, newdata=.) %>% + ggplot(aes(x=dose, ydist=.epred)) + + stat_lineribbon() + + scale_fill_brewer(palette="Blues") + + ylab("Model predicted proportion") + + xlab("Dose [mg]") +``` + +If you prefer, replace the default fill colors e.g. with shades of blue +using `+ scale_fill_brewer(palette="Blues") + +Next obtain the posterior distribution for the difference in proportions for +each of these dose levels compared with placebo. + +```{r model1diffs} +tibble(dose = seq(0, 300, 1), n=1) %>% + tidybayes::add_epred_rvars(object=brmfit1, newdata=.) %>% + (\(x) x %>% + left_join(x %>% + filter(dose==0) %>% + dplyr::select(-dose) %>% + rename(.pbo=.epred), + by="n"))() %>% + mutate(diff = .epred - .pbo) %>% + ggplot(aes(x=dose, ydist=diff)) + + stat_lineribbon() + + scale_fill_brewer(palette="Blues") + + ylab("Model predicted difference\nin proportion vs. placebo") + + xlab("Dose [mg]") +``` + +#### Excercise 1B* (optional) + +Try to perform a more efficient statistical analysis by either using an ordinal +outcome or treating the data as interval censored. + +Try treating the data about the logarithm of the amount of protein +tolerated as interval censored +($-\infty$ to = log(3) to = 1000 mg, still conceivable could reach 300000 + prior(normal(2.7, 2.5), nlpar="logED50") + # No particular reason to change prior here (ED50 could be same as for binomial outcome) + prior(lognormal(5, 10), class=sigma) # Additional parameter: residual SD, clearly a decent amount of variability, want to allow large SD on the log-scale + +brmfit2 <- brm( + formula = model2, + prior = priors2, + data = peanut %>% + mutate(logAVAL = ifelse(AVAL==0, -28, log(AVAL)), + logAVALnext = case_when(AVAL==0 ~ log(3), + AVAL==3 ~ log(10), + AVAL==10 ~ log(100), + AVAL==100 ~ log(300), + AVAL==300 ~ log(600), + AVAL==600 ~ log(1000), + TRUE ~ log(30000))) + ) + +summary(brmfit2) +``` + + +Now use an ordinal +outcome via `family = cumulative(link = "logit", threshold = "flexible")`. +Details of this model are described in +[a journal article](https://doi.org/10.1177/2515245918823199), for which there +is also [a PsyArXiv preprint](https://doi.org/10.31234/osf.io/x8swp) in case +you do not have access to the journal. + +Note that the placebo response is described by a series of $K-1$ ordered +thresholds $\theta_1, \ldots, \theta_{K-1}$ on the logit scale for $K$ ordered +categories. For the placebo group, the probability of being in categories +$k=1, \ldots, K-1$ is be given by $\text{logit}^{-1} \theta_k$ and for category +$K$ by $1-\text{logit}^{-1} \theta_{K-1}$. In this case, these parameters will +appear in `summary(brmfit3)` as `Intercept[1]`, ..., `Intercept[6]`. + +How would you change the model and the prior distributions in this case? + +```{r ordinal} +# Note that we omit E0 from the model, because the exepcted placebo outcome +# is already described by the intercept parameters of the distribution +model3 <- bf( AVAL ~ Emax * dose^h/(dose^h + ED50^h), + family = cumulative(link = "logit", threshold = "flexible"), + nlf(h ~ exp(logh)), nlf(ED50 ~ exp(logED50)), + logED50 ~ 1, logh ~ 1, Emax ~ 1, + nl=TRUE) + +priors3 <- prior(normal(0, 1), nlpar="logh") + # No particular reason to change prior here + prior(normal(0, 6), nlpar="Emax") + # Prior could be different (odds from one category to the next not necessarily the same as for top two vs. rest) + prior(normal(2.7, 2.5), nlpar="logED50") + # No particular reason to change prior here + prior(student_t(3, -0.2, 1), class=Intercept, coef=1) + + prior(student_t(3, 0, 2.5), class=Intercept, coef=2) + + prior(student_t(3, 0, 2.5), class=Intercept, coef=3) + + prior(student_t(3, 0, 2.5), class=Intercept, coef=4) + + prior(student_t(3, 2.197, 1), class=Intercept, coef=5) + + prior(student_t(3, 0, 2.5), class=Intercept, coef=6) + +brmfit3 <- brm( + formula = model3, + prior = priors3, + data = peanut %>% mutate(AVAL = ordered(AVAL)) + ) + +summary(brmfit3) +``` + +For both the ordinal model and the interval-censored model, if we had a baseline +amount of protein tolerated, we could treat this as a monotonic covariate e.g. +using `mo(BASE)` or assume a particular functional form. \ No newline at end of file diff --git a/workshops/jsm2024/schedule.png b/workshops/jsm2024/schedule.png new file mode 100644 index 0000000..f8bacf1 Binary files /dev/null and b/workshops/jsm2024/schedule.png differ diff --git a/workshops/jsm2024/slides/R/01_overview.R b/workshops/jsm2024/slides/R/01_overview.R new file mode 100644 index 0000000..34e853c --- /dev/null +++ b/workshops/jsm2024/slides/R/01_overview.R @@ -0,0 +1,117 @@ +here::i_am("/home/beanan1/work/brms_course/slides/R/01_overview.R") +library(here) + +library(here) +library(knitr) +library(tidyverse) +library(ggrepel) +library(latex2exp) +library(patchwork) +library(glue) +library(RBesT) +library(rstan) +library(brms) +library(posterior) +library(tidybayes) +library(bayesplot) +library(gt) +library(ggdist) +library(distributional) +library(mvtnorm) +library(dqrng) +library(emmeans) +library(simsurv) + +## ----echo=FALSE, out.height="2in", out.width="2in"---------------------------- +knitr::include_graphics("graphics/brms.png") + + +## ----------------------------------------------------------------------------- +#| eval: false +## formula = y ~ 1 + x + (1 | g) + + +## ----------------------------------------------------------------------------- +#| eval: false +## formula = y ~ 1 + x + (1 + x | g) + + +## ----------------------------------------------------------------------------- +#| eval: false +## formula = y ~ 1 + gp(x) + (1 + x | g) + + +## ----------------------------------------------------------------------------- +#| eval: false +#| echo: true +#| mysize: true +#| size: '\small' +## formula = bf( +## y ~ 1 + x + (1 | g) + ..., +## par2 ~ 1 + x + (1 | g) + ..., +## par3 ~ 1 + x + (1 | g) + ..., +## ) + + +## ----------------------------------------------------------------------------- +#| eval: false +#| echo: true +#| mysize: true +#| size: '\small' +## formula = bf( +## y ~ fun(x, nlpar1, nlpar2), +## nlpar1 ~ 1 + x + (1 | g) + ..., +## nlpar2 ~ 1 + (1 | g) + ..., +## nl = TRUE +## ) + + +## ----------------------------------------------------------------------------- +#| eval: false +#| echo: true +#| mysize: true +#| size: '\small' +## family = brmsfamily( +## family = "", link = "", +## more_link_arguments +## ) + + +## ----------------------------------------------------------------------------- +#| eval: false +#| echo: true +#| mysize: true +#| size: '\small' +## family = brmsfamily(family = "gaussian", link = "identity", +## link_sigma = "log") + + +## ----------------------------------------------------------------------------- +#| eval: false +#| echo: true +#| mysize: true +#| size: '\small' +## family = brmsfamily(family = "poisson", link = "log") + + +## ----------------------------------------------------------------------------- +#| mysize: true +#| size: '\tiny' +options( + # how many processor cores would you like to use? + mc.cores = 4, + # how would you like to access Stan? + brms.backend = "cmdstanr", + # cache model binaries + cmdstanr_write_stan_file_dir=here::here("_brms-cache"), + # no need to normalize likelihoods + brms.normalize = FALSE, + # when you are storing your model to file, + # how shall it be updated? + brms.file_refit = "on_change" + # alternatives: "never", "always" + # use "never" for production +) +# create cache directory if not yet available +dir.create(here::here("_brms-cache"), FALSE) + diff --git a/workshops/jsm2024/slides/R/02_hist_control.R b/workshops/jsm2024/slides/R/02_hist_control.R new file mode 100644 index 0000000..f821fdf --- /dev/null +++ b/workshops/jsm2024/slides/R/02_hist_control.R @@ -0,0 +1,129 @@ +here::i_am("slides/R/02_hist_control.R") +library(here) + +library(here) +library(knitr) +library(tidyverse) +library(ggrepel) +library(latex2exp) +library(patchwork) +library(glue) +library(RBesT) +library(rstan) +library(brms) +library(posterior) +library(tidybayes) +library(bayesplot) +library(gt) +library(ggdist) +library(distributional) +library(mvtnorm) +library(dqrng) +library(emmeans) +library(simsurv) + +## ----echo = FALSE, include = FALSE-------------------------------------------- +AS_region <- withr::with_seed(63523, bind_cols(AS, region=sample(c("asia", "europe", "north_america"), 8, TRUE))) + +## ----echo=FALSE--------------------------------------------------------------- +kable(AS_region) + + +## ----------------------------------------------------------------------------- +#| mysize: true +#| size: '\small' +form_AS <- bf(r | trials(n) ~ 1 + (1|study), + family = binomial("logit")) + + +## ----------------------------------------------------------------------------- +#| eval=FALSE +## get_prior(form_AS, data = AS) + + +## ----------------------------------------------------------------------------- +#| mysize: true +#| size: '\small' +bprior_AS <- prior(normal(0, 2), class = "Intercept") + + prior(normal(0, 1), class = "sd", coef = "Intercept", + group = "study") + + +## ----------------------------------------------------------------------------- +#| mysize: true +#| size: '\small' +#| results: "hide" +fit_AS <- brm( + form_AS, data = AS, prior = bprior_AS, + seed = 2454 +) + + +## ----------------------------------------------------------------------------- +#| mysize: true +#| size: '\tiny' +summary(fit_AS) + + +## ----------------------------------------------------------------------------- +AS_new <- data.frame(study = "new_study", n = 1) +pe <- posterior_epred( + fit_AS, newdata = AS_new, allow_new_levels = TRUE, + sample_new_levels = "gaussian" +) +posterior_summary(pe) + + +## ----fig.height=4, fig.width=7------------------------------------------------ +pe_mix <- RBesT::automixfit(pe[, 1], type = "beta") +plot(pe_mix)$mix + + +## ----------------------------------------------------------------------------- +#| mysize: true +#| size: '\small' +form_AS_region <- bf(r | trials(n) ~ 1 + (1 | region/study), + family = binomial("logit")) + + +## ----------------------------------------------------------------------------- +#| mysize: true +#| size: '\footnotesize' +bprior_AS_region <- prior(normal(0, 2), class="Intercept") + + prior(normal(0, 0.5), class="sd", coef="Intercept", + group="region") + + prior(normal(0, 0.25), class="sd", coef="Intercept", + group="region:study") + + +## ----------------------------------------------------------------------------- +#| mysize: true +#| size: '\small' +#| results: 'hide' +fit_AS_region <- brm( + form_AS_region, data = AS_region, + prior = bprior_AS_region, seed = 2341 +) + + +## ----mysize=TRUE, size = "\\tiny"--------------------------------------------- +summary(fit_AS_region) + + +## ----------------------------------------------------------------------------- +AS_region_new <- data.frame(study = "new_study_asia", + n = 1, region = "asia") + +pe_region <- posterior_epred( + fit_AS_region, newdata = AS_region_new, + allow_new_levels = TRUE, + sample_new_levels = "gaussian" +) +posterior_summary(pe_region) + + +## ----fig.height=4, fig.width=7------------------------------------------------ +pe_mix_region <- + RBesT::automixfit(pe_region[, 1], type = "beta") +plot(pe_mix_region)$mix + diff --git a/workshops/jsm2024/slides/R/05_mmrm.R b/workshops/jsm2024/slides/R/05_mmrm.R new file mode 100644 index 0000000..3949212 --- /dev/null +++ b/workshops/jsm2024/slides/R/05_mmrm.R @@ -0,0 +1,398 @@ +here::i_am("slides/R/05_mmrm.R") +library(here) + +library(here) +library(knitr) +library(tidyverse) +library(ggrepel) +library(latex2exp) +library(patchwork) +library(glue) +library(RBesT) +library(rstan) +library(brms) +library(posterior) +library(tidybayes) +library(bayesplot) +library(gt) +library(ggdist) +library(distributional) +library(mvtnorm) +library(dqrng) +library(emmeans) +library(simsurv) + +## ----------------------------------------------------------------------------- +#| echo: false +#| fig-height: 4 +tibble(arm = factor(rep(c("Drug 40 mg (N=50)", "Drug 20 mg (N=50)", "Drug 10 mg (N=50)", "Placebo (N=50)"), each=5), + levels=rev(c("Drug 40 mg (N=50)", "Drug 20 mg (N=50)", "Drug 10 mg (N=50)", "Placebo (N=50)"))), + x=rep(c(1, 3.65, 3.9, 3.65, 1), 4), + y=rep(c(1, 1, 0.6, 0.2, 0.2), 4)+ rep(3:0, each=5), + design="Parallel group dose finding") %>% + ggplot(aes(x=x, y=y, label=arm, fill=arm)) + + geom_polygon() + + theme_void() + + theme(legend.position="none", panel.border=element_rect(fill = NA), + strip.background=element_rect(fill="lightgrey"), + strip.text.x = element_text( margin = margin( b = 5, t = 5) ), + axis.line.x = element_line(color="black"), + axis.text.x = element_text(color="black", angle=0, hjust=0.5, size=18), + axis.ticks.x = element_line(color="black"), + axis.ticks.length.x = unit(0.2, "cm"), + axis.title.x = element_blank()) + + geom_text(data=.%>% filter(x==1 & y %in% c(1:10)), + aes(x=x+0.1, y=y-0.3), + size=10, + hjust = 0, vjust=0.75) + + geom_rect(aes(xmin=0, xmax=0.95, ymin=0.2, ymax=4), fill="darkgrey") + + geom_text(aes(x=0.45, y=1.9+0.2, label="Run-in"), size=10) + + scale_fill_manual(values=c("#377eb8", "#fd8d3c", "#f03b20", "#bd0026")) + + scale_x_continuous(breaks = c(0, 0.975, 1.6, 2.15, 2.9, 3.65), + labels = c(" Screening", "Baseline /\nRandomization", "Week 2", "Week 4", "Week 8", "Week 12")) + + +## ----------------------------------------------------------------------------- +#| echo: false +#| fig.height: 5 +# Correlation matrix between visits (baseline + 4 post-baseline visits) +corr_matrix <- diag(5) +rho <- c(0.6, 0.48, 0.4, 0.375) +corr_matrix[1,2:5] <- rho[1:4] +corr_matrix[2,3:5] <- rho[1:3] +corr_matrix[3,4:5] <- rho[1:2] +corr_matrix[4,5:5] <- rho[1:1] +corr_matrix[lower.tri(corr_matrix)] <- t(corr_matrix)[lower.tri(corr_matrix)] + +# Standard deviations by visit (baseline + 4 post-baseline visits) +sds <- sqrt(c(0.75, 0.8, 0.85, 0.95, 1.1)) + +cov_matrix <- diag(sds) %*% corr_matrix %*% diag(sds) +# print(cov_matrix, digits=2) + + +## ----------------------------------------------------------------------------- +#| echo: false +apply_colnames <- function(x,y){ + colnames(x) <- y + return(x) +} + + +## ----------------------------------------------------------------------------- +#| echo: false +# Simulate from multivariate normal for control group +# (before adding treatment effect later) +# We simulate 1000 patients and then apply inclusion criteria and keep the +# first 200 that meet them. +set.seed(4095867) +N <- 1000 +Nf <- 200 +simulated_data <- rmvnorm(n=N, mean=rep(0, 5), sigma = cov_matrix) %>% + # turn into tibble + apply_colnames(c("BASE", paste0("visit", 1:4))) %>% + as_tibble() %>% + # Apply inclusion criteria and keep first 200 patients + filter(BASE>0) %>% + filter(row_number()<=Nf) %>% + ##filter(row_number()<=200) %>% + # Assign subject ID, treatment group and create missing data + mutate(USUBJID = row_number(), + TRT01P = dqsample(x=c(0L, 10L, 20L, 40L), size=Nf, replace=T), + # Simulate dropouts + dropp2 = plogis(visit1-2), + dropp3 = plogis(visit2-2), + dropp4 = plogis(visit3-2), + dropv = case_when(runif(n=n())% + dplyr::select(-dropp2, -dropp3, -dropp4, -dropv) %>% + # Turn data into long-format + pivot_longer(cols=starts_with("visit"), names_to = "AVISIT", values_to="AVAL") %>% + mutate( + # Assign visit days + ADY = case_when(AVISIT=="visit1" ~ 2L*7L, + AVISIT=="visit2" ~ 4L*7L, + AVISIT=="visit3" ~ 8L*7L, + AVISIT=="visit4" ~ 12L*7L), + # Turn to factor with defined order of visits + AVISIT = factor(AVISIT, paste0("visit", 1:4)), + # Assume rising treatment effect over time (half there by week 3) with an + # Emax dose response (ED50 = 5 mg) + AVAL = AVAL + 0.9 * (ADY/7)^3/((ADY/7)^3+3^3) * TRT01P/(TRT01P+5), + # Change from baseline = value - baseline + CHG = AVAL - BASE, + TRT01P=factor(TRT01P)) %>% + relocate(USUBJID, TRT01P, AVISIT, ADY, AVAL, CHG, BASE) %>% + # Discard missing data + filter(!is.na(AVAL)) + +simulated_data <- simulated_data %>% + mutate(USUBJID=factor(USUBJID)) + + +## ----------------------------------------------------------------------------- +#| echo: false +#| fig-height: 6 +p1 <- simulated_data %>% + bind_rows(simulated_data %>% + group_by(USUBJID) %>% + slice_head(n=1) %>% + mutate(AVISIT="Baseline", ADY=0, AVAL=BASE) + ) %>% arrange(USUBJID, ADY) %>% + ggplot(aes(x=AVISIT, y=AVAL, col=TRT01P)) + + theme_bw(base_size=24) + + geom_jitter(height=0, width=0.2) + + scale_color_manual(values=c("#377eb8", "#fd8d3c", "#f03b20", "#bd0026")) + + guides(x = guide_axis(angle = 45)) + + theme(axis.title.x = element_blank(), + legend.position="none") + +p2 <- simulated_data %>% + ggplot(aes(x=AVISIT, y=CHG, fill=TRT01P)) + + geom_hline(yintercept=0) + + geom_jitter(alpha=0.4, size=0.65, col="black", shape=22, + position=position_jitterdodge(dodge.width=0.75, + jitter.width=0.2, jitter.height=0)) + + geom_boxplot(outlier.alpha = 0, alpha=0.8) + + theme_bw(base_size=24) + + scale_fill_manual(values=c("#377eb8", "#fd8d3c", "#f03b20", "#bd0026")) + + guides(x = guide_axis(angle = 45)) + + theme(legend.position="right", + axis.title.x = element_blank()) + +p1 + p2 + + +## ----------------------------------------------------------------------------- +#| echo: false +simulated_data %>% + filter(USUBJID %in% c(3, 9, 13)) %>% + gt() %>% + fmt_number(columns=c("AVAL", "CHG", "BASE"), decimals=2) %>% + opt_stylize(style = 6, color = "red", add_row_striping = TRUE) %>% + tab_style(locations = cells_body(rows = c(4, 8, 10)), + style = cell_borders(sides = "bottom", color = "black", weight = px(1.5),style = "solid")) + + +## PROC MIXED DATA=simulated_data; +## CLASS TRT01P AVISIT USUBJID; +## MODEL CHG ~ TRT01P AVISIT BASE TRT01P*AVISIT AVISIT*BASE +## / SOLUTION DDFM=KR ALPHA = 0.05; +## REPEATED AVISIT / TYPE=UN SUBJECT = USUBJID R Rcorr GROUP=TRT01P; +## LSMEANS TRT01P*AVISIT / DIFFS PDIFF CL OM E; +## RUN; + +## ----------------------------------------------------------------------------- +#| eval: false +#| mysize: true +#| size: '\small' +## library(mmrm) +## mmrm_fit <- mmrm( +## formula = CHG ~ TRT01P + AVISIT + BASE + AVISIT:TRT01P + +## AVISIT:BASE + us(AVISIT | TRT01P / USUBJID), +## method = "Kenward-Roger", +## vcov = "Kenward-Roger-Linear", # to match SAS +## data = simulated_data %>% mutate(USUBJID=factor(USUBJID)) +## ) + + +## ----------------------------------------------------------------------------- +#| eval: true +#| echo: false +library(mmrm) +mmrm_fit <- mmrm( + formula = CHG ~ TRT01P + AVISIT + BASE + AVISIT:TRT01P + + AVISIT:BASE + us(AVISIT | TRT01P / USUBJID), + method = "Kenward-Roger", + data = simulated_data %>% mutate(USUBJID=factor(USUBJID)) +) + + +## ----mysize=TRUE, size = "\\small"-------------------------------------------- +#| echo: false +#| include: false +options( + # how many processor cores would you like to use? + mc.cores = 4, + # how would you like to access Stan? + brms.backend = "cmdstanr", + # ensure the models don't need to be recompiled + # by storing the compiled model files in a folder + cmdstanr_write_stan_file_dir = here::here("_brms-cache") +) + + +## ----------------------------------------------------------------------------- +#| echo: FALSE +#| fig-height: 5 +tibble(treatment = c(rep("PHEN/TPM CR 15/92", 15), + rep("PHEN/TPM CR 3.75/23", 15), + rep("placebo", 15)), + week = rep(c(0,4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56), 3), + chg = c(0, -3.73913043478261, -5.88405797101449, -7.50724637681159,-8.95652173913043, -10.0289855072464, -11.1014492753623, -11.8260869565217, -12.3478260869565, -12.8405797101449, -13.2173913043478, -13.304347826087, -13.3333333333333, -13.4492753623188, -13.1884057971014, 0, -2.57971014492753, -3.50724637681159, -4.17391304347826, -4.98550724637681, -5.56521739130435, -6.14492753623188, -6.55072463768116, -6.72463768115942, -6.89855072463768, -7.18840579710145, -7.15942028985507, -6.98550724637681, -6.84057971014492, -6.7536231884058, 0, -1.07246376811594, -1.47826086956522, -1.88405797101449, -2.08695652173913, -2.31884057971014, -2.43478260869565, -2.60869565217391, -2.69565217391304, -2.78260869565217, -2.92753623188406, -2.66666666666666, -2.46376811594203, -2.34782608695652, + -2.20289855072464), + stderr = c(NA, 0.594202898550725, 0.347826086956522, 0.434782608695651, 0.521739130434784, 0.594202898550725, 0.666666666666667, 0.710144927536232, 0.826086956521739, 0.869565217391306, 0.898550724637681, 0.942028985507246, 0.956521739130435, 0.971014492753623, 1.02898550724638, NA, 0.449275362318841, 0.449275362318841, 0.579710144927535, 0.652173913043478, 0.797101449275361, 0.956521739130435, 1.02898550724638, 1.13043478260869, 1.15942028985507, 1.23188405797102, 1.31884057971014, 1.3768115942029, 1.44927536231884, 1.42028985507246, NA, 0.333333333333332, 0.289855072463769, 0.391304347826086, 0.492753623188404, 0.623188405797102, 0.724637681159422, 0.782608695652174, 0.840579710144927, 0.869565217391304, 0.956521739130434, 1.01449275362319, 1.05797101449275, 1.10144927536232, 1.10144927536232), + lcl = c(NA, -4.90374671545133, -6.56578457433277, -8.35940463095944, -9.97911164410786, -11.1936017879151, -12.4080919317224, -13.2179454382966, -13.9669267698374, -14.5448962184406, -14.97851836292, -15.150690710074, -15.2080814924586, -15.3524287965824, -15.205180331918, NA, -3.46027367421365, -4.3878099060977, -5.31012404900872, -6.26374462759858, -7.12750752390873, -8.01967569535715, -8.56749917249773, -8.94024914194382, -9.17097273569861, -9.60285418385369, -9.74430032743688, -9.68400838451167, -9.68110722397109, -9.53734015195544, NA, -1.72578509629596, -2.04636637233045, -2.65100039974755, -3.05273587644002, -3.54026741065539, -3.85504636560874, -4.14258050964004, -4.34315813193221, -4.48692520394787, -4.80228439100932, -4.65503592634498, -4.53735320103513, -4.5066269974644, -4.36169946123252), + ucl = c(NA, -2.57451415411388, -5.20233136769621, -6.65508812266374, -7.93393183415301, -8.86436922657765, -9.79480661900228, -10.4342284747469, -10.7287254040756, -11.1362632018492, -11.4562642457756, -11.4580049420999, -11.4585851742081, -11.5461219280553, -11.1716312622849, NA, -1.69914661564142, -2.62668284752548, -3.0377020379478, -3.70726986515503, -4.00292725869996, -4.27017937710661, -4.53395010286458, -4.50902622037501, -4.62612871357675, -4.7739574103492, -4.57454025227326, -4.28700610824195, -4.00005219631876, -3.96990622485616, NA, -0.419142439935926, -0.910155366799983, -1.11711554228143, -1.12117716703824, -1.09741374876489, -1.01451885178257, -1.07481079470778, -1.04814621589387, -1.07829218735647, -1.05278807275879, -0.678297406988347, -0.390183030848927, -0.189025176448641, -0.0440976402167492)) %>% + ggplot(aes(x=week, y=chg, ymin=lcl, ymax=ucl, col=treatment, label=treatment)) + + theme_bw(base_size=18) + + theme(legend.position="none") + + geom_hline(yintercept=0, alpha=0.5) + + geom_errorbar(width=0.75) + + geom_line() + + geom_point() + + geom_text(data=.%>% filter(week==48), size=6, + nudge_y=c(-3, -3.25, 3)) + + xlab("Time since randomization (weeks)") + + ylab("Percent change in body weight (%)") + + scale_color_manual(values=c("#e41a1c", "#ff7f00", "#377eb8")) + + +## ----------------------------------------------------------------------------- +contrasts(simulated_data$AVISIT) <- MASS::contr.sdif + + +## ----------------------------------------------------------------------------- +#| echo: false +contrasts(simulated_data$AVISIT) %>% MASS::fractions() + + +## ----------------------------------------------------------------------------- +# add the intercept +cmat <- cbind("1" = 1, contrasts(simulated_data$AVISIT)) +# compute the inverse matrix +solve(cmat) %>% MASS::fractions() + + +## ----------------------------------------------------------------------------- +#| mysize: true +#| size: '\footnotesize' +mmrm_model1 <- bf( + CHG ~ 1 + AVISIT + BASE + BASE:AVISIT + TRT01P + TRT01P:AVISIT + + unstr(time = AVISIT, gr = USUBJID), + sigma ~ 1 + AVISIT + TRT01P + AVISIT:TRT01P +) + + +## ----------------------------------------------------------------------------- +#| mysize: true +#| size: '\footnotesize' +mmrm_prior1 <- prior(normal(0, 2), class=Intercept) + + prior(normal(0, 1), class=b) + + prior(normal(0, log(10.0)/1.64), class=Intercept, dpar=sigma) + + prior(normal(0, log(2.0)/1.64), class=b, dpar=sigma) + + prior(lkj(1), class=cortime) + + +## ----------------------------------------------------------------------------- +#| eval: false +#| mysize: true +#| size: '\small' +## fit_mmrm1 <- brm( +## formula = mmrm_model1, +## data = simulated_data, +## prior = mmrm_prior1, +## ... +## ) + + +## ----------------------------------------------------------------------------- +#| echo: false +#| include: false +fit_mmrm1 <- brm( + formula = mmrm_model1, + prior = mmrm_prior1, + data = simulated_data, + seed = 234235 +) + + +## ----------------------------------------------------------------------------- +#| eval: false +#| mysize: true +#| size: '\scriptsize' + +## emm2 <- fit_mmrm1 %>% +## emmeans(~ TRT01P | AVISIT, weights="proportional") + + +## ----mysize = TRUE, size = "\\tiny\n"----------------------------------------- +#| echo: false + +emm2 <- fit_mmrm1 %>% + emmeans(~ TRT01P | AVISIT, weights="proportional", + at = list(AVISIT = c("visit1", "visit4"))) +emm2 + + +## ----------------------------------------------------------------------------- +#| mysize: true +#| size: '\footnotesize' +contrast(emm2, adjust="none", method="trt.vs.ctrl", ref="TRT01P0") + + +## ----------------------------------------------------------------------------- +#| mysize: true +#| size: '\footnotesize' +mmrm_model2 <- bf( + CHG ~ 1 + AVISIT + mo(TRT01P) + BASE + mo(TRT01P):AVISIT + + BASE:AVISIT + unstr(time = AVISIT, gr = USUBJID), + sigma ~1 + AVISIT + mo(TRT01P) + mo(TRT01P):AVISIT +) + + +## ----------------------------------------------------------------------------- +#| mysize: true +#| size: '\small' +#| eval: false +## fit_mmrm2 <- brm( +## formula = mmrm_model2, +## data = simulated_data %>% mutate(TRT01P=ordered(TRT01P)), +## prior = mmrm_prior1, +## ...) + + +## ----------------------------------------------------------------------------- +#| echo: false +#| include: false +fit_mmrm2 <- brm( + formula = mmrm_model2, + data = simulated_data %>% mutate(TRT01P=ordered(TRT01P)), + prior = mmrm_prior1, + seed = 234235 +) + + +## ----------------------------------------------------------------------------- +#| echo: false +#| fig-height: 6 +map2_dfr(list(mmrm_fit, fit_mmrm1, fit_mmrm2), + c("frequentist", "Bayesian", "Bayesian (monotonic)"), + \(x, y) emmeans(x, ~ TRT01P | AVISIT, weights="proportional") %>% + contrast(adjust="none", method="trt.vs.ctrl", ref="TRT01P0") %>% + confint() %>% + as_tibble() %>% + mutate(Model=y)) %>% + mutate(Week = c(2L, 4L, 8L, 12L)[as.integer(str_extract(AVISIT, "[0-9]+"))], + Model = factor(Model, + c("frequentist", "Bayesian", "Bayesian (monotonic)")), + Dose = str_remove(str_extract(contrast, "[0-9]+ -"), " -"), + lower.CL = ifelse(is.na(lower.CL), lower.HPD, lower.CL), + upper.CL = ifelse(is.na(upper.CL), upper.HPD, upper.CL)) %>% + ggplot(aes(x=Dose, y=estimate, ymin=lower.CL, ymax=upper.CL, col=Model)) + + geom_hline(yintercept=0, col="darkred", lty=2) + + theme_bw(base_size=24) + + theme(legend.position="bottom", + legend.title = element_text(size=22), + legend.text = element_text(size=22)) + + geom_point(position=position_dodge(width=0.4), size=4) + + geom_errorbar(position=position_dodge(width=0.4), lwd=1) + + scale_color_manual(values=c("#000000", "#E69F00", "#56B4E9")) + + facet_wrap(~Week, label=label_both, nrow=1, ncol=4) + + ylab("Difference to placebo") #+ guides(col=guide_legend(nrow=2, byrow=TRUE)) + diff --git a/workshops/jsm2024/slides/R/06_dose_finding.R b/workshops/jsm2024/slides/R/06_dose_finding.R new file mode 100644 index 0000000..fc99c42 --- /dev/null +++ b/workshops/jsm2024/slides/R/06_dose_finding.R @@ -0,0 +1,417 @@ +here::i_am("slides/R/06_dose_finding.R") +library(here) + +library(here) +library(knitr) +library(tidyverse) +library(ggrepel) +library(latex2exp) +library(patchwork) +library(glue) +library(RBesT) +library(rstan) +library(brms) +library(posterior) +library(tidybayes) +library(bayesplot) +library(gt) +library(ggdist) +library(distributional) +library(mvtnorm) +library(dqrng) +library(emmeans) +library(simsurv) + +## ----------------------------------------------------------------------------- +#| echo: false +#| warning: false +#| cache: false +#| fig.height: 6.5 +drfdata <- tibble(Dose=(0:5000)/10, + Emax=Dose/(Dose+70), + `sigmoid Emax`=1/(1+(100/Dose)^3), + `non-monotone`=1/(1+(10/Dose)^0.9)-(Dose>300)*(Dose-300)^2/120000) %>% + gather(value = delta, key=Function, -Dose) %>% + mutate( pointlabel = ifelse((Dose==300 & Function=="Emax") | (Dose==100 & Function=="non-monotone") | (Dose==120 & Function=="sigmoid Emax"), Function, NA)) + +p1 <- drfdata %>% + filter(Function=="Emax") %>% + ggplot(aes(x=Dose, y=delta, col=Function, label=pointlabel)) + + scale_color_manual(values=c("#000000", "#E69F00", "#56B4E9")) + + theme_bw(base_size = 24) + + ylab("Percentage of full treatment effect") + + geom_hline(yintercept=0, size=2) + + coord_cartesian(ylim=c(0, 1)) + + scale_y_continuous(labels=scales::percent) + + geom_line(size=3) + + geom_text(data = . %>% filter(!is.na(pointlabel)), + size = 10, nudge_y=c(-0.1, -0.15, 0.11), nudge_x=c(10, 80, 2.5)) + + guides(color=FALSE) + +p2 <- drfdata %>% + filter(Function %in% c("Emax", "sigmoid Emax")) %>% + ggplot(aes(x=Dose, y=delta, col=Function, label=pointlabel)) + + scale_color_manual(values=c("#000000", "#E69F00", "#56B4E9")) + + theme_bw(base_size = 24) + + ylab("Percentage of full treatment effect") + + coord_cartesian(ylim=c(0, 1)) + + scale_y_continuous(labels=scales::percent) + + geom_hline(yintercept=0, size=2) + + geom_line(size=3) + + geom_text(data = . %>% filter(!is.na(pointlabel)), + size = 10, nudge_y=c(-0.1, -0.15, 0.11), nudge_x=c(10, 80, 2.5)) + + guides(color=FALSE) + +p3 <- drfdata %>% + ggplot(aes(x=Dose, y=delta, col=Function, label=pointlabel)) + + scale_color_manual(values=c("#000000", "#56B4E9", "#E69F00")) + + theme_bw(base_size = 24) + + ylab("Percentage of full treatment effect") + + coord_cartesian(ylim=c(0, 1)) + + scale_y_continuous(labels=scales::percent) + + geom_hline(yintercept=0, size=2) + + geom_line(size=3) + + geom_text(data = . %>% filter(!is.na(pointlabel)), + size = 10, nudge_y=c(-0.1, -0.15, 0.11), nudge_x=c(10, 80, 2.5)) + + guides(color=FALSE) + +p1 + + +## ----------------------------------------------------------------------------- +#| echo: false +#| cache: false +#| warning: false +#| fig.height: 6.5 +p2 + + +## ----------------------------------------------------------------------------- +#| echo: false +#| cache: false +#| warning: false +#| fig.height: 6.5 +p3 + + +## ----include=FALSE------------------------------------------------------------ +# This is the PATHWAY DRF data by group +pathway = tibble(dose = c(0, 70, 210, 280*2), + group = c("placebo", "tezepelumab 70 mg q4w", + "tezepelumab 210 mg q4w", "tezepelumab 280 mg q2w"), + log_est = log(c(0.67, 0.26, 0.19, 0.22)), + log_stderr = c(0.10304, 0.17689, 0.22217, 0.19108)) + + +## ----echo=FALSE--------------------------------------------------------------- +gt(pathway) %>% + fmt_number(columns=c("log_est", "log_stderr"), decimals=3) %>% + opt_stylize(style = 6, color = 'blue') + + +## ----sigemax_plots------------------------------------------------------------ +#| echo: FALSE +#| fig-height: 6 +# Plot some example sigEmax curves +expand_grid(Dose=seq(0, 100, 0.1), Emax=1, E0=0, ED50=c(5,25), h=c(1/3, 1, 3)) %>% + mutate(`Dose response` = E0 + Emax * Dose^h/(Dose^h + ED50^h), + text = paste0("ED50=", ED50, "/h=",round(h,2))) %>% + ggplot(aes(x=Dose, y=`Dose response`, col=text, label=text)) + + theme_bw(base_size=24) + + geom_line(lwd=3) + + scale_y_continuous(labels=scales::percent(seq(0,1,0.25))) + + geom_text_repel(data=. %>% filter( Dose==50), max.iter = 100, #max.time = 10, + size=6, + nudge_x=c(35, 30,-16, 30, 10, 5), segment.color = NA, + nudge_y=c(0,-0.02,-0.05, -0.02, -0.03,-0.1)) + + scale_color_brewer(palette="Dark2") + + theme(legend.position = "none") + + +## ----fit_sigemax-------------------------------------------------------------- +#| results: 'hide' +form_sig <- bf( + log_est | se(log_stderr) ~ E0 + Emax * dose^h / + (dose^h + ED50^h), + nlf(h ~ exp(logh)), nlf(ED50 ~ exp(logED50)), + E0 ~ 1, Emax ~ 1, logh ~ 1, logED50 ~ 1, + nl = TRUE, + family = gaussian() +) + +prior_sig <- prior(normal(0,1), nlpar="E0") + + prior(normal(0,1), nlpar="logh") + + prior(normal(0,1), nlpar="Emax") + + prior(normal(4,2), nlpar="logED50") + + +## ----------------------------------------------------------------------------- +#| eval: FALSE +## fit_sig = brm( +## formula = form_sig, +## data = pathway, +## prior = prior_sig, +## control = list(adapt_delta = 0.999) +## ) + + +## ----------------------------------------------------------------------------- +#| echo: FALSE +#| results: 'hide' +fit_sig = brm( + formula = form_sig, + data = pathway, + prior = prior_sig, + control = list(adapt_delta = 0.999), + seed = 3624, + save_pars = save_pars(all = TRUE) +) + + +## ----------------------------------------------------------------------------- +#| mysize: true +#| size: '\tiny' +summary(fit_sig) + + +## ----------------------------------------------------------------------------- +#| eval: false +#| mysize: true +#| size: '\tiny' +## tibble(dose = seq(0, 560, 1), log_stderr=1) %>% +## add_epred_rvars(object=fit_sig) %>% +## (\(x) x %>% +## left_join(x %>% filter(dose==0) %>% rename(pbo = .epred) %>% dplyr::select(-dose), +## by="log_stderr"))() %>% +## mutate(.delta = .epred - pbo) %>% +## ggplot(aes(x=dose, ydist=.delta)) + +## stat_lineribbon() + + +## ----------------------------------------------------------------------------- +#| echo: false +#| fig.height: 4 +tibble(dose = seq(0, 560, 1), log_stderr=1) %>% + add_epred_rvars(object=fit_sig) %>% + (\(x) x %>% left_join(x %>% filter(dose==0) %>% rename(pbo = .epred) %>% dplyr::select(-dose), by="log_stderr"))() %>% + mutate(.delta = .epred - pbo) %>% + ggplot(aes(x=dose, ydist=.delta)) + + theme_bw(base_size=24) + + theme(legend.position = "right") + + geom_hline(yintercept=0, color="darkred", lty=2) + + stat_lineribbon() + + scale_fill_brewer(palette="Blues") + + geom_point(aes(x = dose, y = log_rr), inherit.aes = FALSE, + col="#E69F00", + data = tibble(dose=c(70, 210, 280*2), + log_rr= log(c(1-0.62, 1-0.71, 1-0.66)), + log_rr_ucl = log(1-c(0.42, 0.54, 0.47)), + log_rr_lcl = log(1-c(0.75, 0.82, 0.79))), + size = 3) + + geom_errorbar(aes(x = dose, ymin=log_rr_lcl, ymax=log_rr_ucl), + inherit.aes = FALSE, col="#E69F00", + data = tibble(dose=c(70, 210, 280*2), + log_rr= log(c(1-0.62, 1-0.71, 1-0.66)), + log_rr_ucl = log(1-c(0.42, 0.54, 0.47)), + log_rr_lcl = log(1-c(0.75, 0.82, 0.79))), + size = 1, width=30) + + ylab("Rate ratio vs. placebo") + + scale_y_continuous(breaks=log(c(1, 0.7, 0.5, 0.35, 0.25, 0.15)), + labels=c(1, 0.7, 0.5, 0.35, 0.25, 0.15)) + + scale_x_continuous(breaks=c(0, 70, 210, 280*2)) + + xlab("Total tezepelumab dose [mg/month]") + + +## ----------------------------------------------------------------------------- +#| mysize: true +#| size: '\small' +form_mbeta <- bf( + log_est | se(log_stderr) ~ E0 + + Emax * (delta1+delta2)^(delta1+delta2) / + (delta1^delta1 * delta2^delta2) * + (dose/850)^delta1 * (1-dose/850)^delta2, + nlf(delta1 ~ exp(logdelta1)), nlf(delta2 ~ exp(logdelta2)), + E0 ~ 1, Emax ~ 1, logdelta1 ~ 1, logdelta2 ~ 1, + nl = TRUE, + family = gaussian() +) + +prior_mbeta <- prior(normal(0,1), nlpar="E0") + + prior(normal(0,1), nlpar="Emax") + + prior(normal(0,1), nlpar="logdelta1") + + prior(normal(0,1), nlpar="logdelta2") + + +## ----------------------------------------------------------------------------- +#| eval: FALSE +## fit_mbeta <- brm( +## form_mbeta, +## data = pathway, +## prior = prior_mbeta, +## control = list(adapt_delta = 0.999) +## ) + + +## ----------------------------------------------------------------------------- +#| echo: FALSE +#| results: 'hide' +fit_mbeta <- brm( + form_mbeta, data = pathway, prior = prior_mbeta, + control = list(adapt_delta = 0.999), seed = 7304, + save_pars = save_pars(all = TRUE) +) + + +## ----------------------------------------------------------------------------- +#| echo: false +#| fig.height: 6.5 +tibble(dose = seq(0, 560, 1), log_stderr=1) %>% + add_epred_rvars(object=fit_mbeta) %>% + (\(x) x %>% left_join(x %>% filter(dose==0) %>% rename(pbo = .epred) %>% dplyr::select(-dose), by="log_stderr"))() %>% + mutate(.delta = .epred - pbo) %>% + ggplot(aes(x=dose, ydist=.delta)) + + theme_bw(base_size=24) + + theme(legend.position = "bottom") + + geom_hline(yintercept=0, color="darkred", lty=2) + + stat_lineribbon() + + scale_fill_brewer(palette="Blues") + + geom_point(aes(x = dose, y = log_rr), inherit.aes = FALSE, + col="#E69F00", + data = tibble(dose=c(70, 210, 280*2), + log_rr= log(c(1-0.62, 1-0.71, 1-0.66)), + log_rr_ucl = log(1-c(0.42, 0.54, 0.47)), + log_rr_lcl = log(1-c(0.75, 0.82, 0.79))), + size = 3) + + geom_errorbar(aes(x = dose, ymin=log_rr_lcl, ymax=log_rr_ucl), + inherit.aes = FALSE, col="#E69F00", + data = tibble(dose=c(70, 210, 280*2), + log_rr= log(c(1-0.62, 1-0.71, 1-0.66)), + log_rr_ucl = log(1-c(0.42, 0.54, 0.47)), + log_rr_lcl = log(1-c(0.75, 0.82, 0.79))), + size = 1, width=30) + + ylab("Rate ratio compared with placebo") + + scale_y_continuous(breaks=log(c(1, 0.7, 0.5, 0.35, 0.25, 0.15)), + labels=c(1, 0.7, 0.5, 0.35, 0.25, 0.15)) + + scale_x_continuous(breaks=c(0, 70, 210, 280*2)) + + xlab("Total tezepelumab dose [mg/month]") + + +## ----------------------------------------------------------------------------- +#| mysize: true +#| size: '\scriptsize' +#| cache: true +(loo_mbeta <- loo(fit_mbeta)) + + +## ----------------------------------------------------------------------------- +#| mysize: true +#| size: '\scriptsize' +#| cache: true +(loo_mm_mbeta <- loo_moment_match(fit_mbeta, loo_mbeta)) + + +## ----------------------------------------------------------------------------- +#| echo: false +#! message: false +#! warning: false +loo_sig <- loo(fit_sig) +loo_mm_sig <- loo_moment_match(fit_sig, loo_sig) + + +## ----------------------------------------------------------------------------- +#| mysize: true +#| size: '\footnotesize' +#| cache: true +(loo_exact_mbeta <- kfold(fit_mbeta, folds = "loo")) + + +## ----------------------------------------------------------------------------- +#| mysize: true +#| size: '\small' +#| cache: true +loo_compare(loo_mm_sig, loo_exact_mbeta) + + +## ----------------------------------------------------------------------------- +#| mysize: true +#| size: '\small' +fit_sig$criteria$loo <- loo_mm_sig +fit_mbeta$criteria$loo <- loo_exact_mbeta +(w_dose <- model_weights(fit_sig, fit_mbeta, weights = "loo")) + + +## ----------------------------------------------------------------------------- +#| echo: false +dose_df <- data.frame( + dose = seq(min(pathway$dose), max(pathway$dose), length.out = 100), + log_stderr = mean(pathway$log_stderr) +) + + +## ----------------------------------------------------------------------------- +#| mysize: true +#| size: '\small' +pe_sig <- posterior_epred(fit_sig, newdata = dose_df) +pe_mbeta <- posterior_epred(fit_mbeta, newdata = dose_df) +pe_avg <- pe_sig * w_dose[1] + pe_mbeta * w_dose[2] + + +## ----------------------------------------------------------------------------- +#| mysize: true +#| size: '\small' +pe_avg <- pe_avg %>% + posterior_summary() %>% + as.data.frame() %>% + bind_cols(dose_df) + + +## ----------------------------------------------------------------------------- +#| echo: false +pe_avg %>% select(-log_stderr) %>% head(4) + + +## ----------------------------------------------------------------------------- +#| echo: false +#| fig.height: 6.5 +tibble(dose = seq(0, 560, 1), log_stderr=1) %>% + add_epred_rvars(object=fit_sig) %>% + rename(SigEmax = .epred) %>% + add_epred_rvars(object=fit_mbeta) %>% + rename(ModBeta = .epred) %>% + mutate(Averaged = w_dose["fit_sig"] * SigEmax + w_dose["fit_mbeta"] * ModBeta) %>% + (\(x) x %>% left_join(x %>% filter(dose==0) %>% rename(SigEmaxPbo = SigEmax, ModBetaPbo = ModBeta, AveragedPbo=Averaged) %>% dplyr::select(-dose), by="log_stderr"))() %>% + mutate(SigEmax = SigEmax - SigEmaxPbo, + ModBeta = ModBeta - ModBetaPbo, + Averaged = Averaged - AveragedPbo) %>% + dplyr::select(-SigEmaxPbo, -ModBetaPbo, -AveragedPbo) %>% + pivot_longer(cols=c("Averaged", "SigEmax", "ModBeta"), + names_to="Model", + values_to=".delta") %>% + mutate(Model = factor(Model, c("SigEmax", "ModBeta", "Averaged"))) %>% + ggplot(aes(x=dose, ydist=.delta)) + + theme_bw(base_size=18) + + theme(legend.position = "bottom") + + geom_hline(yintercept=0, color="darkred", lty=2) + + stat_lineribbon() + + scale_fill_brewer(palette="Blues") + + geom_point(aes(x = dose, y = log_rr), inherit.aes = FALSE, + col="#E69F00", + data = tibble(dose=c(70, 210, 280*2), + log_rr= log(c(1-0.62, 1-0.71, 1-0.66)), + log_rr_ucl = log(1-c(0.42, 0.54, 0.47)), + log_rr_lcl = log(1-c(0.75, 0.82, 0.79))), + size = 3) + + geom_errorbar(aes(x = dose, ymin=log_rr_lcl, ymax=log_rr_ucl), + inherit.aes = FALSE, col="#E69F00", + data = tibble(dose=c(70, 210, 280*2), + log_rr= log(c(1-0.62, 1-0.71, 1-0.66)), + log_rr_ucl = log(1-c(0.42, 0.54, 0.47)), + log_rr_lcl = log(1-c(0.75, 0.82, 0.79))), + size = 1, width=30) + + facet_wrap(~Model, ncol=3, nrow=1, labeller = label_both) + + ylab("Rate ratio compared with placebo") + + scale_y_continuous(breaks=log(c(1, 0.7, 0.5, 0.35, 0.25, 0.15)), + labels=c(1, 0.7, 0.5, 0.35, 0.25, 0.15)) + + scale_x_continuous(breaks=c(0, 70, 210, 280*2)) + + xlab("Total tezepelumab dose [mg/month]") + diff --git a/workshops/jsm2024/slides/R/07_time_to_event.R b/workshops/jsm2024/slides/R/07_time_to_event.R new file mode 100644 index 0000000..0237b2f --- /dev/null +++ b/workshops/jsm2024/slides/R/07_time_to_event.R @@ -0,0 +1,252 @@ +here::i_am("slides/R/07_time_to_event.R") +library(here) + +library(here) +library(knitr) +library(tidyverse) +library(ggrepel) +library(latex2exp) +library(patchwork) +library(glue) +library(RBesT) +library(rstan) +library(brms) +library(posterior) +library(tidybayes) +library(bayesplot) +library(gt) +library(ggdist) +library(distributional) +library(mvtnorm) +library(dqrng) +library(emmeans) +library(simsurv) + +## ----------------------------------------------------------------------------- +#| echo: false +set.seed(46767) +## n per group +n_grp <- 100 +n_hist <- 400 +## use month as time-unit +rate_1 <- 1 / 6 +rate_cens <- 1 / 10 +beta <- c(trt=-0.2, ## roughly 20% HR reduction + soc_alt=0.05, ## alternative chemotherapy is 5% worse + hist1=0.02) ## simulated historical data has a 2% worse outcome + +## covariates of simulated trial data +covs <- data.frame(id = seq(1, 2*n_grp), trt = c(0, 1), + soc_alt=rbinom(2*n_grp, 1L, 0.3), hist1=0L, hist2=0L) + +## covariates of historical data +hcovs <- data.frame(id = seq(2*n_grp+1, 2*n_grp+1 +n_hist - 1), + trt = c(0), soc_alt=0, hist1=1L, hist2=0L) + +simulate_trial <- function(lambda, gamma, lambda_cens, x, betas) { + ## simulate censoring times, note that we do not simulate end of + ## trial with maxt for now... + cens <- simsurv(lambdas = lambda_cens, gammas = 1, x=x) + events <- simsurv(lambdas = lambda, gammas = gamma, x=x, betas=betas) + names(cens) <- paste0(names(cens), "_cens") + bind_cols(events, select(cens, -id_cens), select(x, -id)) %>% + rename(censtime=eventtime_cens) %>% + mutate(event=1*(eventtime <= censtime), + y=if_else(event==1, eventtime, censtime), + status=NULL, status_cens=NULL) %>% + relocate(id, y, event) %>% + mutate(soc=factor(soc_alt, c(0, 1), c("ChA", "ChB")), + trt_ind=trt, + trt=factor(trt_ind, c(0,1), c("ctl", "act")), + arm=factor(paste0(c("ctl", "act")[trt_ind + 1], soc), + levels=c("actChA", "ctlChA", + "actChB", "ctlChB"))) +} + +sim <- simulate_trial(rate_1, 1, rate_cens, covs, beta) +hdata1 <- simulate_trial(rate_1, 1, rate_cens, hcovs, beta) %>% + mutate(id=id+max(sim$id)) + + +## ----------------------------------------------------------------------------- +#| echo: false +sim %>% + select(y, event, trt, soc, arm) %>% + head(8) %>% + kable() + + +## ----------------------------------------------------------------------------- +#| eval: false +## cc_inv + + +## ----------------------------------------------------------------------------- +#| echo: false +cc_inv <- matrix(c(1/4, 1/4, 1/4, 1/4, + 1/2, -1/2, 1/2, -1/2, + 1/2, -1/2, -1/2, 1/2, + 0, -1, 0, 1), +nrow=4, ncol=4, byrow=TRUE, +dimnames=list(contrast=c("intercept", "effectAvg", + "deltaEffect", "deltaControl"), + arm=c("actChA", "ctlChA", + "actChB", "ctlChB"))) + +cc_inv %>% MASS::fractions() + + +## ----------------------------------------------------------------------------- +cc <- solve(cc_inv) + + +## ----------------------------------------------------------------------------- +#| echo: false +cc %>% MASS::fractions() + + +## ----------------------------------------------------------------------------- +#| echo: false +contrasts(sim$arm) <- cc[, -1] + + +## ----------------------------------------------------------------------------- +#| mysize: true +#| size: '\small' +model_weibull1 <- bf(y | cens(1-event) ~ 1 + arm, + family=weibull()) + + +## ----------------------------------------------------------------------------- +#| mysize: true +#| size: '\small' +prior_weibull1 <- + prior(normal(meanInter, log(4)/1.64), class="Intercept") + + prior(normal(0, sdEffect), coef=armeffectAvg) + + prior(normal(0, sdDeltaEffect), coef=armdeltaEffect) + + prior(normal(0, sdDeltaControl), coef=armdeltaControl) + + prior(gamma(0.1, 0.1), class=shape) + + +## ----------------------------------------------------------------------------- +#| mysize: true +#| size: '\small' +stanvars_weibull1 <- + stanvar(-log(log(2)/8), name = "meanInter") + + stanvar(log(2)/1.64, name = "sdEffect") + + stanvar(log(1.25)/1.64, name = "sdDeltaEffect") + + stanvar(log(1.25)/1.64, name = "sdDeltaControl") + + +## ----------------------------------------------------------------------------- +#| eval: false +## fit_weibull1 <- brm( +## formula = model_weibull1, +## data = sim, +## prior = weibull_prior1, +## stanvars = stanvars_weibull1, +## ... +## ) + + +## ----------------------------------------------------------------------------- +#| echo: false +#| include: false +fit_weibull1 <- brm( + formula = model_weibull1, + data = sim, + prior = prior_weibull1, + stanvars = stanvars_weibull1 +) + + +## ----------------------------------------------------------------------------- +#| mysize: true +#| size: '\tiny' +summary(fit_weibull1) + + +## ----------------------------------------------------------------------------- +#| echo: true +#| eval: false +## p_full_fup <- pp_check( +## fit_weibull1, type = "km_overlay", +## status_y = sim$event, ndraws = 100 +## ) + + +## ----------------------------------------------------------------------------- +#| echo: false +#| fig-height: 4 +p_full_fup <- pp_check( + fit_weibull1, status_y=sim$event, + type="km_overlay", ndraws=100 +) + + scale_y_continuous(breaks=seq(0,1,by=0.1)) + + xlab("Time [month]") + coord_cartesian(xlim=c(0, 35)) + +p_full_fup + coord_cartesian(xlim=c(0, 15)) +# p_full_fup + + +## ----------------------------------------------------------------------------- +#| echo: false +hdata1 %>% + select(y, event, arm, hist1) %>% + head(8) %>% + kable() + + +## ----------------------------------------------------------------------------- +sim_comb <- bind_rows(sim, hdata1) + + +## ----------------------------------------------------------------------------- +#| echo: false +contrasts(sim_comb$arm) <- cc[, -1] + + +## ----------------------------------------------------------------------------- +model_weibull2 <- bf( + y | cens(1-event) ~ 1 + arm + hist1, + family=weibull() +) + + +## ----------------------------------------------------------------------------- +prior_weibull2 <- prior_weibull1 + + prior(student_t(6, 0, sdHist), coef = hist1) + + +## ----------------------------------------------------------------------------- +stanvars_weibull2 <- stanvars_weibull1 + + stanvar(log(1.8)/1.64, name = "sdHist") + + +## ----------------------------------------------------------------------------- +#| eval: false +## fit_weibull2 <- brm( +## formula = model_weibull2, +## data = sim_comb, +## prior = weibull_prior2, +## stanvars = stanvars_weibull2, +## ... +## ) + + +## ----------------------------------------------------------------------------- +#| echo: false +#| include: false +fit_weibull2 <- brm( + formula = model_weibull2, + data = sim_comb, + prior = prior_weibull2, + stanvars = stanvars_weibull2 +) + + +## ----------------------------------------------------------------------------- +#| mysize: true +#| size: '\tiny' +summary(fit_weibull2) + diff --git a/workshops/jsm2024/slides/bamdd_jsm2024.pdf b/workshops/jsm2024/slides/bamdd_jsm2024.pdf new file mode 100644 index 0000000..0f6feef Binary files /dev/null and b/workshops/jsm2024/slides/bamdd_jsm2024.pdf differ