Skip to content

Commit f9aab1a

Browse files
authored
Merge pull request #446 from Merck/441-info-frac-driven-design-does-not-work-for-gs_design_wlr
Info frac driven design does not work for `gs_design_wlr()`
2 parents f965dae + 6f6ba62 commit f9aab1a

File tree

4 files changed

+374
-185
lines changed

4 files changed

+374
-185
lines changed

R/gs_design_wlr.R

+146-58
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
#' library(gsDesign2)
4646
#'
4747
#' # set enrollment rates
48-
#' enroll_rate <- define_enroll_rate(duration = 12, rate = 500 / 12)
48+
#' enroll_rate <- define_enroll_rate(duration = 12, rate = 1)
4949
#'
5050
#' # set failure rates
5151
#' fail_rate <- define_fail_rate(
@@ -56,22 +56,25 @@
5656
#' )
5757
#'
5858
#' # Example 1 ----
59-
#' # Boundary is fixed
60-
#' x <- gsSurv(
61-
#' k = 3,
62-
#' test.type = 4,
59+
#' # Information fraction driven design
60+
#' gs_design_wlr(
61+
#' enroll_rate = enroll_rate,
62+
#' fail_rate = fail_rate,
63+
#' ratio = 1,
6364
#' alpha = 0.025, beta = 0.2,
64-
#' astar = 0, timing = 1,
65-
#' sfu = sfLDOF, sfupar = 0,
66-
#' sfl = sfLDOF, sflpar = 0,
67-
#' lambdaC = 0.1,
68-
#' hr = 0.6, hr0 = 1,
69-
#' eta = 0.01, gamma = 10,
70-
#' R = 12, S = NULL,
71-
#' T = 36, minfup = 24,
72-
#' ratio = 1
65+
#' weight = function(x, arm0, arm1) {
66+
#' wlr_weight_fh(x, arm0, arm1, rho = 0, gamma = 0.5)
67+
#' },
68+
#' upper = gs_spending_bound,
69+
#' upar = list(sf = gsDesign::sfLDOF, total_spend = 0.025),
70+
#' lower = gs_spending_bound,
71+
#' lpar = list(sf = gsDesign::sfLDOF, total_spend = 0.2),
72+
#' analysis_time = 36,
73+
#' info_frac = 1:3/3
7374
#' )
7475
#'
76+
#' # Example 2 ----
77+
#' # Calendar time driven design
7578
#' gs_design_wlr(
7679
#' enroll_rate = enroll_rate,
7780
#' fail_rate = fail_rate,
@@ -80,15 +83,16 @@
8083
#' weight = function(x, arm0, arm1) {
8184
#' wlr_weight_fh(x, arm0, arm1, rho = 0, gamma = 0.5)
8285
#' },
83-
#' upper = gs_b,
84-
#' upar = x$upper$bound,
85-
#' lower = gs_b,
86-
#' lpar = x$lower$bound,
87-
#' analysis_time = c(12, 24, 36)
86+
#' upper = gs_spending_bound,
87+
#' upar = list(sf = gsDesign::sfLDOF, total_spend = 0.025),
88+
#' lower = gs_spending_bound,
89+
#' lpar = list(sf = gsDesign::sfLDOF, total_spend = 0.2),
90+
#' analysis_time = 1:3*12,
91+
#' info_frac = NULL
8892
#' )
8993
#'
90-
#' # Example 2 ----
91-
#' # Boundary derived by spending function
94+
#' # Example 3 ----
95+
#' # Both calendar time and information fraction driven design
9296
#' gs_design_wlr(
9397
#' enroll_rate = enroll_rate,
9498
#' fail_rate = fail_rate,
@@ -101,7 +105,8 @@
101105
#' upar = list(sf = gsDesign::sfLDOF, total_spend = 0.025),
102106
#' lower = gs_spending_bound,
103107
#' lpar = list(sf = gsDesign::sfLDOF, total_spend = 0.2),
104-
#' analysis_time = c(12, 24, 36)
108+
#' analysis_time = 1:3*12,
109+
#' info_frac = c(0.3, 0.7, 1)
105110
#' )
106111
gs_design_wlr <- function(
107112
enroll_rate = define_enroll_rate(
@@ -128,7 +133,9 @@ gs_design_wlr <- function(
128133
h1_spending = TRUE,
129134
r = 18, tol = 1e-6,
130135
interval = c(.01, 1000)) {
131-
# Check input values ----
136+
# --------------------------------------- #
137+
# check input values #
138+
# --------------------------------------- #
132139
msg <- "gs_design_wlr(): analysis_time must be a
133140
positive number or positive increasing sequence"
134141
if (!is.vector(analysis_time, mode = "numeric")) stop(msg)
@@ -145,52 +152,103 @@ gs_design_wlr <- function(
145152
stop("gs_design_wlr() hr must not be equal to 1 throughout the study as this is the null hypothesis.")
146153
}
147154

148-
# get the info_scale
155+
# ---------------------------------------- #
156+
# get some basic parameters #
157+
# ---------------------------------------- #
149158
info_scale <- match.arg(info_scale)
159+
n_analysis <- max(length(analysis_time), length(info_frac))
150160

151-
# Get information at input analysis_time ----
161+
# ---------------------------------------- #
162+
# get information at input #
163+
# analysis_time, covering 3 scenarios:#
164+
# (1) fixed design #
165+
# (2) info-frac driven design with #
166+
# a known study duration #
167+
# (3) calendar time driven design #
168+
# ---------------------------------------- #
152169
y <- gs_info_wlr(enroll_rate, fail_rate,
153-
ratio = ratio, event = NULL,
154-
analysis_time = analysis_time, weight = weight, approx = approx,
155-
interval = interval
156-
)
170+
ratio = ratio, event = NULL,
171+
analysis_time = analysis_time, weight = weight, approx = approx,
172+
interval = interval) %>%
173+
dplyr::select(-c(n, delta, sigma2))
157174

158175
final_event <- y$event[nrow(y)]
159-
if_alt <- y$event / final_event
176+
final_info <- max(y$info)
177+
info_frac_by_time <- y$info / max(y$info)
160178

161-
# Check if info_frac needed for (any) IA timing
162-
n_analysis <- max(length(analysis_time), length(info_frac))
179+
# if it is info frac driven group sequential design
180+
# relabel the analysis to FA, and back calculate IAs from FA
181+
if (n_analysis > 1 && length(analysis_time) == 1 && length(info_frac) > 1) {
182+
y$analysis <- n_analysis
183+
}
184+
185+
# ---------------------------------------- #
186+
# calculate the design to meet #
187+
# targeted info_frac or analysis_time #
188+
# or both #
189+
# ---------------------------------------- #
163190
next_time <- max(analysis_time)
164191

192+
# if it is calendar time driven design,
193+
# e.g., info_frac = NULL, analysis_time = c(12, 14, 36)
165194
if (length(info_frac) == 1) {
166-
info_frac <- if_alt
195+
info_frac <- info_frac_by_time
167196
} else {
197+
# if info_frac != NULL
168198
if_indx <- info_frac[1:(n_analysis - 1)]
169199
for (i in seq_along(if_indx)) {
170-
if (length(if_alt) == 1) {
171-
y <- rbind(
172-
expected_time(enroll_rate, fail_rate,
173-
target_event = info_frac[n_analysis - i] * final_event,
174-
ratio = ratio, interval = c(.01, next_time)
175-
) %>%
176-
mutate(theta = -log(ahr), analysis = n_analysis - i),
177-
y
178-
)
179-
} else if (info_frac[n_analysis - i] > if_alt[n_analysis - i]) {
180-
y[n_analysis - i, ] <- expected_time(enroll_rate, fail_rate,
181-
target_event = IF[n_analysis - i] * final_event,
182-
ratio = ratio, interval = c(.01, next_time)
183-
) %>%
184-
dplyr::transmute(analysis = n_analysis - i, time, event, ahr, theta = -log(ahr), info, info0)
200+
# if it is info frac driven design with a known study duration,
201+
# e.g., info_frac = 1:3/3, analysis_time = 36
202+
if (length(info_frac_by_time) == 1) {
203+
# search the analysis time when the input info_frac arrives
204+
ia_time <- uniroot(find_time_by_info_frac,
205+
interval = c(0.01, next_time),
206+
enroll_rate = enroll_rate, fail_rate = fail_rate, ratio = ratio,
207+
weight = weight, approx = approx,
208+
final_info = final_info, next_time = next_time,
209+
input_info_frac = info_frac[n_analysis - i])$root
210+
211+
y_ia <- gs_info_wlr(enroll_rate, fail_rate, ratio = ratio,
212+
event = NULL, analysis_time = ia_time,
213+
weight = weight, approx = approx,
214+
interval = c(.01, next_time)) %>%
215+
dplyr::select(-c(n, delta, sigma2)) %>%
216+
dplyr::mutate(theta = -log(ahr), analysis = n_analysis - i)
217+
y <- dplyr::bind_rows(y_ia, y)
218+
# if it is driven by both info frac and analysis time,
219+
# e.g., info_frac = 1:3/2, analysis_time = c(12, 24, 36)
220+
} else if (info_frac[n_analysis - i] > info_frac_by_time[n_analysis - i]) {
221+
# search the events when the input info_frac arrives
222+
ia_event <- uniroot(find_event_by_info_frac,
223+
interval = c(0.01, y$event[y$analysis == n_analysis - i + 1]),
224+
enroll_rate = enroll_rate, fail_rate = fail_rate, ratio = ratio,
225+
weight = weight, approx = approx,
226+
final_info = final_info, next_time = next_time,
227+
input_info_frac = info_frac[n_analysis - i])$root
228+
229+
y_ia <- gs_info_wlr(enroll_rate, fail_rate, ratio = ratio,
230+
event = ia_event, analysis_time = NULL,
231+
weight = weight, approx = approx,
232+
interval = c(.01, next_time)) %>%
233+
dplyr::select(-c(n, delta, sigma2)) %>%
234+
dplyr::mutate(theta = -log(ahr), analysis = n_analysis - i)
235+
236+
y_exclude_ia <- y %>% filter(analysis != n_analysis - i)
237+
y <- dplyr::bind_rows(y_ia, y_exclude_ia)
185238
}
186-
next_time <- y$time[n_analysis - i]
239+
240+
next_time <- y$time[y$analysis == n_analysis - i]
187241
}
188242
}
189243

244+
y <- y %>% arrange(analysis)
190245
y$analysis <- 1:n_analysis
191246
y$n <- expected_accrual(time = y$time, enroll_rate = enroll_rate)
192247

193-
# h1 spending
248+
# ---------------------------------------- #
249+
# adjust theta1 and info1 #
250+
# by h1 spending or not #
251+
# ---------------------------------------- #
194252
if (h1_spending) {
195253
theta1 <- y$theta
196254
info1 <- y$info
@@ -199,7 +257,10 @@ gs_design_wlr <- function(
199257
info1 <- y$info0
200258
}
201259

202-
# Combine all the calculations ----
260+
# ---------------------------------------- #
261+
# calculate the design with known #
262+
# 3 theta and 3 info #
263+
# ---------------------------------------- #
203264
suppressMessages(
204265
allout <- gs_design_npe(
205266
theta = y$theta, theta1 = theta1,
@@ -230,10 +291,11 @@ gs_design_wlr <- function(
230291
# add `~hr at bound` and `nominal p`
231292
allout <- allout %>% mutate(
232293
"~hr at bound" = gsDesign::zn2hr(z = z, n = event, ratio = ratio),
233-
"nominal p" = pnorm(-z)
234-
)
294+
"nominal p" = pnorm(-z))
235295

236-
# Return the output ----
296+
# ---------------------------------------- #
297+
# return the output #
298+
# ---------------------------------------- #
237299
# bounds table
238300
bounds <- allout %>%
239301
select(all_of(c("analysis", "bound", "probability", "probability0", "z", "~hr at bound", "nominal p"))) %>%
@@ -244,6 +306,7 @@ gs_design_wlr <- function(
244306
select(analysis, time, n, event, ahr, theta, info, info0, info_frac) %>%
245307
unique() %>%
246308
arrange(analysis)
309+
247310
# input table
248311
input <- list(
249312
enroll_rate = enroll_rate, fail_rate = fail_rate,
@@ -254,18 +317,43 @@ gs_design_wlr <- function(
254317
lower = lower, lpar = lpar,
255318
test_upper = test_upper, test_lower = test_lower,
256319
h1_spending = h1_spending, binding = binding,
257-
info_scale = info_scale, r = r, tol = tol
258-
)
320+
info_scale = info_scale, r = r, tol = tol)
259321

260322
# final output
261323
ans <- list(
262324
input = input,
263325
enroll_rate = enroll_rate %>% mutate(rate = rate * inflac_fct),
264326
fail_rate = fail_rate,
265327
bounds = bounds %>% filter(!is.infinite(z)),
266-
analysis = analysis
267-
)
328+
analysis = analysis)
268329

269330
ans <- add_class(ans, if (!binding) "non_binding", "wlr", "gs_design")
270331
return(ans)
271332
}
333+
334+
335+
# utility function to find the analysis time to get the planned/input info_frac
336+
find_time_by_info_frac <- function(x, enroll_rate, fail_rate, ratio, weight, approx,
337+
final_info, next_time,
338+
input_info_frac){
339+
ia_info <- gs_info_wlr(analysis_time = x, event = NULL,
340+
enroll_rate = enroll_rate, fail_rate = fail_rate,
341+
weight = weight, approx = approx, ratio = ratio,
342+
interval = c(.01, next_time))
343+
344+
ia_info_frac <- ia_info$info / final_info
345+
return(ia_info_frac - input_info_frac)
346+
}
347+
348+
# utility function to find the event to get the planned/input info_frac
349+
find_event_by_info_frac <- function(x, enroll_rate, fail_rate, ratio, weight, approx,
350+
final_info, next_time,
351+
input_info_frac){
352+
ia_info <- gs_info_wlr(analysis_time = NULL, event = x,
353+
enroll_rate = enroll_rate, fail_rate = fail_rate,
354+
weight = weight, approx = approx, ratio = ratio,
355+
interval = c(.01, next_time))
356+
357+
ia_info_frac <- ia_info$info / final_info
358+
return(ia_info_frac - input_info_frac)
359+
}

man/gs_design_wlr.Rd

+27-22
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)