library(gsDesign)
# Number of analyses
k <- 3
# See gsDesign() help for description of boundary types
test.type <- 4
# 1-sided Type I error
alpha <- 0.025
# Type 2 error (1 - targeted power)
beta <- 0.1
# If test.type = 5 or 6, this sets maximum spending for futility
# under the null hypothesis. Otherwise, this is ignored.
astar <- 0
# Timing (information fraction) at interim analyses
timing <- c(1)
# Efficacy bound spending function
sfu <- sfLDOF
# Upper bound spending function parameters, if any
sfupar <- c(0)
# Lower bound spending function, if used (test.type > 2)
sfl <- sfLDOF
# Lower bound spending function parameters, if any
sflpar <- c(0)
# Assumed hazard ratio under alternate hypothesis
hr <- 0.7
# Null hypothesis hazard ratio
hr0 <- 1
# Dropout rate (exponential rate)
eta <- 0.01
# Enrollment rate (normally will be inflated to achieve power)
gamma <- c(2.5, 5, 7.5, 10)
# Relative enrollment rates by time period
R <- c(2, 2, 2, 6)
# Interval durations for piecewise hazard rate approximation
# (length one less than lambdaC). NULL if length of lambdaC is 1.
S <- NULL
# Calendar time of final analysis
T <- 60
# Minimum follow-up time after enrollment complete
minfup <- 48
# Relative enrollment (experimental/placebo)
ratio <- 1
# Piecewise exponential failure distribution. Column 1 is interval duration
# (last value is ignored and extended indefinitely). Column 2 is median.
obs <- matrix(c(100, 6), ncol = 2)
obs <- obs[(!is.na(obs[, 1])) & (!is.na(obs[, 2])), 2]
# Transform the median time-to-event to exponential event rates
lambdaC <- log(2) / obs
The following code derives the group sequential design using the method of Lachin and Foulkes.
x <- gsSurv(
k = k,
test.type = test.type,
alpha = alpha,
beta = beta,
astar = astar,
timing = timing,
sfu = sfu,
sfupar = sfupar,
sfl = sfl,
sflpar = sflpar,
lambdaC = lambdaC,
hr = hr,
hr0 = hr0,
eta = eta,
gamma = gamma,
R = R,
S = S,
T = T,
minfup = minfup,
ratio = ratio
)
A textual summary for the design is:
cat(summary(x, timeunit = 'months'))
## Asymmetric two-sided group sequential design with non-binding futility bound, 3 analyses, time-to-event outcome with sample size 388 and 350 events required, 90 percent power, 2.5 percent (1-sided) Type I error to detect a hazard ratio of 0.7. Enrollment and total study durations are assumed to be 12 and 60 months, respectively. Efficacy bounds derived using a Lan-DeMets O'Brien-Fleming approximation spending function with none = 1. Futility bounds derived using a Lan-DeMets O'Brien-Fleming approximation spending function with none = 1.
The group sequential design boundaries and properties are summarized in the following table. We further describe the information as follows:
gsBoundSummary(x, ratio = 1, digits = 4, ddigits = 2, tdigits = 1, timename = 'Month')
## Analysis Value Efficacy Futility
## IA 1: 33% Z 3.7103 -0.6946
## N: 364 p (1-sided) 0.0001 0.7563
## Events: 117 ~HR at bound 0.5027 1.1374
## Month: 11.4 P(Cross) if HR=1 0.0001 0.2437
## P(Cross) if HR=0.7 0.0372 0.0044
## IA 2: 67% Z 2.5114 1.0024
## N: 388 p (1-sided) 0.0060 0.1581
## Events: 233 ~HR at bound 0.7195 0.8769
## Month: 17.9 P(Cross) if HR=1 0.0060 0.8434
## P(Cross) if HR=0.7 0.5845 0.0440
## Final Z 1.9930 1.9930
## N: 388 p (1-sided) 0.0231 0.0231
## Events: 350 ~HR at bound 0.8079 0.8079
## Month: 60 P(Cross) if HR=1 0.0233 0.9767
## P(Cross) if HR=0.7 0.9000 0.1000
plot(x, plottype = 8, xlab = 'Events', ylab = 'Estimated hazard ratio')
Following is a plot of expected accrual of subjects and events over time under design assumptions for the alternate hypothesis.
accrual_fn <- function(Time, x) {
xx <- nEventsIA(tIA = Time, x = x, simple = FALSE)
data.frame(
Time = Time,
Accrual = c(xx$eNC + xx$eNE, xx$eDC + xx$eDE),
Count = c("Sample size", "Events")
)
}
accrual <- data.frame(Time = 0, Accrual = c(0, 0), Count = c("Sample size", "Events"))
xtime <- (0:50) / 50 * max(x$T)
for (i in seq_along(xtime[xtime > 0])) {
accrual <- rbind(accrual, accrual_fn(Time = xtime[i + 1], x = x))
}
ggplot(accrual, aes(x = Time, y = Accrual, col = Count)) +
geom_line() +
ylab("Expected Accrual")