From 489ed2a8209cc8bc97e2cf738e305bf330663b89 Mon Sep 17 00:00:00 2001 From: LittleBeannie Date: Wed, 10 Jun 2026 15:41:57 -0400 Subject: [PATCH 01/12] Add harm bound to `gs_power_npe` and `gs_design_npe` --- R/gs_design_npe.R | 88 ++++++++++++++++++++++++++++++--- R/gs_power_npe.R | 121 +++++++++++++++++++++++++++++++++++++++------- 2 files changed, 184 insertions(+), 25 deletions(-) diff --git a/R/gs_design_npe.R b/R/gs_design_npe.R index 346d5a44a..4807c662e 100644 --- a/R/gs_design_npe.R +++ b/R/gs_design_npe.R @@ -21,7 +21,7 @@ #' @param beta Type II error. #' @details -#' The bound specifications (`upper`, `lower`, `upar`, `lpar`) of \code{gs_design_npe()} +#' The bound specifications (`upper`, `lower`, `harm`, `upar`, `lpar`, `hpar`) of \code{gs_design_npe()} #' will be used to ensure Type I error and other boundary properties are as specified. #' See the help file of \code{gs_spending_bound()} for details on spending function. #' @@ -130,7 +130,7 @@ #' test_upper = c(FALSE, TRUE, TRUE) #' ) #' -#' # Example 4 ---- +#' # Example 4a ---- #' # gs_design_npe with spending function bounds #' # 2-sided asymmetric bounds #' # Lower spending based on non-zero effect @@ -144,6 +144,21 @@ #' lpar = list(sf = gsDesign::sfHSD, total_spend = 0.1, param = -1, timing = NULL) #' ) #' +#' # Example 4b ---- +#' # gs_design_npe with an additional harm bound under the null hypothesis +#' gs_design_npe( +#' theta = c(.1, .2, .3), +#' info = (1:3) * 40, +#' info0 = (1:3) * 30, +#' upper = gs_spending_bound, +#' upar = list(sf = gsDesign::sfLDOF, total_spend = 0.025, param = NULL, timing = NULL), +#' lower = gs_spending_bound, +#' lpar = list(sf = gsDesign::sfHSD, total_spend = 0.1, param = -2, timing = NULL), +#' harm = gs_spending_bound, +#' hpar = list(sf = gsDesign::sfHSD, total_spend = 0.1, param = -4, timing = NULL), +#' test_harm = c(TRUE, FALSE, TRUE) +#' ) +#' #' # Example 5 ---- #' # gs_design_npe with two-sided symmetric spend, O'Brien-Fleming spending #' # Typically, 2-sided bounds are binding @@ -177,6 +192,7 @@ gs_design_npe <- function( upper = gs_b, upar = qnorm(.975), lower = gs_b, lpar = -Inf, test_upper = TRUE, test_lower = TRUE, binding = FALSE, + harm = gs_b, hpar = -Inf, test_harm = FALSE, r = 18, tol = 1e-6) { # Check & set up parameters ---- n_analysis <- length(info) @@ -191,9 +207,15 @@ gs_design_npe <- function( upper <- match.fun(upper) lower <- match.fun(lower) + harm <- match.fun(harm) # check test_upper & test_lower test_upper <- check_test_upper(test_upper, n_analysis) test_lower <- check_test_lower(test_lower, n_analysis) + test_harm <- check_test_ul(test_harm, n_analysis, "test_harm") + + if (identical(harm, gs_b) && length(hpar) == 1 && n_analysis > 1) { + hpar <- rep(hpar, n_analysis) + } # Set up info ---- if (is.null(info0)) { @@ -248,6 +270,41 @@ gs_design_npe <- function( min_x <- ((qnorm(alpha) / sqrt(info0[n_analysis]) + qnorm(beta) / sqrt(info[n_analysis])) / theta[n_analysis])^2 # for a fixed design, this is all you need. if (n_analysis == 1) { + if (any(test_harm)) { + ans_h1 <- gs_power_npe( + theta = theta, theta0 = theta0, theta1 = theta1, + info = info * min_x, info0 = info0 * min_x, info1 = info1 * min_x, + info_scale = info_scale, + upper = gs_b, upar = qnorm(1 - alpha), test_upper = test_upper, + lower = if (two_sided) lower else gs_b, + lpar = if (two_sided) lpar else rep(-Inf, n_analysis), + test_lower = test_lower, binding = binding, + harm = harm, hpar = hpar, test_harm = test_harm, + r = r, tol = tol + ) + ans_h0 <- gs_power_npe( + theta = 0, theta0 = theta0, theta1 = theta1, + info = info0 * min_x, info0 = info0 * min_x, info1 = info1 * min_x, + info_scale = info_scale, + upper = gs_b, upar = qnorm(1 - alpha), test_upper = test_upper, + lower = if (two_sided) lower else gs_b, + lpar = if (two_sided) lpar else rep(-Inf, n_analysis), + test_lower = test_lower, binding = binding, + harm = harm, hpar = hpar, test_harm = test_harm, + r = r, tol = tol + ) + suppressMessages( + ans <- ans_h1 |> + full_join( + ans_h0 |> + select(analysis, bound, probability) |> + rename(probability0 = probability) + ) + ) + ans <- ans |> select(analysis, bound, z, probability, probability0, theta, info_frac, info, info0, info1) + return(ans) + } + ans <- tibble( analysis = 1, bound = "upper", z = qnorm(1 - alpha), probability = 1 - beta, probability0 = alpha, theta = theta, @@ -265,7 +322,9 @@ gs_design_npe <- function( info = info * min_x, info0 = info0 * min_x, info1 = info * min_x, info_scale = info_scale, upper = upper, upar = upar, test_upper = test_upper, lower = lower, lpar = lpar, test_lower = test_lower, - binding = binding, r = r, tol = tol + binding = binding, + harm = harm, hpar = hpar, test_harm = test_harm, + r = r, tol = tol ) min_power <- (min_temp[min_temp$bound == "upper" & min_temp$analysis == n_analysis, ])$probability @@ -283,7 +342,9 @@ gs_design_npe <- function( info = info * max_x, info0 = info0 * max_x, info1 = info * max_x, info_scale = info_scale, upper = upper, upar = upar, test_upper = test_upper, lower = lower, lpar = lpar, test_lower = test_lower, - binding = binding, r = r, tol = tol + binding = binding, + harm = harm, hpar = hpar, test_harm = test_harm, + r = r, tol = tol ) max_power <- (max_temp[max_temp$bound == "upper" & max_temp$analysis == n_analysis, ])$probability @@ -308,7 +369,9 @@ gs_design_npe <- function( info = info * micro_x, info0 = info0 * micro_x, info1 = info * micro_x, info_scale = info_scale, upper = upper, upar = upar, test_upper = test_upper, lower = lower, lpar = lpar, test_lower = test_lower, - binding = binding, r = r, tol = tol + binding = binding, + harm = harm, hpar = hpar, test_harm = test_harm, + r = r, tol = tol ) micro_power <- (micro_temp[micro_temp$bound == "upper" & micro_temp$analysis == n_analysis, ])$probability @@ -333,8 +396,15 @@ gs_design_npe <- function( errbeta <- function(x) { # calculate the probability under H1 ans_h1 <<- cache_fun( - gs_power_npe, theta, theta0, theta1, info * x, info0 * x, info1 * x, - info_scale, upper, upar, lower, lpar, test_upper, test_lower, binding, r, tol + gs_power_npe, + theta = theta, theta0 = theta0, theta1 = theta1, + info = info * x, info0 = info0 * x, info1 = info1 * x, + info_scale = info_scale, + upper = upper, upar = upar, test_upper = test_upper, + lower = lower, lpar = lpar, test_lower = test_lower, + binding = binding, + harm = harm, hpar = hpar, test_harm = test_harm, + r = r, tol = tol ) power <- subset(ans_h1, bound == "upper" & analysis == n_analysis)$probability 1 - beta - power @@ -357,7 +427,9 @@ gs_design_npe <- function( lower = if (two_sided) lower else gs_b, lpar = if (two_sided) lpar else rep(-Inf, n_analysis), test_upper = test_upper, test_lower = test_lower, - binding = binding, r = r, tol = tol + binding = binding, + harm = harm, hpar = hpar, test_harm = test_harm, + r = r, tol = tol ) # combine probability under H0 and H1 diff --git a/R/gs_power_npe.R b/R/gs_power_npe.R index 7cafe7171..c4a5c3923 100644 --- a/R/gs_power_npe.R +++ b/R/gs_power_npe.R @@ -73,6 +73,7 @@ #' - \code{gs_b()}: fixed efficacy bounds. #' @param lower Function to compute lower bound, which can be set up similarly as `upper`. #' See [this vignette](https://merck.github.io/gsDesign2/articles/story-seven-test-types.html). +#' @param harm Function to compute harm bound, which can be set up similarly as `lower`. #' @param upar Parameters passed to `upper`. #' - If `upper = gs_b`, then `upar` is a numerical vector specifying the fixed efficacy bounds per analysis. #' - If `upper = gs_spending_bound`, then `upar` is a list including @@ -81,6 +82,7 @@ #' - `param` for the parameter of the spending function. #' - `timing` specifies spending time if different from information-based spending; see details. #' @param lpar Parameters passed to `lower`, which can be set up similarly as `upar.` +#' @param hpar Parameters passed to `harm`, which can be set up similarly as `lpar.` #' @param test_upper Indicator of which analyses should include #' an upper (efficacy) bound; #' single value of `TRUE` (default) indicates all analyses; otherwise, @@ -91,6 +93,11 @@ #' single value of `FALSE` indicated no lower bound; otherwise, #' a logical vector of the same length as `info` should #' indicate which analyses will have a lower bound. +#' @param test_harm Indicator of which analyses should include a harm bound; +#' single value of `TRUE` (default) indicates all analyses; +#' single value of `FALSE` indicates no harm bound; otherwise, +#' a logical vector of the same length as `info` should +#' indicate which analyses will have a harm bound. #' @param r Integer value controlling grid for numerical integration as in #' Jennison and Turnbull (2000); default is 18, range is 1 to 80. #' Larger values provide larger number of grid points and greater accuracy. @@ -99,7 +106,7 @@ #' #' @return A tibble with columns of #' - `analysis`: analysis index. -#' - `bound`: either of value `"upper"` or `"lower"`, indicating the upper and lower bound. +#' - `bound`: one of value `"upper"`, `"lower"`, or `"harm"`, indicating the upper, lower, and harm bound. #' - `z`: the Z-score bounds. #' - `probability`: cumulative probability of crossing the bound at or before the analysis. #' - `theta`: same as the input. @@ -188,7 +195,7 @@ #' lpar = c(qnorm(.1), -Inf, -Inf) #' ) #' -#' # Example 9 ---- +#' # Example 9a ---- #' # gs_power_npe with spending function bounds #' # Lower spending based on non-zero effect #' gs_power_npe( @@ -210,6 +217,21 @@ #' lpar = list(sf = gsDesign::sfHSD, total_spend = 0.1, param = -1, timing = NULL) #' ) #' +#' # Example 9b ---- +#' # gs_power_npe with an additional harm bound under the null hypothesis +#' gs_power_npe( +#' theta = c(.1, .2, .3), +#' info = (1:3) * 40, +#' upper = gs_spending_bound, +#' upar = list(sf = gsDesign::sfLDOF, total_spend = 0.025, param = NULL, timing = NULL), +#' lower = gs_spending_bound, +#' lpar = list(sf = gsDesign::sfHSD, total_spend = 0.1, param = -2, timing = NULL), +#' test_lower = c(TRUE, TRUE, TRUE), +#' harm = gs_spending_bound, +#' hpar = list(sf = gsDesign::sfHSD, total_spend = 0.1, param = -4, timing = NULL), +#' test_harm = c(TRUE, TRUE, TRUE) +#' ) +#' #' # Example 10 ---- #' # gs_power_npe with two-sided symmetric spend, O'Brien-Fleming spending #' # Typically, 2-sided bounds are binding @@ -262,7 +284,9 @@ gs_power_npe <- function(theta = .1, theta0 = 0, theta1 = theta, # 3 theta upper = gs_b, upar = qnorm(.975), lower = gs_b, lpar = -Inf, test_upper = TRUE, test_lower = TRUE, binding = FALSE, - r = 18, tol = 1e-6) { + harm = gs_b, hpar = -Inf, test_harm = FALSE, + r = 18, tol = 1e-6 + ) { # Check & set up parameters ---- n_analysis <- length(info) theta <- check_theta(theta, n_analysis) @@ -270,8 +294,14 @@ gs_power_npe <- function(theta = .1, theta0 = 0, theta1 = theta, # 3 theta theta1 <- check_theta(theta1, n_analysis) upper <- match.fun(upper) lower <- match.fun(lower) + harm <- match.fun(harm) test_upper <- check_test_upper(test_upper, n_analysis) test_lower <- check_test_lower(test_lower, n_analysis) + test_harm <- check_test_ul(test_harm, n_analysis, "test_harm") + + if (identical(harm, gs_b) && length(hpar) == 1 && n_analysis > 1) { + hpar <- rep(hpar, n_analysis) + } # Set up info ---- # impute info @@ -305,24 +335,39 @@ gs_power_npe <- function(theta = .1, theta0 = 0, theta1 = theta, # 3 theta # Initialization ---- a <- rep(-Inf, n_analysis) b <- rep(Inf, n_analysis) + harm_z <- rep(-Inf, n_analysis) hgm1_0 <- NULL hgm1_1 <- NULL hgm1 <- NULL + hgm1_harm0 <- NULL # harm-bound grid under theta0 + hgm1_harm <- NULL # harm-bound grid under the input theta upper_prob <- rep(NA, n_analysis) lower_prob <- rep(NA, n_analysis) + harm_prob <- rep(NA, n_analysis) # Calculate crossing prob under H1 ---- for (k in 1:n_analysis) { - # compute/update lower/upper bound + # compute/update lower/upper/harm bound a[k] <- lower( k = k, par = lpar, hgm1 = hgm1_1, info = info1, r = r, tol = tol, test_bound = test_lower, theta = theta1, efficacy = FALSE ) b[k] <- upper(k = k, par = upar, hgm1 = hgm1_0, info = info0, r = r, tol = tol, test_bound = test_upper) + harm_z[k] <- harm( + k = k, par = hpar, hgm1 = hgm1_harm0, info = info0, r = r, tol = tol, test_bound = test_harm, + theta = theta0, efficacy = FALSE + ) + if (!test_harm[k]) { + harm_z[k] <- -Inf + } + # Cap harm bound: if harm > futility, set to futility + if (!is.na(a[k]) && !is.na(harm_z[k]) && harm_z[k] > a[k]) { + harm_z[k] <- a[k] + } # if it is the first analysis if (k == 1) { - # compute the probability to cross upper/lower bound + # compute the probability to cross upper/lower/harm bound upper_prob[1] <- if (b[1] < Inf) { pnorm(sqrt(info[1]) * (theta[1] - b[1] / sqrt(info0[1]))) } else { @@ -333,6 +378,11 @@ gs_power_npe <- function(theta = .1, theta0 = 0, theta1 = theta, # 3 theta } else { 0 } + harm_prob[1] <- if (harm_z[1] > -Inf) { + pnorm(-sqrt(info[1]) * (theta[1] - harm_z[1] / sqrt(info0[1]))) + } else { + 0 + } # update the grids hgm1_0 <- h1(r = r, theta = theta0[1], info = info0[1], a = if (binding) { a[1] @@ -341,6 +391,8 @@ gs_power_npe <- function(theta = .1, theta0 = 0, theta1 = theta, # 3 theta }, b = b[1]) hgm1_1 <- h1(r = r, theta = theta1[1], info = info1[1], a = a[1], b = b[1]) hgm1 <- h1(r = r, theta = theta[1], info = info[1], a = a[1], b = b[1]) + hgm1_harm0 <- h1(r = r, theta = theta0[1], info = info0[1], a = harm_z[1], b = b[1]) + hgm1_harm <- h1(r = r, theta = theta[1], info = info[1], a = harm_z[1], b = b[1]) } else { # compute the probability to cross upper bound upper_prob[k] <- if (b[k] < Inf) { @@ -362,6 +414,16 @@ gs_power_npe <- function(theta = .1, theta0 = 0, theta1 = theta, # 3 theta } else { 0 } + # compute the probability to cross harm bound + harm_prob[k] <- if (harm_z[k] > -Inf) { + sum(hupdate( + theta = theta[k], thetam1 = theta[k - 1], + info = info[k], im1 = info[k - 1], + a = -Inf, b = harm_z[k], gm1 = hgm1_harm, r = r + )$h) + } else { + 0 + } # update the grids if (k < n_analysis) { @@ -380,22 +442,47 @@ gs_power_npe <- function(theta = .1, theta0 = 0, theta1 = theta, # 3 theta a = a[k], b = b[k], thetam1 = theta[k - 1], im1 = info[k - 1], gm1 = hgm1 ) + hgm1_harm0 <- hupdate( + r = r, theta = theta0[k], info = info0[k], + a = harm_z[k], b = b[k], thetam1 = theta0[k - 1], + im1 = info0[k - 1], gm1 = hgm1_harm0 + ) + hgm1_harm <- hupdate( + r = r, theta = theta[k], info = info[k], + a = harm_z[k], b = b[k], thetam1 = theta[k - 1], + im1 = info[k - 1], gm1 = hgm1_harm + ) } } } - ans <- data.frame( - analysis = rep(1:n_analysis, 2), - bound = c(rep("upper", n_analysis), rep("lower", n_analysis)), - z = c(b, a), - probability = c(cumsum(upper_prob), cumsum(lower_prob)), - theta = rep(theta, 2), - theta1 = rep(theta1, 2), - info_frac = rep(info / max(info), 2), - info = rep(info, 2), - info0 = rep(info0, 2), - info1 = rep(info1, 2) - ) + if (all(!test_harm)) { + ans <- data.frame( + analysis = rep(1:n_analysis, 2), + bound = c(rep("upper", n_analysis), rep("lower", n_analysis)), + z = c(b, a), + probability = c(cumsum(upper_prob), cumsum(lower_prob)), + theta = rep(theta, 2), + theta1 = rep(theta1, 2), + info_frac = rep(info / max(info), 2), + info = rep(info, 2), + info0 = rep(info0, 2), + info1 = rep(info1, 2) + ) + } else { + ans <- data.frame( + analysis = rep(1:n_analysis, 3), + bound = c(rep("upper", n_analysis), rep("lower", n_analysis), rep("harm", n_analysis)), + z = c(b, a, harm_z), + probability = c(cumsum(upper_prob), cumsum(lower_prob), cumsum(harm_prob)), + theta = rep(theta, 3), + theta1 = rep(theta1, 3), + info_frac = rep(info / max(info), 3), + info = rep(info, 3), + info0 = rep(info0, 3), + info1 = rep(info1, 3) + ) + } return(ans) } From 5e415a4dd29d2a29797aa54518bc6e22aae164de Mon Sep 17 00:00:00 2001 From: LittleBeannie Date: Wed, 10 Jun 2026 15:42:19 -0400 Subject: [PATCH 02/12] Add harm bound to `gs_xxx_ahr` --- R/gs_design_ahr.R | 42 ++++++++++++++++++++++++++++++++++++++++-- R/gs_power_ahr.R | 38 +++++++++++++++++++++++++++++++++++++- 2 files changed, 77 insertions(+), 3 deletions(-) diff --git a/R/gs_design_ahr.R b/R/gs_design_ahr.R index 786e81beb..6f338d86b 100644 --- a/R/gs_design_ahr.R +++ b/R/gs_design_ahr.R @@ -98,7 +98,13 @@ #' # Example 2 ---- #' # Single analysis #' gs_design_ahr(analysis_time = 40) -#' +#' gs_design_ahr( +#' analysis_time = 40, +#' upper = gs_b, upar = -qnorm(0.025), test_upper = TRUE, +#' lower = gs_b, lpar = -1, test_lower = TRUE, +#' harm = gs_b, hpar = -2, test_harm = TRUE +#' ) +#' #' # Example 3 ---- #' # Multiple analysis_time #' gs_design_ahr(analysis_time = c(12, 24, 36)) @@ -168,6 +174,22 @@ #' lpar = rep(-Inf, 3) #' ) #' } +#' +#' # Example 8 ---- +#' # Design with an additional harm bound +#' \donttest{ +#' gs_design_ahr( +#' analysis_time = c(12, 24, 36), +#' upper = gs_spending_bound, +#' upar = list(sf = gsDesign::sfLDOF, total_spend = 0.025, param = NULL, timing = NULL), +#' lower = gs_spending_bound, +#' lpar = list(sf = gsDesign::sfHSD, total_spend = 0.1, param = -2, timing = NULL), +#' test_lower = c(TRUE, TRUE, FALSE), +#' harm = gs_spending_bound, +#' hpar = list(sf = gsDesign::sfHSD, total_spend = 0.1, param = -4, timing = NULL), +#' test_harm = c(TRUE, TRUE, FALSE) +#' ) +#' } gs_design_ahr <- function( enroll_rate = define_enroll_rate( duration = c(2, 2, 10), @@ -186,9 +208,12 @@ gs_design_ahr <- function( upar = list(sf = gsDesign::sfLDOF, total_spend = alpha), lower = gs_spending_bound, lpar = list(sf = gsDesign::sfLDOF, total_spend = beta), + harm = gs_b, + hpar = -Inf, h1_spending = TRUE, test_upper = TRUE, test_lower = TRUE, + test_harm = FALSE, info_scale = c("h0_h1_info", "h0_info", "h1_info"), r = 18, tol = 1e-6, @@ -200,6 +225,7 @@ gs_design_ahr <- function( info_scale <- match.arg(info_scale) upper <- match.fun(upper) lower <- match.fun(lower) + harm <- match.fun(harm) # Check inputs ---- check_analysis_time(analysis_time) @@ -309,6 +335,7 @@ gs_design_ahr <- function( alpha = alpha, beta = beta, binding = binding, upper = upper, upar = upar, test_upper = test_upper, lower = lower, lpar = lpar, test_lower = test_lower, + harm = harm, hpar = hpar, test_harm = test_harm, r = r, tol = tol ) ) @@ -378,6 +405,16 @@ gs_design_ahr <- function( bound$spending_time[which(bound$bound == "lower")] <- spending_time_lower } + if (identical(harm, gs_spending_bound)) { + if (!is.null(hpar$timing)) { + spending_time_harm <- hpar$timing + } else { + spending_time_harm <- info0 / info0_final + } + + bound$spending_time[which(bound$bound == "harm")] <- spending_time_harm + } + if (all(is.na(bound$spending_time))){ bound$spending_time <- NULL } @@ -397,7 +434,8 @@ gs_design_ahr <- function( info_scale = info_scale, upper = upper, upar = upar, lower = lower, lpar = lpar, - test_upper = test_upper, test_lower = test_lower, + harm = harm, hpar = hpar, + test_upper = test_upper, test_lower = test_lower, test_harm = test_harm, h1_spending = h1_spending, binding = binding, info_scale = info_scale, r = r, tol = tol ) diff --git a/R/gs_power_ahr.R b/R/gs_power_ahr.R index 16e371589..6d49946db 100644 --- a/R/gs_power_ahr.R +++ b/R/gs_power_ahr.R @@ -130,6 +130,29 @@ #' lpar = list(sf = gsDesign::sfLDOF, total_spend = 0.025) #' ) #' } +#' Example 5 ---- +#' # 2-sided asymmetric O'Brien-Fleming spending bound with harm bound +#' # driven by both `event` and `analysis_time`, i.e., +#' # both `event` and `analysis_time` are not `NULL`, +#' # then the analysis will driven by the maximal one, i.e., +#' # Time = max(analysis_time, calculated Time for targeted event) +#' # Events = max(events, calculated events for targeted analysis_time) +#' \donttest{ +#' gs_power_ahr( +#' analysis_time = c(12, 24, 36), +#' event = c(30, 40, 50), h1_spending = FALSE, +#' binding = TRUE, +#' upper = gs_spending_bound, +#' upar = list(sf = gsDesign::sfLDOF, total_spend = 0.025), +#' test_upper = c(TRUE, TRUE, TRUE), +#' lower = gs_spending_bound, +#' lpar = list(sf = gsDesign::sfHSD, total_spend = 0.1, param = -2), +#' test_lower = c(TRUE, TRUE, TRUE), +#' harm = gs_spending_bound, +#' hpar = list(sf = gsDesign::sfHSD, total_spend = 0.1, param = -4), +#' test_harm = c(TRUE, TRUE, TRUE) +#' ) +#' } gs_power_ahr <- function( enroll_rate = define_enroll_rate( duration = c(2, 2, 10), @@ -147,8 +170,11 @@ gs_power_ahr <- function( upar = list(sf = gsDesign::sfLDOF, total_spend = 0.025), lower = gs_spending_bound, lpar = list(sf = gsDesign::sfLDOF, total_spend = NULL), + harm = gs_b, + hpar = -Inf, test_lower = TRUE, test_upper = TRUE, + test_harm = FALSE, ratio = 1, binding = FALSE, h1_spending = TRUE, @@ -165,6 +191,7 @@ gs_power_ahr <- function( upper <- match.fun(upper) lower <- match.fun(lower) + harm <- match.fun(harm) # Check if it is two-sided design or not if ((identical(lower, gs_b) && (!is.list(lpar))) || all(!test_lower)) { @@ -190,6 +217,12 @@ gs_power_ahr <- function( } } + if (identical(harm, gs_spending_bound)) { + if (is.null(hpar$total_spend) && any(test_harm)) { + stop("gs_power_ahr: please input the total_spend to the harm spending function.") + } + } + # Calculate the asymptotic variance and statistical information ---- x <- gs_info_ahr( enroll_rate = enroll_rate, fail_rate = fail_rate, @@ -224,6 +257,7 @@ gs_power_ahr <- function( info = x$info, info0 = x$info0, info1 = info1, info_scale = info_scale, upper = upper, upar = upar, test_upper = test_upper, lower = lower, lpar = lpar, test_lower = test_lower, + harm = harm, hpar = hpar, test_harm = test_harm, binding = binding, r = r, tol = tol ) @@ -242,6 +276,7 @@ gs_power_ahr <- function( lpar }, test_lower = test_lower, + harm = harm, hpar = hpar, test_harm = test_harm, binding = binding, r = r, tol = tol ) @@ -286,7 +321,8 @@ gs_power_ahr <- function( alpha = if (identical(upper, gs_spending_bound)) {upar$total_spend} else {NULL}, upper = upper, upar = upar, lower = lower, lpar = lpar, - test_lower = test_lower, test_upper = test_upper, + harm = harm, hpar = hpar, + test_lower = test_lower, test_upper = test_upper, test_harm = test_harm, ratio = ratio, binding = binding, h1_spending = h1_spending, info_scale = info_scale, r = r, tol = tol ) From 568d4927635f9d86f860ef31345582e0fdce1e85 Mon Sep 17 00:00:00 2001 From: LittleBeannie Date: Wed, 10 Jun 2026 15:42:40 -0400 Subject: [PATCH 03/12] Update summary functions when harm bound is added --- R/as_gt.R | 28 ++++++++++++++++++++++------ R/gs_bound_summary.R | 41 ++++++++++++++++++++++++++--------------- R/summary.R | 9 ++++++--- 3 files changed, 54 insertions(+), 24 deletions(-) diff --git a/R/as_gt.R b/R/as_gt.R index c7a2b8bb0..99e66ec84 100644 --- a/R/as_gt.R +++ b/R/as_gt.R @@ -111,7 +111,7 @@ as_gt.fixed_design_summary <- function(x, title = NULL, footnote = NULL, ...) { #' "spanner")`; users can use the functions in the `gt` package to customize #' the table. To disable footnotes, use `footnote = FALSE`. #' @param display_bound A vector of strings specifying the label of the bounds. -#' The default is `c("Efficacy", "Futility")`. +#' The default is `c("Efficacy", "Futility", "Harm")`. #' @param display_columns A vector of strings specifying the variables to be #' displayed in the summary table. #' @param display_inf_bound Logical, whether to display the +/-inf bound. @@ -128,7 +128,22 @@ as_gt.fixed_design_summary <- function(x, title = NULL, footnote = NULL, ...) { #' gs_design_ahr() |> #' summary() |> #' as_gt() -#' +#' +#' gs_design_ahr( +#' analysis_time = c(12, 24, 36), +#' upper = gs_spending_bound, +#' upar = list(sf = gsDesign::sfLDOF, total_spend = 0.025), +#' test_upper = c(FALSE, TRUE, TRUE), +#' lower = gs_spending_bound, +#' lpar = list(sf = gsDesign::sfHSD, total_spend = 0.1, param = -2), +#' test_lower = c(TRUE, TRUE, FALSE), +#' harm = gs_spending_bound, +#' hpar = list(sf = gsDesign::sfHSD, total_spend = 0.1, param = -4), +#' test_harm = c(TRUE, TRUE, FALSE) +#' ) |> +#' summary() |> +#' as_gt() +#' #' gs_power_ahr(lpar = list(sf = gsDesign::sfLDOF, total_spend = 0.1)) |> #' summary() |> #' as_gt() @@ -193,7 +208,7 @@ as_gt.fixed_design_summary <- function(x, title = NULL, footnote = NULL, ...) { #' #' # Example 5 ---- #' # Usage of display_bound = ... -#' # to either show efficacy bound or futility bound, or both(default) +#' # to show selected bounds #' gs_power_wlr(lpar = list(sf = gsDesign::sfLDOF, total_spend = 0.1)) |> #' summary() |> #' as_gt(display_bound = "Efficacy") @@ -212,7 +227,7 @@ as_gt.gs_design_summary <- function( colname_spanner = "Cumulative boundary crossing probability", colname_spannersub = c("Alternate hypothesis", "Null hypothesis"), footnote = NULL, - display_bound = c("Efficacy", "Futility"), + display_bound = c("Efficacy", "Futility", "Harm"), display_columns = NULL, display_inf_bound = FALSE, ...) { @@ -364,6 +379,7 @@ gsd_parts <- function( x2 <- x2[, columns] x2 <- subset(x2, !is.na(`Alternate hypothesis`) & !is.na(`Null hypothesis`)) x2 <- subset(x2, Bound %in% bound) + x2$Bound <- factor(x2$Bound, levels = bound) i <- match(c("Alternate hypothesis", "Null hypothesis"), names(x2)) names(x2)[i] <- spannersub @@ -382,10 +398,10 @@ gsd_parts <- function( ) list( - x = arrange(x2, Analysis), + x = arrange(x2, Analysis, Bound), title = title, subtitle = subtitle, footnote = if (!isFALSE(footnote)) footnote %||% gsd_footnote(method, columns), - alpha = max(filter(x, Bound == bound[1])[["Null hypothesis"]]) + alpha = max(filter(x, Bound == "Efficacy")[["Null hypothesis"]]) ) } diff --git a/R/gs_bound_summary.R b/R/gs_bound_summary.R index f2625b58a..35d333e2e 100644 --- a/R/gs_bound_summary.R +++ b/R/gs_bound_summary.R @@ -1,6 +1,6 @@ #' Bound summary table #' -#' Summarizes the efficacy and futility bounds for each analysis. +#' Summarizes the efficacy, futility, and harm bounds for each analysis. #' #' @param x Design object. #' @param alpha Vector of alpha values to compute additional efficacy columns. @@ -52,14 +52,25 @@ gs_bound_summary <- function(x, digits = 4, ddigits = 2, tdigits = 0, timename = } } out <- Reduce(cbind, outlist) - # Use of union() allows placement of column "Futility" at the far right, but - # only if it is returned by gs_bound_summary_single(). This is because - # one-sided designs do not produce a Futility column. + # Use of union() allows placement of columns "Futility" and "Harm" at the far + # right, but only if they are returned by gs_bound_summary_single(). This is + # because one-sided designs do not produce a Futility column, and designs + # without harm bounds do not produce a Harm column. column_order <- union(c("Analysis", "Value", col_efficacy_name), colnames(out)) out <- out[, column_order] return(out) } +gs_bound_summary_values <- function(bound, analysis, bound_name, columns) { + row_bound <- bound[ + bound$analysis == analysis & bound$bound == bound_name, + columns, + drop = FALSE + ] + if (nrow(row_bound) == 0) return(rep(NA_real_, length(columns))) + as.numeric(unlist(row_bound[1, columns], use.names = FALSE)) +} + gs_bound_summary_single <- function(x, col_efficacy_name = "Efficacy", digits, ddigits, tdigits, timename) { # Input @@ -72,6 +83,8 @@ gs_bound_summary_single <- function(x, col_efficacy_name = "Efficacy", digits, col_value <- character() col_efficacy <- numeric() col_futility <- numeric() + col_harm <- numeric() + bound_columns <- c("z", "nominal p", "~hr at bound", "probability0", "probability") for (i in seq_len(nrow(analysis))) { @@ -113,33 +126,31 @@ gs_bound_summary_single <- function(x, col_efficacy_name = "Efficacy", digits, ) # Efficacy column - row_efficacy <- bound[ - bound$analysis == i & bound$bound == "upper", - c("z", "nominal p", "~hr at bound", "probability0", "probability") - ] - col_efficacy <- c(col_efficacy, as.numeric(row_efficacy)) + col_efficacy <- c(col_efficacy, gs_bound_summary_values(bound, i, "upper", bound_columns)) # Futility column - row_futility <- bound[ - bound$analysis == i & bound$bound == "lower", - c("z", "nominal p", "~hr at bound", "probability0", "probability") - ] - col_futility <- c(col_futility, as.numeric(row_futility)) + col_futility <- c(col_futility, gs_bound_summary_values(bound, i, "lower", bound_columns)) + + # Harm column + col_harm <- c(col_harm, gs_bound_summary_values(bound, i, "harm", bound_columns)) } col_efficacy <- round(col_efficacy, digits) col_futility <- round(col_futility, digits) + col_harm <- round(col_harm, digits) out <- data.frame( Analysis = col_analysis, Value = col_value, Efficacy = col_efficacy, - Futility = col_futility + Futility = col_futility, + Harm = col_harm ) colnames(out)[3] <- col_efficacy_name # One-sided design should not include Futility column if (all(is.na(out[["Futility"]]))) out[["Futility"]] <- NULL + if (all(is.na(out[["Harm"]]))) out[["Harm"]] <- NULL return(out) } diff --git a/R/summary.R b/R/summary.R index 821944c9d..6c0717c51 100644 --- a/R/summary.R +++ b/R/summary.R @@ -103,7 +103,9 @@ summary.fixed_design <- function(object, ...) { #' If the vector is unnamed, it must match the length of `col_vars`. If the #' vector is named, you only have to specify the number of digits for the #' columns you want to be displayed differently than the defaults. -#' @param bound_names Names for bounds; default is `c("Efficacy", "Futility")`. +#' @param bound_names Names for bounds; default is `c("Efficacy", "Futility", "Harm")`. +#' The first two values label upper and lower bounds. If a third value is provided, +#' it labels harm bounds; otherwise harm bounds are labeled `"Harm"`. #' @param display_spending_time A logical value (TRUE/FALSE) indicating if the spending time #' is summarized in the table. #' @@ -257,7 +259,7 @@ summary.gs_design <- function(object, analysis_decimals = NULL, col_vars = NULL, col_decimals = NULL, - bound_names = c("Efficacy", "Futility"), + bound_names = c("Efficacy", "Futility", "Harm"), display_spending_time = FALSE, ...) { x <- object @@ -295,7 +297,8 @@ summary.gs_design <- function(object, xy <- x_bound # change Upper -> bound_names[1], e.g., Efficacy # change Lower -> bound_names[2], e.g., Futility - xy$bound <- replace_values(xy$bound, c(upper = bound_names[1], lower = bound_names[2])) + # change Harm -> bound_names[3], e.g., Harm + xy$bound <- replace_values(xy$bound, c(upper = bound_names[1], lower = bound_names[2], harm = bound_names[3])) if (!"probability0" %in% names(xy)) xy$probability0 <- "-" xy <- xy |> arrange(analysis, desc(bound)) From 399cd2adccc434deb9ed49196352c79027657ed6 Mon Sep 17 00:00:00 2001 From: LittleBeannie Date: Wed, 10 Jun 2026 15:44:18 -0400 Subject: [PATCH 04/12] add developer tests --- tests/testit/test-developer-gs_power_npe.R | 34 ++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/testit/test-developer-gs_power_npe.R b/tests/testit/test-developer-gs_power_npe.R index acb9f08b6..e48cd3863 100644 --- a/tests/testit/test-developer-gs_power_npe.R +++ b/tests/testit/test-developer-gs_power_npe.R @@ -320,3 +320,37 @@ assert("Expect equal with gsDesign::gsProbability outcome for efficacy bounds", (all.equal(x$z[x$bound == "upper"], z$upper$bound, tolerance = 2e-6)) (all.equal(x$probability[x$bound == "upper"], cumsum(z$upper$prob), tolerance = 6e-6)) }) + +assert("Harm bound - Cap harm bound at futility bound", { + x <- gs_power_npe( + theta = c(.1, .2, .3), + info = (1:3) * 40, + upper = gs_spending_bound, + upar = list(sf = gsDesign::sfLDOF, total_spend = 0.025, param = NULL, timing = NULL), + lower = gs_spending_bound, + lpar = list(sf = gsDesign::sfHSD, total_spend = 0.1, param = -2, timing = NULL), + test_lower = c(TRUE, TRUE, TRUE), + harm = gs_spending_bound, + hpar = list(sf = gsDesign::sfHSD, total_spend = 0.1, param = -4, timing = NULL), + test_harm = c(TRUE, TRUE, TRUE) + ) + harm_bound <- x |> dplyr::filter(bound == "harm") |> dplyr::pull(z) + futility_bound <- x |> dplyr::filter(bound == "lower") |> dplyr::pull(z) + (all(harm_bound <= futility_bound)) + + x <- gs_power_npe( + theta = c(.1, .2, .3), + info = (1:3) * 40, + upper = gs_spending_bound, + upar = list(sf = gsDesign::sfLDOF, total_spend = 0.025, param = NULL, timing = NULL), + lower = gs_spending_bound, + lpar = list(sf = gsDesign::sfHSD, total_spend = 0.1, param = -2, timing = NULL), + test_lower = c(TRUE, TRUE, FALSE), + harm = gs_spending_bound, + hpar = list(sf = gsDesign::sfHSD, total_spend = 0.1, param = -4, timing = NULL), + test_harm = c(TRUE, TRUE, TRUE) + ) + harm_bound <- x |> dplyr::filter(bound == "harm") |> dplyr::pull(z) + futility_bound <- x |> dplyr::filter(bound == "lower") |> dplyr::pull(z) + (all(harm_bound <= futility_bound)) +}) From f2bcae361b7c6b6694d73711d1576b15cfe1a65c Mon Sep 17 00:00:00 2001 From: LittleBeannie Date: Thu, 11 Jun 2026 10:31:21 -0400 Subject: [PATCH 05/12] Add as_rtf test file and update snapshot --- tests/testit/test-independent_as_rtf.R | 18 +++++++ tests/testit/test-independent_as_rtf.md | 64 ++++++++++++------------- 2 files changed, 50 insertions(+), 32 deletions(-) create mode 100644 tests/testit/test-independent_as_rtf.R diff --git a/tests/testit/test-independent_as_rtf.R b/tests/testit/test-independent_as_rtf.R new file mode 100644 index 000000000..fe4c5815a --- /dev/null +++ b/tests/testit/test-independent_as_rtf.R @@ -0,0 +1,18 @@ +# Test as_rtf() snapshot +source("tests/testit/helper-support-as_rtf.R") + +assert("gs_power_wlr_example() produces expected as_rtf output", { + path <- tempfile(fileext = ".rtf") + gs_power_wlr_example() |> + summary() |> + as_rtf(file = path) + TRUE # Snapshot comparison done by testit .md file +}) + +assert("gs_design_ahr() produces expected as_rtf output", { + path <- tempfile(fileext = ".rtf") + gs_design_ahr() |> + summary() |> + as_rtf(file = path) + TRUE # Snapshot comparison done by testit .md file +}) diff --git a/tests/testit/test-independent_as_rtf.md b/tests/testit/test-independent_as_rtf.md index e08267cd6..3cda25efa 100644 --- a/tests/testit/test-independent_as_rtf.md +++ b/tests/testit/test-independent_as_rtf.md @@ -462,20 +462,6 @@ cat(readLines(path), sep = "\n") \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx6000 \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx7500 \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx9000 -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 Futility}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 -1.17}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.8798}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 1.535}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.0341}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.1202}\cell -\intbl\row\pard -\trowd\trgaph108\trleft0\trqc -\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx1500 -\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx3000 -\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx4500 -\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx6000 -\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx7500 -\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx9000 \pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 Efficacy}\cell \pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 2.68}\cell \pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.0037}\cell @@ -484,10 +470,6 @@ cat(readLines(path), sep = "\n") \pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.0037}\cell \intbl\row\pard \trowd\trgaph108\trleft0\trqc -\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx9000 -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 Analysis: 2 Time: 19.2 N: 108 Events: 40 AHR: 0.75 Information fraction: 0.8 {\super c}}\cell -\intbl\row\pard -\trowd\trgaph108\trleft0\trqc \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx1500 \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx3000 \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx4500 @@ -495,11 +477,15 @@ cat(readLines(path), sep = "\n") \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx7500 \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx9000 \pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 Futility}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 -0.66}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.7452}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 1.232}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.0664}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.2664}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 -1.17}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.8798}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 1.535}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.0341}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.1202}\cell +\intbl\row\pard +\trowd\trgaph108\trleft0\trqc +\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx9000 +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 Analysis: 2 Time: 19.2 N: 108 Events: 40 AHR: 0.75 Information fraction: 0.8 {\super c}}\cell \intbl\row\pard \trowd\trgaph108\trleft0\trqc \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx1500 @@ -516,10 +502,6 @@ cat(readLines(path), sep = "\n") \pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.0121}\cell \intbl\row\pard \trowd\trgaph108\trleft0\trqc -\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx9000 -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 Analysis: 3 Time: 24.5 N: 108 Events: 50 AHR: 0.71 Information fraction: 1 {\super c}}\cell -\intbl\row\pard -\trowd\trgaph108\trleft0\trqc \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx1500 \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx3000 \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx4500 @@ -527,11 +509,15 @@ cat(readLines(path), sep = "\n") \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx7500 \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx9000 \pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 Futility}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 -0.22}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.5881}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 1.065}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.1002}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.4319}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 -0.66}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.7452}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 1.232}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.0664}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.2664}\cell +\intbl\row\pard +\trowd\trgaph108\trleft0\trqc +\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx9000 +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 Analysis: 3 Time: 24.5 N: 108 Events: 50 AHR: 0.71 Information fraction: 1 {\super c}}\cell \intbl\row\pard \trowd\trgaph108\trleft0\trqc \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx1500 @@ -548,6 +534,20 @@ cat(readLines(path), sep = "\n") \pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.025}\cell \intbl\row\pard \trowd\trgaph108\trleft0\trqc +\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx1500 +\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx3000 +\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx4500 +\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx6000 +\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx7500 +\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx9000 +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 Futility}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 -0.22}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.5881}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 1.065}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.1002}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.4319}\cell +\intbl\row\pard +\trowd\trgaph108\trleft0\trqc \clbrdrl\brdrs\brdrw15\clbrdrt\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrdb\brdrw15\clvertalt\cellx9000 \pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 {\super a} One-sided p-value for experimental vs control treatment. Value < 0.5 favors experimental, > 0.5 favors control.\line{\super b} Approximate hazard ratio to cross bound.\line{\super c} wAHR is the weighted AHR.}\cell \intbl\row\pard From 9b59c45107030ff874f8bbabb0ff956d0e884e2e Mon Sep 17 00:00:00 2001 From: LittleBeannie Date: Thu, 11 Jun 2026 10:31:46 -0400 Subject: [PATCH 06/12] Rename test file to match new naming convention --- .../{test-independent_as_rtf.R => test-independent-as_rtf.R} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/testit/{test-independent_as_rtf.R => test-independent-as_rtf.R} (100%) diff --git a/tests/testit/test-independent_as_rtf.R b/tests/testit/test-independent-as_rtf.R similarity index 100% rename from tests/testit/test-independent_as_rtf.R rename to tests/testit/test-independent-as_rtf.R From b577e09a8623e0ce536a964ed0805438520ce87c Mon Sep 17 00:00:00 2001 From: LittleBeannie Date: Thu, 11 Jun 2026 10:38:46 -0400 Subject: [PATCH 07/12] Revert "Rename test file to match new naming convention" This reverts commit 9b59c45107030ff874f8bbabb0ff956d0e884e2e. --- .../{test-independent-as_rtf.R => test-independent_as_rtf.R} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/testit/{test-independent-as_rtf.R => test-independent_as_rtf.R} (100%) diff --git a/tests/testit/test-independent-as_rtf.R b/tests/testit/test-independent_as_rtf.R similarity index 100% rename from tests/testit/test-independent-as_rtf.R rename to tests/testit/test-independent_as_rtf.R From 6d1efe76a34cccef5919408e195a3f8a03898172 Mon Sep 17 00:00:00 2001 From: LittleBeannie Date: Thu, 11 Jun 2026 10:42:58 -0400 Subject: [PATCH 08/12] Rename testing file --- .../{test-independent_as_rtf.R => test-independent-as_rtf.R} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/testit/{test-independent_as_rtf.R => test-independent-as_rtf.R} (100%) diff --git a/tests/testit/test-independent_as_rtf.R b/tests/testit/test-independent-as_rtf.R similarity index 100% rename from tests/testit/test-independent_as_rtf.R rename to tests/testit/test-independent-as_rtf.R From ec131446b8bcf99d0e49ff2446a1434fe4c19826 Mon Sep 17 00:00:00 2001 From: Yihui Xie Date: Thu, 11 Jun 2026 15:06:39 -0400 Subject: [PATCH 09/12] Fix CI: remove manual source() and update snapshots The helper-support-as_rtf.R is auto-sourced by testit, so the explicit source() call failed during R CMD check. Also update as_gt and as_rtf snapshots to reflect the new bound ordering. Co-Authored-By: Claude Opus 4.6 --- tests/testit/test-independent-as_gt.md | 68 +++---- tests/testit/test-independent-as_rtf.R | 1 - tests/testit/test-independent-as_rtf.md | 248 ++++++++++++------------ 3 files changed, 158 insertions(+), 159 deletions(-) diff --git a/tests/testit/test-independent-as_gt.md b/tests/testit/test-independent-as_gt.md index c47fb1e89..1da54ef05 100644 --- a/tests/testit/test-independent-as_gt.md +++ b/tests/testit/test-independent-as_gt.md @@ -147,7 +147,7 @@ gt_to_latex(output) {\fontsize{14}{17}\selectfont AHR approximations of \textasciitilde{}HR at bound\fontsize{12}{15}\selectfont } } \fontsize{12.0pt}{14.0pt}\selectfont -\begin{tabular*}{\linewidth}{@{\extracolsep{\fill}}lrrrrr} +\begin{tabular*}{\linewidth}{@{\extracolsep{\fill}}crrrrr} \toprule & & & & \multicolumn{2}{c}{{Cumulative boundary crossing probability}} \\ \cmidrule(lr){5-6} @@ -182,7 +182,7 @@ gt_to_latex(output) {\fontsize{14}{17}\selectfont AHR approximations of \textasciitilde{}HR at bound\fontsize{12}{15}\selectfont } } \fontsize{12.0pt}{14.0pt}\selectfont -\begin{tabular*}{\linewidth}{@{\extracolsep{\fill}}lrrrrr} +\begin{tabular*}{\linewidth}{@{\extracolsep{\fill}}crrrrr} \toprule & & & & \multicolumn{2}{c}{{Cumulative boundary crossing probability}} \\ \cmidrule(lr){5-6} @@ -190,18 +190,18 @@ Bound & Z & Nominal p\textsuperscript{\textit{1}} & \textasciitilde{}HR at bound \midrule\addlinespace[2.5pt] \multicolumn{6}{l}{Analysis: 1 Time: 14.9 N: 108 Events: 30 AHR: 0.79 Information fraction: 0.6} \\[2.5pt] \midrule\addlinespace[2.5pt] -Futility & -1.17 & 0.8792 & 1.5336 & 0.0349 & 0.1208 \\ Efficacy & 2.67 & 0.0038 & 0.3774 & 0.0231 & 0.0038 \\ +Futility & -1.17 & 0.8792 & 1.5336 & 0.0349 & 0.1208 \\ \midrule\addlinespace[2.5pt] \multicolumn{6}{l}{Analysis: 2 Time: 19.2 N: 108 Events: 40 AHR: 0.74 Information fraction: 0.8} \\[2.5pt] \midrule\addlinespace[2.5pt] -Futility & -0.66 & 0.7462 & 1.2331 & 0.0668 & 0.2655 \\ Efficacy & 2.29 & 0.0110 & 0.4849 & 0.0897 & 0.0122 \\ +Futility & -0.66 & 0.7462 & 1.2331 & 0.0668 & 0.2655 \\ \midrule\addlinespace[2.5pt] \multicolumn{6}{l}{Analysis: 3 Time: 24.5 N: 108 Events: 50 AHR: 0.71 Information fraction: 1} \\[2.5pt] \midrule\addlinespace[2.5pt] -Futility & -0.23 & 0.5897 & 1.0662 & 0.1008 & 0.4303 \\ Efficacy & 2.03 & 0.0211 & 0.5631 & 0.2070 & 0.0250 \\ +Futility & -0.23 & 0.5897 & 1.0662 & 0.1008 & 0.4303 \\ \bottomrule \end{tabular*} \begin{minipage}{\linewidth} @@ -228,7 +228,7 @@ gt_to_latex(output) {\fontsize{14}{17}\selectfont WLR approximation of \textasciitilde{}wHR at bound\fontsize{12}{15}\selectfont } } \fontsize{12.0pt}{14.0pt}\selectfont -\begin{tabular*}{\linewidth}{@{\extracolsep{\fill}}lrrrrr} +\begin{tabular*}{\linewidth}{@{\extracolsep{\fill}}crrrrr} \toprule & & & & \multicolumn{2}{c}{{Cumulative boundary crossing probability}} \\ \cmidrule(lr){5-6} @@ -275,7 +275,7 @@ gt_to_latex(output) {\fontsize{14}{17}\selectfont WLR approximation of \textasciitilde{}wHR at bound\fontsize{12}{15}\selectfont } } \fontsize{12.0pt}{14.0pt}\selectfont -\begin{tabular*}{\linewidth}{@{\extracolsep{\fill}}lrrrrr} +\begin{tabular*}{\linewidth}{@{\extracolsep{\fill}}crrrrr} \toprule & & & & \multicolumn{2}{c}{{Cumulative boundary crossing probability}}\textsuperscript{\textit{2}} \\ \cmidrule(lr){5-6} @@ -283,18 +283,18 @@ Bound & Z & Nominal p & \textasciitilde{}wHR at bound\textsuperscript{\textit{3} \midrule\addlinespace[2.5pt] \multicolumn{6}{l}{Analysis: 1 Time: 14.9 N: 108 Events: 30 AHR: 0.79 Information fraction: 0.6\textsuperscript{\textit{4}}} \\[2.5pt] \midrule\addlinespace[2.5pt] -Futility & -1.17 & 0.8798 & 1.5353 & 0.0341 & 0.1202 \\ Efficacy & 2.68 & 0.0037 & 0.3765 & 0.0217 & 0.0037 \\ +Futility & -1.17 & 0.8798 & 1.5353 & 0.0341 & 0.1202 \\ \midrule\addlinespace[2.5pt] \multicolumn{6}{l}{Analysis: 2 Time: 19.2 N: 108 Events: 40 AHR: 0.75 Information fraction: 0.8\textsuperscript{\textit{4}}} \\[2.5pt] \midrule\addlinespace[2.5pt] -Futility & -0.66 & 0.7452 & 1.2319 & 0.0664 & 0.2664 \\ Efficacy & 2.29 & 0.0110 & 0.4846 & 0.0886 & 0.0121 \\ +Futility & -0.66 & 0.7452 & 1.2319 & 0.0664 & 0.2664 \\ \midrule\addlinespace[2.5pt] \multicolumn{6}{l}{Analysis: 3 Time: 24.5 N: 108 Events: 50 AHR: 0.71 Information fraction: 1\textsuperscript{\textit{4}}} \\[2.5pt] \midrule\addlinespace[2.5pt] -Futility & -0.22 & 0.5881 & 1.0650 & 0.1002 & 0.4319 \\ Efficacy & 2.03 & 0.0212 & 0.5631 & 0.2071 & 0.0250 \\ +Futility & -0.22 & 0.5881 & 1.0650 & 0.1002 & 0.4319 \\ \bottomrule \end{tabular*} \begin{minipage}{\linewidth} @@ -337,7 +337,7 @@ gt_to_latex(output) {\fontsize{14}{17}\selectfont MaxCombo approximation\fontsize{12}{15}\selectfont } } \fontsize{12.0pt}{14.0pt}\selectfont -\begin{tabular*}{\linewidth}{@{\extracolsep{\fill}}lrrrr} +\begin{tabular*}{\linewidth}{@{\extracolsep{\fill}}crrrr} \toprule & & & \multicolumn{2}{c}{{Cumulative boundary crossing probability}} \\ \cmidrule(lr){4-5} @@ -345,18 +345,18 @@ Bound & Z & Nominal p\textsuperscript{\textit{1}} & Alternate hypothesis & Null \midrule\addlinespace[2.5pt] \multicolumn{5}{l}{Analysis: 1 Time: 12 N: 500 Events: 107.4 AHR: 0.84 Event fraction: 0.32\textsuperscript{\textit{2}}} \\[2.5pt] \midrule\addlinespace[2.5pt] -Futility & -1 & 0.8413 & 0.0293 & 0.0000 \\ Efficacy & 3 & 0.0013 & 0.0175 & 0.0013 \\ +Futility & -1 & 0.8413 & 0.0293 & 0.0000 \\ \midrule\addlinespace[2.5pt] \multicolumn{5}{l}{Analysis: 2 Time: 24 N: 500 Events: 246.3 AHR: 0.72 Event fraction: 0.74\textsuperscript{\textit{2}}} \\[2.5pt] \midrule\addlinespace[2.5pt] -Futility & 0 & 0.5000 & 0.0314 & 0.0000 \\ Efficacy & 2 & 0.0228 & 0.7261 & 0.0233 \\ +Futility & 0 & 0.5000 & 0.0314 & 0.0000 \\ \midrule\addlinespace[2.5pt] \multicolumn{5}{l}{Analysis: 3 Time: 36 N: 500 Events: 331.3 AHR: 0.68 Event fraction: 1\textsuperscript{\textit{2}}} \\[2.5pt] \midrule\addlinespace[2.5pt] -Futility & 1 & 0.1587 & 0.0326 & 0.0000 \\ Efficacy & 1 & 0.1587 & 0.9674 & 0.1956 \\ +Futility & 1 & 0.1587 & 0.0326 & 0.0000 \\ \bottomrule \end{tabular*} \begin{minipage}{\linewidth} @@ -383,7 +383,7 @@ gt_to_latex(output) {\fontsize{14}{17}\selectfont measured by risk difference\fontsize{12}{15}\selectfont } } \fontsize{12.0pt}{14.0pt}\selectfont -\begin{tabular*}{\linewidth}{@{\extracolsep{\fill}}lrrrrr} +\begin{tabular*}{\linewidth}{@{\extracolsep{\fill}}crrrrr} \toprule & & & & \multicolumn{2}{c}{{Cumulative boundary crossing probability}} \\ \cmidrule(lr){5-6} @@ -417,7 +417,7 @@ gt_to_latex(output) {\fontsize{14}{17}\selectfont measured by risk difference\fontsize{12}{15}\selectfont } } \fontsize{12.0pt}{14.0pt}\selectfont -\begin{tabular*}{\linewidth}{@{\extracolsep{\fill}}lrrrrr} +\begin{tabular*}{\linewidth}{@{\extracolsep{\fill}}crrrrr} \toprule & & & & \multicolumn{2}{c}{{Cumulative boundary crossing probability}} \\ \cmidrule(lr){5-6} @@ -425,8 +425,8 @@ Bound & Z & Nominal p\textsuperscript{\textit{1}} & \textasciitilde{}Risk differ \midrule\addlinespace[2.5pt] \multicolumn{6}{l}{Analysis: 1 N: 40 Risk difference: 0.05 Information fraction: 0.67} \\[2.5pt] \midrule\addlinespace[2.5pt] -Futility & -1.28 & 0.9000 & -0.1537 & 0.0444 & 0.1000 \\ Efficacy & 3.71 & 0.0001 & 0.4448 & 0.0005 & 0.0001 \\ +Futility & -1.28 & 0.9000 & -0.1537 & 0.0444 & 0.1000 \\ \midrule\addlinespace[2.5pt] \multicolumn{6}{l}{Analysis: 2 N: 50 Risk difference: 0.05 Information fraction: 0.83} \\[2.5pt] \midrule\addlinespace[2.5pt] @@ -461,7 +461,7 @@ gt_to_latex(output) {\fontsize{14}{17}\selectfont from gs\_power\_wlr\fontsize{12}{15}\selectfont } } \fontsize{12.0pt}{14.0pt}\selectfont -\begin{tabular*}{\linewidth}{@{\extracolsep{\fill}}lrrrrr} +\begin{tabular*}{\linewidth}{@{\extracolsep{\fill}}crrrrr} \toprule & & & & \multicolumn{2}{c}{{Cumulative boundary crossing probability}} \\ \cmidrule(lr){5-6} @@ -469,18 +469,18 @@ Bound & Z & Nominal p\textsuperscript{\textit{1}} & \textasciitilde{}wHR at boun \midrule\addlinespace[2.5pt] \multicolumn{6}{l}{Analysis: 1 Time: 14.9 N: 108 Events: 30 AHR: 0.79 Information fraction: 0.6\textsuperscript{\textit{3}}} \\[2.5pt] \midrule\addlinespace[2.5pt] -Futility & -1.17 & 0.8798 & 1.5353 & 0.0341 & 0.1202 \\ Efficacy & 2.68 & 0.0037 & 0.3765 & 0.0217 & 0.0037 \\ +Futility & -1.17 & 0.8798 & 1.5353 & 0.0341 & 0.1202 \\ \midrule\addlinespace[2.5pt] \multicolumn{6}{l}{Analysis: 2 Time: 19.2 N: 108 Events: 40 AHR: 0.75 Information fraction: 0.8\textsuperscript{\textit{3}}} \\[2.5pt] \midrule\addlinespace[2.5pt] -Futility & -0.66 & 0.7452 & 1.2319 & 0.0664 & 0.2664 \\ Efficacy & 2.29 & 0.0110 & 0.4846 & 0.0886 & 0.0121 \\ +Futility & -0.66 & 0.7452 & 1.2319 & 0.0664 & 0.2664 \\ \midrule\addlinespace[2.5pt] \multicolumn{6}{l}{Analysis: 3 Time: 24.5 N: 108 Events: 50 AHR: 0.71 Information fraction: 1\textsuperscript{\textit{3}}} \\[2.5pt] \midrule\addlinespace[2.5pt] -Futility & -0.22 & 0.5881 & 1.0650 & 0.1002 & 0.4319 \\ Efficacy & 2.03 & 0.0212 & 0.5631 & 0.2071 & 0.0250 \\ +Futility & -0.22 & 0.5881 & 1.0650 & 0.1002 & 0.4319 \\ \bottomrule \end{tabular*} \begin{minipage}{\linewidth} @@ -511,7 +511,7 @@ gt_to_latex(output) {\fontsize{14}{17}\selectfont WLR approximation of \textasciitilde{}wHR at bound\fontsize{12}{15}\selectfont } } \fontsize{12.0pt}{14.0pt}\selectfont -\begin{tabular*}{\linewidth}{@{\extracolsep{\fill}}lrrrrr} +\begin{tabular*}{\linewidth}{@{\extracolsep{\fill}}crrrrr} \toprule & & & & \multicolumn{2}{c}{{Cumulative probability to cross boundaries}} \\ \cmidrule(lr){5-6} @@ -519,18 +519,18 @@ Bound & Z & Nominal p\textsuperscript{\textit{1}} & \textasciitilde{}wHR at boun \midrule\addlinespace[2.5pt] \multicolumn{6}{l}{Analysis: 1 Time: 14.9 N: 108 Events: 30 AHR: 0.79 Information fraction: 0.6\textsuperscript{\textit{3}}} \\[2.5pt] \midrule\addlinespace[2.5pt] -Futility & -1.17 & 0.8798 & 1.5353 & 0.0341 & 0.1202 \\ Efficacy & 2.68 & 0.0037 & 0.3765 & 0.0217 & 0.0037 \\ +Futility & -1.17 & 0.8798 & 1.5353 & 0.0341 & 0.1202 \\ \midrule\addlinespace[2.5pt] \multicolumn{6}{l}{Analysis: 2 Time: 19.2 N: 108 Events: 40 AHR: 0.75 Information fraction: 0.8\textsuperscript{\textit{3}}} \\[2.5pt] \midrule\addlinespace[2.5pt] -Futility & -0.66 & 0.7452 & 1.2319 & 0.0664 & 0.2664 \\ Efficacy & 2.29 & 0.0110 & 0.4846 & 0.0886 & 0.0121 \\ +Futility & -0.66 & 0.7452 & 1.2319 & 0.0664 & 0.2664 \\ \midrule\addlinespace[2.5pt] \multicolumn{6}{l}{Analysis: 3 Time: 24.5 N: 108 Events: 50 AHR: 0.71 Information fraction: 1\textsuperscript{\textit{3}}} \\[2.5pt] \midrule\addlinespace[2.5pt] -Futility & -0.22 & 0.5881 & 1.0650 & 0.1002 & 0.4319 \\ Efficacy & 2.03 & 0.0212 & 0.5631 & 0.2071 & 0.0250 \\ +Futility & -0.22 & 0.5881 & 1.0650 & 0.1002 & 0.4319 \\ \bottomrule \end{tabular*} \begin{minipage}{\linewidth} @@ -569,7 +569,7 @@ gt_to_latex(output) {\fontsize{14}{17}\selectfont WLR approximation of \textasciitilde{}wHR at bound\fontsize{12}{15}\selectfont } } \fontsize{12.0pt}{14.0pt}\selectfont -\begin{tabular*}{\linewidth}{@{\extracolsep{\fill}}lrrrrr} +\begin{tabular*}{\linewidth}{@{\extracolsep{\fill}}crrrrr} \toprule & & & & \multicolumn{2}{c}{{Cumulative boundary crossing probability}}\textsuperscript{\textit{2}} \\ \cmidrule(lr){5-6} @@ -577,18 +577,18 @@ Bound & Z & Nominal p & \textasciitilde{}wHR at bound\textsuperscript{\textit{3} \midrule\addlinespace[2.5pt] \multicolumn{6}{l}{Analysis: 1 Time: 14.9 N: 108 Events: 30 AHR: 0.79 Information fraction: 0.6\textsuperscript{\textit{4}}} \\[2.5pt] \midrule\addlinespace[2.5pt] -Futility & -1.17 & 0.8798 & 1.5353 & 0.0341 & 0.1202 \\ Efficacy & 2.68 & 0.0037 & 0.3765 & 0.0217 & 0.0037 \\ +Futility & -1.17 & 0.8798 & 1.5353 & 0.0341 & 0.1202 \\ \midrule\addlinespace[2.5pt] \multicolumn{6}{l}{Analysis: 2 Time: 19.2 N: 108 Events: 40 AHR: 0.75 Information fraction: 0.8\textsuperscript{\textit{4}}} \\[2.5pt] \midrule\addlinespace[2.5pt] -Futility & -0.66 & 0.7452 & 1.2319 & 0.0664 & 0.2664 \\ Efficacy & 2.29 & 0.0110 & 0.4846 & 0.0886 & 0.0121 \\ +Futility & -0.66 & 0.7452 & 1.2319 & 0.0664 & 0.2664 \\ \midrule\addlinespace[2.5pt] \multicolumn{6}{l}{Analysis: 3 Time: 24.5 N: 108 Events: 50 AHR: 0.71 Information fraction: 1\textsuperscript{\textit{4}}} \\[2.5pt] \midrule\addlinespace[2.5pt] -Futility & -0.22 & 0.5881 & 1.0650 & 0.1002 & 0.4319 \\ Efficacy & 2.03 & 0.0212 & 0.5631 & 0.2071 & 0.0250 \\ +Futility & -0.22 & 0.5881 & 1.0650 & 0.1002 & 0.4319 \\ \bottomrule \end{tabular*} \begin{minipage}{\linewidth} @@ -617,7 +617,7 @@ gt_to_latex(output) {\fontsize{14}{17}\selectfont WLR approximation of \textasciitilde{}wHR at bound\fontsize{12}{15}\selectfont } } \fontsize{12.0pt}{14.0pt}\selectfont -\begin{tabular*}{\linewidth}{@{\extracolsep{\fill}}lrrrrr} +\begin{tabular*}{\linewidth}{@{\extracolsep{\fill}}crrrrr} \toprule & & & & \multicolumn{2}{c}{{Cumulative boundary crossing probability}} \\ \cmidrule(lr){5-6} @@ -661,7 +661,7 @@ gt_to_latex(output) {\fontsize{14}{17}\selectfont WLR approximation of \textasciitilde{}wHR at bound\fontsize{12}{15}\selectfont } } \fontsize{12.0pt}{14.0pt}\selectfont -\begin{tabular*}{\linewidth}{@{\extracolsep{\fill}}lrrrr} +\begin{tabular*}{\linewidth}{@{\extracolsep{\fill}}crrrr} \toprule & & & \multicolumn{2}{c}{{Cumulative boundary crossing probability}} \\ \cmidrule(lr){4-5} @@ -669,18 +669,18 @@ Bound & Nominal p\textsuperscript{\textit{1}} & Z & Alternate hypothesis & Null \midrule\addlinespace[2.5pt] \multicolumn{5}{l}{Analysis: 1 Time: 14.9 N: 108 Events: 30 AHR: 0.79 Information fraction: 0.6\textsuperscript{\textit{2}}} \\[2.5pt] \midrule\addlinespace[2.5pt] -Futility & 0.8798 & -1.17 & 0.0341 & 0.1202 \\ Efficacy & 0.0037 & 2.68 & 0.0217 & 0.0037 \\ +Futility & 0.8798 & -1.17 & 0.0341 & 0.1202 \\ \midrule\addlinespace[2.5pt] \multicolumn{5}{l}{Analysis: 2 Time: 19.2 N: 108 Events: 40 AHR: 0.75 Information fraction: 0.8\textsuperscript{\textit{2}}} \\[2.5pt] \midrule\addlinespace[2.5pt] -Futility & 0.7452 & -0.66 & 0.0664 & 0.2664 \\ Efficacy & 0.0110 & 2.29 & 0.0886 & 0.0121 \\ +Futility & 0.7452 & -0.66 & 0.0664 & 0.2664 \\ \midrule\addlinespace[2.5pt] \multicolumn{5}{l}{Analysis: 3 Time: 24.5 N: 108 Events: 50 AHR: 0.71 Information fraction: 1\textsuperscript{\textit{2}}} \\[2.5pt] \midrule\addlinespace[2.5pt] -Futility & 0.5881 & -0.22 & 0.1002 & 0.4319 \\ Efficacy & 0.0212 & 2.03 & 0.2071 & 0.0250 \\ +Futility & 0.5881 & -0.22 & 0.1002 & 0.4319 \\ \bottomrule \end{tabular*} \begin{minipage}{\linewidth} diff --git a/tests/testit/test-independent-as_rtf.R b/tests/testit/test-independent-as_rtf.R index fe4c5815a..4521c27ce 100644 --- a/tests/testit/test-independent-as_rtf.R +++ b/tests/testit/test-independent-as_rtf.R @@ -1,5 +1,4 @@ # Test as_rtf() snapshot -source("tests/testit/helper-support-as_rtf.R") assert("gs_power_wlr_example() produces expected as_rtf output", { path <- tempfile(fileext = ".rtf") diff --git a/tests/testit/test-independent-as_rtf.md b/tests/testit/test-independent-as_rtf.md index 3cda25efa..c14df378e 100644 --- a/tests/testit/test-independent-as_rtf.md +++ b/tests/testit/test-independent-as_rtf.md @@ -704,20 +704,6 @@ cat(readLines(path), sep = "\n") \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx6000 \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx7500 \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx9000 -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 Futility}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 -1.17}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.8798}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 1.535}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.0341}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.1202}\cell -\intbl\row\pard -\trowd\trgaph108\trleft0\trqc -\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx1500 -\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx3000 -\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx4500 -\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx6000 -\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx7500 -\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx9000 \pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 Efficacy}\cell \pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 2.68}\cell \pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.0037}\cell @@ -726,10 +712,6 @@ cat(readLines(path), sep = "\n") \pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.0037}\cell \intbl\row\pard \trowd\trgaph108\trleft0\trqc -\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx9000 -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 Analysis: 2 Time: 19.2 N: 108 Events: 40 AHR: 0.75 Information fraction: 0.8 {\super c}}\cell -\intbl\row\pard -\trowd\trgaph108\trleft0\trqc \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx1500 \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx3000 \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx4500 @@ -737,11 +719,15 @@ cat(readLines(path), sep = "\n") \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx7500 \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx9000 \pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 Futility}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 -0.66}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.7452}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 1.232}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.0664}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.2664}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 -1.17}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.8798}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 1.535}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.0341}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.1202}\cell +\intbl\row\pard +\trowd\trgaph108\trleft0\trqc +\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx9000 +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 Analysis: 2 Time: 19.2 N: 108 Events: 40 AHR: 0.75 Information fraction: 0.8 {\super c}}\cell \intbl\row\pard \trowd\trgaph108\trleft0\trqc \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx1500 @@ -758,10 +744,6 @@ cat(readLines(path), sep = "\n") \pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.0121}\cell \intbl\row\pard \trowd\trgaph108\trleft0\trqc -\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx9000 -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 Analysis: 3 Time: 24.5 N: 108 Events: 50 AHR: 0.71 Information fraction: 1 {\super c}}\cell -\intbl\row\pard -\trowd\trgaph108\trleft0\trqc \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx1500 \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx3000 \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx4500 @@ -769,11 +751,15 @@ cat(readLines(path), sep = "\n") \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx7500 \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx9000 \pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 Futility}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 -0.22}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.5881}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 1.065}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.1002}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.4319}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 -0.66}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.7452}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 1.232}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.0664}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.2664}\cell +\intbl\row\pard +\trowd\trgaph108\trleft0\trqc +\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx9000 +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 Analysis: 3 Time: 24.5 N: 108 Events: 50 AHR: 0.71 Information fraction: 1 {\super c}}\cell \intbl\row\pard \trowd\trgaph108\trleft0\trqc \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx1500 @@ -790,6 +776,20 @@ cat(readLines(path), sep = "\n") \pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.025}\cell \intbl\row\pard \trowd\trgaph108\trleft0\trqc +\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx1500 +\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx3000 +\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx4500 +\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx6000 +\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx7500 +\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx9000 +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 Futility}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 -0.22}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.5881}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 1.065}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.1002}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.4319}\cell +\intbl\row\pard +\trowd\trgaph108\trleft0\trqc \clbrdrl\brdrs\brdrw15\clbrdrt\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrdb\brdrw15\clvertalt\cellx9000 \pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 {\super a} One-sided p-value for experimental vs control treatment. Value < 0.5 favors experimental, > 0.5 favors control.\line{\super b} Approximate hazard ratio to cross bound.\line{\super c} wAHR is the weighted AHR.}\cell \intbl\row\pard @@ -866,20 +866,6 @@ cat(readLines(path), sep = "\n") \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx6000 \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx7500 \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx9000 -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 Futility}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 -1.17}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.8798}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 1.535}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.0341}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.1202}\cell -\intbl\row\pard -\trowd\trgaph108\trleft0\trqc -\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx1500 -\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx3000 -\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx4500 -\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx6000 -\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx7500 -\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx9000 \pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 Efficacy}\cell \pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 2.68}\cell \pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.0037}\cell @@ -888,10 +874,6 @@ cat(readLines(path), sep = "\n") \pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.0037}\cell \intbl\row\pard \trowd\trgaph108\trleft0\trqc -\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx9000 -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 Analysis: 2 Time: 19.2 N: 108 Events: 40 AHR: 0.75 Information fraction: 0.8 {\super c}}\cell -\intbl\row\pard -\trowd\trgaph108\trleft0\trqc \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx1500 \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx3000 \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx4500 @@ -899,11 +881,15 @@ cat(readLines(path), sep = "\n") \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx7500 \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx9000 \pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 Futility}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 -0.66}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.7452}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 1.232}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.0664}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.2664}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 -1.17}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.8798}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 1.535}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.0341}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.1202}\cell +\intbl\row\pard +\trowd\trgaph108\trleft0\trqc +\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx9000 +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 Analysis: 2 Time: 19.2 N: 108 Events: 40 AHR: 0.75 Information fraction: 0.8 {\super c}}\cell \intbl\row\pard \trowd\trgaph108\trleft0\trqc \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx1500 @@ -920,10 +906,6 @@ cat(readLines(path), sep = "\n") \pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.0121}\cell \intbl\row\pard \trowd\trgaph108\trleft0\trqc -\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx9000 -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 Analysis: 3 Time: 24.5 N: 108 Events: 50 AHR: 0.71 Information fraction: 1 {\super c}}\cell -\intbl\row\pard -\trowd\trgaph108\trleft0\trqc \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx1500 \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx3000 \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx4500 @@ -931,11 +913,15 @@ cat(readLines(path), sep = "\n") \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx7500 \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx9000 \pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 Futility}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 -0.22}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.5881}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 1.065}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.1002}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.4319}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 -0.66}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.7452}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 1.232}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.0664}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.2664}\cell +\intbl\row\pard +\trowd\trgaph108\trleft0\trqc +\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx9000 +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 Analysis: 3 Time: 24.5 N: 108 Events: 50 AHR: 0.71 Information fraction: 1 {\super c}}\cell \intbl\row\pard \trowd\trgaph108\trleft0\trqc \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx1500 @@ -952,6 +938,20 @@ cat(readLines(path), sep = "\n") \pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.025}\cell \intbl\row\pard \trowd\trgaph108\trleft0\trqc +\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx1500 +\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx3000 +\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx4500 +\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx6000 +\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx7500 +\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx9000 +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 Futility}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 -0.22}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.5881}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 1.065}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.1002}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.4319}\cell +\intbl\row\pard +\trowd\trgaph108\trleft0\trqc \clbrdrl\brdrs\brdrw15\clbrdrt\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrdb\brdrw15\clvertalt\cellx9000 \pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 {\super a} One-sided p-value for experimental vs control treatment. Value < 0.5 favors experimental, > 0.5 favors control.\line{\super b} Approximate hazard ratio to cross bound.\line{\super c} wAHR is the weighted AHR.}\cell \intbl\row\pard @@ -1036,20 +1036,6 @@ cat(readLines(path), sep = "\n") \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx6000 \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx7500 \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx9000 -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 Futility}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 -1.17}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.8798}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 1.535}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.0341}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.1202}\cell -\intbl\row\pard -\trowd\trgaph108\trleft0\trqc -\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx1500 -\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx3000 -\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx4500 -\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx6000 -\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx7500 -\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx9000 \pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 Efficacy}\cell \pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 2.68}\cell \pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.0037}\cell @@ -1058,10 +1044,6 @@ cat(readLines(path), sep = "\n") \pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.0037}\cell \intbl\row\pard \trowd\trgaph108\trleft0\trqc -\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx9000 -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 Analysis: 2 Time: 19.2 N: 108 Events: 40 AHR: 0.75 Information fraction: 0.8 {\super b}}\cell -\intbl\row\pard -\trowd\trgaph108\trleft0\trqc \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx1500 \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx3000 \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx4500 @@ -1069,11 +1051,15 @@ cat(readLines(path), sep = "\n") \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx7500 \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx9000 \pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 Futility}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 -0.66}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.7452}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 1.232}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.0664}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.2664}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 -1.17}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.8798}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 1.535}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.0341}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.1202}\cell +\intbl\row\pard +\trowd\trgaph108\trleft0\trqc +\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx9000 +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 Analysis: 2 Time: 19.2 N: 108 Events: 40 AHR: 0.75 Information fraction: 0.8 {\super b}}\cell \intbl\row\pard \trowd\trgaph108\trleft0\trqc \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx1500 @@ -1090,10 +1076,6 @@ cat(readLines(path), sep = "\n") \pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.0121}\cell \intbl\row\pard \trowd\trgaph108\trleft0\trqc -\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx9000 -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 Analysis: 3 Time: 24.5 N: 108 Events: 50 AHR: 0.71 Information fraction: 1 {\super b}}\cell -\intbl\row\pard -\trowd\trgaph108\trleft0\trqc \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx1500 \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx3000 \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx4500 @@ -1101,11 +1083,15 @@ cat(readLines(path), sep = "\n") \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx7500 \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx9000 \pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 Futility}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 -0.22}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.5881}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 1.065}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.1002}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.4319}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 -0.66}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.7452}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 1.232}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.0664}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.2664}\cell +\intbl\row\pard +\trowd\trgaph108\trleft0\trqc +\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx9000 +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 Analysis: 3 Time: 24.5 N: 108 Events: 50 AHR: 0.71 Information fraction: 1 {\super b}}\cell \intbl\row\pard \trowd\trgaph108\trleft0\trqc \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx1500 @@ -1122,6 +1108,20 @@ cat(readLines(path), sep = "\n") \pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.025}\cell \intbl\row\pard \trowd\trgaph108\trleft0\trqc +\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx1500 +\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx3000 +\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx4500 +\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx6000 +\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx7500 +\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx9000 +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 Futility}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 -0.22}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.5881}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 1.065}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.1002}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.4319}\cell +\intbl\row\pard +\trowd\trgaph108\trleft0\trqc \clbrdrl\brdrs\brdrw15\clbrdrt\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrdb\brdrw15\clvertalt\cellx9000 \pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 {\super a} approximate weighted hazard ratio to cross bound.\line{\super b} wAHR is the weighted AHR.\line{\super c} the crossing probability.\line{\super d} this table is generated by gs_power_wlr.}\cell \intbl\row\pard @@ -1194,18 +1194,6 @@ cat(readLines(path), sep = "\n") \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx5400 \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx7200 \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx9000 -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 Futility}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.8798}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 -1.17}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.0341}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.1202}\cell -\intbl\row\pard -\trowd\trgaph108\trleft0\trqc -\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx1800 -\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx3600 -\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx5400 -\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx7200 -\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx9000 \pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 Efficacy}\cell \pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.0037}\cell \pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 2.68}\cell @@ -1213,20 +1201,20 @@ cat(readLines(path), sep = "\n") \pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.0037}\cell \intbl\row\pard \trowd\trgaph108\trleft0\trqc -\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx9000 -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 Analysis: 2 Time: 19.2 N: 108 Events: 40 AHR: 0.75 Information fraction: 0.8 {\super b}}\cell -\intbl\row\pard -\trowd\trgaph108\trleft0\trqc \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx1800 \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx3600 \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx5400 \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx7200 \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx9000 \pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 Futility}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.7452}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 -0.66}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.0664}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.2664}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.8798}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 -1.17}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.0341}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.1202}\cell +\intbl\row\pard +\trowd\trgaph108\trleft0\trqc +\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx9000 +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 Analysis: 2 Time: 19.2 N: 108 Events: 40 AHR: 0.75 Information fraction: 0.8 {\super b}}\cell \intbl\row\pard \trowd\trgaph108\trleft0\trqc \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx1800 @@ -1241,20 +1229,20 @@ cat(readLines(path), sep = "\n") \pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.0121}\cell \intbl\row\pard \trowd\trgaph108\trleft0\trqc -\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx9000 -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 Analysis: 3 Time: 24.5 N: 108 Events: 50 AHR: 0.71 Information fraction: 1 {\super b}}\cell -\intbl\row\pard -\trowd\trgaph108\trleft0\trqc \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx1800 \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx3600 \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx5400 \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx7200 \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx9000 \pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 Futility}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.5881}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 -0.22}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.1002}\cell -\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.4319}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.7452}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 -0.66}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.0664}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.2664}\cell +\intbl\row\pard +\trowd\trgaph108\trleft0\trqc +\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx9000 +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 Analysis: 3 Time: 24.5 N: 108 Events: 50 AHR: 0.71 Information fraction: 1 {\super b}}\cell \intbl\row\pard \trowd\trgaph108\trleft0\trqc \clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx1800 @@ -1269,6 +1257,18 @@ cat(readLines(path), sep = "\n") \pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.025}\cell \intbl\row\pard \trowd\trgaph108\trleft0\trqc +\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx1800 +\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx3600 +\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx5400 +\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx7200 +\clbrdrl\brdrs\brdrw15\clbrdrt\brdrs\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrs\brdrw15\clvertalt\cellx9000 +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 Futility}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.5881}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 -0.22}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.1002}\cell +\pard\hyphpar0\sb15\sa15\fi0\li0\ri0\qc\fs18{\f0 0.4319}\cell +\intbl\row\pard +\trowd\trgaph108\trleft0\trqc \clbrdrl\brdrs\brdrw15\clbrdrt\brdrw15\clbrdrr\brdrs\brdrw15\clbrdrb\brdrdb\brdrw15\clvertalt\cellx9000 \pard\hyphpar0\sb15\sa15\fi0\li0\ri0\ql\fs18{\f0 {\super a} One-sided p-value for experimental vs control treatment. Value < 0.5 favors experimental, > 0.5 favors control.\line{\super b} wAHR is the weighted AHR.}\cell \intbl\row\pard From 4e081f7ce17919475733711d8a8abc8df55c4372 Mon Sep 17 00:00:00 2001 From: Yihui Xie Date: Thu, 11 Jun 2026 15:52:52 -0400 Subject: [PATCH 10/12] Roxygenize, remove redundant test file, fix all() usage - Run roxygenize to sync Rd files with code (fixes WARNING) - Remove test-independent-as_rtf.R (the .md snapshot is sufficient) - Replace all() with bare logical vector in test assertions Co-Authored-By: Claude Opus 4.6 --- DESCRIPTION | 2 +- man/as_gt.Rd | 21 +++++++-- man/gsDesign2-package.Rd | 1 + man/gs_bound_summary.Rd | 2 +- man/gs_design_ahr.Rd | 35 ++++++++++++++ man/gs_power_ahr.Rd | 36 +++++++++++++++ man/gs_power_design_npe.Rd | 54 ++++++++++++++++++++-- man/summary.Rd | 6 ++- tests/testit/test-developer-gs_power_npe.R | 4 +- tests/testit/test-independent-as_rtf.R | 17 ------- 10 files changed, 148 insertions(+), 30 deletions(-) delete mode 100644 tests/testit/test-independent-as_rtf.R diff --git a/DESCRIPTION b/DESCRIPTION index 0f18853b1..70e0b3c17 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -68,4 +68,4 @@ VignetteBuilder: LinkingTo: Rcpp Roxygen: list(markdown = TRUE) -RoxygenNote: 7.3.3 +Config/roxygen2/version: 8.0.0 diff --git a/man/as_gt.Rd b/man/as_gt.Rd index 4912d73de..4c53c9bbf 100644 --- a/man/as_gt.Rd +++ b/man/as_gt.Rd @@ -17,7 +17,7 @@ as_gt(x, ...) colname_spanner = "Cumulative boundary crossing probability", colname_spannersub = c("Alternate hypothesis", "Null hypothesis"), footnote = NULL, - display_bound = c("Efficacy", "Futility"), + display_bound = c("Efficacy", "Futility", "Harm"), display_columns = NULL, display_inf_bound = FALSE, ... @@ -45,7 +45,7 @@ the table. To disable footnotes, use \code{footnote = FALSE}.} of the gt table.} \item{display_bound}{A vector of strings specifying the label of the bounds. -The default is \code{c("Efficacy", "Futility")}.} +The default is \code{c("Efficacy", "Futility", "Harm")}.} \item{display_columns}{A vector of strings specifying the variables to be displayed in the summary table.} @@ -114,6 +114,21 @@ gs_design_ahr() |> summary() |> as_gt() +gs_design_ahr( + analysis_time = c(12, 24, 36), + upper = gs_spending_bound, + upar = list(sf = gsDesign::sfLDOF, total_spend = 0.025), + test_upper = c(FALSE, TRUE, TRUE), + lower = gs_spending_bound, + lpar = list(sf = gsDesign::sfHSD, total_spend = 0.1, param = -2), + test_lower = c(TRUE, TRUE, FALSE), + harm = gs_spending_bound, + hpar = list(sf = gsDesign::sfHSD, total_spend = 0.1, param = -4), + test_harm = c(TRUE, TRUE, FALSE) + ) |> + summary() |> + as_gt() + gs_power_ahr(lpar = list(sf = gsDesign::sfLDOF, total_spend = 0.1)) |> summary() |> as_gt() @@ -178,7 +193,7 @@ gs_power_wlr(lpar = list(sf = gsDesign::sfLDOF, total_spend = 0.1)) |> # Example 5 ---- # Usage of display_bound = ... -# to either show efficacy bound or futility bound, or both(default) +# to show selected bounds gs_power_wlr(lpar = list(sf = gsDesign::sfLDOF, total_spend = 0.1)) |> summary() |> as_gt(display_bound = "Efficacy") diff --git a/man/gsDesign2-package.Rd b/man/gsDesign2-package.Rd index c31fdc74c..e5a286eeb 100644 --- a/man/gsDesign2-package.Rd +++ b/man/gsDesign2-package.Rd @@ -24,6 +24,7 @@ Useful links: Authors: \itemize{ + \item Yujie Zhao \email{yujie.zhao@merck.com} \item Keaven Anderson \email{keaven_anderson@merck.com} \item Yilong Zhang \email{elong0527@gmail.com} \item John Blischak \email{jdblischak@gmail.com} diff --git a/man/gs_bound_summary.Rd b/man/gs_bound_summary.Rd index e4fe3fc8f..91ff48ad3 100644 --- a/man/gs_bound_summary.Rd +++ b/man/gs_bound_summary.Rd @@ -33,7 +33,7 @@ estimated timing of each analysis.} A data frame } \description{ -Summarizes the efficacy and futility bounds for each analysis. +Summarizes the efficacy, futility, and harm bounds for each analysis. } \examples{ library(gsDesign2) diff --git a/man/gs_design_ahr.Rd b/man/gs_design_ahr.Rd index de4fc18c4..a635cef7d 100644 --- a/man/gs_design_ahr.Rd +++ b/man/gs_design_ahr.Rd @@ -18,9 +18,12 @@ gs_design_ahr( upar = list(sf = gsDesign::sfLDOF, total_spend = alpha), lower = gs_spending_bound, lpar = list(sf = gsDesign::sfLDOF, total_spend = beta), + harm = gs_b, + hpar = -Inf, h1_spending = TRUE, test_upper = TRUE, test_lower = TRUE, + test_harm = FALSE, info_scale = c("h0_h1_info", "h0_info", "h1_info"), r = 18, tol = 1e-06, @@ -70,6 +73,10 @@ See \href{https://merck.github.io/gsDesign2/articles/story-seven-test-types.html \item{lpar}{Parameters passed to \code{lower}, which can be set up similarly as \code{upar.}} +\item{harm}{Function to compute harm bound, which can be set up similarly as \code{lower}.} + +\item{hpar}{Parameters passed to \code{harm}, which can be set up similarly as \code{lpar.}} + \item{h1_spending}{Indicator that lower bound to be set by spending under alternate hypothesis (input \code{fail_rate}) if spending is used for lower bound. @@ -89,6 +96,12 @@ single value of \code{FALSE} indicated no lower bound; otherwise, a logical vector of the same length as \code{info} should indicate which analyses will have a lower bound.} +\item{test_harm}{Indicator of which analyses should include a harm bound; +single value of \code{TRUE} (default) indicates all analyses; +single value of \code{FALSE} indicates no harm bound; otherwise, +a logical vector of the same length as \code{info} should +indicate which analyses will have a harm bound.} + \item{info_scale}{Information scale for calculation. Options are: \itemize{ \item \code{"h0_h1_info"} (default): variance under both null and alternative hypotheses is used. @@ -176,6 +189,12 @@ gs_design_ahr() # Example 2 ---- # Single analysis gs_design_ahr(analysis_time = 40) +gs_design_ahr( + analysis_time = 40, + upper = gs_b, upar = -qnorm(0.025), test_upper = TRUE, + lower = gs_b, lpar = -1, test_lower = TRUE, + harm = gs_b, hpar = -2, test_harm = TRUE +) # Example 3 ---- # Multiple analysis_time @@ -246,4 +265,20 @@ gs_design_ahr( lpar = rep(-Inf, 3) ) } + +# Example 8 ---- +# Design with an additional harm bound +\donttest{ +gs_design_ahr( + analysis_time = c(12, 24, 36), + upper = gs_spending_bound, + upar = list(sf = gsDesign::sfLDOF, total_spend = 0.025, param = NULL, timing = NULL), + lower = gs_spending_bound, + lpar = list(sf = gsDesign::sfHSD, total_spend = 0.1, param = -2, timing = NULL), + test_lower = c(TRUE, TRUE, FALSE), + harm = gs_spending_bound, + hpar = list(sf = gsDesign::sfHSD, total_spend = 0.1, param = -4, timing = NULL), + test_harm = c(TRUE, TRUE, FALSE) +) +} } diff --git a/man/gs_power_ahr.Rd b/man/gs_power_ahr.Rd index eed8978a8..63ff555c0 100644 --- a/man/gs_power_ahr.Rd +++ b/man/gs_power_ahr.Rd @@ -15,8 +15,11 @@ gs_power_ahr( upar = list(sf = gsDesign::sfLDOF, total_spend = 0.025), lower = gs_spending_bound, lpar = list(sf = gsDesign::sfLDOF, total_spend = NULL), + harm = gs_b, + hpar = -Inf, test_lower = TRUE, test_upper = TRUE, + test_harm = FALSE, ratio = 1, binding = FALSE, h1_spending = TRUE, @@ -61,6 +64,10 @@ See \href{https://merck.github.io/gsDesign2/articles/story-seven-test-types.html \item{lpar}{Parameters passed to \code{lower}, which can be set up similarly as \code{upar.}} +\item{harm}{Function to compute harm bound, which can be set up similarly as \code{lower}.} + +\item{hpar}{Parameters passed to \code{harm}, which can be set up similarly as \code{lpar.}} + \item{test_lower}{Indicator of which analyses should include a lower bound; single value of \code{TRUE} (default) indicates all analyses; single value of \code{FALSE} indicated no lower bound; otherwise, @@ -73,6 +80,12 @@ single value of \code{TRUE} (default) indicates all analyses; otherwise, a logical vector of the same length as \code{info} should indicate which analyses will have an efficacy bound.} +\item{test_harm}{Indicator of which analyses should include a harm bound; +single value of \code{TRUE} (default) indicates all analyses; +single value of \code{FALSE} indicates no harm bound; otherwise, +a logical vector of the same length as \code{info} should +indicate which analyses will have a harm bound.} + \item{ratio}{Experimental:Control randomization ratio.} \item{binding}{Indicator of whether futility bound is binding; @@ -218,4 +231,27 @@ gs_power_ahr( lpar = list(sf = gsDesign::sfLDOF, total_spend = 0.025) ) } +Example 5 ---- +# 2-sided asymmetric O'Brien-Fleming spending bound with harm bound +# driven by both `event` and `analysis_time`, i.e., +# both `event` and `analysis_time` are not `NULL`, +# then the analysis will driven by the maximal one, i.e., +# Time = max(analysis_time, calculated Time for targeted event) +# Events = max(events, calculated events for targeted analysis_time) +\donttest{ +gs_power_ahr( + analysis_time = c(12, 24, 36), + event = c(30, 40, 50), h1_spending = FALSE, + binding = TRUE, + upper = gs_spending_bound, + upar = list(sf = gsDesign::sfLDOF, total_spend = 0.025), + test_upper = c(TRUE, TRUE, TRUE), + lower = gs_spending_bound, + lpar = list(sf = gsDesign::sfHSD, total_spend = 0.1, param = -2), + test_lower = c(TRUE, TRUE, TRUE), + harm = gs_spending_bound, + hpar = list(sf = gsDesign::sfHSD, total_spend = 0.1, param = -4), + test_harm = c(TRUE, TRUE, TRUE) +) +} } diff --git a/man/gs_power_design_npe.Rd b/man/gs_power_design_npe.Rd index f80bb25f3..733a6ff33 100644 --- a/man/gs_power_design_npe.Rd +++ b/man/gs_power_design_npe.Rd @@ -23,6 +23,9 @@ gs_design_npe( test_upper = TRUE, test_lower = TRUE, binding = FALSE, + harm = gs_b, + hpar = -Inf, + test_harm = FALSE, r = 18, tol = 1e-06 ) @@ -42,6 +45,9 @@ gs_power_npe( test_upper = TRUE, test_lower = TRUE, binding = FALSE, + harm = gs_b, + hpar = -Inf, + test_harm = FALSE, r = 18, tol = 1e-06 ) @@ -126,6 +132,16 @@ indicate which analyses will have a lower bound.} \item{binding}{Indicator of whether futility bound is binding; default of \code{FALSE} is recommended.} +\item{harm}{Function to compute harm bound, which can be set up similarly as \code{lower}.} + +\item{hpar}{Parameters passed to \code{harm}, which can be set up similarly as \code{lpar.}} + +\item{test_harm}{Indicator of which analyses should include a harm bound; +single value of \code{TRUE} (default) indicates all analyses; +single value of \code{FALSE} indicates no harm bound; otherwise, +a logical vector of the same length as \code{info} should +indicate which analyses will have a harm bound.} + \item{r}{Integer value controlling grid for numerical integration as in Jennison and Turnbull (2000); default is 18, range is 1 to 80. Larger values provide larger number of grid points and greater accuracy. @@ -137,7 +153,7 @@ Normally, \code{r} will not be changed by the user.} A tibble with columns of \itemize{ \item \code{analysis}: analysis index. -\item \code{bound}: either of value \code{"upper"} or \code{"lower"}, indicating the upper and lower bound. +\item \code{bound}: one of value \code{"upper"}, \code{"lower"}, or \code{"harm"}, indicating the upper, lower, and harm bound. \item \code{z}: the Z-score bounds. \item \code{probability}: cumulative probability of crossing the bound at or before the analysis. \item \code{theta}: same as the input. @@ -175,7 +191,7 @@ The only differences in arguments between the two functions are the \code{alpha} parameters used in the \code{gs_design_npe()}. } \details{ -The bound specifications (\code{upper}, \code{lower}, \code{upar}, \code{lpar}) of \code{gs_design_npe()} +The bound specifications (\code{upper}, \code{lower}, \code{harm}, \code{upar}, \code{lpar}, \code{hpar}) of \code{gs_design_npe()} will be used to ensure Type I error and other boundary properties are as specified. See the help file of \code{gs_spending_bound()} for details on spending function. } @@ -316,7 +332,7 @@ gs_design_npe( test_upper = c(FALSE, TRUE, TRUE) ) -# Example 4 ---- +# Example 4a ---- # gs_design_npe with spending function bounds # 2-sided asymmetric bounds # Lower spending based on non-zero effect @@ -330,6 +346,21 @@ gs_design_npe( lpar = list(sf = gsDesign::sfHSD, total_spend = 0.1, param = -1, timing = NULL) ) +# Example 4b ---- +# gs_design_npe with an additional harm bound under the null hypothesis +gs_design_npe( + theta = c(.1, .2, .3), + info = (1:3) * 40, + info0 = (1:3) * 30, + upper = gs_spending_bound, + upar = list(sf = gsDesign::sfLDOF, total_spend = 0.025, param = NULL, timing = NULL), + lower = gs_spending_bound, + lpar = list(sf = gsDesign::sfHSD, total_spend = 0.1, param = -2, timing = NULL), + harm = gs_spending_bound, + hpar = list(sf = gsDesign::sfHSD, total_spend = 0.1, param = -4, timing = NULL), + test_harm = c(TRUE, FALSE, TRUE) +) + # Example 5 ---- # gs_design_npe with two-sided symmetric spend, O'Brien-Fleming spending # Typically, 2-sided bounds are binding @@ -391,7 +422,7 @@ gs_power_npe( lpar = c(qnorm(.1), -Inf, -Inf) ) -# Example 9 ---- +# Example 9a ---- # gs_power_npe with spending function bounds # Lower spending based on non-zero effect gs_power_npe( @@ -413,6 +444,21 @@ gs_power_npe( lpar = list(sf = gsDesign::sfHSD, total_spend = 0.1, param = -1, timing = NULL) ) +# Example 9b ---- +# gs_power_npe with an additional harm bound under the null hypothesis +gs_power_npe( + theta = c(.1, .2, .3), + info = (1:3) * 40, + upper = gs_spending_bound, + upar = list(sf = gsDesign::sfLDOF, total_spend = 0.025, param = NULL, timing = NULL), + lower = gs_spending_bound, + lpar = list(sf = gsDesign::sfHSD, total_spend = 0.1, param = -2, timing = NULL), + test_lower = c(TRUE, TRUE, TRUE), + harm = gs_spending_bound, + hpar = list(sf = gsDesign::sfHSD, total_spend = 0.1, param = -4, timing = NULL), + test_harm = c(TRUE, TRUE, TRUE) +) + # Example 10 ---- # gs_power_npe with two-sided symmetric spend, O'Brien-Fleming spending # Typically, 2-sided bounds are binding diff --git a/man/summary.Rd b/man/summary.Rd index 4cdb08d84..fda3cc608 100644 --- a/man/summary.Rd +++ b/man/summary.Rd @@ -13,7 +13,7 @@ analysis_decimals = NULL, col_vars = NULL, col_decimals = NULL, - bound_names = c("Efficacy", "Futility"), + bound_names = c("Efficacy", "Futility", "Harm"), display_spending_time = FALSE, ... ) @@ -37,7 +37,9 @@ If the vector is unnamed, it must match the length of \code{col_vars}. If the vector is named, you only have to specify the number of digits for the columns you want to be displayed differently than the defaults.} -\item{bound_names}{Names for bounds; default is \code{c("Efficacy", "Futility")}.} +\item{bound_names}{Names for bounds; default is \code{c("Efficacy", "Futility", "Harm")}. +The first two values label upper and lower bounds. If a third value is provided, +it labels harm bounds; otherwise harm bounds are labeled \code{"Harm"}.} \item{display_spending_time}{A logical value (TRUE/FALSE) indicating if the spending time is summarized in the table.} diff --git a/tests/testit/test-developer-gs_power_npe.R b/tests/testit/test-developer-gs_power_npe.R index e48cd3863..b137e3f6a 100644 --- a/tests/testit/test-developer-gs_power_npe.R +++ b/tests/testit/test-developer-gs_power_npe.R @@ -336,7 +336,7 @@ assert("Harm bound - Cap harm bound at futility bound", { ) harm_bound <- x |> dplyr::filter(bound == "harm") |> dplyr::pull(z) futility_bound <- x |> dplyr::filter(bound == "lower") |> dplyr::pull(z) - (all(harm_bound <= futility_bound)) + (harm_bound <= futility_bound) x <- gs_power_npe( theta = c(.1, .2, .3), @@ -352,5 +352,5 @@ assert("Harm bound - Cap harm bound at futility bound", { ) harm_bound <- x |> dplyr::filter(bound == "harm") |> dplyr::pull(z) futility_bound <- x |> dplyr::filter(bound == "lower") |> dplyr::pull(z) - (all(harm_bound <= futility_bound)) + (harm_bound <= futility_bound) }) diff --git a/tests/testit/test-independent-as_rtf.R b/tests/testit/test-independent-as_rtf.R deleted file mode 100644 index 4521c27ce..000000000 --- a/tests/testit/test-independent-as_rtf.R +++ /dev/null @@ -1,17 +0,0 @@ -# Test as_rtf() snapshot - -assert("gs_power_wlr_example() produces expected as_rtf output", { - path <- tempfile(fileext = ".rtf") - gs_power_wlr_example() |> - summary() |> - as_rtf(file = path) - TRUE # Snapshot comparison done by testit .md file -}) - -assert("gs_design_ahr() produces expected as_rtf output", { - path <- tempfile(fileext = ".rtf") - gs_design_ahr() |> - summary() |> - as_rtf(file = path) - TRUE # Snapshot comparison done by testit .md file -}) From fa8463574f42cde99dc8e5d933131228c3dbd4b1 Mon Sep 17 00:00:00 2001 From: Yihui Xie Date: Thu, 11 Jun 2026 15:53:27 -0400 Subject: [PATCH 11/12] Clarify test skill: helpers are auto-sourced, .md snapshots are standalone Co-Authored-By: Claude Opus 4.6 --- .claude/skills/write-tests.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.claude/skills/write-tests.md b/.claude/skills/write-tests.md index 98dfc01c1..01a9f3c20 100644 --- a/.claude/skills/write-tests.md +++ b/.claude/skills/write-tests.md @@ -16,11 +16,11 @@ This project uses **testit** for testing. testit assertions are plain R expressi tests/ ├── test-all.R # Runner: library(testit); test_pkg("gsDesign2") └── testit/ - ├── helper.R # Shared setup (sourced before test files) - ├── helper-*.R # Additional helpers + ├── helper.R # Shared setup (auto-sourced before test files) + ├── helper-*.R # Additional helpers (also auto-sourced) ├── fixtures/ # Test data (.Rdata, .rds, etc.) ├── test-*.R # Test files - └── test-*.md # Snapshot files (paired with .R files) + └── test-*.md # Snapshot files (standalone, no .R file needed) ``` ## Core Pattern @@ -131,7 +131,7 @@ assert("output structure is correct", { ## Snapshot Tests -Create a `.md` file alongside the `.R` file (same base name). Format: +A `.md` snapshot file is a standalone test — it does NOT require a paired `.R` file. The `.md` file contains both the code and the expected output. Format: ````markdown ## `function_name()` description @@ -154,7 +154,7 @@ testit runs the R code block and compares output to the text block. To initializ 3. **Use `all.equal(..., tolerance = t)` with the tightest tolerance that passes** — don't use overly loose tolerances. 4. **Group related assertions in one `assert()` block** — each block should test one logical concept. 5. **Use descriptive assert messages** — they appear in failure output. -6. **Shared setup goes in `helper.R`** — it's sourced before all test files. +6. **Shared setup goes in `helper*.R` files** — testit auto-sources all `helper*.R` files before test files. Never `source()` them manually. 7. **Load fixture data with `load("fixtures/file.Rdata")`** — paths are relative to `tests/testit/`. 8. **Use `all.equal()` only when exact comparison fails in CI** — typically macOS produces slightly different floating-point results while `identical()` works fine on Windows/Linux. From b56172050961c05543d4cda9ea71ebf29c497ad4 Mon Sep 17 00:00:00 2001 From: Yihui Xie Date: Thu, 11 Jun 2026 16:08:41 -0400 Subject: [PATCH 12/12] Fix parse error in gs_power_ahr examples (missing # before Example 5) Co-Authored-By: Claude Opus 4.6 --- R/gs_power_ahr.R | 2 +- man/gs_power_ahr.Rd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/R/gs_power_ahr.R b/R/gs_power_ahr.R index 6d49946db..102571984 100644 --- a/R/gs_power_ahr.R +++ b/R/gs_power_ahr.R @@ -130,7 +130,7 @@ #' lpar = list(sf = gsDesign::sfLDOF, total_spend = 0.025) #' ) #' } -#' Example 5 ---- +#' # Example 5 ---- #' # 2-sided asymmetric O'Brien-Fleming spending bound with harm bound #' # driven by both `event` and `analysis_time`, i.e., #' # both `event` and `analysis_time` are not `NULL`, diff --git a/man/gs_power_ahr.Rd b/man/gs_power_ahr.Rd index 63ff555c0..8ec869d8c 100644 --- a/man/gs_power_ahr.Rd +++ b/man/gs_power_ahr.Rd @@ -231,7 +231,7 @@ gs_power_ahr( lpar = list(sf = gsDesign::sfLDOF, total_spend = 0.025) ) } -Example 5 ---- +# Example 5 ---- # 2-sided asymmetric O'Brien-Fleming spending bound with harm bound # driven by both `event` and `analysis_time`, i.e., # both `event` and `analysis_time` are not `NULL`,