diff --git a/DESCRIPTION b/DESCRIPTION index 86b91fd..f1dd0ed 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,9 +1,8 @@ Package: OptimalGoldstandardDesigns Type: Package Title: Design parameter optimization for gold-standard non-inferiority trials -Version: 0.2.0 -Author: Jan Meis -Maintainer: Jan Meis +Version: 1.0.0 +Authors@R: c(person("Jan", "Meis", role = c("aut", "cre"), email="meis@imbi.uni-heidelberg.de", comment = c(ORCID = "0000-0001-5407-7220"))) Description: This package contains methods to calculate optimal design parameters for one- and two-stage three-arm group-sequential gold-standard non-inferiority trial designs @@ -30,6 +29,7 @@ Suggests: future.apply Config/testthat/edition: 3 RoxygenNote: 7.2.3 +Roxygen: list(markdown = TRUE) Collate: 'pmv_upper_smaller_slower_fix.R' 'conditional_probability_functions.R' diff --git a/R/conditional_probability_functions.R b/R/conditional_probability_functions.R index 5a1fa2b..5568741 100644 --- a/R/conditional_probability_functions.R +++ b/R/conditional_probability_functions.R @@ -1,6 +1,6 @@ #' Calculate the conditional mean of a multivariate normal distribution #' -#' See e.g. chapter 8.1.2 in The Matrix Cookbook [1]. +#' See e.g. Chapter 8.1.2 in [The Matrix Cookbook](https://www.math.uwaterloo.ca/~hwolkowi/matrixcookbook.pdf). #' #' @template x_a #' @template mu_a @@ -21,7 +21,7 @@ conditional_mean <- function(x_a, mu_a, mu_b, Sigma) { #' Calculate the conditional mean of a multivariate normal distribution #' -#' See e.g. chapter 8.1.2 in The Matrix Cookbook [1]. +#' See e.g. Chapter 8.1.2 in [The Matrix Cookbook](https://www.math.uwaterloo.ca/~hwolkowi/matrixcookbook.pdf). #' #' @template x_a #' @template mu_a diff --git a/R/global_constants.R b/R/global_constants.R index 56a13c1..7474aef 100644 --- a/R/global_constants.R +++ b/R/global_constants.R @@ -44,4 +44,4 @@ projection[["TP12_TC12"]] <- rbind( projection_components[[1]][["TC"]], projection_components[[2]][["TC"]] ) -.skip_slow_test <- TRUE +# .skip_slow_test <- TRUE diff --git a/R/optimization_methods.R b/R/optimization_methods.R index a90cc95..f29f383 100644 --- a/R/optimization_methods.R +++ b/R/optimization_methods.R @@ -19,7 +19,7 @@ #' @template varC #' @param binding_futility (logical) controls if futility boundaries are binding. #' @param always_both_futility_tests (logical) if true, both futility tests are performed after the first stage. If false, -#' a 'completely sequential' testing procedure is employed (see Appendix of paper). +#' a 'completely sequential' testing procedure is employed (see [Appendix of paper](https://doi.org/10.1002/sim.9630)). #' @template round_n #' @template lambda #' @template kappa @@ -39,12 +39,9 @@ #' @importFrom nloptr nloptr #' @importFrom mvtnorm Miwa #' @details -#' TODO: Documentation is still work in progross... -#' #' This function calculates optimal design parameters for a two-stage three-arm gold-standard -#' non-inferiority trial. Run \code{vignette("mathematical_details", package = "OptimalGoldstandardDesigns")} -#' to see details about the test statistics and testing procedure corresponding to this design. -#' +#' non-inferiority trial. Run \code{vignette("Introduction", package = "OptimalGoldstandardDesigns")} +#' to see some examples related to the associated paper [(Meis et al., 2023)](https://doi.org/10.1002/sim.9630). #' #' Parameters which can be optimized are the allocation ratios for all groups and stages and the #' futility and efficacy boundaries of the first stage. The allocation ratios are @@ -64,7 +61,11 @@ #' #' #' The design is optimized with respect to the objective criterion given by the parameter -#' \code{objective}. By default, ... +#' \code{objective}. The default objective function is described in the +#' Subsection *Optimizing group sequential gold-standard designs* in Section 2 +#' of [the associated paper](https://doi.org/10.1002/sim.9630). Additionally, +#' this objective includes a term to penalize the maximum sample size of a trial, +#' which can be controlled by the parameter `nu` (default is `nu=0`). #' #' Designs are calculated to fulfill the following constraints: the family-wise type I error #' rate is controlled at \code{alpha} under any combination of the two null hypotheses @@ -83,7 +84,23 @@ #' #' #' @examples -#' D <- optimize_design_twostage(nloptr_opts = list(maxeval = 1, algorithm = "NLOPT_LN_SBPLX")) +#' \dontrun{ +#' optimize_design_twostage( +#' beta = 0.2, +#' alternative_TP = 0.4, +#' alternative_TC = 0, +#' Delta = 0.2, +#' print_progress = FALSE, +#' binding_futility = TRUE, +#' lambda = .9, +#' kappa = 1 +#' ) +#' } +#' @references +#' Meis, J, Pilz, M, Herrmann, C, Bokelmann, B, Rauch, G, Kieser, M. Optimization of the two-stage group +#' sequential three-arm gold-standard design for non-inferiority trials. *Statistics in Medicine.* 2023; +#' 42(4): 536– 558. [doi:10.1002/sim.9630](https://doi.org/10.1002/sim.9630). +#' optimize_design_twostage <- function(cT2 = NULL, cP1 = NULL, @@ -404,11 +421,9 @@ optimize_design_twostage <- #' @export #' @importFrom nloptr nloptr #' @details -#' TODO: Documentation is still work in progross... -#' #' This function calculates optimal design parameters for a two-stage three-arm gold-standard -#' non-inferiority trial. Run \code{vignette("mathematical_details", package = "OptimalGoldstandardDesigns")} -#' to see details about the test statistics and testing procedure corresponding to this design. +#' non-inferiority trial. Run \code{vignette("Introduction", package = "OptimalGoldstandardDesigns")} +#' to see some examples related to the associated paper [(Meis et al., 2023)](https://doi.org/10.1002/sim.9630). #' #' #' Parameters which can be optimized are the allocation ratios for all groups and stages and the @@ -429,7 +444,8 @@ optimize_design_twostage <- #' #' #' The design is optimized with respect to the objective criterion given by the parameter -#' \code{objective}. By default, ... +#' \code{objective}. By default, this is the overall sample size plus an optional +#' penalty for the placebo group sample size, controlled by the parameter `kappa`. #' #' Designs are calculated to fulfill the following constraints: the family-wise type I error #' rate is controlled at \code{alpha} under any combination of the two null hypotheses @@ -448,7 +464,17 @@ optimize_design_twostage <- #' #' #' @examples -#' D <- optimize_design_twostage(nloptr_opts = list(maxeval = 1, algorithm = "NLOPT_LN_SBPLX")) +#' \dontrun{ +#' optimize_design_onestage( +#' alpha = .025, +#' beta = .2, +#' alternative_TP = .4, +#' alternative_TC = 0, +#' Delta = .2, +#' print_progress = FALSE +#' ) +#' } +#' optimize_design_onestage <- function(cP1 = NULL, cC1 = NULL, diff --git a/man/conditional_Sigma.Rd b/man/conditional_Sigma.Rd index 3d698f4..48eed83 100644 --- a/man/conditional_Sigma.Rd +++ b/man/conditional_Sigma.Rd @@ -19,7 +19,7 @@ conditional_Sigma(x_a, mu_a, mu_b, Sigma) numeric vector with the conditional covariance matrix } \description{ -See e.g. chapter 8.1.2 in The Matrix Cookbook [1]. +See e.g. Chapter 8.1.2 in \href{https://www.math.uwaterloo.ca/~hwolkowi/matrixcookbook.pdf}{The Matrix Cookbook}. } \references{ Petersen, K. B., & Pedersen, M. S. (2008). The matrix cookbook. Technical University of Denmark, 7(15), 510. diff --git a/man/conditional_mean.Rd b/man/conditional_mean.Rd index b1fcb0a..188ed9d 100644 --- a/man/conditional_mean.Rd +++ b/man/conditional_mean.Rd @@ -19,7 +19,7 @@ conditional_mean(x_a, mu_a, mu_b, Sigma) numeric vector with the conditional mean } \description{ -See e.g. chapter 8.1.2 in The Matrix Cookbook [1]. +See e.g. Chapter 8.1.2 in \href{https://www.math.uwaterloo.ca/~hwolkowi/matrixcookbook.pdf}{The Matrix Cookbook}. } \references{ Petersen, K. B., & Pedersen, M. S. (2008). The matrix cookbook. Technical University of Denmark, 7(15), 510. diff --git a/man/optimize_design_onestage.Rd b/man/optimize_design_onestage.Rd index 18ff353..40d9dac 100644 --- a/man/optimize_design_onestage.Rd +++ b/man/optimize_design_onestage.Rd @@ -81,12 +81,9 @@ Design object (a list) with optimized design parameters. Calculate optimal design parameters for a single-stage gold-standard design } \details{ -TODO: Documentation is still work in progross... - This function calculates optimal design parameters for a two-stage three-arm gold-standard -non-inferiority trial. Run \code{vignette("mathematical_details", package = "OptimalGoldstandardDesigns")} -to see details about the test statistics and testing procedure corresponding to this design. - +non-inferiority trial. Run \code{vignette("Introduction", package = "OptimalGoldstandardDesigns")} +to see some examples related to the associated paper \href{https://doi.org/10.1002/sim.9630}{(Meis et al., 2023)}. Parameters which can be optimized are the allocation ratios for all groups and stages and the futility and efficacy boundaries of the first stage. The allocation ratios are @@ -104,9 +101,9 @@ parameters to solve a constrained optimization problem. For example, you can sup \code{cT2 = 1, cP2 = quote(cP1), cC2 = quote(cC1)} to ensure that the first and second stage allocation ratios are equal. - The design is optimized with respect to the objective criterion given by the parameter -\code{objective}. By default, ... +\code{objective}. By default, this is the overall sample size plus an optional +penalty for the placebo group sample size, controlled by the parameter \code{kappa}. Designs are calculated to fulfill the following constraints: the family-wise type I error rate is controlled at \code{alpha} under any combination of the two null hypotheses @@ -123,5 +120,15 @@ test for the treatment vs. control testing problem only needs to be done if the treatment vs. placebo testing problem was rejected in the first stage. } \examples{ -D <- optimize_design_twostage(nloptr_opts = list(maxeval = 1, algorithm = "NLOPT_LN_SBPLX")) +\dontrun{ +optimize_design_onestage( + alpha = .025, + beta = .2, + alternative_TP = .4, + alternative_TC = 0, + Delta = .2, + print_progress = FALSE +) +} + } diff --git a/man/optimize_design_twostage.Rd b/man/optimize_design_twostage.Rd index d9fc66f..ab2884f 100644 --- a/man/optimize_design_twostage.Rd +++ b/man/optimize_design_twostage.Rd @@ -84,7 +84,7 @@ optimize_design_twostage( \item{binding_futility}{(logical) controls if futility boundaries are binding.} \item{always_both_futility_tests}{(logical) if true, both futility tests are performed after the first stage. If false, -a 'completely sequential' testing procedure is employed (see Appendix of paper).} +a 'completely sequential' testing procedure is employed (see \href{https://doi.org/10.1002/sim.9630}{Appendix of paper}).} \item{round_n}{(logical) if TRUE, a design with integer valued sample sizes is returned.} @@ -121,12 +121,9 @@ Design object (a list) with optimized design parameters. Calculate optimal design parameters for a two-stage gold-standard design } \details{ -TODO: Documentation is still work in progross... - This function calculates optimal design parameters for a two-stage three-arm gold-standard -non-inferiority trial. Run \code{vignette("mathematical_details", package = "OptimalGoldstandardDesigns")} -to see details about the test statistics and testing procedure corresponding to this design. - +non-inferiority trial. Run \code{vignette("Introduction", package = "OptimalGoldstandardDesigns")} +to see some examples related to the associated paper \href{https://doi.org/10.1002/sim.9630}{(Meis et al., 2023)}. Parameters which can be optimized are the allocation ratios for all groups and stages and the futility and efficacy boundaries of the first stage. The allocation ratios are @@ -144,9 +141,12 @@ parameters to solve a constrained optimization problem. For example, you can sup \code{cT2 = 1, cP2 = quote(cP1), cC2 = quote(cC1)} to ensure that the first and second stage allocation ratios are equal. - The design is optimized with respect to the objective criterion given by the parameter -\code{objective}. By default, ... +\code{objective}. The default objective function is described in the +Subsection \emph{Optimizing group sequential gold-standard designs} in Section 2 +of \href{https://doi.org/10.1002/sim.9630}{the associated paper}. Additionally, +this objective includes a term to penalize the maximum sample size of a trial, +which can be controlled by the parameter \code{nu} (default is \code{nu=0}). Designs are calculated to fulfill the following constraints: the family-wise type I error rate is controlled at \code{alpha} under any combination of the two null hypotheses @@ -163,5 +163,21 @@ test for the treatment vs. control testing problem only needs to be done if the treatment vs. placebo testing problem was rejected in the first stage. } \examples{ -D <- optimize_design_twostage(nloptr_opts = list(maxeval = 1, algorithm = "NLOPT_LN_SBPLX")) +\dontrun{ +optimize_design_twostage( + beta = 0.2, + alternative_TP = 0.4, + alternative_TC = 0, + Delta = 0.2, + print_progress = FALSE, + binding_futility = TRUE, + lambda = .9, + kappa = 1 +) +} +} +\references{ +Meis, J, Pilz, M, Herrmann, C, Bokelmann, B, Rauch, G, Kieser, M. Optimization of the two-stage group +sequential three-arm gold-standard design for non-inferiority trials. \emph{Statistics in Medicine.} 2023; +42(4): 536– 558. \href{https://doi.org/10.1002/sim.9630}{doi:10.1002/sim.9630}. } diff --git a/tests/testthat/test_design_helper_functions.R b/tests/testthat/test_design_helper_functions.R index 4580fbb..bad5639 100644 --- a/tests/testthat/test_design_helper_functions.R +++ b/tests/testthat/test_design_helper_functions.R @@ -55,7 +55,7 @@ test_that( "Covariance matrix calculation is correct and works as expected.", { skip_on_cran() - skip_if(.skip_slow_test, "Slow test skipped.") + # skip_if(.skip_slow_test, "Slow test skipped.") set.seed(123) D <- list() D$n <- list() diff --git a/tests/testthat/test_design_twostage_binding.R b/tests/testthat/test_design_twostage_binding.R index 423f287..e6924ff 100644 --- a/tests/testthat/test_design_twostage_binding.R +++ b/tests/testthat/test_design_twostage_binding.R @@ -1,5 +1,5 @@ skip_on_cran() -skip_if(.skip_slow_test, "Slow test skipped.") +# skip_if(.skip_slow_test, "Slow test skipped.") D <- optimize_design_twostage(print_progress = FALSE, binding_futility = TRUE) test_that( diff --git a/tests/testthat/test_design_twostage_binding_sequential.R b/tests/testthat/test_design_twostage_binding_sequential.R index b766bb9..7a7a5da 100644 --- a/tests/testthat/test_design_twostage_binding_sequential.R +++ b/tests/testthat/test_design_twostage_binding_sequential.R @@ -1,5 +1,5 @@ skip_on_cran() -skip_if(.skip_slow_test, "Slow test skipped.") +# skip_if(.skip_slow_test, "Slow test skipped.") D <- optimize_design_twostage( always_both_futility_tests = FALSE, binding_futility = TRUE, diff --git a/tests/testthat/test_design_twostage_nonbinding.R b/tests/testthat/test_design_twostage_nonbinding.R index df36da2..8681ad2 100644 --- a/tests/testthat/test_design_twostage_nonbinding.R +++ b/tests/testthat/test_design_twostage_nonbinding.R @@ -1,5 +1,5 @@ skip_on_cran() -skip_if(.skip_slow_test, "Slow test skipped.") +# skip_if(.skip_slow_test, "Slow test skipped.") D <- optimize_design_twostage(print_progress = FALSE) test_that( diff --git a/tests/testthat/test_schloemer_comparison.R b/tests/testthat/test_schloemer_comparison.R index 9cc3da5..ee156e1 100644 --- a/tests/testthat/test_schloemer_comparison.R +++ b/tests/testthat/test_schloemer_comparison.R @@ -1,5 +1,5 @@ skip_on_cran() -skip_if(.skip_slow_test, "Slow test skipped.") +# skip_if(.skip_slow_test, "Slow test skipped.") # Compare results with work from Patrick Schloemer library(mvtnorm) library(mnormt) diff --git a/vignettes/Introduction.Rmd b/vignettes/Introduction.Rmd index b62b00d..96e9d27 100644 --- a/vignettes/Introduction.Rmd +++ b/vignettes/Introduction.Rmd @@ -10,7 +10,8 @@ vignette: > ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, - comment = "#>" + comment = "#>", + eval = identical(Sys.getenv("NOT_CRAN"), "true") ) ``` @@ -63,7 +64,7 @@ computation times: You may also want to put -```{r} +```{r, eval=FALSE} print_progress = TRUE ``` @@ -200,18 +201,41 @@ sample size. ### Design 5, $\beta = 0.2$, $\lambda = 0.9$, $\kappa = 1$ This is (approximately) the seventh line in Table 2 from the paper: +```{r} +optimize_design_twostage( + beta = 0.2, + alternative_TP = 0.4, + alternative_TC = 0, + Delta = 0.2, + print_progress = FALSE, + binding_futility = TRUE, + lambda = .9, + kappa = 1 +) +``` + + ## Other options -### Optimizing between group allocation with enforced between stage allocation at 1 +### Penalizing the maximum sample size +```{r} +optimize_design_twostage( + beta = 0.2, + alternative_TP = 0.4, + alternative_TC = 0, + Delta = 0.2, + print_progress = FALSE, + nu = 1 +) +``` +### Optimizing between group allocation with enforced between stage allocation at 1 ```{r} optimize_design_twostage( - cP1 = tab1_D1$stagec[[1]]$P, # The allocation ratios are enforced to be - cC1 = tab1_D1$stagec[[1]]$C, # the same as in the optimal single-stage design. - cT2 = 1, - cP2 = tab1_D1$stagec[[1]]$P, - cC2 = tab1_D1$stagec[[1]]$C, + cT2 = 1, # These three boundary conditions enforce a + cP2 = quote(cP1), # between-stage allocation ratio of one. + cC2 = quote(cC1), # The quote() command is necessary for this to work. - bTP1f = -Inf, # These two boundary conditions enforce no futility stops. + bTP1f = -Inf, # These two boundary conditions enforce no futility stops. bTC1f = -Inf, beta = 0.2, @@ -222,6 +246,32 @@ optimize_design_twostage( ) ``` +### Using a custom objective function +You can replace the default objective function by any quoted expression. +In the following example, we optimize the design parameters to minimize +the expected squared sample size under the alternative hypothesis. +These expressions can make use of internal objects created in the +objective evaluation methods, check out the source code of +`optimize_design_twostage` in the `optimization_methods.R` file for more +information. `ASN`, `ASNP`, `n` and `final_state_probs` could be useful object +for crafting a custom objective function. + +```{r} +optimize_design_twostage( + beta = 0.2, + alternative_TP = 0.4, + alternative_TC = 0, + Delta = 0.2, + print_progress = FALSE, + objective = quote((final_state_probs[["H1"]][["TP1E_TC1E"]] + final_state_probs[["H1"]][["TP1F_TC1F"]]) * + (n[[1]][["T"]] + n[[1]][["P"]] + n[[1]][["C"]])^2 + + (final_state_probs[["H1"]][["TP1E_TC12E"]] + final_state_probs[["H1"]][["TP1E_TC12F"]]) * + (n[[1]][["T"]] + n[[1]][["P"]] + n[[1]][["C"]] + n[[2]][["T"]] + n[[2]][["C"]])^2 + + (final_state_probs[["H1"]][["TP12F_TC1"]] + final_state_probs[["H1"]][["TP12E_TC12E"]] + + final_state_probs[["H1"]][["TP12E_TC12F"]]) * + (n[[1]][["T"]] + n[[1]][["P"]] + n[[1]][["C"]] + n[[2]][["T"]] + n[[2]][["P"]] + n[[2]][["C"]])^2) +) +``` # References Meis, J, Pilz, M, Herrmann, C, Bokelmann, B, Rauch, G, Kieser, M. Optimization of the two-stage group