Skip to contents

Calculate optimal design parameters for a two-stage gold-standard design

Usage

optimize_design_twostage(
  cT2 = NULL,
  cP1 = NULL,
  cP2 = NULL,
  cC1 = NULL,
  cC2 = NULL,
  bTP1f = NULL,
  bTP1e = NULL,
  bTC1f = NULL,
  bTC1e = NULL,
  alpha = 0.025,
  beta = 0.2,
  alternative_TP = 0.4,
  alternative_TC = 0,
  Delta = 0.2,
  varT = 1,
  varP = 1,
  varC = 1,
  binding_futility = FALSE,
  always_both_futility_tests = TRUE,
  round_n = TRUE,
  lambda = 1,
  kappa = 0,
  eta = 0,
  objective = quote(sqrt(lambda)^2 * ASN[["H11"]] + (1 - sqrt(lambda)) * sqrt(lambda) *
    ASN[["H10"]] + (1 - sqrt(lambda)) * sqrt(lambda) * ASN[["H01"]] + (1 -
    sqrt(lambda))^2 * ASN[["H00"]] + kappa * (sqrt(lambda)^2 * ASNP[["H11"]] + (1 -
    sqrt(lambda)) * sqrt(lambda) * ASNP[["H10"]] + (1 - sqrt(lambda)) * sqrt(lambda) *
    ASNP[["H01"]] + (1 - sqrt(lambda))^2 * ASNP[["H00"]] + eta * cumn[[2]][["P"]]) + eta
    * (cumn[[2]][["T"]] + cumn[[2]][["P"]] + cumn[[2]][["C"]])),
  inner_tol_objective = .Machine$double.eps^0.25,
  mvnorm_algorithm = mvtnorm::Miwa(steps = 128, checkCorr = FALSE, maxval = 1000),
  nloptr_x0 = NULL,
  nloptr_lb = NULL,
  nloptr_ub = NULL,
  nloptr_opts = list(algorithm = "NLOPT_LN_SBPLX", ftol_rel = 1e-04, xtol_abs = 0.001,
    xtol_rel = 0.01, maxeval = 1000, print_level = 0),
  print_progress = TRUE,
  ...
)

Arguments

cT2

(numeric) allocation ratio nT2 / nT1. Parameter to be optimized if left unspecified.

cP1

(numeric) allocation ratio nP1 / nT1. Parameter to be optimized if left unspecified.

cP2

(numeric) allocation ratio nP2 / nT1. Parameter to be optimized if left unspecified.

cC1

(numeric) allocation ratio nC1 / nT1. Parameter to be optimized if left unspecified.

cC2

(numeric) allocation ratio nC2 / nT1. Parameter to be optimized if left unspecified.

bTP1f

(numeric) first stage futility boundary for the T vs. P testing problem. Parameter to be optimized if left unspecified.

bTP1e

(numeric) first stage critical value for the T vs. P testing problem. Parameter to be optimized if left unspecified.

bTC1f

(numeric) first stage futility boundary for the T vs. C testing problem. Parameter to be optimized if left unspecified.

bTC1e

(numeric) first stage critical value for the T vs. C testing problem. Parameter to be optimized if left unspecified.

alpha

type I error rate.

beta

type II error rate.

alternative_TP

assumed difference between T and P under H1. Positive values favor T.

alternative_TC

assumed difference between T and C under H1. Positive values favor T.

Delta

non-inferiority margin for the test \(X_T - X_C \leq - \Delta\) vs. \(X_T - X_C > - \Delta\).

varT

variance of treatment group.

varP

variance of placebo group.

varC

variance of control group.

binding_futility

(logical) controls if futility boundaries are binding.

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 (Meis et al. 2023) ).

round_n

(logical) if TRUE, a design with integer valued sample sizes is returned.

lambda

(numeric) weight of the alternative hypothesis in the default objective function. 1-lambda is the weight of the null hypothesis.

kappa

(numeric) penalty factor for placebo patients in the default objective function.

eta

(numeric) penalty factor for the maximum sample size in the default objective function.

objective

(expression) objective criterion.

inner_tol_objective

(numeric) used to determine the tolerances for integrals and nuisance optimization problems inside the objective function.

mvnorm_algorithm

algorithm for multivariate integration passed to pmvnorm.

nloptr_x0

(numeric vector) starting point for optimization.

nloptr_lb

(numeric vector) lower bound box for box constrained optimization.

nloptr_ub

(numeric vector) upper bound box for box constrained optimization.

nloptr_opts

(list) nloptr options. See nloptr.

print_progress

(logical) controls whether optimization progress should be visualized during the calculation.

...

additional arguments passed along.

Value

Design object (a list) with optimized design parameters.

Details

This function calculates optimal design parameters for a two-stage three-arm gold-standard non-inferiority trial. Run vignette("Introduction", package = "OptimalGoldstandardDesigns") to see some examples related to the associated paper (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 cT2 = nT2 / nT1, cP1 = nP1 / nT1, cP2 = nP2 / nT1, cC1 = nC1 / nT1 and cC2 = nC2 / nT1. Here, nT1 denotes the sample size of the treatment group in the first stage, nP2 the sample size of the placebo group in the second stage, etc. The first stage efficacy boundaries are bTP1e for the treatment vs placebo testing problem, and bTC1e for the treatment vs control non-inferiority testing problem. The futility boundaries are denoted by bTP1f and bTC1f.

If these parameters are left unspecified or set to NULL, they will be included into the optimization process, otherwise they will be considered boundary constraints. You may also supply quoted expressions as arguments for these parameters to solve a constrained optimization problem. For example, you can supply 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 objective. The default objective function is described in the Subsection Optimizing group sequential gold-standard designs in Section 2 of (Meis et al. 2023) . Additionally, this objective includes a term to penalize the maximum sample size of a trial, which can be controlled by the parameter eta (default is eta=0).

Designs are calculated to fulfill the following constraints: the family-wise type I error rate is controlled at alpha under any combination of the two null hypotheses muT - muP = 0 and muT - muC + Delta = 0. The power to reject both hypothesis given both alternative hypotheses muT - muP = alternative_TP and muT - muC + Delta = alternative_TC + Delta is at least 1 - beta. Variances are assumed to be given by varT, varP and varC.

If binding_futility is TRUE, type I error recycling is used. If always_both_futility_tests is TRUE, it is assumed that futility tests for both hypotheses are performed at interim, regardless of whether the treatment vs placebo null hypothesis was successfully rejected. If always_both_futility_tests is FALSE, the futility test for the treatment vs. control testing problem only needs to be done if the null for the treatment vs. placebo testing problem was rejected in the first stage.

References

Meis J, Pilz M, Herrmann C, Bokelmann B, Rauch G, Kieser M (2023). “Optimization of the two-stage group sequential three-arm gold-standard design for non-inferiority trials.” Statistics in Medicine, 42(4), 536-558. doi:10.1002/sim.9630 .

Examples

# Should take about 15 seconds.
# \donttest{
optimize_design_twostage(
  cT2 = 1,
  cP2 = quote(cP1),
  cC2 = quote(cC1),
  bTP1f = -Inf,
  bTC1f = -Inf,
  beta = 0.2,
  alternative_TP = 0.4,
  alternative_TC = 0,
  Delta = 0.2,
  binding_futility = TRUE,
  lambda = .9,
  kappa = 1,
  nloptr_opts = list(algorithm = "NLOPT_LN_SBPLX", ftol_rel = 1e-01)
)
#> 
 iteration: 1/100                                                                    cP1 <- x[1L] cC1 <- x[2L] bTP1e <- x[3L] bTC1e <- x[4L]                             x = c(0.250000, 1.000000, 2.105099, 2.270933)                                      f(x) = 915.8373                                                             
 iteration: 2/100                                                                    cP1 <- x[1L] cC1 <- x[2L] bTP1e <- x[3L] bTC1e <- x[4L]                             x = c(0.250000, 1.000000, 2.105099, 2.270933)                                      f(x) = 915.8373                                                             
 iteration: 3/100                                                                    cP1 <- x[1L] cC1 <- x[2L] bTP1e <- x[3L] bTC1e <- x[4L]                             x = c(0.250000, 1.000000, 2.105099, 2.270933)                                      f(x) = 915.8373                                                             
 iteration: 4/100                                                                    cP1 <- x[1L] cC1 <- x[2L] bTP1e <- x[3L] bTC1e <- x[4L]                             x = c(0.428125, 1.000000, 2.105099, 2.270933)                                      f(x) = 917.2013                                                             
 iteration: 5/100                                                                    cP1 <- x[1L] cC1 <- x[2L] bTP1e <- x[3L] bTC1e <- x[4L]                             x = c(0.250000, 1.712500, 2.105099, 2.270933)                                      f(x) = 1022.588                                                             
 iteration: 6/100                                                                    cP1 <- x[1L] cC1 <- x[2L] bTP1e <- x[3L] bTC1e <- x[4L]                             x = c(0.428125, 0.287500, 2.105099, 2.270933)                                      f(x) = 1412.46                                                              
 iteration: 7/100                                                                    cP1 <- x[1L] cC1 <- x[2L] bTP1e <- x[3L] bTC1e <- x[4L]                             x = c(0.2945313, 1.3562500, 2.1050990, 2.2709330)                                  f(x) = 935.5908                                                             
 iteration: 8/100                                                                    cP1 <- x[1L] cC1 <- x[2L] bTP1e <- x[3L] bTC1e <- x[4L]                             x = c(0.3835937, 0.6437500, 2.1050990, 2.2709330)                                  f(x) = 963.2407                                                             
 iteration: 9/100                                                                    cP1 <- x[1L] cC1 <- x[2L] bTP1e <- x[3L] bTC1e <- x[4L]                             x = c(0.3167969, 1.1781250, 2.1050990, 2.2709330)                                  f(x) = 911.3385                                                             
 iteration: 10/100                                                                   cP1 <- x[1L] cC1 <- x[2L] bTP1e <- x[3L] bTC1e <- x[4L]                             x = c(0.1386719, 1.1781250, 2.1050990, 2.2709330)                                  f(x) = 1108.736                                                             
 iteration: 11/100                                                                   cP1 <- x[1L] cC1 <- x[2L] bTP1e <- x[3L] bTC1e <- x[4L]                             x = c(0.3557617, 1.0445312, 2.1050990, 2.2709330)                                  f(x) = 904.044                                                              
 iteration: 12/100                                                                   cP1 <- x[1L] cC1 <- x[2L] bTP1e <- x[3L] bTC1e <- x[4L]                             x = c(0.3557617, 1.0445312, 2.2139503, 2.2709330)                                  f(x) = 908.8018                                                             
 iteration: 13/100                                                                   cP1 <- x[1L] cC1 <- x[2L] bTP1e <- x[3L] bTC1e <- x[4L]                             x = c(0.3557617, 1.0445312, 2.1050990, 2.5041598)                                  f(x) = 907.0289                                                             
 iteration: 14/100                                                                   cP1 <- x[1L] cC1 <- x[2L] bTP1e <- x[3L] bTC1e <- x[4L]                             x = c(0.3557617, 1.0445312, 1.9962477, 2.5041598)                                  f(x) = 918.6257                                                             
 iteration: 15/100                                                                   cP1 <- x[1L] cC1 <- x[2L] bTP1e <- x[3L] bTC1e <- x[4L]                             x = c(0.3557617, 1.0445312, 2.1595246, 2.3292397)                                  f(x) = 903.9803                                                             
 iteration: 16/100                                                                   cP1 <- x[1L] cC1 <- x[2L] bTP1e <- x[3L] bTC1e <- x[4L]                             x = c(0.3557617, 1.0445312, 2.1595246, 2.0960129)                                  f(x) = 935.7317                                                             
 iteration: 17/100                                                                   cP1 <- x[1L] cC1 <- x[2L] bTP1e <- x[3L] bTC1e <- x[4L]                             x = c(0.3557617, 1.0445312, 2.1187054, 2.4021231)                                  f(x) = 903.2534                                                             
 iteration: 18/100                                                                   cP1 <- x[1L] cC1 <- x[2L] bTP1e <- x[3L] bTC1e <- x[4L]                             x = c(0.3557617, 1.0445312, 2.1731310, 2.4604297)                                  f(x) = 906.1748                                                             
 iteration: 19/100                                                                   cP1 <- x[1L] cC1 <- x[2L] bTP1e <- x[3L] bTC1e <- x[4L]                             x = c(0.3557617, 1.0445312, 2.1221070, 2.3183072)                                  f(x) = 902.9637                                                             
 iteration: 20/100                                                                   cP1 <- x[1L] cC1 <- x[2L] bTP1e <- x[3L] bTC1e <- x[4L]                             x = c(0.3557617, 1.0445312, 2.0812878, 2.3911905)                                  f(x) = 903.0437                                                             
 iteration: 21/100                                                                   cP1 <- x[1L] cC1 <- x[2L] bTP1e <- x[3L] bTC1e <- x[4L]                             x = c(0.3557617, 1.0445312, 2.0846894, 2.3073747)                                  f(x) = 902.7042                                                             
 iteration: 22/100                                                                   cP1 <- x[1L] cC1 <- x[2L] bTP1e <- x[3L] bTC1e <- x[4L]                             x = c(0.3557617, 1.0445312, 2.0676814, 2.2600005)                                  f(x) = 904.3596                                                             
 iteration: 23/100                                                                   cP1 <- x[1L] cC1 <- x[2L] bTP1e <- x[3L] bTC1e <- x[4L]                             x = c(0.3557617, 1.0445312, 2.1255086, 2.2344913)                                  f(x) = 906.9245                                                             
 iteration: 24/100                                                                   cP1 <- x[1L] cC1 <- x[2L] bTP1e <- x[3L] bTC1e <- x[4L]                             x = c(0.3557617, 1.0445312, 2.0923430, 2.3520157)                                  f(x) = 902.4161                                                             
 iteration: 25/100                                                                   cP1 <- x[1L] cC1 <- x[2L] bTP1e <- x[3L] bTC1e <- x[4L]                             x = c(0.3557617, 1.0445312, 2.0549254, 2.3410832)                                  f(x) = 903.1108                                                             
 iteration: 26/100                                                                   cP1 <- x[1L] cC1 <- x[2L] bTP1e <- x[3L] bTC1e <- x[4L]                             x = c(0.3557617, 1.0445312, 2.1053116, 2.3240012)                                  f(x) = 902.5799                                                             
 iteration: 27/100                                                                   cP1 <- x[1L] cC1 <- x[2L] bTP1e <- x[3L] bTC1e <- x[4L]                             x = c(0.3557617, 1.0445312, 2.1129652, 2.3686423)                                  f(x) = 902.6578                                                             
 iteration: 28/100                                                                   cP1 <- x[1L] cC1 <- x[2L] bTP1e <- x[3L] bTC1e <- x[4L]                             x = c(0.3557617, 1.0445312, 2.1058963, 2.3533254)                                  f(x) = 902.4788                                                             
 iteration: 29/100                                                                   cP1 <- x[1L] cC1 <- x[2L] bTP1e <- x[3L] bTC1e <- x[4L]                             x = c(0.3557617, 1.0445312, 2.0929276, 2.3813399)                                  f(x) = 902.7331                                                             
 iteration: 30/100                                                                   cP1 <- x[1L] cC1 <- x[2L] bTP1e <- x[3L] bTC1e <- x[4L]                             x = c(0.3557617, 1.0445312, 2.1022156, 2.3383359)                                  f(x) = 902.4439                                                             
#>  Optimization finished. Calculating final design with greater accuracy...
#> Sample sizes (stage 1): T: 209, P: 75, C: 219
#> Sample sizes (stage 2): T: 209, P: 75, C: 219
#> Efficacy boundaries (stage 1): Z_TP_e: 2.09234, Z_TC_e: 2.35202
#> Futility boundaries (stage 1): Z_TP_f: -Inf, Z_TC_f: -Inf
#> Efficacy boundaries (stage 2): Z_TP_e: 2.29395, Z_TC_e: 2.06436
#> Inverse normal combination test weights (TP): w1: 0.70711, w2: 0.70711
#> Inverse normal combination test weights (TC): w1: 0.70711, w2: 0.70711
#> Maximum overall sample size: 1006
#> Expected sample size (H1): 795.7
#> Expected sample size (H0): 1004.2
#> Expected placebo group sample size (H1): 89.4
#> Expected placebo group sample size (H0): 148.6
#> Objective function value: 905.7
#> (local) type I error for TP testing: 2.50%
#> (local) type I error for TC testing: 2.50%
#> Probability of futility stop (H1): 0.00%
#> Probability of futility stop (H0): 0.00%
#> Minimum conditional power: 0.00%
#> Power: 80.03%
#> Futility boundaries: binding
#> Futility testing method: always both futility tests
# }