library(gsDesign)
# Example 1 ----
# gs_design_npe with single analysis
# Lachin book p 71 difference of proportions example
pc <- .28 # Control response rate
pe <- .40 # Experimental response rate
p0 <- (pc + pe) / 2 # Ave response rate under H0
# Information per increment of 1 in sample size
info0 <- 1 / (p0 * (1 - p0) * 4)
info <- 1 / (pc * (1 - pc) * 2 + pe * (1 - pe) * 2)
# Result should round up to next even number = 652
# Divide information needed under H1 by information per patient added
gs_design_npe(theta = pe - pc, info = info, info0 = info0)
# Example 2 ----
# gs_design_npe with with fixed bound
x <- gs_design_npe(
alpha = 0.0125,
theta = c(.1, .2, .3),
info = (1:3) * 80,
info0 = (1:3) * 80,
upper = gs_b,
upar = gsDesign::gsDesign(k = 3, sfu = gsDesign::sfLDOF, alpha = 0.0125)$upper$bound,
lower = gs_b,
lpar = c(-1, 0, 0)
)
x
# Same upper bound; this represents non-binding Type I error and will total 0.025
gs_power_npe(
theta = rep(0, 3),
info = (x |> dplyr::filter(bound == "upper"))$info,
upper = gs_b,
upar = (x |> dplyr::filter(bound == "upper"))$z,
lower = gs_b,
lpar = rep(-Inf, 3)
)
# Example 3 ----
# gs_design_npe with spending bound
# Design with futility only at analysis 1; efficacy only at analyses 2, 3
# Spending bound for efficacy; fixed bound for futility
# NOTE: test_upper and test_lower DO NOT WORK with gs_b; must explicitly make bounds infinite
# test_upper and test_lower DO WORK with gs_spending_bound
gs_design_npe(
theta = c(.1, .2, .3),
info = (1:3) * 40,
info0 = (1:3) * 40,
upper = gs_spending_bound,
upar = list(sf = gsDesign::sfLDOF, total_spend = 0.025, param = NULL, timing = NULL),
lower = gs_b,
lpar = c(-1, -Inf, -Inf),
test_upper = c(FALSE, TRUE, TRUE)
)
# one can try `info_scale = "h1_info"` or `info_scale = "h0_info"` here
gs_design_npe(
theta = c(.1, .2, .3),
info = (1:3) * 40,
info0 = (1:3) * 30,
info_scale = "h1_info",
upper = gs_spending_bound,
upar = list(sf = gsDesign::sfLDOF, total_spend = 0.025, param = NULL, timing = NULL),
lower = gs_b,
lpar = c(-1, -Inf, -Inf),
test_upper = c(FALSE, TRUE, TRUE)
)
# Example 4 ----
# gs_design_npe with spending function bounds
# 2-sided asymmetric bounds
# Lower spending based on non-zero effect
gs_design_npe(
theta = c(.1, .2, .3),
info = (1:3) * 40,
info0 = (1:3) * 30,
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)
)
# Example 5 ----
# gs_design_npe with two-sided symmetric spend, O'Brien-Fleming spending
# Typically, 2-sided bounds are binding
xx <- gs_design_npe(
theta = c(.1, .2, .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)
)
xx
# 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,
upper = gs_b,
lower = gs_b,
upar = (xx |> dplyr::filter(bound == "upper"))$z,
lpar = -(xx |> dplyr::filter(bound == "upper"))$z
)
# Example 6 ----
# Default of gs_power_npe (single analysis; Type I error controlled)
gs_power_npe(theta = 0) |> dplyr::filter(bound == "upper")
# Example 7 ----
# gs_power_npe with 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)
)
# 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)
) |>
dplyr::filter(bound == "upper")
# Example 8 ----
# gs_power_npe with fixed bound testing 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)
)
# Example 9 ----
# gs_power_npe with 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)
)
# 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)
)
# Example 10 ----
# gs_power_npe with 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 |> dplyr::filter(bound == "upper"))$z,
lpar = -(x |> dplyr::filter(bound == "upper"))$z
)
# Example 11 ----
# 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 = 0.1,
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
)
Run the code above in your browser using DataLab