inst/doc/VaccineEfficacy.R

## ----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%"
)

options(width = 58)

## ----message=FALSE, warning=FALSE, class.source="fold-hide"----
library(gsDesign)
library(gt)
library(dplyr)

## -------------------------------------------------------
pi1 <- .7
ratio <- 3
p1 <- ratio / (ratio + 1 / (1 - pi1))
p1

## -------------------------------------------------------
1 - 1 / (ratio * (1 / p1 - 1))

## -------------------------------------------------------
pi0 <- .3
p0 <- ratio / (ratio + 1 / (1 - pi0))
p0

## -------------------------------------------------------
ve <- c(.5, .6, .65, .7, .75, .8)
prob_experimental <- ratio / (ratio + 1 / (1 - ve))
tibble(VE = ve, "P(Experimental)" = prob_experimental) %>%
  gt() %>%
  tab_options(data_row.padding = px(1)) %>%
  fmt_number(columns = 2, decimals = 3)

## -------------------------------------------------------
alpha <- 0.025 # Type I error
beta <- 0.1 # Type II error (1 - power)
k <- 3 # number of analyses in group sequential design
timing <- c(.45, .7) # Relative timing of interim analyses compared to final
sfu <- sfHSD # Efficacy bound spending function (Hwang-Shih-DeCani)
sfupar <- -3 # Parameter for efficacy spending function
sfl <- sfHSD # Futility bound spending function (Hwang-Shih-DeCani)
sflpar <- -3 # Futility bound spending function parameter
timename <- "Month" # Time unit
failRate <- .002 # Exponential failure rate
dropoutRate <- .0001 # Exponential dropout rate
enrollDuration <- 8 # Enrollment duration
trialDuration <- 24 # Planned trial duration
VE1 <- .7 # Alternate hypothesis vaccine efficacy
VE0 <- .3 # Null hypothesis vaccine efficacy
ratio <- 3 # Experimental/Control enrollment ratio
test.type <- 4 # 1 for one-sided, 4 for non-binding futility

## -------------------------------------------------------
# Derive Group Sequential Design
# This determines final sample size
x <- gsSurv(
  k = k, test.type = test.type, alpha = alpha, beta = beta, timing = timing,
  sfu = sfu, sfupar = sfupar, sfl = sfl, sflpar = sflpar,
  lambdaC = failRate, eta = dropoutRate,
  # Translate vaccine efficacy to HR
  hr = 1 - VE1, hr0 = 1 - VE0,
  R = enrollDuration, T = trialDuration,
  minfup = trialDuration - enrollDuration, ratio = ratio
)

## -------------------------------------------------------
xx <- toInteger(x)
gsBoundSummary(xx,
  tdigits = 1, logdelta = TRUE, deltaname = "HR", Nname = "Events",
  exclude = c("B-value", "CP", "CP H1", "PP")
) %>%
  gt() %>%
  tab_header(
    title = "Initial group sequential approximation",
    subtitle = "Integer event counts at analyses"
  ) %>%
  tab_options(data_row.padding = px(1))

## ----results='asis'-------------------------------------
cat(summary(xx, timeunit = "months"))

## -------------------------------------------------------
xb <- toBinomialExact(x)

## ----echo = FALSE---------------------------------------
# Function to print summary table for VE design
veTable <- function(xbDesign, tteDesign, ve) {
  # Analysis, N, Time, Total Cases, Success(Cases, VE, VE lower CI, Spend), Futility (Cases, VE, Spend), Type I Error, Power table
  ratio <- tteDesign$ratio
  prob_experimental <- ratio / (ratio + 1 / (1 - ve))
  power_table <- gsBinomialExact(
    k = xbDesign$k, theta = prob_experimental,
    n.I = xbDesign$n.I, a = xbDesign$lower$bound,
    b = xbDesign$upper$bound
  )$lower$prob
  # Cumulative sum within rows
  power_table <- apply(power_table, 2, cumsum)
  colnames(power_table) <- paste(ve * 100, "%", sep = "")
  out_tab <- tibble(
    Analysis = 1:tteDesign$k,
    Time = tteDesign$T,
    N = as.vector(round(tteDesign$eNC + tteDesign$eNE)),
    Cases = xbDesign$n.I,
    Success = xb$lower$bound,
    Futility = xb$upper$bound,
    ve_efficacy = 1 - 1 / (ratio * (xbDesign$n.I / xbDesign$lower$bound - 1)), # Efficacy bound
    ve_futility = 1 - 1 / (ratio * (xbDesign$n.I / xbDesign$upper$bound - 1)), # Futility bound
    alpha = as.vector(cumsum(
      gsBinomialExact(
        k = k, theta = xbDesign$theta[1], n.I = xbDesign$n.I,
        a = xbDesign$lower$bound, b = xbDesign$n.I + 1
      )$lower$prob
    )),
    beta = as.vector(cumsum(xbDesign$upper$prob[, 2]))
  )
  out_tab <- cbind(out_tab, power_table)
}

## ----echo=FALSE-----------------------------------------
veTable(xb, x, ve) %>%
  gt() %>%
  fmt_number(columns = 2, decimals = 1) %>%
  fmt_number(columns = c(7:8, 11:16), decimals = 2) %>%
  fmt_number(columns = 9:10, decimals = 4) %>%
  tab_spanner(label = "Experimental Cases at Bound", columns = 5:6, id = "cases") %>%
  tab_spanner(label = "Power by Vaccine Efficacy", columns = 11:16, id = "power") %>%
  tab_spanner(label = "Error Spending", columns = 9:10, id = "spend") %>%
  tab_spanner(label = "Vaccine Efficacy at Bound", columns = 7:8, id = "vebound") %>%
  cols_label(
    ve_efficacy = "Efficacy",
    ve_futility = "Futility"
  ) %>%
  tab_footnote(
    footnote = "Cumulative spending at each analysis",
    locations = cells_column_spanners(spanners = "spend")
  ) %>%
  tab_footnote(
    footnote = "Experimental case counts to cross between success and futility counts do not stop trial",
    locations = cells_column_spanners(spanners = "cases")
  ) %>%
  tab_footnote(
    footnote = "Exact vaccine efficacy required to cross bound",
    locations = cells_column_spanners(spanners = "vebound")
  ) %>%
  tab_footnote(
    footnote = "Cumulative power at each analysis by underlying vaccine efficacy",
    locations = cells_column_spanners(spanners = "power")
  ) %>%
  tab_footnote(
    footnote = "alpha-spending for efficacy ignores non-binding futility bound",
    location = cells_column_labels(columns = alpha)
  ) %>%
  tab_header("Design Bounds and Operating Characteristics")

## -------------------------------------------------------
efficacyNominalPValue <- pnorm(-xx$upper$bound)
efficacyNominalPValue

## -------------------------------------------------------
qbinom(p = efficacyNominalPValue, size = xx$n.I, prob = p0) - 1

## -------------------------------------------------------
xb$lower$bound

## -------------------------------------------------------
xb$init_approx$a
xb$init_approx$b

## -------------------------------------------------------
xb$upper$bound

## -------------------------------------------------------
# Exact design cumulative alpha-spending at efficacy bounds
# (non-binding)
nb <- gsBinomialExact(k = xb$k, theta = xb$theta, n.I = xb$n.I, b= xb$n.I + 1, a = xb$lower$bound)
cumsum(nb$lower$prob[,1])
# Targeted alpha-spending
xx$upper$sf(alpha, t = xx$timing, xx$upper$param)$spend

## -------------------------------------------------------
# Check that increasing any bound goes above cumulative spend
excess_alpha_spend <- matrix(0, nrow = nb$k, ncol=nb$k)
for(i in 1:xb$k){
  a <- xb$lower$bound
  a[i] <- a[i] + 1
  excess_alpha_spend[i,] <- 
    cumsum(gsBinomialExact(k = xb$k, theta = xb$theta, n.I = xb$n.I, b= xb$n.I + 1, a = a)$lower$prob[,1])
}
excess_alpha_spend

## -------------------------------------------------------
# Cumulative beta-spending for exact design
cumsum(xb$upper$prob[,2])

## -------------------------------------------------------
# Targeted beta-spending
xx$lower$sf(beta, t = xx$timing, xx$lower$param)$spend

## -------------------------------------------------------
# Check that increasing any bound goes above cumulative spend
excess_beta_spend <- matrix(0, nrow = nb$k - 1, ncol=nb$k)
for(i in 1:(xb$k - 1)){
  b <- xb$upper$bound
  b[i] <- b[i] - 1
  excess_beta_spend[i,] <- 
    cumsum(as.numeric(gsBinomialExact(k = xb$k, theta = xb$theta, n.I = xb$n.I, b = b, a = xb$lower$bound)$upper$prob[,2]))
}
excess_beta_spend

## -------------------------------------------------------
ebUpdate <- toBinomialExact(xx, observedEvents = c(20, 78))

## ----echo = FALSE---------------------------------------
  ve <- c(.65, .75, .85)
  # Analysis, Total Cases, Success(Cases, VE, VE lower CI, Spend), Futility (Cases, VE, Spend), Type I Error, Power table
  ratio <- xx$ratio
  prob_experimental <- ratio / (ratio + 1 / (1 - ve))
  power_table <- gsBinomialExact(
    k = ebUpdate$k, theta = prob_experimental,
    n.I = ebUpdate$n.I, a = ebUpdate$lower$bound,
    b = ebUpdate$upper$bound
  )$lower$prob
  # Cumulative sum within rows
  power_table <- apply(power_table, 2, cumsum)
  colnames(power_table) <- paste(ve * 100, "%", sep = "")
  out_tab <- tibble(
    Analysis = 1:ebUpdate$k,
    Cases = ebUpdate$n.I,
    Success = ebUpdate$lower$bound,
    Futility = ebUpdate$upper$bound,
    ve_efficacy = 1 - 1 / (ratio * (ebUpdate$n.I / ebUpdate$lower$bound - 1)), # Efficacy bound
    ve_futility = 1 - 1 / (ratio * (ebUpdate$n.I / ebUpdate$upper$bound - 1)), # Futility bound
    alpha = as.vector(cumsum(
      gsBinomialExact(
        k = ebUpdate$k, theta = ebUpdate$theta[1], n.I = ebUpdate$n.I,
        a = ebUpdate$lower$bound, b = ebUpdate$n.I + 1
      )$lower$prob
    )),
    beta = as.vector(cumsum(ebUpdate$upper$prob[, 2]))
  )
  out_tab <- cbind(out_tab, power_table)
  out_tab %>% gt() %>%
  fmt_number(columns = c(5:6, 9:11), decimals = 2) %>%
  fmt_number(columns = 7:8, decimals = 4) %>%
  tab_spanner(label = "Cases at Bound", columns = 3:4, id = "cases") %>%
  tab_spanner(label = "Power by VE", columns = 9:11, id = "power") %>%
  tab_spanner(label = "Error Spending", columns = 7:8, id = "spend") %>%
  tab_spanner(label = "VE at Bound", columns = 5:6, id = "vebound") %>%
  cols_label(
    ve_efficacy = "Efficacy",
    ve_futility = "Futility"
  ) %>%
  tab_footnote(
    footnote = "Cumulative spending at each analysis",
    locations = cells_column_spanners(spanners = "spend")
  ) %>%
  tab_footnote(
    footnote = "Experimental case counts; counts between success and futility bounds do not stop trial",
    locations = cells_column_spanners(spanners = "cases")
  ) %>%
  tab_footnote(
    footnote = "Exact vaccine efficacy required to cross bound",
    locations = cells_column_spanners(spanners = "vebound")
  ) %>%
  tab_footnote(
    footnote = "Cumulative power at each analysis by underlying vaccine efficacy",
    locations = cells_column_spanners(spanners = "power")
  ) %>%
  tab_footnote(
    footnote = "Efficacy spending ignores non-binding futility bound",
    location = cells_column_labels(columns = alpha)
  ) %>%
  tab_header(title = "Updated Bounds for Actual Analyses from SPUTNIK trial")

Try the gsDesign package in your browser

Any scripts or data that you put into this service are public.

gsDesign documentation built on Nov. 12, 2023, 9:06 a.m.