Using tidy

suppressPackageStartupMessages({
  library(crmPack)
  library(knitr)
  library(kableExtra)
  library(tidyr)
  library(magrittr)
  library(dplyr)
})

Introducing tidy methods to crmPack

The latest release of crmPack introduces broom-like tidy methods for all crmPack classes. These methods convert the underlying S4 classes to (lists of) tibbles. This should facilitate reporting of all aspects of CRM trials as well as making it easier to integrate crmPack with other packages such as ggplot2.

Basic approach

The following is the general approach we take to tidying crmPack classes:

Exceptions

Examples

CohortSizeConst is a trivial example and illustrates the default approach for all classes.

CohortSizeConst(size = 3) %>% tidy()
#> # A tibble: 1 × 1
#>    size
#>   <int>
#> 1     3

IncrementsRelative illustrate how ranges are handled.

IncrementsRelative(
  intervals = c(0, 20),
  increments = c(1, 0.33)
) %>%
  tidy()
#> # A tibble: 2 × 3
#>     min   max increment
#>   <dbl> <dbl>     <dbl>
#> 1     0    20      1   
#> 2    20   Inf      0.33

CohortSizeMax contains a slot whose value is a list.

cs_max <- maxSize(
  CohortSizeConst(3),
  CohortSizeDLT(intervals = 0:1, cohort_size = c(1, 3))
)
cs_max %>% tidy()
#> [[1]]
#> # A tibble: 1 × 1
#>    size
#>   <int>
#> 1     3
#> 
#> [[2]]
#> # A tibble: 2 × 3
#>     min   max cohort_size
#>   <dbl> <dbl>       <int>
#> 1     0     1           1
#> 2     1   Inf           3
#> 
#> attr(,"class")
#> [1] "tbl_CohortSizeMax" "tbl_CohortSizeMax" "list"

The Samples class likely to the most useful when making presentations not yet supported by crmPack directly.

options <- McmcOptions(
  burnin = 100,
  step = 1,
  samples = 2000
)

emptydata <- Data(doseGrid = c(1, 3, 5, 10, 15, 20, 25, 40, 50, 80, 100))

model <- LogisticLogNormal(
  mean = c(-0.85, 1),
  cov =
    matrix(c(1, -0.5, -0.5, 1),
      nrow = 2
    ),
  ref_dose = 56
)
samples <- mcmc(emptydata, model, options)
tidySamples <- samples %>% tidy()
tidySamples %>% head()
#> $data
#> # A tibble: 2,000 × 10
#>    Iteration Chain alpha0 alpha1 nChains nParameters nIterations nBurnin nThin
#>        <int> <int>  <dbl>  <dbl>   <int>       <int>       <int>   <int> <int>
#>  1         1     1 -1.56   3.06        1           1        2100     100     1
#>  2         2     1 -0.873  8.39        1           1        2100     100     1
#>  3         3     1 -1.44  17.2         1           1        2100     100     1
#>  4         4     1  1.59   1.49        1           1        2100     100     1
#>  5         5     1 -0.908  2.72        1           1        2100     100     1
#>  6         6     1 -0.403  0.883       1           1        2100     100     1
#>  7         7     1 -1.46   2.08        1           1        2100     100     1
#>  8         8     1  0.156  0.438       1           1        2100     100     1
#>  9         9     1  0.736  0.824       1           1        2100     100     1
#> 10        10     1 -1.98   3.57        1           1        2100     100     1
#> # ℹ 1,990 more rows
#> # ℹ 1 more variable: parallel <lgl>
#> 
#> $options
#> # A tibble: 1 × 5
#>   iterations burnin  step rng_kind rng_seed
#>        <int>  <int> <int> <chr>       <int>
#> 1       2100    100     1 <NA>           NA

Using tidy crmPack data

Tidy crmPack data can be easily reported using knitr or similar packages in the obvious way.

Cohort size

The cohort size for this trial is determined by the dose to be used in the current cohort according to the rules described in the table below:

CohortSizeRange(
  intervals = c(0, 50, 300),
  cohort_size = c(1, 3, 5)
) %>%
  tidy() %>%
  kable(
    col.names = c("Min", "Max", "Cohort size"),
    caption = "Rules for selecting the cohort size"
  ) %>%
  add_header_above(c("Dose" = 2, " " = 1))
Rules for selecting the cohort size
Dose
Min Max Cohort size
0 50 1
50 300 3
300 Inf 5

Or presentations not directly supported by crmPack can be easily produced. Here, we create plots of the dose-specific PDFs for prior probabilities of toxicity after the first DLT is observed in a fictional trial.

options <- McmcOptions(
  burnin = 5000,
  step = 1,
  samples = 40000
)

data <- Data(
  doseGrid = c(1, 3, 5, 10, 15, 20, 25, 40, 50, 80, 100),
  x = c(1, 3, 5, 10, 15, 15, 15),
  y = c(0, 0, 0, 0, 0, 1, 0),
  ID = 1L:7L,
  cohort = as.integer(c(1:4, 5, 5, 5))
)

model <- LogisticLogNormal(
  mean = c(-1, 0),
  cov =
    matrix(c(3, -0.1, -0.1, 4),
      nrow = 2
    ),
  ref_dose = 56
)
samples <- mcmc(data, model, options)
tidySamples <- samples %>% tidy()

# The magrittr pipe is necessary here
tidySamples$data %>%
  expand(
    nesting(!!!.[1:10]),
    Dose = data@doseGrid[2:11]
  ) %>%
  mutate(Prob = probFunction(model, alpha0 = alpha0, alpha1 = alpha1)(Dose)) %>%
  ggplot() +
  geom_density(aes(x = Prob, colour = as.factor(Dose)), adjust = 1.5) +
  labs(
    title = "Posterior dose-specific PDFs for p(Tox)",
    caption = "Dose 1 omitted as p(Tox) is essentially 0",
    x = "p(Tox)"
  ) +
  scale_colour_discrete("Dose") +
  theme_light() +
  theme(
    axis.ticks.y = element_blank(),
    axis.text.y = element_blank(),
    axis.title.y = element_blank()
  )

Environment

sessionInfo()
#> R version 4.5.1 (2025-06-13 ucrt)
#> Platform: x86_64-w64-mingw32/x64
#> Running under: Windows 11 x64 (build 26200)
#> 
#> Matrix products: default
#>   LAPACK version 3.12.1
#> 
#> locale:
#> [1] LC_COLLATE=C                        LC_CTYPE=German_Switzerland.utf8   
#> [3] LC_MONETARY=German_Switzerland.utf8 LC_NUMERIC=C                       
#> [5] LC_TIME=German_Switzerland.utf8    
#> 
#> time zone: Asia/Taipei
#> tzcode source: internal
#> 
#> attached base packages:
#> [1] stats     graphics  grDevices utils     datasets  methods   base     
#> 
#> other attached packages:
#> [1] dplyr_1.1.4      magrittr_2.0.4   tidyr_1.3.1      kableExtra_1.4.0
#> [5] knitr_1.50       crmPack_2.0.0    ggplot2_4.0.1   
#> 
#> loaded via a namespace (and not attached):
#>  [1] gtable_0.3.6         xfun_0.52            bslib_0.9.0         
#>  [4] processx_3.8.6       lattice_0.22-7       vctrs_0.6.5         
#>  [7] tools_4.5.1          Rdpack_2.6.4         ps_1.9.1            
#> [10] generics_0.1.4       parallel_4.5.1       tibble_3.3.0        
#> [13] pkgconfig_2.0.3      checkmate_2.3.3      RColorBrewer_1.1-3  
#> [16] S7_0.2.0             lifecycle_1.0.4      compiler_4.5.1      
#> [19] farver_2.1.2         stringr_1.6.0        textshaping_1.0.4   
#> [22] htmltools_0.5.8.1    sass_0.4.10          yaml_2.3.10         
#> [25] later_1.4.2          pillar_1.11.1        jquerylib_0.1.4     
#> [28] cachem_1.1.0         parallelly_1.45.1    tidyselect_1.2.1    
#> [31] digest_0.6.37        mvtnorm_1.3-3        stringi_1.8.7       
#> [34] purrr_1.1.0          bookdown_0.44        labeling_0.4.3      
#> [37] quarto_1.5.1         fastmap_1.2.0        grid_4.5.1          
#> [40] cli_3.6.5            utf8_1.2.6           GenSA_1.1.14.1      
#> [43] withr_3.0.2          scales_1.4.0         backports_1.5.0     
#> [46] rmarkdown_2.30       lambda.r_1.2.4       gridExtra_2.3       
#> [49] futile.logger_1.4.3  rjags_4-17           coda_0.19-4.1       
#> [52] evaluate_1.0.5       rbibutils_2.4        viridisLite_0.4.2   
#> [55] rlang_1.1.6          futile.options_1.0.1 Rcpp_1.1.0          
#> [58] glue_1.8.0           formatR_1.14         xml2_1.3.8          
#> [61] svglite_2.2.1        rstudioapi_0.17.1    jsonlite_2.0.0      
#> [64] R6_2.6.1             systemfonts_1.3.1