inst/doc/PoissonMixtureModel.R

## ----include=FALSE--------------------------------------
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  dev = "ragg_png",
  dpi = 96,
  fig.retina = 1,
  fig.width = 7.2916667,
  fig.asp = 0.618,
  fig.align = "center",
  out.width = "80%"
)

## ----echo=FALSE, message=FALSE, warning=FALSE-----------
library(gsDesign)
library(dplyr)
library(tibble)
library(ggplot2)
library(gt)

## -------------------------------------------------------
# Poisson mixture survival
pPM <- function(x = 0:20, cure_rate = .5, t1 = 10, s1 = .6) {
  theta <- -log(cure_rate)
  lambda <- -log(1 + log(s1) / theta) / t1
  return(exp(-theta * (1 - exp(-lambda * x))))
}
# Poisson mixture hazard rate
hPM <- function(x = 0:20, cure_rate = .5, t1 = 10, s1 = .6) {
  theta <- -log(cure_rate)
  lambda <- -log(1 + log(s1) / theta) / t1
  return(theta * lambda * exp(-lambda * x))
}

## -------------------------------------------------------
# Control group assumptions for three Poisson mixture cure models
cure_rate <- c(.5, .35, .55)
# Second time point for respective models
t1 <- c(24, 24, 24)
# Survival rate at 2nd time point for respective models
s1 <- c(.65, .5, .68)
time_unit <- "month"
# Hazard ratio for experimental versus control for respective models
hr <- c(.7, .75, .7)
# Total study duration
study_duration <- c(48, 48, 56)
# Number of bins for piecewise approximation of Poisson mixture rates
bins <- 5

## -------------------------------------------------------
# This code should be updated by user for their scenario
# Enrollment duration by scenario
enroll_duration <- c(12, 12, 20)
# Dropout rate (exponential failure rate per time unit) by scenario
dropout_rate <- c(.002, .001, .001)

## ----warning=FALSE, message=FALSE, echo=FALSE-----------
t <- seq(0, study_duration[1] + 12, (study_duration[1] + 12) / bins)
survival <- NULL
for (scenario in 1:length(cure_rate)) {
  survival <- rbind(
    survival,
    tibble(
      Scenario = scenario, Treatment = "Control", Time = t,
      Survival = pPM(
        x = t, cure_rate = cure_rate[scenario],
        t1 = t1[scenario], s1 = s1[scenario]
      )
    ),
    tibble(
      Scenario = scenario, Treatment = "Experimental", Time = t,
      Survival = pPM(
        x = t, cure_rate = cure_rate[scenario]^hr[scenario],
        t1 = t1[scenario], s1 = s1[scenario]^hr[scenario]
      )
    )
  )
}
survival <- survival %>% mutate(Scenario = as.factor(Scenario))
ggplot(survival, aes(x = Time, y = Survival, lty = Treatment, col = Scenario)) +
  geom_point() +
  scale_x_continuous(breaks = seq(0, study_duration[1] + 12, (study_duration[1] + 12) / bins)) +
  geom_line() +
  ggtitle("Poisson Mixture Model with Proportional Hazards") +
  theme(legend.position = "bottom")

## ----echo = FALSE---------------------------------------
hazard <- survival %>%
  filter(Time > 0) %>%
  group_by(Scenario, Treatment) %>%
  mutate(
    cumulative_hazard = -log(Survival),
    time_lagged = lag(Time, default = 0),
    hazard_rate = (cumulative_hazard - lag(cumulative_hazard, default = 0)) /
      (Time - time_lagged),
  ) %>%
  ungroup()
hazardC1 <- tibble(time_lagged = seq(0, study_duration[1] + 12, .5)) %>%
  mutate(hazard_rate = hPM(time_lagged, cure_rate = cure_rate[1], t1 = t1[1], s1 = s1[1]))
ggplot() +
  geom_step(
    data = hazard %>% filter(Treatment == "Control", Scenario == 1),
    aes(x = time_lagged, y = hazard_rate)
  ) +
  ylab("Hazard rate") +
  xlab("Time") +
  ggtitle("Step Function Approximated Hazard Rate for Design Cure Model",
    subtitle = "Control Group, Scenario 1"
  ) +
  geom_line(data = hazardC1, aes(x = time_lagged, y = hazard_rate), lty = 2) +
  annotate(geom = "text", x = 35, y = .02, label = "Dashed line shows actual hazard rate")

## ----echo=FALSE, echo=FALSE-----------------------------
# DO NOT ALTER CODE
event_accrual <- NULL
for (scenario in 1:length(cure_rate)) {
  control_accrual <- tibble(Time = 0, Events = 0, Treatment = "Control")
  experimental_accrual <- control_accrual %>% mutate(Treatment = "Experimental")
  control <- hazard %>% filter(Scenario == scenario, Treatment == "Control")
  for (T in 1:(study_duration[1] + 12)) {
    xc <- eEvents(
      lambda = control$hazard_rate, S = (control$Time - control$time_lagged)[1:(bins - 1)],
      gamma = 100 / enroll_duration[scenario], R = enroll_duration[scenario], eta = dropout_rate[scenario],
      T = T, Tfinal = study_duration[1]
    )
    control_accrual <- rbind(control_accrual, tibble(Time = xc$T, Events = xc$d, Treatment = "Control"))
    xe <- eEvents(
      lambda = control$hazard_rate * hr[scenario], S = (control$Time - control$time_lagged)[1:(bins - 1)],
      gamma = 100 / enroll_duration[scenario], R = enroll_duration[scenario], eta = dropout_rate[scenario],
      T = T, Tfinal = study_duration[1]
    )
    experimental_accrual <- rbind(experimental_accrual, tibble(Time = xe$T, Events = xe$d, Treatment = "Experimental"))
  }
  overall_accrual <- rbind(control_accrual, experimental_accrual) %>%
    group_by(Time) %>%
    summarize(Events = sum(Events), Scenario = scenario, Hypothesis = "H1")
  # Get max planned events for enrollment rates for scenario 1 under H1
  # This will be modified for the design, but relative accrual will remain the same
  if (scenario == 1) max_events_planned <- overall_accrual$Events[study_duration[1] + 1]
  overallH0_accrual <- rbind(control_accrual, control_accrual) %>%
    group_by(Time) %>%
    summarize(Events = sum(Events), Scenario = scenario, Hypothesis = "H0") %>%
    ungroup()
  # Combine for all scenarios
  event_accrual <- rbind(
    event_accrual,
    overall_accrual,
    overallH0_accrual
  )
}
event_accrual <- event_accrual %>% mutate(EF = Events / max_events_planned, Scenario = as.factor(Scenario))
ggplot(event_accrual, aes(x = Time, y = EF, color = Scenario, lty = Hypothesis)) +
  geom_line() +
  scale_y_continuous(breaks = seq(0, 2, .2)) +
  scale_x_continuous(breaks = seq(0, study_duration[1] + 12, 12)) +
  ylab("Fraction of planned events") +
  ggtitle(
    "Fraction of planned events expected over time by scenario",
    subtitle = "Fraction based on planned final events for scenario 1"
  ) +
  theme(legend.position = "bottom")

## -------------------------------------------------------
# Calendar time from start of randomization until each analysis time
calendarTime <- c(14, 24, 36, 48)

## -------------------------------------------------------
# Get hazard rate info for Scenario 1 control group
control <- hazard %>% filter(Scenario == 1, Treatment == "Control")
# Failure rates
lambdaC <- control$hazard_rate
# Interval durations
S <- (control$Time - control$time_lagged)[1:(bins - 1)]
# 1-sided Type I error
alpha <- 0.025
# Type II error (1 - power)
beta <- .1
# Test type 6: asymmetric 2-sided design, non-binding futility bound
test.type <- 6
# 1-sided Type I error used for safety (for asymmetric 2-sided design)
astar <- .2
# Spending functions (sfu, sfl) and parameters (sfupar, sflpar)
sfu <- sfHSD 
sfupar <- -3 
sfl <- sfLDPocock # Near-equal Z-values for each analysis
sflpar <- NULL # Not needed for Pocock spending
# Dropout rate (exponential parameter per unit of time)
dropout_rate <- 0.002
# Experimental / control randomization ratio
ratio <- 1

## -------------------------------------------------------
design_calendar <-
  gsSurvCalendar(
    calendarTime = calendarTime,
    spending = "calendar",
    alpha = alpha,
    beta = beta,
    astar = astar,
    test.type = test.type,
    hr = hr[1],
    R = enroll_duration[1],
    gamma = 1,
    minfup = study_duration[1] - enroll_duration[1],
    ratio = ratio,
    sfu = sfu,
    sfupar = sfupar,
    sfl = sfl,
    sflpar = sflpar,
    lambdaC = lambdaC,
    S = S
  )
design_calendar %>%
  gsBoundSummary(exclude = c("B-value", "CP", "CP H1", "PP")) %>%
  gt() %>%
  tab_header(
    title = "Calendar-Based Design",
    subtitle = "Calendar Spending"
  )

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.