diff --git a/DESCRIPTION b/DESCRIPTION index 12df7565..789c64dc 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: gsDesign -Version: 3.9.0.9005 +Version: 3.9.0.9006 Title: Group Sequential Design Authors@R: c( person("Keaven", "Anderson", email = "keaven_anderson@merck.com", role = c("aut", "cre")), diff --git a/NEWS.md b/NEWS.md index 3e5ced6d..f1a13961 100644 --- a/NEWS.md +++ b/NEWS.md @@ -62,6 +62,11 @@ - `simBinomialSeasonalExact()` now supports `usTime`/`lsTime` inputs and reports futility stopping probabilities (`futility_stop_rate` with `futility_mc_se`) in scenario summaries. +- `simBinomialSeasonalExact()` now accepts `ve = 0` and `ve < 0`, allowing + null-hypothesis (`ve = 0`) and non-inferiority margin (`ve < 0`) scenarios. + Validation now requires only that `ve` values are finite and less than 1. + A feasibility check verifies that the implied experimental-arm event rates + (`control_event_rate * (1 - ve)`) remain in `[0, 1)` (#267). ## Bug fixes diff --git a/R/simBinomialSeasonalExact.R b/R/simBinomialSeasonalExact.R index 20659218..7b826cb9 100644 --- a/R/simBinomialSeasonalExact.R +++ b/R/simBinomialSeasonalExact.R @@ -13,7 +13,10 @@ #' #' @param gsD A `gsSurv` object with `test.type` 1 or 4. #' @param ve Numeric vector of vaccine efficacy (or prevention efficacy) -#' scenarios to simulate. +#' scenarios to simulate. Each value must be finite and less than 1. +#' `ve = 0` corresponds to equal event rates (superiority null); `ve < 0` +#' corresponds to experimental-arm event rates above control (non-inferiority +#' margin or harmful scenarios). #' @param nsim Integer scalar or vector giving the number of simulations per #' element of `ve`. #' @param control_event_rate Numeric scalar or vector with control seasonal @@ -105,8 +108,8 @@ simBinomialSeasonalExact <- function( if (!(gsD$test.type %in% c(1, 4))) { stop("gsD$test.type must be 1 or 4", call. = FALSE) } - if (!is.numeric(ve) || length(ve) < 1 || any(!is.finite(ve)) || any(ve <= 0) || any(ve >= 1)) { - stop("ve must be a numeric vector with values strictly between 0 and 1", call. = FALSE) + if (!is.numeric(ve) || length(ve) < 1 || any(!is.finite(ve)) || any(ve >= 1)) { + stop("ve must be a numeric vector with finite values less than 1", call. = FALSE) } if (!is.numeric(season_length) || length(season_length) != 1 || !is.finite(season_length) || season_length <= 0) { stop("season_length must be a positive scalar", call. = FALSE) @@ -146,6 +149,10 @@ simBinomialSeasonalExact <- function( any(!is.finite(control_event_rate)) || any(control_event_rate <= 0) || any(control_event_rate >= 1)) { stop("control_event_rate must be a scalar or vector in (0, 1) with one value per ve scenario", call. = FALSE) } + experimental_event_rate <- control_event_rate * (1 - ve) + if (any(experimental_event_rate < 0) || any(experimental_event_rate >= 1)) { + stop("ve and control_event_rate imply experimental event rates outside [0, 1)", call. = FALSE) + } if (!is.logical(adaptive) || length(adaptive) < 1 || any(is.na(adaptive))) { stop("adaptive must be a logical vector with at least one value", call. = FALSE) diff --git a/tests/testthat/test-independent-test-simBinomialSeasonalExact.R b/tests/testthat/test-independent-test-simBinomialSeasonalExact.R index 99d58040..77c04b65 100644 --- a/tests/testthat/test-independent-test-simBinomialSeasonalExact.R +++ b/tests/testthat/test-independent-test-simBinomialSeasonalExact.R @@ -54,7 +54,17 @@ test_that("simBinomialSeasonalExact validates core inputs", { expect_error( simBinomialSeasonalExact(gsD = design, ve = c(0.3, 1)), - "strictly between 0 and 1" + "finite values less than 1" + ) + expect_no_error( + simBinomialSeasonalExact(gsD = design, ve = c(0, 0.3), nsim = c(1, 1)) + ) + expect_no_error( + simBinomialSeasonalExact(gsD = design, ve = c(-0.1, 0, 0.3), nsim = c(1, 1, 1)) + ) + expect_error( + simBinomialSeasonalExact(gsD = design, ve = -10, control_event_rate = 0.5), + "experimental event rates outside" ) expect_error(