Nothing
#' Go/NoGo/Gray Decision Probabilities for a Clinical Trial with a Single Binary Endpoint
#'
#' Evaluates operating characteristics (Go, NoGo, Gray probabilities) for binary-outcome
#' clinical trials under the Bayesian framework by enumerating all possible trial
#' outcomes. The function supports controlled, uncontrolled, and external designs.
#'
#' @param prob A character string specifying the probability type.
#' Must be \code{'posterior'} or \code{'predictive'}.
#' @param design A character string specifying the trial design.
#' Must be \code{'controlled'}, \code{'uncontrolled'}, or
#' \code{'external'}.
#' @param theta_TV A numeric scalar giving the target value (TV) threshold used for
#' the Go decision when \code{prob = 'posterior'}. Set to \code{NULL} when
#' \code{prob = 'predictive'}.
#' @param theta_MAV A numeric scalar giving the minimum acceptable value (MAV)
#' threshold used for the NoGo decision when \code{prob = 'posterior'}.
#' Must satisfy \code{theta_TV > theta_MAV}. Set to \code{NULL} when
#' \code{prob = 'predictive'}.
#' @param theta_NULL A numeric scalar giving the null hypothesis threshold used for
#' both Go and NoGo decisions when \code{prob = 'predictive'}. Set to
#' \code{NULL} when \code{prob = 'posterior'}.
#' @param gamma_go A numeric scalar in \code{(0, 1)} giving the minimum posterior or
#' predictive probability required for a Go decision.
#' @param gamma_nogo A numeric scalar in \code{(0, 1)} giving the minimum posterior or
#' predictive probability required for a NoGo decision. No ordering
#' constraint on \code{gamma_go} and \code{gamma_nogo} is imposed, though
#' their combination determines the frequency of Miss outcomes.
#' @param pi_t A numeric value or vector giving the true response probability(s) for
#' the treatment group used to evaluate operating characteristics. Each element
#' must be in \code{(0, 1)}.
#' @param pi_c A numeric value or vector giving the true response probability(s) for
#' the control group. For \code{design = 'uncontrolled'}, this parameter is
#' not used in calculations but must be supplied; it is excluded from the output.
#' When supplied as a vector, must have the same length as \code{pi_t}.
#' @param n_t A positive integer giving the number of patients in the
#' treatment group in the proof-of-concept (PoC) trial.
#' @param n_c A positive integer giving the number of patients in the
#' control group in the PoC trial. For \code{design = 'uncontrolled'},
#' this is the hypothetical control sample size (required for
#' consistency with other designs).
#' @param a_t A positive numeric scalar giving the first shape parameter
#' (alpha) of the prior Beta distribution for the treatment group.
#' @param a_c A positive numeric scalar giving the first shape parameter
#' (alpha) of the prior Beta distribution for the control group.
#' @param b_t A positive numeric scalar giving the second shape parameter
#' (beta) of the prior Beta distribution for the treatment group.
#' @param b_c A positive numeric scalar giving the second shape parameter
#' (beta) of the prior Beta distribution for the control group.
#' @param z A non-negative integer giving the hypothetical number of responders
#' in the control group. Required when \code{design = 'uncontrolled'};
#' otherwise set to \code{NULL}. When used, \code{y_c} should be
#' \code{NULL}.
#' @param m_t A positive integer giving the number of patients in the
#' treatment group for the future trial. Required when
#' \code{prob = 'predictive'}; otherwise set to \code{NULL}.
#' @param m_c A positive integer giving the number of patients in the
#' control group for the future trial. Required when
#' \code{prob = 'predictive'}; otherwise set to \code{NULL}.
#' @param ne_t A positive integer giving the number of patients in the
#' treatment group of the external data set. Required when
#' \code{design = 'external'} and external treatment data are
#' available; otherwise set to \code{NULL}.
#' @param ne_c A positive integer giving the number of patients in the
#' control group of the external data set. Required when
#' \code{design = 'external'} and external control data are available;
#' otherwise set to \code{NULL}.
#' @param ye_t A non-negative integer giving the number of responders in the
#' treatment group of the external data set. Required when
#' \code{design = 'external'}; otherwise set to \code{NULL}.
#' @param ye_c A non-negative integer giving the number of responders in the
#' control group of the external data set. Required when
#' \code{design = 'external'}; otherwise set to \code{NULL}.
#' @param alpha0e_t A numeric scalar in \code{(0, 1]} giving the power prior weight for
#' the treatment group. Required when \code{design = 'external'};
#' otherwise \code{NULL}.
#' @param alpha0e_c A numeric scalar in \code{(0, 1]} giving the power prior weight for
#' the control group. Required when \code{design = 'external'};
#' otherwise \code{NULL}.
#' @param error_if_Miss A logical scalar; if \code{TRUE} (default), the function stops
#' with an error if Miss probability > 0, prompting reconsideration of thresholds.
#' @param Gray_inc_Miss A logical scalar; if \code{TRUE}, Miss probability is added
#' to Gray probability. If \code{FALSE} (default), Miss is reported separately.
#' Active only when \code{error_if_Miss = FALSE}.
#'
#' @return A data frame with one row per \code{pi_t} scenario and columns:
#' \describe{
#' \item{pi_t}{True treatment response probability.}
#' \item{pi_c}{True control response probability (omitted for uncontrolled design).}
#' \item{Go}{Probability of making a Go decision.}
#' \item{Gray}{Probability of making a Gray (inconclusive) decision.}
#' \item{NoGo}{Probability of making a NoGo decision.}
#' \item{Miss}{(Optional) Probability where Go and NoGo criteria are simultaneously
#' met. Included when \code{error_if_Miss = FALSE} and
#' \code{Gray_inc_Miss = FALSE}.}
#' }
#' The returned object has S3 class \code{pbayesdecisionprob1bin} with an associated
#' \code{print} method.
#'
#' @details
#' Operating characteristics are computed by exact enumeration:
#' \enumerate{
#' \item All possible outcome pairs \eqn{(y_t, y_c)} with \eqn{y_t \in \{0,\ldots,n_t\}}
#' and \eqn{y_c \in \{0,\ldots,n_c\}} (or fixed at \eqn{z} for uncontrolled) are
#' evaluated.
#' \item For each pair, \code{\link{pbayespostpred1bin}} computes the posterior or predictive
#' probability at both thresholds (TV/MAV or NULL).
#' \item Outcomes are classified into Go, NoGo, Miss, or Gray:
#' \itemize{
#' \item \strong{Go}: \eqn{P(\mathrm{Go}) \ge \gamma_1} AND
#' \eqn{P(\mathrm{NoGo}) < \gamma_2}
#' \item \strong{NoGo}: \eqn{P(\mathrm{Go}) < \gamma_1} AND
#' \eqn{P(\mathrm{NoGo}) \ge \gamma_2}
#' \item \strong{Miss}: both Go and NoGo criteria met simultaneously
#' \item \strong{Gray}: neither Go nor NoGo criteria met
#' }
#' \item Each outcome is weighted by its binomial probability under the true rates.
#' }
#'
#' @examples
#' # Example 1: Controlled design with posterior probability
#' pbayesdecisionprob1bin(
#' prob = 'posterior', design = 'controlled',
#' theta_TV = 0.4, theta_MAV = 0.2, theta_NULL = NULL,
#' gamma_go = 0.8, gamma_nogo = 0.2,
#' pi_t = c(0.2, 0.4, 0.6, 0.8), pi_c = rep(0.2, 4),
#' n_t = 12, n_c = 12,
#' a_t = 0.5, a_c = 0.5, b_t = 0.5, b_c = 0.5,
#' z = NULL, m_t = NULL, m_c = NULL,
#' ne_t = NULL, ne_c = NULL, ye_t = NULL, ye_c = NULL, alpha0e_t = NULL, alpha0e_c = NULL,
#' error_if_Miss = TRUE, Gray_inc_Miss = FALSE
#' )
#'
#' # Example 2: Uncontrolled design with hypothetical control
#' pbayesdecisionprob1bin(
#' prob = 'posterior', design = 'uncontrolled',
#' theta_TV = 0.30, theta_MAV = 0.15, theta_NULL = NULL,
#' gamma_go = 0.75, gamma_nogo = 0.25,
#' pi_t = c(0.3, 0.5, 0.7), pi_c = NULL,
#' n_t = 15, n_c = 15,
#' a_t = 0.5, a_c = 0.5, b_t = 0.5, b_c = 0.5,
#' z = 5, m_t = NULL, m_c = NULL,
#' ne_t = NULL, ne_c = NULL, ye_t = NULL, ye_c = NULL, alpha0e_t = NULL, alpha0e_c = NULL,
#' error_if_Miss = TRUE, Gray_inc_Miss = FALSE
#' )
#'
#' # Example 3: External design with 50 percent power prior borrowing
#' pbayesdecisionprob1bin(
#' prob = 'posterior', design = 'external',
#' theta_TV = 0.4, theta_MAV = 0.2, theta_NULL = NULL,
#' gamma_go = 0.8, gamma_nogo = 0.2,
#' pi_t = c(0.2, 0.4, 0.6, 0.8), pi_c = rep(0.2, 4),
#' n_t = 12, n_c = 12,
#' a_t = 0.5, a_c = 0.5, b_t = 0.5, b_c = 0.5,
#' z = NULL, m_t = NULL, m_c = NULL,
#' ne_t = 15, ne_c = 15, ye_t = 6, ye_c = 4, alpha0e_t = 0.5, alpha0e_c = 0.5,
#' error_if_Miss = TRUE, Gray_inc_Miss = FALSE
#' )
#'
#' # Example 4: Posterior predictive probability for controlled design
#' pbayesdecisionprob1bin(
#' prob = 'predictive', design = 'controlled',
#' theta_TV = NULL, theta_MAV = NULL, theta_NULL = 0,
#' gamma_go = 0.9, gamma_nogo = 0.3,
#' pi_t = c(0.2, 0.4, 0.6, 0.8), pi_c = rep(0.2, 4),
#' n_t = 12, n_c = 12,
#' a_t = 0.5, a_c = 0.5, b_t = 0.5, b_c = 0.5,
#' z = NULL, m_t = 30, m_c = 30,
#' ne_t = NULL, ne_c = NULL, ye_t = NULL, ye_c = NULL, alpha0e_t = NULL, alpha0e_c = NULL,
#' error_if_Miss = TRUE, Gray_inc_Miss = FALSE
#' )
#'
#' # Example 5: Uncontrolled design with posterior predictive probability
#' pbayesdecisionprob1bin(
#' prob = 'predictive', design = 'uncontrolled',
#' theta_TV = NULL, theta_MAV = NULL, theta_NULL = 0,
#' gamma_go = 0.75, gamma_nogo = 0.25,
#' pi_t = c(0.3, 0.5, 0.7), pi_c = NULL,
#' n_t = 15, n_c = 15,
#' a_t = 0.5, a_c = 0.5, b_t = 0.5, b_c = 0.5,
#' z = 5, m_t = 30, m_c = 30,
#' ne_t = NULL, ne_c = NULL, ye_t = NULL, ye_c = NULL, alpha0e_t = NULL, alpha0e_c = NULL,
#' error_if_Miss = TRUE, Gray_inc_Miss = FALSE
#' )
#'
#' # Example 6: External design with posterior predictive probability
#' pbayesdecisionprob1bin(
#' prob = 'predictive', design = 'external',
#' theta_TV = NULL, theta_MAV = NULL, theta_NULL = 0,
#' gamma_go = 0.9, gamma_nogo = 0.3,
#' pi_t = c(0.2, 0.4, 0.6, 0.8), pi_c = rep(0.2, 4),
#' n_t = 12, n_c = 12,
#' a_t = 0.5, a_c = 0.5, b_t = 0.5, b_c = 0.5,
#' z = NULL, m_t = 30, m_c = 30,
#' ne_t = 15, ne_c = 15, ye_t = 6, ye_c = 4, alpha0e_t = 0.5, alpha0e_c = 0.5,
#' error_if_Miss = TRUE, Gray_inc_Miss = FALSE
#' )
#'
#' @importFrom stats dbinom
#' @export
pbayesdecisionprob1bin <- function(prob = 'posterior', design = 'controlled',
theta_TV = NULL, theta_MAV = NULL,
theta_NULL = NULL,
gamma_go, gamma_nogo, pi_t, pi_c = NULL,
n_t, n_c, a_t, a_c, b_t, b_c,
z = NULL, m_t = NULL, m_c = NULL,
ne_t = NULL, ne_c = NULL,
ye_t = NULL, ye_c = NULL,
alpha0e_t = NULL, alpha0e_c = NULL,
error_if_Miss = TRUE,
Gray_inc_Miss = FALSE) {
# --- Input validation ---
if (!is.character(prob) || length(prob) != 1L ||
!prob %in% c('posterior', 'predictive')) {
stop("'prob' must be either 'posterior' or 'predictive'")
}
if (!is.character(design) || length(design) != 1L ||
!design %in% c('controlled', 'uncontrolled', 'external')) {
stop("'design' must be 'controlled', 'uncontrolled', or 'external'")
}
if (!is.numeric(gamma_go) || length(gamma_go) != 1L || is.na(gamma_go) ||
gamma_go <= 0 || gamma_go >= 1) {
stop("'gamma_go' must be a single numeric value in (0, 1)")
}
if (!is.numeric(gamma_nogo) || length(gamma_nogo) != 1L || is.na(gamma_nogo) ||
gamma_nogo <= 0 || gamma_nogo >= 1) {
stop("'gamma_nogo' must be a single numeric value in (0, 1)")
}
for (nm in c("n_t", "n_c")) {
val <- get(nm)
if (!is.numeric(val) || length(val) != 1L || is.na(val) ||
val != floor(val) || val < 1L) {
stop(paste0("'", nm, "' must be a single positive integer"))
}
}
for (nm in c("a_t", "a_c", "b_t", "b_c")) {
val <- get(nm)
if (!is.numeric(val) || length(val) != 1L || is.na(val) || val <= 0) {
stop(paste0("'", nm, "' must be a single positive numeric value"))
}
}
if (!is.logical(error_if_Miss) || length(error_if_Miss) != 1L ||
is.na(error_if_Miss)) {
stop("'error_if_Miss' must be a single logical value (TRUE or FALSE)")
}
if (!is.logical(Gray_inc_Miss) || length(Gray_inc_Miss) != 1L ||
is.na(Gray_inc_Miss)) {
stop("'Gray_inc_Miss' must be a single logical value (TRUE or FALSE)")
}
# Validate pi vectors
pi_t <- as.numeric(pi_t)
pi_c <- as.numeric(pi_c)
if (any(is.na(pi_t)) || any(pi_t <= 0) || any(pi_t >= 1)) {
stop("All elements of 'pi_t' must be in (0, 1)")
}
if (design != 'uncontrolled') {
if (any(is.na(pi_c)) || any(pi_c <= 0) || any(pi_c >= 1)) {
stop("All elements of 'pi_c' must be in (0, 1)")
}
if (length(pi_t) != length(pi_c)) {
stop("'pi_t' and 'pi_c' must have the same length")
}
}
# Validate prob-specific parameters
if (prob == 'posterior') {
if (is.null(theta_TV) || is.null(theta_MAV)) {
stop("'theta_TV' and 'theta_MAV' must be non-NULL when prob = 'posterior'")
}
if (!is.numeric(theta_TV) || length(theta_TV) != 1L || is.na(theta_TV)) {
stop("'theta_TV' must be a single numeric value")
}
if (!is.numeric(theta_MAV) || length(theta_MAV) != 1L || is.na(theta_MAV)) {
stop("'theta_MAV' must be a single numeric value")
}
if (theta_TV <= theta_MAV) {
stop("'theta_TV' must be strictly greater than 'theta_MAV'")
}
} else {
if (is.null(theta_NULL)) {
stop("'theta_NULL' must be non-NULL when prob = 'predictive'")
}
if (!is.numeric(theta_NULL) || length(theta_NULL) != 1L || is.na(theta_NULL)) {
stop("'theta_NULL' must be a single numeric value")
}
if (is.null(m_t) || is.null(m_c)) {
stop("'m_t' and 'm_c' must be non-NULL when prob = 'predictive'")
}
if (!is.numeric(m_t) || length(m_t) != 1L || is.na(m_t) ||
m_t != floor(m_t) || m_t < 1L) {
stop("'m_t' must be a single positive integer")
}
if (!is.numeric(m_c) || length(m_c) != 1L || is.na(m_c) ||
m_c != floor(m_c) || m_c < 1L) {
stop("'m_c' must be a single positive integer")
}
}
# Validate design-specific parameters
if (design == 'uncontrolled') {
if (is.null(z)) {
stop("'z' must be non-NULL when design = 'uncontrolled'")
}
if (!is.numeric(z) || length(z) != 1L || is.na(z) ||
z != floor(z) || z < 0L || z > n_c) {
stop("'z' must be a single non-negative integer not exceeding n_c")
}
}
if (design == 'external') {
if (any(sapply(list(ne_t, ne_c, ye_t, ye_c, alpha0e_t, alpha0e_c), is.null))) {
stop("'ne_t', 'ne_c', 'ye_t', 'ye_c', 'alpha0e_t', and 'alpha0e_c' must all be non-NULL when design = 'external'")
}
for (nm in c("ne_t", "ne_c")) {
val <- get(nm)
if (!is.numeric(val) || length(val) != 1L || is.na(val) ||
val != floor(val) || val < 1L) {
stop(paste0("'", nm, "' must be a single positive integer"))
}
}
for (nm in c("ye_t", "ye_c")) {
val <- get(nm)
ne_v <- if (nm == "ye_t") ne_t else ne_c
if (!is.numeric(val) || length(val) != 1L || is.na(val) ||
val != floor(val) || val < 0L || val > ne_v) {
stop(paste0("'", nm, "' must be a single non-negative integer not exceeding the corresponding ne"))
}
}
for (nm in c("alpha0e_t", "alpha0e_c")) {
val <- get(nm)
if (!is.numeric(val) || length(val) != 1L || is.na(val) ||
val <= 0 || val > 1) {
stop(paste0("'", nm, "' must be a single numeric value in (0, 1]"))
}
}
}
# --- Set decision thresholds ---
if (prob == 'posterior') {
# Go threshold: TV, NoGo threshold: MAV
theta0 <- c(theta_TV, theta_MAV)
} else {
# Both thresholds equal to theta_NULL for predictive probability
theta0 <- c(theta_NULL, theta_NULL)
}
# --- Enumerate all possible outcome combinations ---
Y_t <- 0:n_t
if (design == 'uncontrolled') {
all_y_t <- Y_t
all_y_c <- rep(z, length(Y_t))
} else {
grid <- expand.grid(y_t = Y_t, y_c = 0:n_c)
all_y_t <- grid$y_t
all_y_c <- grid$y_c
}
# --- Compute posterior/predictive probabilities for all outcome pairs ---
# Use z argument only for uncontrolled design; pass NULL otherwise
z_arg <- if (design == 'uncontrolled') z else NULL
gPost_Go <- pbayespostpred1bin(
prob = prob, design = design, theta0 = theta0[1],
n_t = n_t, n_c = n_c, y_t = all_y_t, y_c = if (design == 'uncontrolled') NULL else all_y_c,
a_t = a_t, a_c = a_c, b_t = b_t, b_c = b_c,
m_t = m_t, m_c = m_c, z = z_arg,
ne_t = ne_t, ne_c = ne_c, ye_t = ye_t, ye_c = ye_c, alpha0e_t = alpha0e_t, alpha0e_c = alpha0e_c,
lower.tail = FALSE
)
gPost_NoGo <- pbayespostpred1bin(
prob = prob, design = design, theta0 = theta0[2],
n_t = n_t, n_c = n_c, y_t = all_y_t, y_c = if (design == 'uncontrolled') NULL else all_y_c,
a_t = a_t, a_c = a_c, b_t = b_t, b_c = b_c,
m_t = m_t, m_c = m_c, z = z_arg,
ne_t = ne_t, ne_c = ne_c, ye_t = ye_t, ye_c = ye_c, alpha0e_t = alpha0e_t, alpha0e_c = alpha0e_c,
lower.tail = TRUE
)
# --- Decision indicators (Go, NoGo, Miss are mutually exclusive; Gray is the complement) ---
probs_Go <- (gPost_Go >= gamma_go) & (gPost_NoGo < gamma_nogo)
probs_NoGo <- (gPost_Go < gamma_go) & (gPost_NoGo >= gamma_nogo)
probs_Miss <- (gPost_Go >= gamma_go) & (gPost_NoGo >= gamma_nogo)
# --- Weight outcomes by binomial probability under each scenario ---
n_scenarios <- length(pi_t)
GoNogoProb <- matrix(0, nrow = n_scenarios, ncol = 3L)
for (scenario in seq_len(n_scenarios)) {
if (design == 'uncontrolled') {
# y_c is fixed at z; sum over y_t only
binom_w <- dbinom(all_y_t, n_t, pi_t[scenario])
GoNogoProb[scenario, 1L] <- sum(probs_Go * binom_w)
GoNogoProb[scenario, 2L] <- sum(probs_NoGo * binom_w)
GoNogoProb[scenario, 3L] <- sum(probs_Miss * binom_w)
} else {
# Sum over all (y_t, y_c) combinations
w <- dbinom(all_y_t, n_t, pi_t[scenario]) * dbinom(all_y_c, n_c, pi_c[scenario])
GoNogoProb[scenario, 1L] <- sum(probs_Go * w)
GoNogoProb[scenario, 2L] <- sum(probs_NoGo * w)
GoNogoProb[scenario, 3L] <- sum(probs_Miss * w)
}
}
# --- Check for positive Miss probability ---
if (error_if_Miss && sum(GoNogoProb[, 3L]) > 0) {
stop("Positive Miss probability detected. Please re-consider the chosen thresholds.")
}
# --- Gray probability (complement of Go and NoGo) ---
if (Gray_inc_Miss) {
# Include Miss in Gray: Gray = 1 - Go - NoGo
GrayProb <- 1 - rowSums(GoNogoProb[, -3L, drop = FALSE])
} else {
# Exclude Miss from Gray: Gray = 1 - Go - NoGo - Miss
GrayProb <- 1 - rowSums(GoNogoProb[, , drop = FALSE])
}
# --- Build results data frame ---
if (design == 'uncontrolled') {
results <- data.frame(
pi_t = pi_t,
Go = GoNogoProb[, 1L],
Gray = GrayProb,
NoGo = GoNogoProb[, 2L]
)
} else {
results <- data.frame(
pi_t = pi_t,
pi_c = pi_c,
Go = GoNogoProb[, 1L],
Gray = GrayProb,
NoGo = GoNogoProb[, 2L]
)
}
if (!error_if_Miss && !Gray_inc_Miss) {
results$Miss <- GoNogoProb[, 3L]
}
# Address floating-point rounding artefacts
results[results < .Machine$double.eps ^ 0.25] <- 0
# Attach metadata as attributes for use in print()
attr(results, 'prob') <- prob
attr(results, 'design') <- design
attr(results, 'theta_TV') <- theta_TV
attr(results, 'theta_MAV') <- theta_MAV
attr(results, 'theta_NULL') <- theta_NULL
attr(results, 'gamma_go') <- gamma_go
attr(results, 'gamma_nogo') <- gamma_nogo
attr(results, 'n_t') <- n_t
attr(results, 'n_c') <- n_c
attr(results, 'a_t') <- a_t
attr(results, 'a_c') <- a_c
attr(results, 'b_t') <- b_t
attr(results, 'b_c') <- b_c
attr(results, 'z') <- z
attr(results, 'm_t') <- m_t
attr(results, 'm_c') <- m_c
attr(results, 'ne_t') <- ne_t
attr(results, 'ne_c') <- ne_c
attr(results, 'ye_t') <- ye_t
attr(results, 'ye_c') <- ye_c
attr(results, 'alpha0e_t') <- alpha0e_t
attr(results, 'alpha0e_c') <- alpha0e_c
attr(results, 'error_if_Miss') <- error_if_Miss
attr(results, 'Gray_inc_Miss') <- Gray_inc_Miss
# Assign S3 class
class(results) <- c('pbayesdecisionprob1bin', 'data.frame')
return(results)
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.