Group sequential bound computation with non-constant effect
Source:R/gs_power_npe.R
gs_power_npe.Rd
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 and statistical information at each analysis as well as a method of deriving bounds, such as spending. The routine enables two things not available in the gsDesign package:
non-constant effect, 2) more flexibility in boundary selection. For many applications, the non-proportional-hazards design function
gs_design_nph()
will be used; it calls this function. Initial bound types supported are 1) spending bounds,fixed bounds, and 3) Haybittle-Peto-like bounds. The requirement is to have a boundary update method that can each bound without knowledge of future bounds. As an example, bounds based on conditional power that require knowledge of all future bounds are not supported by this routine; a more limited conditional power method will be demonstrated. Boundary family designs Wang-Tsiatis designs including the original (non-spending-function-based) O'Brien-Fleming and Pocock designs are not supported by
gs_power_npe()
.
Arguments
- theta
Natural parameter for group sequential design representing expected incremental drift at all analyses; used for power calculation.
- theta0
Natural parameter for null hypothesis, if needed for upper bound computation.
- theta1
Natural parameter for alternate hypothesis, if needed for lower bound computation.
- info
Statistical information at all analyses for input
theta
.- info0
Statistical information under null hypothesis, if different than
info
; impacts null hypothesis bound calculation.- info1
Statistical information under hypothesis used for futility bound calculation if different from
info
; impacts futility hypothesis bound calculation.- info_scale
Information scale for calculation. Options are:
"h0_h1_info"
(default): variance under both null and alternative hypotheses is used."h0_info"
: variance under null hypothesis is used."h1_info"
: variance under alternative hypothesis is used.
- upper
Function to compute upper bound.
- upar
Parameters passed to
upper
.- lower
Function to compare lower bound.
- lpar
parameters passed to
lower
.- test_upper
Indicator of which analyses should include an upper (efficacy) bound; single value of
TRUE
(default) indicates all analyses; otherwise, a logical vector of the same length asinfo
should indicate which analyses will have an efficacy bound.- test_lower
Indicator of which analyses should include a lower bound; single value of
TRUE
(default) indicates all analyses; single value ofFALSE
indicated no lower bound; otherwise, a logical vector of the same length asinfo
should indicate which analyses will have a lower bound.- binding
Indicator of whether futility bound is binding; default of
FALSE
is recommended.- r
Integer value controlling grid for numerical integration as in Jennison and Turnbull (2000); default is 18, range is 1 to 80. Larger values provide larger number of grid points and greater accuracy. Normally,
r
will not be changed by the user.- tol
Tolerance parameter for boundary convergence (on Z-scale).
Value
A tibble with columns as analysis index, bounds, z, crossing probability, theta (standardized treatment effect), theta1 (standardized treatment effect under alternative hypothesis), information fraction, and statistical information.
Examples
library(gsDesign)
library(gsDesign2)
library(dplyr)
# Default (single analysis; Type I error controlled)
gs_power_npe(theta = 0) %>% filter(bound == "upper")
#> # A tibble: 1 × 10
#> analysis bound z probability theta theta1 info_frac info info0 info1
#> <int> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 1 upper 1.96 0.0250 0 0 1 1 1 1
# 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)
)
#> # A tibble: 6 × 10
#> analysis bound z probability theta theta1 info_frac 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.869 0.3 0.3 1 120 120 120
#> 4 1 lower -1 0.0513 0.1 0.1 0.333 40 40 40
#> 5 2 lower 0 0.0715 0.2 0.2 0.667 80 80 80
#> 6 3 lower 0 0.0715 0.3 0.3 1 120 120 120
# Same fixed efficacy bounds, no futility bound (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")
#> # A tibble: 3 × 10
#> analysis bound z probability theta theta1 info_frac info info0 info1
#> <int> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 1 upper 3.71 0.000104 0 0 0.333 40 40 40
#> 2 2 upper 2.51 0.00605 0 0 0.667 80 80 80
#> 3 3 upper 1.99 0.0250 0 0 1 120 120 120
# Fixed bound with futility only at analysis 1; efficacy only at analyses 2, 3
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)
)
#> # A tibble: 6 × 10
#> analysis bound z probability theta theta1 info_frac info info0 info1
#> <int> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 1 upper Inf 0 0.1 0.1 0.333 40 40 40
#> 2 2 upper 3 0.113 0.2 0.2 0.667 80 80 80
#> 3 3 upper 2 0.887 0.3 0.3 1 120 120 120
#> 4 1 lower -1.28 0.0278 0.1 0.1 0.333 40 40 40
#> 5 2 lower -Inf 0.0278 0.2 0.2 0.667 80 80 80
#> 6 3 lower -Inf 0.0278 0.3 0.3 1 120 120 120
# Spending function bounds
# Lower spending based on non-zero effect
gs_power_npe(
theta = c(.1, .2, .3),
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 info_frac 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
# Same bounds, but power under different theta
gs_power_npe(
theta = c(.15, .25, .35),
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 info_frac info info0 info1
#> <int> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 1 upper 3.71 0.00288 0.15 0.15 0.333 40 40 40
#> 2 2 upper 2.51 0.391 0.25 0.25 0.667 80 80 80
#> 3 3 upper 1.99 0.931 0.35 0.35 1 120 120 120
#> 4 1 lower -1.05 0.0230 0.15 0.15 0.333 40 40 40
#> 5 2 lower 0.520 0.0552 0.25 0.25 0.667 80 80 80
#> 6 3 lower 2.41 0.100 0.35 0.35 1 120 120 120
# Two-sided symmetric spend, O'Brien-Fleming spending
# Typically, 2-sided bounds are binding
x <- gs_power_npe(
theta = rep(0, 3),
info = (1:3) * 40,
binding = TRUE,
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::sfLDOF, total_spend = 0.025, param = NULL, timing = NULL)
)
# 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
)
#> # A tibble: 6 × 10
#> analysis bound z probability theta theta1 info_frac 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.902 0.3 0.3 1 120 120 120
#> 4 1 lower -3.71 0.00000704 0.1 0.1 0.333 40 40 40
#> 5 2 lower -2.51 0.0000151 0.2 0.2 0.667 80 80 80
#> 6 3 lower -1.99 0.0000151 0.3 0.3 1 120 120 120
# Different values of `r` and `tol` lead to different numerical accuracy
# Larger `r` and smaller `tol` give better accuracy, but leads to slow computation
n_analysis <- 5
gs_power_npe(
theta = rep(0.1, n_analysis),
theta0 = NULL,
theta1 = NULL,
info = 1:n_analysis,
info0 = 1:n_analysis,
info1 = NULL,
info_scale = "h0_info",
upper = gs_spending_bound,
upar = list(sf = gsDesign::sfLDOF, total_spend = 0.025, param = NULL, timing = NULL),
lower = gs_b,
lpar = -rep(Inf, n_analysis),
test_upper = TRUE,
test_lower = FALSE,
binding = FALSE,
# Try different combinations of (r, tol) with
# r in 6, 18, 24, 30, 35, 40, 50, 60, 70, 80, 90, 100
# tol in 1e-6, 1e-12
r = 6,
tol = 1e-6
)
#> # A tibble: 10 × 10
#> analysis bound z probability theta theta1 info_frac info info0 info1
#> <int> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <int> <int> <int>
#> 1 1 upper 4.88 0.000000890 0.1 0.1 0.2 1 1 1
#> 2 2 upper 3.36 0.000650 0.1 0.1 0.4 2 2 2
#> 3 3 upper 2.68 0.00627 0.1 0.1 0.6 3 3 3
#> 4 4 upper 2.29 0.0200 0.1 0.1 0.8 4 4 4
#> 5 5 upper 2.03 0.0408 0.1 0.1 1 5 5 5
#> 6 1 lower -Inf 0 0.1 0.1 0.2 1 1 1
#> 7 2 lower -Inf 0 0.1 0.1 0.4 2 2 2
#> 8 3 lower -Inf 0 0.1 0.1 0.6 3 3 3
#> 9 4 lower -Inf 0 0.1 0.1 0.8 4 4 4
#> 10 5 lower -Inf 0 0.1 0.1 1 5 5 5