Nothing
## ----include = FALSE----------------------------------------------------------
knitr::opts_chunk$set(
collapse = TRUE,
comment = '#>',
fig.width = 7,
fig.height = 4.5,
dpi = 120,
out.width = '100%'
)
## ----setup, echo = FALSE, message = FALSE-------------------------------------
library(kableExtra)
library(TrialSimulator)
## ----multiple-crossings-------------------------------------------------------
cp_formula <- function(D, z, d, alpha, hazard_ratio, omega = 0.25){
t <- d / D
theta <- log(hazard_ratio)
pnorm((qnorm(alpha) - sqrt(t) * z -
theta * sqrt(omega * D) * (1 - t)) / sqrt(1 - t))
}
z_example <- -1.8
d_example <- 100
target_example <- 0.2
D_grid <- 101:10000
cp_grid <- cp_formula(D_grid, z = z_example, d = d_example,
alpha = 0.025, hazard_ratio = 0.98)
crossing_index <- which(diff(cp_grid >= target_example) != 0) + 1
D_grid[crossing_index]
## ----multiple-crossings-table-------------------------------------------------
selected_D <- c(106, 107, 182, 183, 5000, 8674, 8675)
data.frame(
D = selected_D,
conditional_power = round(
cp_formula(selected_D, z = z_example, d = d_example,
alpha = 0.025, hazard_ratio = 0.98),
4
),
reaches_target = cp_formula(selected_D, z = z_example, d = d_example,
alpha = 0.025,
hazard_ratio = 0.98) >= target_example
)
## ----multiple-crossings-plot--------------------------------------------------
plot(D_grid, cp_grid, type = 'l', log = 'x',
xlab = 'Final event number D (log scale)',
ylab = 'Conditional power', ylim = c(0, 0.3))
abline(h = target_example, lty = 2, col = 'firebrick')
abline(v = c(107, 183, 8675), lty = 3, col = 'grey40')
## ----define-trial-------------------------------------------------------------
pfs_pbo <- endpoint(name = 'pfs', type = 'tte', generator = rexp,
rate = log(2) / 10)
pbo <- arm(name = 'pbo')
pbo$add_endpoints(pfs_pbo)
pfs_trt <- endpoint(name = 'pfs', type = 'tte', generator = rexp,
rate = log(2) / 14)
trt <- arm(name = 'trt')
trt$add_endpoints(pfs_trt)
accrual_rate <- data.frame(end_time = Inf, piecewise_rate = 30)
trial <- trial(name = 'cp-example', n_patients = 400, duration = 40,
seed = 31416, enroller = StaggeredRecruiter,
accrual_rate = accrual_rate, silent = TRUE)
trial$add_arms(sample_ratio = c(1, 1), pbo, trt)
interim <- milestone(name = 'interim', when = calendarTime(time = 15))
final <- milestone(name = 'final', when = calendarTime(time = 40))
listener <- listener(silent = TRUE)
listener$add_milestones(interim, final)
controller <- controller(trial, listener)
controller$run(n = 1, silent = TRUE, plot_event = FALSE)
## ----interim-fit--------------------------------------------------------------
interim_fit <- fitLogrank(
Surv(pfs, pfs_event) ~ arm,
placebo = 'pbo', data = trial$get_locked_data('interim'),
alternative = 'less', tidy = FALSE
)
interim_fit[, c('arm', 'placebo', 'z', 'info')]
## ----compare-effects----------------------------------------------------------
planned_D <- 300
final_alpha <- 0.022
cp_at <- function(effect){
trial$conditionalPower(
'interim', Surv(pfs, pfs_event) ~ arm,
placebo = 'pbo', alternative = 'less',
alpha = final_alpha, D = planned_D, effect = effect
)$cp
}
data.frame(
assumption = c('interim trend', 'null future drift', 'hazard ratio 0.75'),
conditional_power = c(cp_at('trend'), cp_at('null'), cp_at(0.75))
)
## ----compare-directions-------------------------------------------------------
cp_less <- trial$conditionalPower(
'interim', Surv(pfs, pfs_event) ~ arm,
placebo = 'pbo', alternative = 'less',
alpha = final_alpha, D = planned_D, effect = 0.75
)
cp_greater <- trial$conditionalPower(
'interim', Surv(pfs, pfs_event) ~ arm,
placebo = 'trt', alternative = 'greater',
alpha = final_alpha, D = planned_D, effect = 1 / 0.75
)
data.frame(
formulation = c('trt vs pbo, less', 'pbo vs trt, greater'),
z = c(cp_less$z, cp_greater$z),
conditional_power = c(cp_less$cp, cp_greater$cp)
)
## ----reassess-events----------------------------------------------------------
reassess_500 <- trial$eventNumberReestimationFromConditionalPower(
'interim', Surv(pfs, pfs_event) ~ arm,
placebo = 'pbo', alternative = 'less',
alpha = final_alpha, target_cp = 0.9,
effect = 0.75, D_cap = 500
)
reassess_600 <- trial$eventNumberReestimationFromConditionalPower(
'interim', Surv(pfs, pfs_event) ~ arm,
placebo = 'pbo', alternative = 'less',
alpha = final_alpha, target_cp = 0.9,
effect = 0.75, D_cap = 600
)
rbind(cap_500 = reassess_500, cap_600 = reassess_600)[,
c('d', 'D', 'D_cap', 'target_cp', 'achieved_cp', 'target_reached')]
## ----reassess-unbounded-------------------------------------------------------
trial$eventNumberReestimationFromConditionalPower(
'interim', Surv(pfs, pfs_event) ~ arm,
placebo = 'pbo', alternative = 'less',
alpha = final_alpha, target_cp = 0.9, effect = 0.75
)[, c('d', 'D', 'D_cap', 'achieved_cp', 'target_reached')]
## ----promising-zone-----------------------------------------------------------
lower_cp <- 0.30
target_cp <- 0.90
cp_planned <- cp_at(0.75)
zone <- if(cp_planned < lower_cp){
'unfavorable'
}else if(cp_planned < target_cp){
'promising'
}else{
'favorable'
}
data.frame(planned_D, cp_planned, zone,
reassessed_D = reassess_600$D,
target_reached = reassess_600$target_reached)
## ----promising-zone-action----------------------------------------------------
promising_zone_action <- function(trial){
## Pre-specified design parameters. The promising zone is defined using
## conditional power at the originally planned final analysis.
planned_D <- 300
lower_cp <- 0.30
target_cp <- 0.90
D_cap <- 600
## conditionalPower() obtains the data locked at the interim milestone
## and fits the treatment-vs-placebo logrank comparison internally.
cp <- trial$conditionalPower(
'interim', Surv(pfs, pfs_event) ~ arm,
placebo = 'pbo', alternative = 'less',
alpha = 0.022, D = planned_D, effect = 0.75
)$cp
if(cp < lower_cp){
zone <- 'unfavorable'
decision <- 'unfavorable: retain planned event number'
selected_D <- planned_D
}else if(cp < target_cp){
zone <- 'promising'
## Only a promising result invokes event-number reassessment. The solver
## searches for the earliest solution after the observed interim events.
reassessment <- trial$eventNumberReestimationFromConditionalPower(
'interim', Surv(pfs, pfs_event) ~ arm,
placebo = 'pbo', alternative = 'less',
alpha = 0.022, target_cp = target_cp,
effect = 0.75, D_cap = D_cap
)
## This policy permits increases only. Use the exact solution when it is
## above planned_D; otherwise use the pre-specified cap as the fallback.
if(reassessment$target_reached && reassessment$D > planned_D){
selected_D <- reassessment$D
decision <- 'promising: reassess to target conditional power'
}else{
selected_D <- reassessment$D_cap
decision <- if(reassessment$target_reached){
'promising: use cap because the earliest solution is not an increase'
}else{
'promising: use cap because no solution was found through the cap'
}
}
}else{
zone <- 'favorable'
decision <- 'favorable: retain the planned design'
selected_D <- planned_D
}
## update_milestone() must be called from a milestone action. It queues a
## replacement for the registered, not-yet-triggered final milestone. The
## new event target takes effect after this action returns and applies only
## to the current replicate; the original trigger is restored before the
## next replicate. No update is needed when planned_D is retained.
if(selected_D > planned_D){
trial$update_milestone(
'final',
when = eventNumber(endpoint = 'pfs', n = selected_D,
arms = c('pbo', 'trt'))
)
}
## Re-evaluate conditional power at the event number that will actually be
## used as the final target, including a cap selected as a fallback.
selected_cp <- trial$conditionalPower(
'interim', Surv(pfs, pfs_event) ~ arm,
placebo = 'pbo', alternative = 'less',
alpha = 0.022, D = selected_D, effect = 0.75
)$cp
## Values saved in an action function become simulation-output columns.
trial$save(zone, 'promising_zone')
trial$save(cp, 'conditional_power_at_planned_D')
trial$save(selected_D, 'selected_final_event_number')
trial$save(selected_cp, 'conditional_power_at_final_D')
trial$save(selected_cp >= target_cp, 'target_reached_at_selected_D')
trial$save(decision, 'promising_zone_decision')
}
## ----promising-zone-final-action----------------------------------------------
promising_zone_final_action <- function(trial){
fit <- fitLogrank(
Surv(pfs, pfs_event) ~ arm,
placebo = 'pbo', data = trial$get_locked_data('final'),
alternative = 'less', tidy = FALSE
)
trial$save(fit$info, 'observed_final_event_number')
trial$save(fit$z, 'final_z')
trial$save(fit$p, 'final_p')
trial$save(fit$p <= 0.022, 'reject_final')
}
## ----promising-zone-program---------------------------------------------------
pfs_pbo <- endpoint(name = 'pfs', type = 'tte', generator = rexp,
rate = log(2) / 10)
pbo <- arm(name = 'pbo')
pbo$add_endpoints(pfs_pbo)
pfs_trt <- endpoint(name = 'pfs', type = 'tte', generator = rexp,
rate = 0.75 * log(2) / 10)
trt <- arm(name = 'trt')
trt$add_endpoints(pfs_trt)
accrual_rate <- data.frame(end_time = Inf, piecewise_rate = 30)
trial <- trial(
name = 'promising-zone', n_patients = 700, duration = 120,
seed = 20260831, enroller = StaggeredRecruiter,
accrual_rate = accrual_rate, silent = TRUE
)
trial$add_arms(sample_ratio = c(1, 1), pbo, trt)
interim <- milestone(
name = 'interim', action = promising_zone_action,
when = eventNumber(endpoint = 'pfs', n = 150,
arms = c('pbo', 'trt'))
)
final <- milestone(
name = 'final', action = promising_zone_final_action,
when = eventNumber(endpoint = 'pfs', n = 300,
arms = c('pbo', 'trt'))
)
listener <- listener(silent = TRUE)
listener$add_milestones(interim, final)
controller <- controller(trial, listener)
controller$run(n = 1, silent = TRUE, plot_event = FALSE)
## ----promising-zone-single-output, echo = FALSE-------------------------------
single_output <- controller$get_output(tidy = TRUE)
single_output_table <- single_output[, c(
'promising_zone', 'conditional_power_at_planned_D',
'selected_final_event_number', 'conditional_power_at_final_D',
'target_reached_at_selected_D', 'observed_final_event_number',
'final_p', 'reject_final'
)]
single_output_table$final_p <- format.pval(
single_output_table$final_p, digits = 3, eps = 0.001
)
single_output_table |>
kable(
col.names = c(
'Zone', 'CP at planned D', 'Selected final D', 'CP at final D',
'Target reached', 'Observed final D', 'Final p-value', 'Reject'
),
digits = 3, align = c('l', 'r', 'r', 'r', 'c', 'r', 'r', 'c'),
caption = 'Decision path and final analysis for one replicate'
) |>
kable_styling(
bootstrap_options = 'striped', full_width = FALSE, position = 'left'
) |>
scroll_box(width = '100%')
## ----promising-zone-run, eval = FALSE-----------------------------------------
# controller$reset()
# controller$run(n = 1000, silent = TRUE, plot_event = FALSE)
# conditional_power_output <- controller$get_output()
## ----promising-zone-load-output, echo = FALSE---------------------------------
conditional_power_output <-
TrialSimulator:::getConditionalPowerOutput()
## ----promising-zone-output-head, echo = FALSE---------------------------------
output_columns <- c(
'promising_zone', 'conditional_power_at_planned_D',
'selected_final_event_number', 'conditional_power_at_final_D',
'target_reached_at_selected_D', 'observed_final_event_number',
'final_p', 'reject_final'
)
output_preview <- conditional_power_output[seq_len(5), output_columns]
output_preview$final_p <- format.pval(
output_preview$final_p, digits = 3, eps = 0.001
)
output_preview |>
kable(
col.names = c(
'Zone', 'CP at planned D', 'Selected final D', 'CP at final D',
'Target reached', 'Observed final D', 'Final p-value', 'Reject'
),
digits = 3, align = c('l', 'r', 'r', 'r', 'c', 'r', 'r', 'c'),
caption = 'Decision and final-analysis results for the first five replicates'
) |>
kable_styling(
bootstrap_options = 'striped', full_width = FALSE, position = 'left'
) |>
scroll_box(width = '100%')
## ----promising-zone-zone-summary, echo = FALSE--------------------------------
zone_levels <- c('unfavorable', 'promising', 'favorable')
zone <- factor(conditional_power_output$promising_zone,
levels = zone_levels)
zone_count <- table(zone)
mean_D_by_zone <- tapply(
conditional_power_output$selected_final_event_number,
zone, mean
)
zone_summary <- data.frame(
zone = zone_levels,
replicates = as.integer(zone_count),
percent = round(100 * as.numeric(prop.table(zone_count)), 1),
mean_selected_D = round(as.numeric(mean_D_by_zone), 1)
)
zone_summary |>
kable(
col.names = c('Zone', 'Replicates', 'Percent (%)', 'Mean selected D'),
digits = 1,
caption = 'Promising-zone frequency and selected event number'
) |>
kable_styling(
bootstrap_options = 'striped', full_width = FALSE, position = 'left'
)
## ----promising-zone-overall-summary, echo = FALSE-----------------------------
overall_summary <- data.frame(
metric = c(
'final event target increased',
'target CP reached at selected D',
'final null hypothesis rejected'
),
percent = round(100 * c(
mean(conditional_power_output$selected_final_event_number > 300),
mean(conditional_power_output$target_reached_at_selected_D),
mean(conditional_power_output$reject_final)
), 1)
)
overall_summary |>
kable(
col.names = c('Operating characteristic', 'Percent (%)'), digits = 1,
caption = 'Operating characteristics of the promising-zone design'
) |>
kable_styling(
bootstrap_options = 'striped', full_width = FALSE, position = 'left'
)
## ----multiple-arms-setup, include = FALSE-------------------------------------
trial <- local({
pfs_pbo <- endpoint(name = 'pfs', type = 'tte', generator = rexp,
rate = log(2) / 10)
pbo <- arm(name = 'pbo')
pbo$add_endpoints(pfs_pbo)
pfs_low <- endpoint(name = 'pfs', type = 'tte', generator = rexp,
rate = 0.80 * log(2) / 10)
low <- arm(name = 'low')
low$add_endpoints(pfs_low)
pfs_high <- endpoint(name = 'pfs', type = 'tte', generator = rexp,
rate = 0.70 * log(2) / 10)
high <- arm(name = 'high')
high$add_endpoints(pfs_high)
accrual_rate <- data.frame(end_time = Inf, piecewise_rate = 40)
trial <- trial(
name = 'multiple-arms', n_patients = 1200, duration = 100,
seed = 27183, enroller = StaggeredRecruiter,
accrual_rate = accrual_rate, silent = TRUE
)
trial$add_arms(sample_ratio = c(1, 1, 1), pbo, low, high)
interim <- milestone(
name = 'interim',
when = eventNumber(endpoint = 'pfs', n = 240,
arms = c('pbo', 'low', 'high'))
)
listener <- listener(silent = TRUE)
listener$add_milestones(interim)
controller <- controller(trial, listener)
controller$run(n = 1, silent = TRUE, plot_event = FALSE)
trial
})
## ----multiple-arms-output-----------------------------------------------------
trial$conditionalPower(
'interim', Surv(pfs, pfs_event) ~ arm,
placebo = 'pbo', alternative = 'less',
D = c(low = 350, high = 400),
alpha = c(high = 0.01, low = 0.015),
effect = 0.75
)
trial$eventNumberReestimationFromConditionalPower(
'interim', Surv(pfs, pfs_event) ~ arm,
placebo = 'pbo', alternative = 'less',
alpha = c(low = 0.015, high = 0.01),
target_cp = c(high = 0.90, low = 0.85),
effect = 'trend',
D_cap = c(low = 600, high = 700)
)
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.