Nothing
## ----include=FALSE------------------------------------------------------------
knitr::opts_chunk$set(
collapse = FALSE,
comment = "#>",
dev = "ragg_png",
dpi = 96,
fig.retina = 1,
fig.width = 7.2916667,
fig.asp = 0.618,
fig.align = "center",
out.width = "80%"
)
## ----message=FALSE, warning=FALSE---------------------------------------------
### THERE SHOULD BE NO NEED TO MODIFY THIS CODE SECTION
# Prefer fixed notation
old <- options(scipen = 999)
# Colorblind palette
cbPalette <- c("#999999", "#E69F00", "#56B4E9", "#009E73", "#F0E442", "#0072B2", "#D55E00", "#CC79A7")
# 2 packages used for data storage and manipulation: dplyr, tibble
library(dplyr)
library(tibble)
# 2 packages used for R Markdown capabilities: knitr, kableExtra
library(knitr)
library(kableExtra)
library(gt)
library(ggplot2) # For plotting
library(gsDesign) # Group sequential design capabilities
library(gMCPLite) # Multiplicity evaluation
## ----warning=FALSE, message=FALSE---------------------------------------------
### THIS CODE NEEDS TO BE MODIFIED FOR YOUR STUDY
# If needed, see help file for gMCPLite::hGraph() for explanation of parameters below
# Hypothesis names
nameHypotheses <- c(
"H1: OS\n Subgroup",
"H2: OS\n All subjects",
"H3: PFS\n Subgroup",
"H4: PFS\n All subjects",
"H5: ORR\n Subgroup",
"H6: ORR\n All subjects"
)
# Number of hypotheses to be tested
nHypotheses <- length(nameHypotheses)
# Transition weights for alpha reallocation (square matrix)
m <- matrix(c(
0, 1, 0, 0, 0, 0,
0, 0, .5, .5, 0, 0,
0, 0, 0, 1, 0, 0,
0, 0, 0, 0, .5, .5,
0, 0, 0, 0, 0, 1,
.5, .5, 0, 0, 0, 0
), nrow = 6, byrow = TRUE)
# Initial Type I error assigned to each hypothesis (one-sided)
alphaHypotheses <- c(.01, .01, .004, 0.000, 0.0005, .0005)
fwer <- sum(alphaHypotheses)
# Make a ggplot representation of the above specification and display it
g <- gMCPLite::hGraph(6,
alphaHypotheses = alphaHypotheses, m = m, nameHypotheses = nameHypotheses,
palette = cbPalette,
halfWid = 1, halfHgt = .35, xradius = 2.5, yradius = 1, offset = 0, trhw = .15,
x = c(-1.25, 1.25, -2.5, 2.5, -1.25, 1.25), y = c(2, 2, 1, 1, 0, 0),
trprop = 0.4, fill = as.character(c(2, 2, 4, 4, 3, 3))
)
print(g)
## ----results='asis'-----------------------------------------------------------
osmedian <- 12 # Median control survival
# Derive group sequential design for OS in the targeted subgroup
ossub <- gsDesign::gsSurv(
k = 3, # 3 analyses for OS
test.type = 1, # Efficacy bound only (no futility)
alpha = alphaHypotheses[1], # Allocated alpha from design hypothesis group
beta = 0.1, # Type 2 error (1 - power)
hr = 0.65, # Assumed hazard ratio for power calculation
timing = c(0.61, 0.82), # Choose these to match targeted calendar timing of analyses
sfu = sfLDOF, # Spending function to approximate O'Brien-Fleming bound
lambdaC = log(2) / osmedian, # Exponential control failure rate
eta = 0.001, # Exponential dropout rate
gamma = c(2.5, 5, 7.5, 10), # Relative enrollment rates by time period
R = c(2, 2, 2, 12), # Duration of time periods for rates in gamma
T = 42, # Planned study duration for OS
minfup = 24 # Planned minimum follow-up after end of enrollment
)
tab <- gsDesign::gsBoundSummary(ossub)
rownames(tab) <- 1:nrow(tab)
cat(summary(ossub))
## -----------------------------------------------------------------------------
# tab %>% kable(caption = "Design for OS in the subgroup.") %>% kable_styling()
tab %>%
gt() %>%
tab_header(title = "Design for OS in the Subgroup") %>%
cols_align(align = "left", columns = Value) %>%
tab_footnote(
footnote = "Cumulative boundary crossing probability includes crossing probability at earlier analysis.",
locations = cells_body(columns = "Value", rows = c(9, 10, 14, 15))
) %>%
tab_footnote(
footnote = "Approximate hazard ratio at bound.",
locations = cells_body(columns = "Value", rows = c(3, 8, 13))
)
## ----results = 'asis'---------------------------------------------------------
hr <- .75
beta <- .14
os <- gsDesign::gsSurv(
k = 3, test.type = 4, alpha = 0.01, beta = beta, hr = hr,
timing = c(0.62, 0.83), sfu = sfLDOF,
sfl = sfHSD, sflpar = -3.25,
lambdaC = log(2) / 12, eta = 0.001, S = NULL,
gamma = c(2.5, 5, 7.5, 10), R = c(2, 2, 2, 12),
T = 42, minfup = 24
)
tab <- gsDesign::gsBoundSummary(os)
rownames(tab) <- 1:nrow(tab)
cat(summary(os))
## -----------------------------------------------------------------------------
tab %>%
kable(caption = "Design for OS in all subjects") %>%
kable_styling()
## -----------------------------------------------------------------------------
plot(os, plottype = "HR", xlab = "Events")
## ----results='asis'-----------------------------------------------------------
hr <- .65
beta <- .149
pfssub <- gsDesign::gsSurv(
k = 2, test.type = 6, astar = 0.1, alpha = 0.004, beta = beta, hr = hr,
timing = .87, sfu = sfLDOF,
sfl = sfHSD, sflpar = -8,
lambdaC = log(2) / 5, eta = 0.02, S = NULL,
gamma = c(2.5, 5, 7.5, 10), R = c(2, 2, 2, 12),
T = 32, minfup = 14
)
tab <- gsDesign::gsBoundSummary(pfssub)
rownames(tab) <- 1:nrow(tab)
cat(summary(pfssub))
## -----------------------------------------------------------------------------
tab %>%
kable(caption = "Design for PFS in the subgroup") %>%
kable_styling()
## -----------------------------------------------------------------------------
hr <- .74
beta <- .15
pfs <- gsDesign::gsSurv(
k = 2, test.type = 1, alpha = 0.004, beta = beta, hr = hr,
timing = .86, sfu = sfLDOF,
lambdaC = log(2) / 5, eta = 0.02, S = NULL,
gamma = c(2.5, 5, 7.5, 10), R = c(2, 2, 2, 12),
T = 32, minfup = 14
)
tab <- gsDesign::gsBoundSummary(pfs)
rownames(tab) <- 1:nrow(tab)
tab %>%
kable(caption = "Design for PFS in the overall population") %>%
kable_styling()
## -----------------------------------------------------------------------------
nBinomial(p1 = .35, p2 = .15, alpha = .0005, n = 378)
## -----------------------------------------------------------------------------
nBinomial(p1 = .3, p2 = .15, alpha = .0005, n = 756)
## -----------------------------------------------------------------------------
### THIS NEEDS TO BE MODIFIED TO MATCH STUDY
gsDlist <- list(ossub, os, pfssub, pfs, NULL, NULL)
## -----------------------------------------------------------------------------
### THIS NEEDS TO BE MODIFIED TO MATCH YOUR STUDY
# PFS, overall population
pfs$n.I <- c(675, 750)
# PFS, subgroup
pfssub$n.I <- c(265, 310)
# OS, overall population
os$n.I <- c(529, 700, 800)
# OS, subgroup
ossub$n.I <- c(185, 245, 295)
## ----warnings=FALSE,message=FALSE---------------------------------------------
### THIS NEEDS TO BE MODIFIED TO MATCH YOUR STUDY
inputResults <- tibble(
H = c(rep(1, 3), rep(2, 3), rep(3, 2), rep(4, 2), 5, 6),
Pop = c(
rep("Subgroup", 3), rep("All", 3),
rep("Subgroup", 2), rep("All", 2),
"Subgroup", "All"
),
Endpoint = c(rep("OS", 6), rep("PFS", 4), rep("ORR", 2)),
# Example with some rejections
nominalP = c(
.03, .0001, .000001,
.2, .15, .1,
.2, .001,
.3, .2,
.00001,
.1
),
# Example with no rejections
# nominalP = rep(.03, 12),
Analysis = c(1:3, 1:3, 1:2, 1:2, 1, 1),
events = c(ossub$n.I, os$n.I, pfssub$n.I, pfs$n.I, NA, NA),
spendingTime = c(
ossub$n.I / max(ossub$n.I),
ossub$n.I / max(ossub$n.I),
pfssub$n.I / max(pfssub$n.I),
pfssub$n.I / max(pfssub$n.I),
NA, NA
)
)
kable(inputResults, caption = "DUMMY RESULTS FOR IA2.") %>%
kable_styling() %>%
add_footnote("Dummy results", notation = "none")
## ----message=FALSE------------------------------------------------------------
### USER SHOULD NOT NEED TO MODIFY THIS CODE
EOCtab <- NULL
EOCtab <- inputResults %>%
group_by(H) %>%
slice(1) %>%
ungroup() %>%
select("H", "Pop", "Endpoint", "nominalP")
EOCtab$seqp <- .9999
for (EOCtabline in 1:nHypotheses) {
EOCtab$seqp[EOCtabline] <-
ifelse(is.null(gsDlist[[EOCtabline]]), EOCtab$nominalP[EOCtabline], {
tem <- filter(inputResults, H == EOCtabline)
sequentialPValue(
gsD = gsDlist[[EOCtabline]], interval = c(.0001, .9999),
n.I = tem$events,
Z = -qnorm(tem$nominalP),
usTime = tem$spendingTime
)
})
}
EOCtab <- EOCtab %>% select(-"nominalP")
# kable(EOCtab,caption="Sequential p-values as initially placed in EOCtab") %>% kable_styling()
## ----message=FALSE,warning=FALSE----------------------------------------------
# Make a graph object
rownames(m) <- nameHypotheses
graph <- matrix2graph(m)
# Add weights to the object based on alpha allocation
graph <- setWeights(graph, alphaHypotheses / fwer)
rescale <- 45
d <- g$layers[[2]]$data
rownames(d) <- rownames(m)
# graph@nodeAttr$X <- rescale * d$x * 1.75
# graph@nodeAttr$Y <- -rescale * d$y * 2
## -----------------------------------------------------------------------------
result <- gMCP(graph = graph, pvalues = EOCtab$seqp, alpha = fwer)
result@rejected
# now map back into EOCtable (CHECK AGAIN!!!)
EOCtab$Rejected <- result@rejected
EOCtab$adjPValues <- result@adjPValues
## -----------------------------------------------------------------------------
# Number of graphs is used repeatedly
ngraphs <- length(result@graphs)
# Set up tibble with hypotheses rejected at each stage
rejected <- NULL
for (i in 1:length(result@graphs)) {
rejected <- rbind(
rejected,
tibble(
H = 1:nHypotheses, Stage = i,
Rejected = as.logical(result@graphs[[i]]@nodeAttr$rejected)
)
)
}
rejected <- rejected %>%
filter(Rejected) %>%
group_by(H) %>%
summarize(graphRejecting = min(Stage) - 1, .groups = "drop") %>% # Last graph with weight>0 where H rejected
arrange(graphRejecting)
# Get final weights
# for hypotheses not rejected, this will be final weight where
# no hypothesis could be rejected
lastWeights <- as.numeric(result@graphs[[ngraphs]]@weights)
lastGraph <- rep(ngraphs, nrow(EOCtab))
# We will update for rejected hypotheses with last positive weight for each
if (ngraphs > 1) {
for (i in 1:(ngraphs - 1)) {
lastWeights[rejected$H[i]] <- as.numeric(result@graphs[[i]]@weights[rejected$H[i]])
lastGraph[rejected$H[i]] <- i
}
}
EOCtab$lastAlpha <- fwer * lastWeights
EOCtab$lastGraph <- lastGraph
EOCtabx <- EOCtab
names(EOCtabx) <- c(
"Hypothesis", "Population", "Endpoint", "Sequential p",
"Rejected", "Adjusted p", "Max alpha allocated", "Last Graph"
)
# Display table with desired column order
# Delayed following until after multiplicity graph sequence
# EOCtabx %>% select(c(1:4,7,5:6,8)) %>% kable() %>% kable_styling()
## ----message=FALSE,warning=FALSE,results='asis'-------------------------------
### THERE SHOULD BE NO NEED TO MODIFY THIS CODE SECTION
for (i in 1:ngraphs) {
mx <- result@graphs[[i]]@m
rownames(mx) <- NULL
colnames(mx) <- NULL
g <- gMCPLite::hGraph(
nHypotheses = nHypotheses,
alphaHypotheses = result@graphs[[i]]@weights * fwer,
m = mx,
nameHypotheses = nameHypotheses,
palette = cbPalette,
halfWid = 1, halfHgt = .35, xradius = 2.5, yradius = 1, offset = 0, trhw = .15,
x = c(-1.25, 1.25, -2.5, 2.5, -1.25, 1.25), y = c(2, 2, 1, 1, 0, 0),
trprop = .4, fill = as.character(c(2, 2, 4, 4, 3, 3))
)
cat(" \n")
cat("####", paste(" Graph", as.character(i), " \n\n"))
print(g)
cat(" \n\n\n")
}
## -----------------------------------------------------------------------------
EOCtabx %>%
select(c(1:4, 7, 5:6, 8)) %>%
kable() %>%
kable_styling()
## ----results='asis'-----------------------------------------------------------
for (i in 1:nHypotheses) {
# Set up tab for hypothesis in output
cat("####", paste(" Hypothesis", as.character(i), " \n"))
# Get results for hypothesis
hresults <- inputResults %>% filter(H == i)
# Print out max alpha allocated
xx <- paste("Max alpha allocated from above table: ",
as.character(EOCtab$lastAlpha[i]),
sep = ""
)
d <- gsDlist[[i]]
# If not group sequential for this hypothesis, print the max alpha allocated
# and the nominal p-value
if (is.null(d)) {
cat("Maximum alpha allocated: ")
cat(EOCtab$lastAlpha[i])
cat("\n\n")
cat("Nominal p-value for hypothesis test: ")
cat(hresults$nominalP)
cat("\n\n")
}
# For group sequential tests, print max alpha allocated and
# corresponding group sequential bounds
if (!is.null(gsDlist[[i]])) {
cat("Nominal p-values at each analysis for comparison to bounds in table below:\n\n")
cat(hresults$nominalP)
cat("\n\n")
# Get other info for current hypothesis
n.I <- hresults$events
usTime <- hresults$spendingTime
n.Iplan <- max(d$n.I)
if (length(n.I) == 1) {
n.I <- c(n.I, n.Iplan)
usTime <- c(usTime, 1)
}
# If no alpha allocated, just print text line to note this along with the 0 alpha allocated
if (EOCtab$lastAlpha[i] == 0) {
cat("Maximum alpha allocated: 0\n\n")
cat("No testing required\n\n")
}
if (EOCtab$lastAlpha[i] > 0) {
dupdate <- gsDesign::gsDesign(
alpha = EOCtab$lastAlpha[i],
k = length(n.I),
n.I = n.I,
usTime = usTime,
maxn.IPlan = n.Iplan,
n.fix = d$n.fix,
test.type = 1,
sfu = d$upper$sf,
sfupar = d$upper$param
)
tabl <- gsDesign::gsBoundSummary(dupdate,
Nname = "Events",
exclude = c(
"B-value", "CP", "CP H1", "Spending",
"~delta at bound", "P(Cross) if delta=0",
"PP", "P(Cross) if delta=1"
)
)
kable(tabl, caption = xx, row.names = FALSE) %>%
kable_styling() %>%
cat()
cat("\n\n")
}
}
}
## -----------------------------------------------------------------------------
# Restore default options
options(old)
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.