Skip to contents

Overview

This vignette covers how to implement designs for trials with spending assuming non-proportional hazards. We are primarily concerned with practical issues of implementation rather than design strategies, but we will not ignore design strategy.

Scenario for consideration

Here we set up enrollment, failure and dropout rates along with assumptions for enrollment duration and times of analyses.

analysisTimes <- c(18, 24, 30, 36)
enrollRates <- tibble::tibble(
  Stratum = "All",
  duration = c(2, 2, 2, 6),
  rate = c(8, 12, 16, 24)
)
failRates <- tibble::tibble(
  Stratum = "All",
  duration = c(3, 100),
  failRate = log(2) / c(8, 14),
  hr = c(.9, .6),
  dropoutRate = .001
)

Deriving power for a given sample size

We derive statistical information at targeted analysis times.

xx <- gsDesign2::AHR(enrollRates = enrollRates, failRates = failRates, totalDuration = analysisTimes)
Events <- ceiling(xx$Events)
yy <- gs_info_ahr(enrollRates = enrollRates, failRates = failRates, events = Events)

Now we can examine power using gs_power_npe():

timing <- yy$info0 / max(yy$info0)
d <- gsDesign::gsDesign(k = length(timing), test.type = 2, sfu = sfLDOF, alpha = .025, timing = timing)
zz <- gs_power_npe(
  theta = yy$theta, info = yy$info, info0 = yy$info0,
  upper = gs_b, lower = gs_b,
  upar = d$upper$bound,
  lpar = d$lower$bound
)
zz
#> # A tibble: 8 × 9
#>   Analysis Bound     Z Probability theta theta1  info info0 info1
#>      <int> <chr> <dbl>       <dbl> <dbl>  <dbl> <dbl> <dbl> <dbl>
#> 1        1 Upper  2.67   0.105     0.301  0.301  22.3  22.7  22.3
#> 2        2 Upper  2.36   0.307     0.346  0.346  28.4  29.0  28.4
#> 3        3 Upper  2.18   0.493     0.370  0.370  33.4  34.0  33.4
#> 4        4 Upper  2.07   0.627     0.385  0.385  37.5  38.0  37.5
#> 5        1 Lower -2.67   0.0000214 0.301  0.301  22.3  22.7  22.3
#> 6        2 Lower -2.36   0.0000299 0.346  0.346  28.4  29.0  28.4
#> 7        3 Lower -2.18   0.0000336 0.370  0.370  33.4  34.0  33.4
#> 8        4 Lower -2.07   0.0000353 0.385  0.385  37.5  38.0  37.5

Deriving sample size to power a trial

If we were using a fixed design, we would approximate the sample size as follows:

K <- 4
minx <- ((qnorm(.025) / sqrt(zz$info0[K]) + qnorm(.1) / sqrt(zz$info[K])) / zz$theta[K])^2
minx
#> [1] 1.875516

If we inflate the enrollment rates by minx and use a fixed design, we will see this achieves the targeted power.

gs_power_npe(
  theta = yy$theta[K], info = yy$info[K] * minx, info0 = yy$info0[K] * minx,
  upar = qnorm(.975), lpar = -Inf
) %>%
  filter(Bound == "Upper")
#> # A tibble: 1 × 9
#>   Analysis Bound     Z Probability theta theta1  info info0 info1
#>      <int> <chr> <dbl>       <dbl> <dbl>  <dbl> <dbl> <dbl> <dbl>
#> 1        1 Upper  1.96       0.898 0.385  0.385  70.3  71.3  70.3

The power for a group sequential design with the same final sample size is a bit lower:

zz <- gs_power_npe(
  theta = yy$theta, info = yy$info * minx, info0 = yy$info0 * minx,
  upper = gs_spending_bound, lower = gs_spending_bound,
  upar = list(sf = gsDesign::sfLDOF, total_spend = 0.025, param = NULL, timing = NULL),
  lpar = list(sf = gsDesign::sfLDOF, total_spend = 0.025, param = NULL, timing = NULL)
)
zz
#> # A tibble: 8 × 9
#>   Analysis Bound      Z Probability theta theta1  info info0 info1
#>      <int> <chr>  <dbl>       <dbl> <dbl>  <dbl> <dbl> <dbl> <dbl>
#> 1        1 Upper  2.67      0.233   0.301  0.301  41.8  42.7  41.8
#> 2        2 Upper  2.36      0.569   0.346  0.346  53.3  54.4  53.3
#> 3        3 Upper  2.18      0.778   0.370  0.370  62.7  63.8  62.7
#> 4        4 Upper  2.07      0.881   0.385  0.385  70.3  71.3  70.3
#> 5        1 Lower -0.739     0.00365 0.301  0.301  41.8  42.7  41.8
#> 6        2 Lower  0.159     0.0101  0.346  0.346  53.3  54.4  53.3
#> 7        3 Lower  0.746     0.0176  0.370  0.370  62.7  63.8  62.7
#> 8        4 Lower  1.16      0.0250  0.385  0.385  70.3  71.3  70.3

If we inflate this a bit we will be overpowered.

zz <- gs_power_npe(
  theta = yy$theta, info = yy$info * minx * 1.2, info0 = yy$info0 * minx * 1.2,
  upper = gs_spending_bound, lower = gs_spending_bound,
  upar = list(sf = gsDesign::sfLDOF, total_spend = 0.025, param = NULL, timing = NULL),
  lpar = list(sf = gsDesign::sfLDOF, total_spend = 0.025, param = NULL, timing = NULL)
)
zz
#> # A tibble: 8 × 9
#>   Analysis Bound      Z Probability theta theta1  info info0 info1
#>      <int> <chr>  <dbl>       <dbl> <dbl>  <dbl> <dbl> <dbl> <dbl>
#> 1        1 Upper  2.67      0.294   0.301  0.301  50.2  51.2  50.2
#> 2        2 Upper  2.36      0.660   0.346  0.346  64.0  65.3  64.0
#> 3        3 Upper  2.18      0.851   0.370  0.370  75.3  76.5  75.3
#> 4        4 Upper  2.07      0.931   0.385  0.385  84.4  85.5  84.4
#> 5        1 Lower -0.554     0.00365 0.301  0.301  50.2  51.2  50.2
#> 6        2 Lower  0.400     0.0101  0.346  0.346  64.0  65.3  64.0
#> 7        3 Lower  1.03      0.0176  0.370  0.370  75.3  76.5  75.3
#> 8        4 Lower  1.47      0.0250  0.385  0.385  84.4  85.5  84.4

Now we use gs_design_npe() to inflate the information proportionately to power the trial.

theta <- yy$theta
info <- yy$info
info0 <- yy$info0
upper <- gs_spending_bound
lower <- gs_spending_bound
upar <- list(sf = gsDesign::sfLDOF, total_spend = 0.025, param = NULL, timing = NULL)
lpar <- list(sf = gsDesign::sfLDOF, total_spend = 0.025, param = NULL, timing = NULL)
alpha <- .025
beta <- .1
binding <- FALSE
test_upper <- TRUE
test_lower <- TRUE
r <- 18
tol <- 1e-06

zz <- gs_design_npe(
  theta = yy$theta, info = yy$info, info0 = yy$info0,
  upper = gs_spending_bound, lower = gs_spending_bound,
  upar = list(sf = gsDesign::sfLDOF, total_spend = 0.025, param = NULL, timing = NULL),
  lpar = list(sf = gsDesign::sfLDOF, total_spend = 0.025, param = NULL, timing = NULL)
)
zz
#> # A tibble: 8 × 9
#>   Analysis Bound      Z Probability theta theta1  info info0 info1
#>      <int> <chr>  <dbl>       <dbl> <dbl>  <dbl> <dbl> <dbl> <dbl>
#> 1        1 Upper  2.67      0.252   0.301  0.301  44.5  45.4  44.5
#> 2        2 Upper  2.36      0.600   0.346  0.346  56.8  57.9  56.8
#> 3        3 Upper  2.18      0.804   0.370  0.370  66.8  67.9  66.8
#> 4        4 Upper  2.07      0.900   0.385  0.385  74.9  75.8  74.9
#> 5        1 Lower -0.678     0.00365 0.301  0.301  44.5  45.4  44.5
#> 6        2 Lower  0.238     0.0101  0.346  0.346  56.8  57.9  56.8
#> 7        3 Lower  0.839     0.0176  0.370  0.370  66.8  67.9  66.8
#> 8        4 Lower  1.26      0.0250  0.385  0.385  74.9  75.8  74.9