|
| 1 | +#' @title Sequential p-value computation for gsDesign2 |
| 2 | +#' @description \code{sequential_pval} computes a sequential p-value for a group sequential design |
| 3 | +#' created with \code{gs_design_ahr()} using a spending function approach. |
| 4 | +#' It is the minimum of repeated p-values computed at each analysis (Jennison and Turnbull, 2000). |
| 5 | +#' This is particularly useful for multiplicity methods such as the graphical method for group sequential designs |
| 6 | +#' where sequential p-values for multiple hypotheses can be used as nominal p-values to plug into a multiplicity graph. |
| 7 | +#' A sequential p-value is described as the minimum alpha level at which a one-sided group sequential bound would |
| 8 | +#' be rejected given interim and final observed results. |
| 9 | +#' Mild restrictions are required on spending functions used, but these are satisfied for commonly used spending functions |
| 10 | +#' such as the Lan-DeMets spending function approximating an O'Brien-Fleming bound or a Hwang-Shih-DeCani spending function. |
| 11 | +#' |
| 12 | +#' @param gs_design Group sequential design generated by \code{gs_design_ahr()}. |
| 13 | +#' @param event Event counts; numeric vector with increasing, positive values with at most one |
| 14 | +#' value greater than or equal to largest value in \code{gs_design$analysis$event}; |
| 15 | +#' NOTE: if NULL, planned \code{event} will be used (\code{gs_design$analysis$event}) |
| 16 | +#' @param z z-value tests corresponding to analyses in \code{info}; positive values indicate a positive finding; must have the same length as \code{info}. |
| 17 | +#' @param ustime Spending time for upper bound at specified analyses; specify default: \code{NULL} if this is to be based on information fraction; |
| 18 | +#' if not \code{NULL}, must have the same length as \code{info}; increasing positive values with at most 1 greater than or equal to 1. |
| 19 | +#' @param interval Interval for search to derive p-value; Default: \code{c(1e-05, 0.9999)}. Lower end of interval must be >0 and upper end must be < 1. |
| 20 | +#' The primary reason to not use the defaults would likely be if a test were vs a Type I error <0.0001. |
| 21 | +#' @return Sequential p-value (single numeric one-sided p-value between 0 and 1). |
| 22 | +#' Note that if the sequential p-value is less than the lower end of the input interval, |
| 23 | +#' the lower of interval will be returned. |
| 24 | +#' Similarly, if the sequential p-value is greater than the upper end of the input interval, |
| 25 | +#' then the upper end of interval is returned. |
| 26 | +#' @references |
| 27 | +#' Jennison C and Turnbull BW (2000), \emph{Group Sequential |
| 28 | +#' Methods with Applications to Clinical Trials}. Boca Raton: Chapman and Hall. |
| 29 | +#' |
| 30 | +#' Liu, Qing, and Keaven M. Anderson. "On adaptive extensions of group sequential trials for clinical investigations." \emph{Journal of the American Statistical Association} 103.484 (2008): 1621-1630. |
| 31 | +#' |
| 32 | +#' Maurer, Willi, and Frank Bretz. "Multiple testing in group sequential trials using graphical approaches." \emph{Statistics in Biopharmaceutical Research} 5.4 (2013): 311-320. |
| 33 | +#' @details |
| 34 | +#' Solution is found with a search using \code{uniroot}. |
| 35 | +#' This finds the maximum alpha-level for which an efficacy bound is crossed, |
| 36 | +#' completely ignoring any futility bound. |
| 37 | +#' @export |
| 38 | +#' |
| 39 | +#' @examples |
| 40 | +#' # Derive Group Sequential Design using gsDesign2 |
| 41 | +#' x <- gs_design_ahr( |
| 42 | +#' enroll_rate = define_enroll_rate(duration = c(2, 2, 2, 6), |
| 43 | +#' rate = c(2.5, 5, 7.5, 10)), |
| 44 | +#' fail_rate = define_fail_rate(duration = Inf, fail_rate = log(2) / 6, |
| 45 | +#' hr = 0.6, dropout_rate = .001), |
| 46 | +#' info_frac = c(.5, .65, .8, 1), |
| 47 | +#' analysis_time = 30, |
| 48 | +#' upper = gs_spending_bound, |
| 49 | +#' upar = list(sf = "sfLDOF", total_spend = 0.025), |
| 50 | +#' lower = gs_spending_bound, |
| 51 | +#' lpar = list(sf = "sfHSD", total_spend = 0.1, param = 2), |
| 52 | +#' binding = FALSE |
| 53 | +#' ) |
| 54 | +#' |
| 55 | +#' # Analysis at interim analysis 2 |
| 56 | +#' sequential_pval(gs_design = x, event = c(100, 160), z = c(1.5, 2)) |
| 57 | +#' |
| 58 | +#' # Use planned spending for interim and final analyses |
| 59 | +#' sequential_pval(gs_design = x, |
| 60 | +#' event = c(100, 160, 190), |
| 61 | +#' z = c(1.5, 2, 2.5)) |
| 62 | +sequential_pval <- function(gs_design, |
| 63 | + event = NULL, |
| 64 | + z = NULL, |
| 65 | + ustime = NULL, |
| 66 | + interval = c(.00001, .9999)) { |
| 67 | + q_e <- gs_design$input$ratio / (1 + gs_design$input$ratio) |
| 68 | + |
| 69 | + # gs_design should be object returned from gs_design_ahr or gs_power_ahr with >=2 analyses |
| 70 | + if (gs_design$design != "ahr") stop("gs_design must be an object created with gs_design_ahr() or gs_power_ahr()") |
| 71 | + if (nrow(gs_design$analysis) == 1) stop("gs_design must be an GSD object with more than 1 analysis") |
| 72 | + |
| 73 | + # check if gs_design is 1-sided, or has non-binding futility bounds |
| 74 | + one_sided <- all(gs_design$bound$bound == "upper") |
| 75 | + nonbinding <- !gs_design$input$binding |
| 76 | + if (!(one_sided || nonbinding)) stop("gs_design must be one-sided or have non-binding futility bounds") |
| 77 | + |
| 78 | + # if event is specified, check if it is an increasing numerical vector |
| 79 | + if (!is.null(event)) check_increasing(event) |
| 80 | + |
| 81 | + # if ustime is specified, check if it is an increasing numerical vector ranging within (0, 1] |
| 82 | + if (!is.null(ustime)) check_info_frac(ustime) |
| 83 | + |
| 84 | + # if event not specified, assume planned values used |
| 85 | + if (is.null(event)) event <- gs_design$analysis$event |
| 86 | + |
| 87 | + # if ustime not specified, use information fraction from design |
| 88 | + if (is.null(ustime)) ustime <- event/max(gs_design$analysis$event) |
| 89 | + |
| 90 | + # if z not specified, stop |
| 91 | + if (is.null(z)) stop("z must be provided") |
| 92 | + |
| 93 | + # check that z is numeric vector |
| 94 | + if (!is.vector(z, mode = "numeric")) stop("z must be numeric vector") |
| 95 | + |
| 96 | + # check lengths match |
| 97 | + if (length(event) != length(z)) stop("event and z must have the same length") |
| 98 | + if (length(event) != length(ustime)) stop("event and ustime must have the same length") |
| 99 | + |
| 100 | + # check that ustime has at most 1 value >= 1 |
| 101 | + if (sum(ustime >= 1) > 1) stop("No more than one value of ustime >= 1") |
| 102 | + |
| 103 | + # check the interval is 2 values in (0,1), exclusive of endpoints |
| 104 | + if (!is.vector(interval, mode = "numeric") || length(interval) != 2 || min(interval) <= 0 || max(interval) >= 1) { |
| 105 | + stop("interval must be 2 distinct values strictly between 0 and 1") |
| 106 | + } |
| 107 | + |
| 108 | + # Get spending function and parameters from gs_design |
| 109 | + # sf is a character string, so we need to get the actual function from gsDesign namespace |
| 110 | + sf_upper <- get_sf(gs_design$input$upar$sf) |
| 111 | + sf_param <- gs_design$input$upar$param |
| 112 | + |
| 113 | + # check upper end of p-value interval input |
| 114 | + probhi <- sf_upper(alpha = max(interval), t = ustime, param = sf_param)$spend |
| 115 | + if (length(z) > 1) probhi <- probhi - c(0, probhi[1:(length(z) - 1)]) |
| 116 | + if (min(gsDesign::gsBound1(I = event * (1 - q_e) * q_e, theta = 0, a = rep(-20, length(z)), |
| 117 | + probhi = probhi)$b - z) > 0) return(max(interval)) |
| 118 | + |
| 119 | + # check lower end of p-value interval input |
| 120 | + probhi <- sf_upper(alpha = min(interval), t = ustime, param = sf_param)$spend |
| 121 | + if (length(z) > 1) probhi <- probhi - c(0, probhi[1:(length(z) - 1)]) |
| 122 | + if (min(gsDesign::gsBound1(I = event * (1 - q_e) * q_e, theta = 0, a = rep(-20, length(z)), |
| 123 | + probhi = probhi)$b - z) < 0) return(min(interval)) |
| 124 | + |
| 125 | + # if answer is between interval bounds, find it with root-finding |
| 126 | + x <- try(uniroot(sequential_zdiff, interval = -qnorm(interval), gs_design = gs_design, |
| 127 | + event = event, z = z, ustime = ustime)) |
| 128 | + |
| 129 | + if (inherits(x, "try-error")) stop("Failed to find root for sequential p-value") |
| 130 | + |
| 131 | + pnorm(-x$root) |
| 132 | +} |
| 133 | + |
| 134 | +#' This function calculates the difference between: |
| 135 | +#' Computed efficacy bound at a given alpha level (using the spending function) |
| 136 | +#' Observed Z-statistic |
| 137 | +#' @noRd |
| 138 | +sequential_zdiff <- function(x, |
| 139 | + gs_design, |
| 140 | + event = (1:3) * 50, |
| 141 | + z = 1:3, |
| 142 | + ustime = NULL) { |
| 143 | + q_e <- gs_design$input$ratio / (1 + gs_design$input$ratio) |
| 144 | + alpha <- pnorm(-x) |
| 145 | + |
| 146 | + sf_upper <- get_sf(gs_design$input$upar$sf) |
| 147 | + sf_param <- gs_design$input$upar$param |
| 148 | + |
| 149 | + probhi <- sf_upper(alpha = alpha, t = ustime, param = sf_param)$spend |
| 150 | + if (length(z) > 1) probhi <- probhi - c(0, probhi[1:(length(z) - 1)]) |
| 151 | + |
| 152 | + return(min(gsDesign::gsBound1(I = event * (1 - q_e) * q_e, theta = 0, a = rep(-20, length(z)), |
| 153 | + probhi = probhi)$b - z)) |
| 154 | +} |
0 commit comments