Skip to contents
library(tibble)
library(dplyr)
library(gt)
#library(gsDesign2)
devtools::load_all()

Introduction of gs_power_npe()

gs_power_npe() derives group sequential bounds and boundary crossing probabilities for a design. It allows a non-constant treatment effect over time, but also can be applied for the usual homogeneous effect size designs. It requires + treatment effect (theta, theta1) + statistical information at each analysis (info, info0, info1) + a method of deriving bounds, such as fixed bounds or spending (upper, upar, lower, lpar).

The routine enables two things not available in the gsDesign package:

  1. non-constant effect,
  2. more flexibility in boundary selection.

Usage of gs_power_npe()

Example 1: Fixed bound

no futility bound

# Same fixed efficacy bounds,  (i.e., non-binding bound), null hypothesis
gs_power_npe(
  theta = rep(0, 3),
  info = (1:3) * 40,
  upar = gsDesign::gsDesign(k = 3,sfu = gsDesign::sfLDOF)$upper$bound,
  lpar = rep(-Inf, 3)) %>%
  filter(Bound == "Upper") %>% gt()
Analysis Bound Z Probability theta theta1 IF info info0 info1
1 Upper 3.710303 0.0001035057 0 0 0.3333333 40 40 40
2 Upper 2.511427 0.0060485045 0 0 0.6666667 80 80 80
3 Upper 1.993048 0.0250002354 0 0 1.0000000 120 120 120

with futility bound

# Fixed bound
gs_power_npe(
  theta = c(.1, .2, .3),
  info = (1:3) * 40,
  upper = gs_b,
  upar = gsDesign::gsDesign(k = 3,sfu = gsDesign::sfLDOF)$upper$bound,
  lower = gs_b,
  lpar = c(-1, 0, 0)) %>% gt()
Analysis Bound Z Probability theta theta1 IF info info0 info1
1 Upper 3.710303 0.001042508 0.1 0.1 0.3333333 40 40 40
2 Upper 2.511427 0.234899008 0.2 0.2 0.6666667 80 80 80
3 Upper 1.993048 0.868689026 0.3 0.3 1.0000000 120 120 120
1 Lower -1.000000 0.051291779 0.1 0.1 0.3333333 40 40 40
2 Lower 0.000000 0.071509147 0.2 0.2 0.6666667 80 80 80
3 Lower 0.000000 0.071522125 0.3 0.3 1.0000000 120 120 120

futility only at analysis 1

gs_power_npe(
  theta = c(.1, .2, .3),
  info = (1:3) * 40,
  upper = gs_b,
  upar = c(Inf, 3, 2),
  lower = gs_b,
  lpar = c(qnorm(.1), -Inf, -Inf)) %>% gt()
Analysis Bound Z Probability theta theta1 IF info info0 info1
1 Upper Inf 0.00000000 0.1 0.1 0.3333333 40 40 40
2 Upper 3.000000 0.11291848 0.2 0.2 0.6666667 80 80 80
3 Upper 2.000000 0.88742529 0.3 0.3 1.0000000 120 120 120
1 Lower -1.281552 0.02780962 0.1 0.1 0.3333333 40 40 40
2 Lower -Inf 0.02780962 0.2 0.2 0.6666667 80 80 80
3 Lower -Inf 0.02780962 0.3 0.3 1.0000000 120 120 120

Example 2: spending bounds

lower spending based on non-zero effect

gs_power_npe(
  theta = c(.1, .2, .3), # non-zero effect
  info = (1:3) * 40,
  upper = gs_spending_bound,
  upar = list(sf = gsDesign::sfLDOF, total_spend = 0.025, param = NULL, timing = NULL),
  lower = gs_spending_bound,
  lpar = list(sf = gsDesign::sfHSD, total_spend = 0.1, param = -1, timing = NULL))
## # A tibble: 6 × 10
##   Analysis Bound       Z Probability theta theta1    IF  info info0 info1
##      <int> <chr>   <dbl>       <dbl> <dbl>  <dbl> <dbl> <dbl> <dbl> <dbl>
## 1        1 Upper  3.71       0.00104   0.1    0.1 0.333    40    40    40
## 2        2 Upper  2.51       0.235     0.2    0.2 0.667    80    80    80
## 3        3 Upper  1.99       0.883     0.3    0.3 1       120   120   120
## 4        1 Lower -1.36       0.0230    0.1    0.1 0.333    40    40    40
## 5        2 Lower  0.0726     0.0552    0.2    0.2 0.667    80    80    80
## 6        3 Lower  1.86       0.100     0.3    0.3 1       120   120   120

2-sided symmetric spend

x <- gs_power_npe(
  theta = rep(0, 3),
  info = (1:3) * 40,
  # typically, 2-sided bounds are binding
  binding = TRUE,
  upper = gs_spending_bound,
  # O'Brien-Fleming spending
  upar = list(sf = gsDesign::sfLDOF, total_spend = 0.025, param = NULL, timing = NULL),
  lower = gs_spending_bound,
  lpar = list(sf = gsDesign::sfLDOF, total_spend = 0.025, param = NULL, timing = NULL))

x %>% gt()
Analysis Bound Z Probability theta theta1 IF info info0 info1
1 Upper 3.710303 0.0001035057 0 0 0.3333333 40 40 40
2 Upper 2.511434 0.0060483891 0 0 0.6666667 80 80 80
3 Upper 1.993051 0.0250000000 0 0 1.0000000 120 120 120
1 Lower -3.710303 0.0001035057 0 0 0.3333333 40 40 40
2 Lower -2.511434 0.0060483891 0 0 0.6666667 80 80 80
3 Lower -1.993051 0.0250000000 0 0 1.0000000 120 120 120
# Re-use these bounds under alternate hypothesis
# Always use binding = TRUE for power calculations
gs_power_npe(
  theta = c(.1, .2, .3),
  info = (1:3) * 40,
  binding = TRUE,
  upar = (x %>% filter(Bound == "Upper"))$Z,
  lpar = -(x %>% filter(Bound == "Upper"))$Z) %>% gt()
Analysis Bound Z Probability theta theta1 IF info info0 info1
1 Upper 3.710303 1.042508e-03 0.1 0.1 0.3333333 40 40 40
2 Upper 2.511434 2.349812e-01 0.2 0.2 0.6666667 80 80 80
3 Upper 1.993051 9.020711e-01 0.3 0.3 1.0000000 120 120 120
1 Lower -3.710303 7.035242e-06 0.1 0.1 0.3333333 40 40 40
2 Lower -2.511434 1.510031e-05 0.2 0.2 0.6666667 80 80 80
3 Lower -1.993051 1.512598e-05 0.3 0.3 1.0000000 120 120 120

Inner Logic of gs_spending_bound()

TODO