From eb713e013cdc2880fe7dac976178b3ac938deebf Mon Sep 17 00:00:00 2001 From: keaven Date: Tue, 5 Nov 2024 21:20:15 -0500 Subject: [PATCH 01/11] Draft to be checked --- R/toInteger.R | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/R/toInteger.R b/R/toInteger.R index 6df12ff4..dacb1c10 100644 --- a/R/toInteger.R +++ b/R/toInteger.R @@ -3,21 +3,25 @@ #' #' @param x An object of class \code{gsDesign} or \code{gsSurv}. #' @param ratio A non-negative integer, usually corresponding to experimental:control sample size ratio. -#' Rounding is done to a multiple of \code{ratio + 1}. If input \code{x} has class \code{gsSurv} (design for time-to-event outcome), -#' and \code{x$ratio} is a whole number, \code{ratio} is replaced by \code{x$ratio}. +#' A non-negative integer is required. +#' Rounding is done to a multiple of \code{ratio + 1}. #' See details. -#' @param roundUpFinal Final value in returned \code{n.I} is rounded up -#' if \code{TRUE}; otherwise, just rounded. For \code{gsSurv} input, final total sample size is also controlled by this. See details. +#' @param roundUpFinal Sample size is rounded up to a value of \code{ratio + 1} with the default \code{roundUpFinal = TRUE}. +#' If \code{roundUpFinal = FALSE}, sample size is rounded to the nearest multiple of \code{ratio + 1}. +#' For event counts, \code{roundUpFinal=TRUE} rounds final event count up; otherwise, just rounded if \code{roundUpFinal = FALSE}. +#' See details. #' #' @return Output is an object of the same class as input \code{x}; i.e., \code{gsDesign} with integer vector for \code{n.I} #' or \code{gsSurv} with integer vector \code{n.I} and integer total sample size. See details. #' #' @details -#' If \code{ratio = 3}, rounding for final sample size is done to a multiple of 3 + 1 = 4. -#' For a \code{gsSurv} object input in \code{x}, event counts output in \code{n.I} are rounded to nearest integer and -#' final total sample size is rounded to a multiple of \code{ratio + 1}. -#' For other input values of \code{x} (\code{gsDesign} class), \code{n.I} is interpreted as sample size; -#' final value is rounded to a multiple of \code{ratio + 1}, with \code{roundUpFinal} controlling rounding of last value. +#' The default of \code{ratio = 0, roundUpFinal = TRUE} will just round up the sample size (also event count) being rounded up. +#' Rounding of event count targets is not impacted by \code{ratio}. +#' A positive integer value of \code{ratio} is required and the sample size will be rounded to a multiple of \code{ratio + 1}. +#' The most common example would be if there is 1:1 randomization and the user wishes an even sample size, then set \code{ratio = 1}. +#' If \code{ratio = 3}, rounding for final sample size is done to a multiple of 3 + 1 = 4; +#' this could represent a 3:1 or 1:3 randomization ratio. +#' For 3:2 randomization, \code{ratio = 4} would ensure rounding sample size to a multiple of 5. #' #' @export #' @@ -49,14 +53,19 @@ #' toInteger(x, ratio = 3) toInteger <- function(x, ratio = 0, roundUpFinal = TRUE) { if (!inherits(x, "gsDesign")) stop("must have class gsDesign as input") - if (!isInteger(ratio) || ratio < 0) stop("input ratio must be a non-negative integer") + if (!isInteger(ratio) || ratio < 0) stop("toInteger: input ratio must be a non-negative integer") counts <- round(x$n.I) # Round counts (event counts for survival; otherwise sample size) - # For time-to-event endpoint, just round final count up + # For time-to-event endpoint, just round final event count up if (inherits(x, "gsSurv")) { - if (roundUpFinal) counts[x$k] <- ceiling(x$n.I[x$k]) + if (abs(counts[x$k] - x$n.I[x$k]) <= .01){ + counts[x$k] <- round(x$n.I[x$k]) + } else if (roundUpFinal) counts[x$k] <- ceiling(x$n.I[x$k]) } else { + # Check if control size is close to integer multiple of ratio + 1 + if (abs(x$n.I[x$k] - round(x$n.I[x$k] / (ratio + 1)) * (ratio + 1)) <= .01) { + counts[x$k] <- round(x$n.I[x$k] / (ratio + 1)) * (ratio + 1) # For non-survival designs round sample size based on randomization ratio - if (roundUpFinal) { + }else if (roundUpFinal) { counts[x$k] <- ceiling(x$n.I[x$k] / (ratio + 1)) * (ratio + 1) # Round up for final count } else { counts[x$k] <- round(x$n.I[x$k] / (ratio + 1)) * (ratio + 1) @@ -71,18 +80,17 @@ toInteger <- function(x, ratio = 0, roundUpFinal = TRUE) { lsTime = x$lsTime, usTime = x$usTime ) if (max(abs(xi$n.I - counts)) > .01) warning("toInteger: check n.I input versus output") - xi$n.I <- counts # ensure these are integers as they become real in gsDesign call + xi$n.I <- counts # ensure these are integers as they became real in gsDesign call + # Non-binding futility designs have x$test.type either 4 or 6 if (x$test.type %in% c(4, 6)) { xi$falseposnb <- as.vector(gsprob(0, xi$n.I, rep(-20, xi$k), xi$upper$bound, r = xi$r)$probhi) } - if ("gsSurv" %in% class(x) || x$nFixSurv > 0) { + if (inherits(x, "gsSurv") || x$nFixSurv > 0) { xi$hr0 <- x$hr0 # H0 hazard ratio xi$hr <- x$hr # H1 hazard ratio N <- rowSums(x$eNC + x$eNE)[x$k] # get input total sample size N_continuous <- N - # if ratio = 0 and x$ratio is positive integer, replace ratio - if(ratio == 0 && is.wholenumber(x$ratio)) ratio <- x$ratio # Update sample size to integer N <- N / (ratio + 1) if (roundUpFinal) { From cf142f7184bb2ee79c78b1f9942e782ddb09e271 Mon Sep 17 00:00:00 2001 From: keaven Date: Thu, 7 Nov 2024 13:29:49 -0500 Subject: [PATCH 02/11] finalized vignette for review --- vignettes/toInteger.Rmd | 73 ++++++++++++++++++++++++++++------------- 1 file changed, 51 insertions(+), 22 deletions(-) diff --git a/vignettes/toInteger.Rmd b/vignettes/toInteger.Rmd index be5f83e4..a719f86c 100644 --- a/vignettes/toInteger.Rmd +++ b/vignettes/toInteger.Rmd @@ -21,15 +21,29 @@ This has a couple of implications on design characteristics: This document goes through examples to demonstrate the calculations. The new function as of July, 2023 is the `toInteger()` which operates on group sequential designs to convert to integer-based total sample size and event counts at analyses. -We begin with an abbreviated example for a time-to-event endpoint design to demonstrate basic concepts. +As of November, 2024 the rounding defaults are changing as documented below. +We begin with a summary of the method. +Then we provide an abbreviated example for a time-to-event endpoint design to demonstrate basic concepts. We follow with a more extended example for a binary endpoint to explain more details. +## Summary of method + +- With defaults, `gsSurv() |> toInteger(ratio = 0, roundUpFinal = TRUE)` + - rounds event counts at interim analyses + - rounds event count up at final analysis + - rounds up sample size at final analysis, but allows continuous (expected) values at interim analyses +- When `ratio` is a positive integer, the final sample size is rounded to a multiple of `ratio + 1`. + - For 1:1 randomization (experimental:control), set `ratio = 1` to round to an even sample size. + - For 2:1 randomization, set `ratio = 2` to round to a multiple of 3. + - For 3:2 randomization, set `ratio = 4` to round to a multiple of 5. + - Note that for the final analysis the sample size is rounded up to the nearest multiple of `ratio + 1` when `roundUpFinal = TRUE` is specified. If `roundUpFinal = FALSE`, the final sample size is rounded to the nearest multiple of `ratio + 1`. + ## Time-to-event endpoint example The initial design for a time-to-event endpoint in a 2-arm trial does not have integer sample size and event counts. See comments in the code and output from the `summary()` function below to understand inputs. -```{r} +```{r, message=FALSE} library(gsDesign) x <- gsSurv( @@ -61,42 +75,57 @@ We can summarize this textually as: cat(summary(x)) ``` -We now adapt this design to integer targeted events at each analysis as well as an sample size per arm at the end of the trial. +We now adapt this design to integer targeted events at each analysis as well as the sample size at the end of the trial. We provide a table summarizing bounds. Due to rounding up of the final event count, we see slightly larger than the targeted 90% trial power in the last row of the efficacy column. ```{r} # Adjust design to integer-based event counts at analyses -# and even integer-based final event count -xi <- toInteger(x) +# Final sample size is rounded up to the nearest even number +# Implied default arguments are toInteger(x, ratio = 0, roundUpFinal = TRUE) +xi <- toInteger(x) gsBoundSummary(xi) # Summarize design bounds ``` -We now summarize sample size and targeted events at analyses. +We now summarize sample size and targeted events at analyses for continuous design `x` and integer-based design `xi`. ```{r} # Integer event counts at analyses are integer xi$n.I -# Control planned sample size at analyses -# Final analysis is integer; interim analyses before enrollment completion -# are continuous -xi$eNC -# Experimental analysis planned sample size at analyses -xi$eNE +# Prior to rounding +x$n.I +# Final analysis sample size is integer +# Interim analysis before enrollment completion are continuous +# In this case at IA 2 the enrollment is complete +xi$eNC + xi$eNE +# Prior to rounding +x$eNC + x$eNE +``` + Note that if we set `roundUpFinal = FALSE` in the call to `toInteger()`, the final sample size is rounded to the nearest even number rather than rounded up. + +```{r} +# Note that ratio = 0 is default +x2 <- x |> toInteger(ratio = 0, roundUpFinal = FALSE) +x2$eNE + x2$eNC ``` +If we had rounded up sample size to an even integer, we get a slightly larger final sample size. +```{r} +x3 <- x |> toInteger(ratio = 1, roundUpFinal = TRUE) +x3$eNE + x3$eNC +``` ## Binomial endpoint designs ### Fixed sample size -We present a simple example based on comparing binomial rates with interim analyses after 50% and 75% of events. We assume a 2:1 experimental:control randomization ratio. Note that the sample size is not an integer. +We present a simple example based on comparing binomial rates with interim analyses after 50% and 75% of events. We assume a 2:1 experimental:control randomization ratio. Note that the sample size is not an integer. We target 80% power (`beta = .2`). ```{r} n.fix <- nBinomial(p1 = .2, p2 = .1, alpha = .025, beta = .2, ratio = 2) n.fix ``` -If we replace the `beta` argument above with a integer sample size that is a multiple of 3 so that we get the desired 2:1 integer sample sizes per arm (432 = 144 control + 288 experimental targeted) we get slightly larger thant the targeted 80% power: +If we replace the `beta` argument above with a integer sample size that is a multiple of 3 so that we get the desired 2:1 integer sample sizes per arm (432 = 144 control + 288 experimental targeted) we get slightly larger than the targeted 80% power: ```{r} nBinomial(p1 = .2, p2 = .1, alpha = .025, n = 432, ratio = 2) @@ -108,9 +137,9 @@ Now we convert the fixed sample size `n.fix` from above to a 1-sided group seque ```{r} # 1-sided design (efficacy bound only; test.type = 1) -x <- gsDesign(alpha = .025, beta = .2, n.fix = n.fix, test.type = 1, sfu = sfLDOF, timing = c(.5, .75)) +xb <- gsDesign(alpha = .025, beta = .2, n.fix = n.fix, test.type = 1, sfu = sfLDOF, timing = c(.5, .75)) # Continuous sample size (non-integer) at planned analyses -x$n.I +xb$n.I ``` Next we convert to integer sample sizes at each analysis. Interim sample sizes are rounded to the nearest integer. The default `roundUpFinal = TRUE` rounds the final sample size to the nearest integer to 1 + the experimental:control randomization ratio. Thus, the final sample size of 441 below is a multiple of 3. @@ -118,7 +147,7 @@ The default `roundUpFinal = TRUE` rounds the final sample size to the nearest in ```{r} # Convert to integer sample size with even multiple of ratio + 1 # i.e., multiple of 3 in this case at final analysis -x_integer <- toInteger(x, ratio = 2) +x_integer <- toInteger(xb, ratio = 2) x_integer$n.I ``` @@ -126,7 +155,7 @@ Next we examine the efficacy bound of the 2 designs as they are slightly differe ```{r} # Bound for continuous sample size design -x$upper$bound +xb$upper$bound # Bound for integer sample size design x_integer$upper$bound ``` @@ -135,7 +164,7 @@ The differences are associated with slightly different timing of the analyses as ```{r} # Continuous design sample size fractions at analyses -x$timing +xb$timing # Integer design sample size fractions at analyses x_integer$timing ``` @@ -145,9 +174,9 @@ These differences also make a difference in the cumulative Type I error associat ```{r} # Continuous sample size design -cumsum(x$upper$prob[, 1]) +cumsum(xb$upper$prob[, 1]) # Specified spending based on the spending function -x$upper$sf(alpha = x$alpha, t = x$timing, x$upper$param)$spend +xb$upper$sf(alpha = xb$alpha, t = xb$timing, xb$upper$param)$spend ``` ```{r} @@ -164,7 +193,7 @@ Interim power is slightly lower for the integer-based design since sample size i ```{r} # Cumulative upper boundary crossing probability under alternate by analysis # under alternate hypothesis for continuous sample size -cumsum(x$upper$prob[, 2]) +cumsum(xb$upper$prob[, 2]) # Same for integer sample sizes at each analysis cumsum(x_integer$upper$prob[, 2]) ``` From e774a1af047dc5a69d7fb86d84fc4bd09e86aecf Mon Sep 17 00:00:00 2001 From: Nan Xiao Date: Fri, 8 Nov 2024 13:18:40 -0500 Subject: [PATCH 03/11] Format toInteger roxygen docs and add vignette link --- R/toInteger.R | 47 +++++++++++++++++++++++++++++------------------ man/toInteger.Rd | 39 +++++++++++++++++++++++++++------------ 2 files changed, 56 insertions(+), 30 deletions(-) diff --git a/R/toInteger.R b/R/toInteger.R index dacb1c10..fc00e85b 100644 --- a/R/toInteger.R +++ b/R/toInteger.R @@ -2,26 +2,37 @@ #' or sample size (other designs) #' #' @param x An object of class \code{gsDesign} or \code{gsSurv}. -#' @param ratio A non-negative integer, usually corresponding to experimental:control sample size ratio. -#' A non-negative integer is required. -#' Rounding is done to a multiple of \code{ratio + 1}. -#' See details. -#' @param roundUpFinal Sample size is rounded up to a value of \code{ratio + 1} with the default \code{roundUpFinal = TRUE}. -#' If \code{roundUpFinal = FALSE}, sample size is rounded to the nearest multiple of \code{ratio + 1}. -#' For event counts, \code{roundUpFinal=TRUE} rounds final event count up; otherwise, just rounded if \code{roundUpFinal = FALSE}. -#' See details. +#' @param ratio A non-negative integer, usually corresponding to +#' experimental:control sample size ratio. +#' A non-negative integer is required. +#' Rounding is done to a multiple of \code{ratio + 1}. +#' See details. +#' @param roundUpFinal Sample size is rounded up to a value of \code{ratio + 1} +#' with the default \code{roundUpFinal = TRUE}. +#' If \code{roundUpFinal = FALSE}, sample size is rounded to the nearest +#' multiple of \code{ratio + 1}. +#' For event counts, \code{roundUpFinal = TRUE} rounds final event count up; +#' otherwise, just rounded if \code{roundUpFinal = FALSE}. +#' See details. +#' +#' @return Output is an object of the same class as input \code{x}; i.e., +#' \code{gsDesign} with integer vector for \code{n.I} or \code{gsSurv} +#' with integer vector \code{n.I} and integer total sample size. See details. #' -#' @return Output is an object of the same class as input \code{x}; i.e., \code{gsDesign} with integer vector for \code{n.I} -#' or \code{gsSurv} with integer vector \code{n.I} and integer total sample size. See details. -#' #' @details -#' The default of \code{ratio = 0, roundUpFinal = TRUE} will just round up the sample size (also event count) being rounded up. +#' The default of \code{ratio = 0, roundUpFinal = TRUE} will just round up +#' the sample size (also event count) being rounded up. #' Rounding of event count targets is not impacted by \code{ratio}. -#' A positive integer value of \code{ratio} is required and the sample size will be rounded to a multiple of \code{ratio + 1}. -#' The most common example would be if there is 1:1 randomization and the user wishes an even sample size, then set \code{ratio = 1}. -#' If \code{ratio = 3}, rounding for final sample size is done to a multiple of 3 + 1 = 4; -#' this could represent a 3:1 or 1:3 randomization ratio. -#' For 3:2 randomization, \code{ratio = 4} would ensure rounding sample size to a multiple of 5. +#' A positive integer value of \code{ratio} is required and the sample size +#' will be rounded to a multiple of \code{ratio + 1}. +#' The most common example would be if there is 1:1 randomization and the user +#' wishes an even sample size, then set \code{ratio = 1}. +#' If \code{ratio = 3}, rounding for final sample size is done to a multiple of +#' 3 + 1 = 4; this could represent a 3:1 or 1:3 randomization ratio. +#' For 3:2 randomization, \code{ratio = 4} would ensure rounding sample size +#' to a multiple of 5. +#' +#' See \code{vignette("toInteger")} for more examples with further details. #' #' @export #' @@ -88,7 +99,7 @@ toInteger <- function(x, ratio = 0, roundUpFinal = TRUE) { if (inherits(x, "gsSurv") || x$nFixSurv > 0) { xi$hr0 <- x$hr0 # H0 hazard ratio xi$hr <- x$hr # H1 hazard ratio - + N <- rowSums(x$eNC + x$eNE)[x$k] # get input total sample size N_continuous <- N # Update sample size to integer diff --git a/man/toInteger.Rd b/man/toInteger.Rd index 420f765c..ecb0d1a8 100644 --- a/man/toInteger.Rd +++ b/man/toInteger.Rd @@ -10,28 +10,43 @@ toInteger(x, ratio = 0, roundUpFinal = TRUE) \arguments{ \item{x}{An object of class \code{gsDesign} or \code{gsSurv}.} -\item{ratio}{A non-negative integer, usually corresponding to experimental:control sample size ratio. -Rounding is done to a multiple of \code{ratio + 1}. If input \code{x} has class \code{gsSurv} (design for time-to-event outcome), -and \code{x$ratio} is a whole number, \code{ratio} is replaced by \code{x$ratio}. +\item{ratio}{A non-negative integer, usually corresponding to +experimental:control sample size ratio. +A non-negative integer is required. +Rounding is done to a multiple of \code{ratio + 1}. See details.} -\item{roundUpFinal}{Final value in returned \code{n.I} is rounded up -if \code{TRUE}; otherwise, just rounded. For \code{gsSurv} input, final total sample size is also controlled by this. See details.} +\item{roundUpFinal}{Sample size is rounded up to a value of \code{ratio + 1} +with the default \code{roundUpFinal = TRUE}. +If \code{roundUpFinal = FALSE}, sample size is rounded to the nearest +multiple of \code{ratio + 1}. +For event counts, \code{roundUpFinal = TRUE} rounds final event count up; +otherwise, just rounded if \code{roundUpFinal = FALSE}. +See details.} } \value{ -Output is an object of the same class as input \code{x}; i.e., \code{gsDesign} with integer vector for \code{n.I} -or \code{gsSurv} with integer vector \code{n.I} and integer total sample size. See details. +Output is an object of the same class as input \code{x}; i.e., + \code{gsDesign} with integer vector for \code{n.I} or \code{gsSurv} + with integer vector \code{n.I} and integer total sample size. See details. } \description{ Translate group sequential design to integer events (survival designs) or sample size (other designs) } \details{ -If \code{ratio = 3}, rounding for final sample size is done to a multiple of 3 + 1 = 4. -For a \code{gsSurv} object input in \code{x}, event counts output in \code{n.I} are rounded to nearest integer and -final total sample size is rounded to a multiple of \code{ratio + 1}. -For other input values of \code{x} (\code{gsDesign} class), \code{n.I} is interpreted as sample size; -final value is rounded to a multiple of \code{ratio + 1}, with \code{roundUpFinal} controlling rounding of last value. +The default of \code{ratio = 0, roundUpFinal = TRUE} will just round up +the sample size (also event count) being rounded up. +Rounding of event count targets is not impacted by \code{ratio}. +A positive integer value of \code{ratio} is required and the sample size +will be rounded to a multiple of \code{ratio + 1}. +The most common example would be if there is 1:1 randomization and the user +wishes an even sample size, then set \code{ratio = 1}. +If \code{ratio = 3}, rounding for final sample size is done to a multiple of +3 + 1 = 4; this could represent a 3:1 or 1:3 randomization ratio. +For 3:2 randomization, \code{ratio = 4} would ensure rounding sample size +to a multiple of 5. + +See \code{vignette("toInteger")} for more examples with further details. } \examples{ # The following code derives the group sequential design using the method From 9a187204db13f70210daa9b49831a87836a6411a Mon Sep 17 00:00:00 2001 From: keaven Date: Wed, 13 Nov 2024 09:23:31 -0500 Subject: [PATCH 04/11] Updated default behavior and documentation --- R/toInteger.R | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/R/toInteger.R b/R/toInteger.R index dacb1c10..c298b533 100644 --- a/R/toInteger.R +++ b/R/toInteger.R @@ -6,20 +6,27 @@ #' A non-negative integer is required. #' Rounding is done to a multiple of \code{ratio + 1}. #' See details. -#' @param roundUpFinal Sample size is rounded up to a value of \code{ratio + 1} with the default \code{roundUpFinal = TRUE}. +#' @param roundUpFinal Sample size is rounded up to a value of \code{ratio + 1} with the default +#' \code{roundUpFinal = TRUE}. #' If \code{roundUpFinal = FALSE}, sample size is rounded to the nearest multiple of \code{ratio + 1}. -#' For event counts, \code{roundUpFinal=TRUE} rounds final event count up; otherwise, just rounded if \code{roundUpFinal = FALSE}. +#' For event counts, \code{roundUpFinal=TRUE} rounds final event count up; interim event counts are +#' just rounded. #' See details. #' #' @return Output is an object of the same class as input \code{x}; i.e., \code{gsDesign} with integer vector for \code{n.I} #' or \code{gsSurv} with integer vector \code{n.I} and integer total sample size. See details. #' #' @details -#' The default of \code{ratio = 0, roundUpFinal = TRUE} will just round up the sample size (also event count) being rounded up. +#' It is useful to explicitly provide the argument \code{ratio} when a \code{gsDesign} object is input since \code{gsDesign} +#' does not have a \code{ratio} in return. +#' \code{ratio = 0, roundUpFinal = TRUE} will just round up the sample size (also event count). #' Rounding of event count targets is not impacted by \code{ratio}. -#' A positive integer value of \code{ratio} is required and the sample size will be rounded to a multiple of \code{ratio + 1}. -#' The most common example would be if there is 1:1 randomization and the user wishes an even sample size, then set \code{ratio = 1}. -#' If \code{ratio = 3}, rounding for final sample size is done to a multiple of 3 + 1 = 4; +#' Since \code{x <- gsSurv(ratio = M)} returns a value for \code{ratio}, \code{toInteger(x)} will round to a multiple of +#' \code{M + 1} if \code{M} is a non-negative integer; otherwise, just rounding will occur. +#' The most common example would be if there is 1:1 randomization (2:1) and the user wishes an even (multiple of 3) sample size, +#' then \code{toInteger) will operate as expected. +#' To just round without concern for randomization ratio, set \code{ratio = 0}. +#' If \code{toInteger(x, ratio = 3)}, rounding for final sample size is done to a multiple of 3 + 1 = 4; #' this could represent a 3:1 or 1:3 randomization ratio. #' For 3:2 randomization, \code{ratio = 4} would ensure rounding sample size to a multiple of 5. #' @@ -49,11 +56,15 @@ #' minfup = 8, # Planned minimum follow-up #' ratio = 3 # Randomization ratio (experimental:control) #' ) -#' # Convert bounds to exact binomial bounds -#' toInteger(x, ratio = 3) -toInteger <- function(x, ratio = 0, roundUpFinal = TRUE) { +#' # Convert sample size to multiple of ratio + 1 = 4, round event counts. +#' # Default is to round up both event count and sample size for final analysis +#' toInteger(x) +toInteger <- function(x, ratio = x$ratio, roundUpFinal = TRUE) { if (!inherits(x, "gsDesign")) stop("must have class gsDesign as input") - if (!isInteger(ratio) || ratio < 0) stop("toInteger: input ratio must be a non-negative integer") + if (!(isInteger(ratio) && ratio >= 0)){ + message("toInteger: rounding done to nearest integer since ratio was not specified as postive integer .") + ratio <- 0 + } counts <- round(x$n.I) # Round counts (event counts for survival; otherwise sample size) # For time-to-event endpoint, just round final event count up if (inherits(x, "gsSurv")) { From e3355d95e074e509d9901e1235eee240fe72b376 Mon Sep 17 00:00:00 2001 From: keaven Date: Wed, 13 Nov 2024 09:32:28 -0500 Subject: [PATCH 05/11] Initial tests. --- .../testthat/test-developer-test-toInteger.R | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 tests/testthat/test-developer-test-toInteger.R diff --git a/tests/testthat/test-developer-test-toInteger.R b/tests/testthat/test-developer-test-toInteger.R new file mode 100644 index 00000000..791a4484 --- /dev/null +++ b/tests/testthat/test-developer-test-toInteger.R @@ -0,0 +1,56 @@ +# Test toInteger function +#---------------------------------------------- + +### Test toInteger +testthat::test_that("Test: toInteger with invalid input", code = { + M <- "a" + testthat::expect_error(toInteger(x = M), + info = "Test toInteger with invalid input" + ) +}) + +x <- gsDesign(n.fix = 100) +testthat::test_that("Test: toInteger for fractional and negative values", code = { + M <- 55.9 + testthat::expect_message(toInteger(x, ratio = M), + info = "Test toInteger for floating values" + ) + M <- -1 + testthat::expect_message(toInteger(x, ratio = M), + info = "Test toInteger for negative value of ratio" + ) + # Note that x$ratio = NULL + testthat::expect_message(toInteger(x), + info = "Test toInteger for NULL value of ratio" + ) +}) + +testthat::test_that("Test: toInteger for even sample size", code = { + testthat::expect_true(max(toInteger(x, ratio = 1)$n.I) %% 2 == 0, + info = "Test toInteger for floating value with decimal value 0" + ) +}) + +testthat::test_that("Test: toInteger for multiple of 5", code = { + testthat::expect_true(max(toInteger(x, ratio = 4)$n.I) %% 5 == 0, + info = "Test toInteger for floating value with decimal value 0" + ) +}) + +# Now test survival endpoint +x <- gsSurvCalendar(hr = .64) # This gives 252.1852 as sample size, 227.1393 as final event count +# Should round event counts (up for final) and +# round up final event count final sample size only as well +y <- toInteger(x) +testthat::test_that("Test: toInteger for survival endpoint event count works properly", code = { + # Interim event counts rounded + testthat::expect_equal(min(round(x$n.I[1:x$k - 1]) - y$n.I[1:x$k - 1]), 0) + # Final event count round up + testthat::expect_true((y$n.I - x$n.I)[x$k] >= 0, + info = "Test toInteger of gsSurv rounds up final event count" + ) + # Final sample size rounds to even + testthat::expect_true((y$eNC + y$eNE)[x$k] %% 2 == 0) + # Final event count rounds up + testthat::expect_gte((y$eNC + y$eNE - x$eNC - x$eNE)[x$k], 0) +}) From 4abf878323b4043b6d5ec75510c3bdabce4c7edc Mon Sep 17 00:00:00 2001 From: keaven Date: Wed, 13 Nov 2024 11:56:52 -0500 Subject: [PATCH 06/11] ongoing updates --- vignettes/toInteger.Rmd | 99 ++++++++--------------------------------- 1 file changed, 19 insertions(+), 80 deletions(-) diff --git a/vignettes/toInteger.Rmd b/vignettes/toInteger.Rmd index a719f86c..5c32f003 100644 --- a/vignettes/toInteger.Rmd +++ b/vignettes/toInteger.Rmd @@ -28,92 +28,31 @@ We follow with a more extended example for a binary endpoint to explain more det ## Summary of method -- With defaults, `gsSurv() |> toInteger(ratio = 0, roundUpFinal = TRUE)` - - rounds event counts at interim analyses - - rounds event count up at final analysis - - rounds up sample size at final analysis, but allows continuous (expected) values at interim analyses +```{r} +library(gsDesign) +x <- gsSurv(ratio = 1, hr = .74) +y <- x |> toInteger() +# Continuous event counts +x$n.I +# Rounded event counts at interims, rounded up at final analysis +y$n.I +# Continuous sample size at interim and final analyses +as.numeric(x$eNE + x$eNC) +# Rounded up to even final sample size given that x$ratio = 1 +# and rounding to multiple of x$ratio + 1 +as.numeric(y$eNE + y$eNC) +# With roundUpFinal = FALSE, final sample size rounded to nearest integer +z <- x |> toInteger(roundUpFinal = FALSE) +as.numeric(z$eNE + z$eNC) +``` + + - When `ratio` is a positive integer, the final sample size is rounded to a multiple of `ratio + 1`. - For 1:1 randomization (experimental:control), set `ratio = 1` to round to an even sample size. - For 2:1 randomization, set `ratio = 2` to round to a multiple of 3. - For 3:2 randomization, set `ratio = 4` to round to a multiple of 5. - Note that for the final analysis the sample size is rounded up to the nearest multiple of `ratio + 1` when `roundUpFinal = TRUE` is specified. If `roundUpFinal = FALSE`, the final sample size is rounded to the nearest multiple of `ratio + 1`. -## Time-to-event endpoint example - -The initial design for a time-to-event endpoint in a 2-arm trial does not have integer sample size and event counts. -See comments in the code and output from the `summary()` function below to understand inputs. - -```{r, message=FALSE} -library(gsDesign) - -x <- gsSurv( - k = 3, # Number of analyses - test.type = 4, # Asymmetric 2-sided design with non-binding futility bound - alpha = 0.025, # 1-sided Type I error - beta = 0.1, # Type II error (1 - power; 90% power) - timing = c(.25, .7), # Fraction of final planned events at interim analyses - sfu = sfLDOF, # O'Brien-Fleming-like spending for efficacy - sfl = sfHSD, # Hwang-Shih-DeCani spending for futility - sflpar = -2.2, # Futility spending parameter to customize bound - lambdaC = log(2) / 12, # 12 month median control survival - hr = 0.75, # Alternate hypothesis hazard ratio - eta = -log(.98) / 12, # 2% dropout rate per year - # Enrollment accelerates over 6 months to steady state - gamma = c(2.5, 5, 7.5, 10), # Relative enrollment rates - # Duration of relative enrollment rate - R = c(2, 2, 2, 100), - # Enrollment duration targeted to T - minfup = 12 months total - T = 36, # Trial duration - minfup = 24, # Minimum follow-up duration - ratio = 1 # Randomization ratio is 1:1 -) -``` - -We can summarize this textually as: - -```{r, results='asis'} -cat(summary(x)) -``` - -We now adapt this design to integer targeted events at each analysis as well as the sample size at the end of the trial. -We provide a table summarizing bounds. -Due to rounding up of the final event count, we see slightly larger than the targeted 90% trial power in the last row of the efficacy column. - -```{r} -# Adjust design to integer-based event counts at analyses -# Final sample size is rounded up to the nearest even number -# Implied default arguments are toInteger(x, ratio = 0, roundUpFinal = TRUE) -xi <- toInteger(x) -gsBoundSummary(xi) # Summarize design bounds -``` - -We now summarize sample size and targeted events at analyses for continuous design `x` and integer-based design `xi`. - -```{r} -# Integer event counts at analyses are integer -xi$n.I -# Prior to rounding -x$n.I -# Final analysis sample size is integer -# Interim analysis before enrollment completion are continuous -# In this case at IA 2 the enrollment is complete -xi$eNC + xi$eNE -# Prior to rounding -x$eNC + x$eNE -``` - Note that if we set `roundUpFinal = FALSE` in the call to `toInteger()`, the final sample size is rounded to the nearest even number rather than rounded up. - -```{r} -# Note that ratio = 0 is default -x2 <- x |> toInteger(ratio = 0, roundUpFinal = FALSE) -x2$eNE + x2$eNC -``` -If we had rounded up sample size to an even integer, we get a slightly larger final sample size. - -```{r} -x3 <- x |> toInteger(ratio = 1, roundUpFinal = TRUE) -x3$eNE + x3$eNC -``` ## Binomial endpoint designs From 23b137c623d91d17b609f092adfc67236e8c63dc Mon Sep 17 00:00:00 2001 From: keaven Date: Wed, 13 Nov 2024 14:51:31 -0500 Subject: [PATCH 07/11] minor update --- tests/testthat/test-developer-test-toInteger.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testthat/test-developer-test-toInteger.R b/tests/testthat/test-developer-test-toInteger.R index 791a4484..a00f4efd 100644 --- a/tests/testthat/test-developer-test-toInteger.R +++ b/tests/testthat/test-developer-test-toInteger.R @@ -44,7 +44,7 @@ x <- gsSurvCalendar(hr = .64) # This gives 252.1852 as sample size, 227.1393 as y <- toInteger(x) testthat::test_that("Test: toInteger for survival endpoint event count works properly", code = { # Interim event counts rounded - testthat::expect_equal(min(round(x$n.I[1:x$k - 1]) - y$n.I[1:x$k - 1]), 0) + testthat::expect_equal(min(abs(round(x$n.I[1:x$k - 1]) - y$n.I[1:x$k - 1])), 0) # Final event count round up testthat::expect_true((y$n.I - x$n.I)[x$k] >= 0, info = "Test toInteger of gsSurv rounds up final event count" From a9b5a66b4ff26786f35f2aab00060919609d1967 Mon Sep 17 00:00:00 2001 From: Nan Xiao Date: Wed, 13 Nov 2024 23:25:42 -0500 Subject: [PATCH 08/11] Fix mismatch brackets in toInteger Rd --- R/toInteger.R | 43 ++++++++++++++++++++++---------------- man/toInteger.Rd | 54 ++++++++++++++++++++++++++++++++++-------------- 2 files changed, 63 insertions(+), 34 deletions(-) diff --git a/R/toInteger.R b/R/toInteger.R index b55a6772..c7dc1e3c 100644 --- a/R/toInteger.R +++ b/R/toInteger.R @@ -2,15 +2,16 @@ #' or sample size (other designs) #' #' @param x An object of class \code{gsDesign} or \code{gsSurv}. -#' @param ratio Usually corresponds to -#' experimental:control sample size ratio. -#' If an integer is provided, rounding is done to a multiple of \code{ratio + 1}. -#' See details. -#' If input is non integer, rounding is done to the nearest integer or nearest larger integer depending on `roundUpFinal`. +#' @param ratio Usually corresponds to experimental:control sample size ratio. +#' If an integer is provided, rounding is done to a multiple of +#' \code{ratio + 1}. See details. +#' If input is non integer, rounding is done to the nearest integer or +#' nearest larger integer depending on \code{roundUpFinal}. #' @param roundUpFinal Sample size is rounded up to a value of \code{ratio + 1} -#' with the default \code{roundUpFinal = TRUE} if `ratio` is a non-negative integer. -#' If \code{roundUpFinal = FALSE} and `ratio` is a non-negative integer, sample size is rounded to the nearest -#' multiple of \code{ratio + 1}. +#' with the default \code{roundUpFinal = TRUE} if \code{ratio} is a +#' non-negative integer. +#' If \code{roundUpFinal = FALSE} and \code{ratio} is a non-negative integer, +#' sample size is rounded to the nearest multiple of \code{ratio + 1}. #' For event counts, \code{roundUpFinal = TRUE} rounds final event count up; #' otherwise, just rounded if \code{roundUpFinal = FALSE}. #' See details. @@ -20,18 +21,24 @@ #' with integer vector \code{n.I} and integer total sample size. See details. #' #' @details -#' It is useful to explicitly provide the argument \code{ratio} when a \code{gsDesign} object is input since \code{gsDesign} -#' does not have a \code{ratio} in return. -#' \code{ratio = 0, roundUpFinal = TRUE} will just round up the sample size (also event count). +#' It is useful to explicitly provide the argument \code{ratio} when a +#' \code{gsDesign} object is input since \code{gsDesign} does not have a +#' \code{ratio} in return. +#' \code{ratio = 0, roundUpFinal = TRUE} will just round up the sample size +#' (also event count). #' Rounding of event count targets is not impacted by \code{ratio}. -#' Since \code{x <- gsSurv(ratio = M)} returns a value for \code{ratio}, \code{toInteger(x)} will round to a multiple of -#' \code{M + 1} if \code{M} is a non-negative integer; otherwise, just rounding will occur. -#' The most common example would be if there is 1:1 randomization (2:1) and the user wishes an even (multiple of 3) sample size, -#' then \code{toInteger) will operate as expected. +#' Since \code{x <- gsSurv(ratio = M)} returns a value for \code{ratio}, +#' \code{toInteger(x)} will round to a multiple of \code{M + 1} if \code{M} +#' is a non-negative integer; otherwise, just rounding will occur. +#' The most common example would be if there is 1:1 randomization (2:1) and +#' the user wishes an even (multiple of 3) sample size, then \code{toInteger()} +#' will operate as expected. #' To just round without concern for randomization ratio, set \code{ratio = 0}. -#' If \code{toInteger(x, ratio = 3)}, rounding for final sample size is done to a multiple of 3 + 1 = 4; -#' this could represent a 3:1 or 1:3 randomization ratio. -#' For 3:2 randomization, \code{ratio = 4} would ensure rounding sample size to a multiple of 5. +#' If \code{toInteger(x, ratio = 3)}, rounding for final sample size is done +#' to a multiple of 3 + 1 = 4; this could represent a 3:1 or 1:3 +#' randomization ratio. +#' For 3:2 randomization, \code{ratio = 4} would ensure rounding sample size +#' to a multiple of 5. #' #' @export #' diff --git a/man/toInteger.Rd b/man/toInteger.Rd index 420f765c..d1e12e41 100644 --- a/man/toInteger.Rd +++ b/man/toInteger.Rd @@ -5,33 +5,54 @@ \title{Translate group sequential design to integer events (survival designs) or sample size (other designs)} \usage{ -toInteger(x, ratio = 0, roundUpFinal = TRUE) +toInteger(x, ratio = x$ratio, roundUpFinal = TRUE) } \arguments{ \item{x}{An object of class \code{gsDesign} or \code{gsSurv}.} -\item{ratio}{A non-negative integer, usually corresponding to experimental:control sample size ratio. -Rounding is done to a multiple of \code{ratio + 1}. If input \code{x} has class \code{gsSurv} (design for time-to-event outcome), -and \code{x$ratio} is a whole number, \code{ratio} is replaced by \code{x$ratio}. -See details.} +\item{ratio}{Usually corresponds to experimental:control sample size ratio. +If an integer is provided, rounding is done to a multiple of +\code{ratio + 1}. See details. +If input is non integer, rounding is done to the nearest integer or +nearest larger integer depending on \code{roundUpFinal}.} -\item{roundUpFinal}{Final value in returned \code{n.I} is rounded up -if \code{TRUE}; otherwise, just rounded. For \code{gsSurv} input, final total sample size is also controlled by this. See details.} +\item{roundUpFinal}{Sample size is rounded up to a value of \code{ratio + 1} +with the default \code{roundUpFinal = TRUE} if \code{ratio} is a +non-negative integer. +If \code{roundUpFinal = FALSE} and \code{ratio} is a non-negative integer, +sample size is rounded to the nearest multiple of \code{ratio + 1}. +For event counts, \code{roundUpFinal = TRUE} rounds final event count up; +otherwise, just rounded if \code{roundUpFinal = FALSE}. +See details.} } \value{ -Output is an object of the same class as input \code{x}; i.e., \code{gsDesign} with integer vector for \code{n.I} -or \code{gsSurv} with integer vector \code{n.I} and integer total sample size. See details. +Output is an object of the same class as input \code{x}; i.e., + \code{gsDesign} with integer vector for \code{n.I} or \code{gsSurv} + with integer vector \code{n.I} and integer total sample size. See details. } \description{ Translate group sequential design to integer events (survival designs) or sample size (other designs) } \details{ -If \code{ratio = 3}, rounding for final sample size is done to a multiple of 3 + 1 = 4. -For a \code{gsSurv} object input in \code{x}, event counts output in \code{n.I} are rounded to nearest integer and -final total sample size is rounded to a multiple of \code{ratio + 1}. -For other input values of \code{x} (\code{gsDesign} class), \code{n.I} is interpreted as sample size; -final value is rounded to a multiple of \code{ratio + 1}, with \code{roundUpFinal} controlling rounding of last value. +It is useful to explicitly provide the argument \code{ratio} when a +\code{gsDesign} object is input since \code{gsDesign} does not have a +\code{ratio} in return. +\code{ratio = 0, roundUpFinal = TRUE} will just round up the sample size +(also event count). +Rounding of event count targets is not impacted by \code{ratio}. +Since \code{x <- gsSurv(ratio = M)} returns a value for \code{ratio}, +\code{toInteger(x)} will round to a multiple of \code{M + 1} if \code{M} +is a non-negative integer; otherwise, just rounding will occur. +The most common example would be if there is 1:1 randomization (2:1) and +the user wishes an even (multiple of 3) sample size, then \code{toInteger()} +will operate as expected. +To just round without concern for randomization ratio, set \code{ratio = 0}. +If \code{toInteger(x, ratio = 3)}, rounding for final sample size is done +to a multiple of 3 + 1 = 4; this could represent a 3:1 or 1:3 +randomization ratio. +For 3:2 randomization, \code{ratio = 4} would ensure rounding sample size +to a multiple of 5. } \examples{ # The following code derives the group sequential design using the method @@ -57,6 +78,7 @@ x <- gsSurv( minfup = 8, # Planned minimum follow-up ratio = 3 # Randomization ratio (experimental:control) ) -# Convert bounds to exact binomial bounds -toInteger(x, ratio = 3) +# Convert sample size to multiple of ratio + 1 = 4, round event counts. +# Default is to round up both event count and sample size for final analysis +toInteger(x) } From d9ab01ba20dc9ce8236dae64e0f495d2ad924ac9 Mon Sep 17 00:00:00 2001 From: Nan Xiao Date: Wed, 13 Nov 2024 23:38:14 -0500 Subject: [PATCH 09/11] Format toInteger developer tests --- .../testthat/test-developer-test-toInteger.R | 68 +++++++++++-------- 1 file changed, 40 insertions(+), 28 deletions(-) diff --git a/tests/testthat/test-developer-test-toInteger.R b/tests/testthat/test-developer-test-toInteger.R index a00f4efd..74716a60 100644 --- a/tests/testthat/test-developer-test-toInteger.R +++ b/tests/testthat/test-developer-test-toInteger.R @@ -1,56 +1,68 @@ -# Test toInteger function -#---------------------------------------------- - -### Test toInteger -testthat::test_that("Test: toInteger with invalid input", code = { +# Test toInteger +test_that("Test: toInteger with invalid input", { M <- "a" - testthat::expect_error(toInteger(x = M), + + expect_error( + toInteger(x = M), info = "Test toInteger with invalid input" ) }) -x <- gsDesign(n.fix = 100) -testthat::test_that("Test: toInteger for fractional and negative values", code = { +test_that("Test: toInteger for fractional and negative values", { + x <- gsDesign(n.fix = 100) M <- 55.9 - testthat::expect_message(toInteger(x, ratio = M), + + expect_message( + toInteger(x, ratio = M), info = "Test toInteger for floating values" ) M <- -1 - testthat::expect_message(toInteger(x, ratio = M), - info = "Test toInteger for negative value of ratio" + expect_message( + toInteger(x, ratio = M), + info = "Test toInteger for negative value of ratio" ) # Note that x$ratio = NULL - testthat::expect_message(toInteger(x), - info = "Test toInteger for NULL value of ratio" + expect_message( + toInteger(x), + info = "Test toInteger for NULL value of ratio" ) }) -testthat::test_that("Test: toInteger for even sample size", code = { - testthat::expect_true(max(toInteger(x, ratio = 1)$n.I) %% 2 == 0, +test_that("Test: toInteger for even sample size", { + x <- gsDesign(n.fix = 100) + + expect_true( + max(toInteger(x, ratio = 1)$n.I) %% 2 == 0, info = "Test toInteger for floating value with decimal value 0" ) }) -testthat::test_that("Test: toInteger for multiple of 5", code = { - testthat::expect_true(max(toInteger(x, ratio = 4)$n.I) %% 5 == 0, - info = "Test toInteger for floating value with decimal value 0" +test_that("Test: toInteger for multiple of 5", { + x <- gsDesign(n.fix = 100) + + expect_true( + max(toInteger(x, ratio = 4)$n.I) %% 5 == 0, + info = "Test toInteger for floating value with decimal value 0" ) }) # Now test survival endpoint -x <- gsSurvCalendar(hr = .64) # This gives 252.1852 as sample size, 227.1393 as final event count -# Should round event counts (up for final) and -# round up final event count final sample size only as well -y <- toInteger(x) -testthat::test_that("Test: toInteger for survival endpoint event count works properly", code = { +test_that("Test: toInteger for survival endpoint event count works properly", { + # This gives 252.1852 as sample size, 227.1393 as final event count + x <- gsSurvCalendar(hr = 0.64) + # Should round event counts (up for final) and + # round up final event count final sample size only as well + y <- toInteger(x) + # Interim event counts rounded - testthat::expect_equal(min(abs(round(x$n.I[1:x$k - 1]) - y$n.I[1:x$k - 1])), 0) + expect_equal(min(abs(round(x$n.I[1:x$k - 1]) - y$n.I[1:x$k - 1])), 0) # Final event count round up - testthat::expect_true((y$n.I - x$n.I)[x$k] >= 0, - info = "Test toInteger of gsSurv rounds up final event count" + expect_true( + (y$n.I - x$n.I)[x$k] >= 0, + info = "Test toInteger of gsSurv rounds up final event count" ) # Final sample size rounds to even - testthat::expect_true((y$eNC + y$eNE)[x$k] %% 2 == 0) + expect_true((y$eNC + y$eNE)[x$k] %% 2 == 0) # Final event count rounds up - testthat::expect_gte((y$eNC + y$eNE - x$eNC - x$eNE)[x$k], 0) + expect_gte((y$eNC + y$eNE - x$eNC - x$eNE)[x$k], 0) }) From 7cd87bcdcbb86bf3204eeacb1d716f165e7bc7ce Mon Sep 17 00:00:00 2001 From: Nan Xiao Date: Wed, 13 Nov 2024 23:42:22 -0500 Subject: [PATCH 10/11] Fix modulus floating point issue in toInteger test --- tests/testthat/test-developer-test-toInteger.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testthat/test-developer-test-toInteger.R b/tests/testthat/test-developer-test-toInteger.R index 74716a60..2a738987 100644 --- a/tests/testthat/test-developer-test-toInteger.R +++ b/tests/testthat/test-developer-test-toInteger.R @@ -62,7 +62,7 @@ test_that("Test: toInteger for survival endpoint event count works properly", { info = "Test toInteger of gsSurv rounds up final event count" ) # Final sample size rounds to even - expect_true((y$eNC + y$eNE)[x$k] %% 2 == 0) + expect_true(as.integer((y$eNC + y$eNE)[x$k]) %% 2 == 0) # Final event count rounds up expect_gte((y$eNC + y$eNE - x$eNC - x$eNE)[x$k], 0) }) From 7b7672fd982db1457b393ca95f85945dc6aa0e66 Mon Sep 17 00:00:00 2001 From: Nan Xiao Date: Wed, 13 Nov 2024 23:50:33 -0500 Subject: [PATCH 11/11] Polish toInteger vignette format --- vignettes/toInteger.Rmd | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/vignettes/toInteger.Rmd b/vignettes/toInteger.Rmd index 5c32f003..1f46eb66 100644 --- a/vignettes/toInteger.Rmd +++ b/vignettes/toInteger.Rmd @@ -20,16 +20,19 @@ This has a couple of implications on design characteristics: - Power on output will not be exactly as input. This document goes through examples to demonstrate the calculations. -The new function as of July, 2023 is the `toInteger()` which operates on group sequential designs to convert to integer-based total sample size and event counts at analyses. -As of November, 2024 the rounding defaults are changing as documented below. +The new function as of July 2023 is the `toInteger()` which operates on group sequential designs to convert to integer-based total sample size and event counts at analyses. +As of November 2024, the rounding defaults are changing as documented below. We begin with a summary of the method. Then we provide an abbreviated example for a time-to-event endpoint design to demonstrate basic concepts. We follow with a more extended example for a binary endpoint to explain more details. ## Summary of method -```{r} +```{r,message=FALSE, warning=FALSE} library(gsDesign) +``` + +```{r} x <- gsSurv(ratio = 1, hr = .74) y <- x |> toInteger() # Continuous event counts @@ -46,13 +49,11 @@ z <- x |> toInteger(roundUpFinal = FALSE) as.numeric(z$eNE + z$eNC) ``` - - When `ratio` is a positive integer, the final sample size is rounded to a multiple of `ratio + 1`. - For 1:1 randomization (experimental:control), set `ratio = 1` to round to an even sample size. - For 2:1 randomization, set `ratio = 2` to round to a multiple of 3. - For 3:2 randomization, set `ratio = 4` to round to a multiple of 5. - Note that for the final analysis the sample size is rounded up to the nearest multiple of `ratio + 1` when `roundUpFinal = TRUE` is specified. If `roundUpFinal = FALSE`, the final sample size is rounded to the nearest multiple of `ratio + 1`. - ## Binomial endpoint designs @@ -110,7 +111,6 @@ x_integer$timing These differences also make a difference in the cumulative Type I error associated with each analysis as shown below. - ```{r} # Continuous sample size design cumsum(xb$upper$prob[, 1])