R/getgamma2cont.R

Defines functions getgamma2cont

Documented in getgamma2cont

#' Find Optimal Go/NoGo Thresholds for Two Continuous Endpoints
#'
#' Computes the optimal Go threshold \eqn{\gamma_{\mathrm{go}}} and NoGo
#' threshold \eqn{\gamma_{\mathrm{nogo}}} for two continuous endpoints by
#' searching independently over candidate threshold grids.  The two
#' thresholds are calibrated marginally under separate scenarios:
#' \itemize{
#'   \item \eqn{\gamma_{\mathrm{go}}} is the \strong{smallest} value in
#'         \code{gamma_go_grid} such that the marginal Go probability is
#'         strictly less than \code{target_go} under the Go-calibration
#'         scenario (\code{mu_t_go}, \code{Sigma_t_go}, \code{mu_c_go},
#'         \code{Sigma_c_go}); typically the Null scenario.
#'   \item \eqn{\gamma_{\mathrm{nogo}}} is the \strong{smallest} value in
#'         \code{gamma_nogo_grid} such that the marginal NoGo probability is
#'         strictly less than \code{target_nogo} under the NoGo-calibration
#'         scenario (\code{mu_t_nogo}, \code{Sigma_t_nogo}, \code{mu_c_nogo},
#'         \code{Sigma_c_nogo}); typically the Alternative scenario.
#' }
#'
#' @param nsim A positive integer giving the number of Monte Carlo
#'        datasets to simulate per calibration scenario.  Default is
#'        \code{10000L}.
#' @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 prior A character string specifying the prior distribution.
#'        Must be \code{'vague'} or \code{'N-Inv-Wishart'}.
#' @param GoRegions An integer vector of region indices (subset of
#'        \code{1:9}) that constitute the Go region.  The 9 regions are
#'        defined by the cross-classification of the two treatment effects
#'        \eqn{(\theta_1, \theta_2)} relative to \code{(TV1, MAV1)} and
#'        \code{(TV2, MAV2)}: region \eqn{k = (z_1 - 1) \times 3 + z_2}
#'        where \eqn{z_i = 1} if \eqn{\theta_i > TV_i}, \eqn{z_i = 2} if
#'        \eqn{MAV_i < \theta_i \le TV_i}, \eqn{z_i = 3} if
#'        \eqn{\theta_i \le MAV_i}.
#' @param NoGoRegions An integer vector of region indices (subset of
#'        \code{1:9}) that constitute the NoGo region.
#'        Must be disjoint from \code{GoRegions}.
#' @param mu_t_go A length-2 numeric vector giving the true bivariate mean
#'        for the treatment group under the Go-calibration scenario
#'        (typically Null).
#' @param Sigma_t_go A 2x2 positive-definite numeric matrix giving the
#'        true within-group covariance in the treatment group under the
#'        Go-calibration scenario.
#' @param mu_c_go A length-2 numeric vector giving the true bivariate mean
#'        for the control group under the Go-calibration scenario.  Required
#'        for \code{design = 'controlled'} or \code{'external'};
#'        set to \code{NULL} for \code{design = 'uncontrolled'}.
#' @param Sigma_c_go A 2x2 positive-definite numeric matrix giving the
#'        true within-group covariance in the control group under the
#'        Go-calibration scenario.  Required for
#'        \code{design = 'controlled'} or \code{'external'};
#'        set to \code{NULL} for \code{design = 'uncontrolled'}.
#' @param mu_t_nogo A length-2 numeric vector giving the true bivariate mean
#'        for the treatment group under the NoGo-calibration scenario
#'        (typically Alternative).
#' @param Sigma_t_nogo A 2x2 positive-definite numeric matrix giving the
#'        true within-group covariance in the treatment group under the
#'        NoGo-calibration scenario.
#' @param mu_c_nogo A length-2 numeric vector giving the true bivariate mean
#'        for the control group under the NoGo-calibration scenario.  Required
#'        for \code{design = 'controlled'} or \code{'external'};
#'        set to \code{NULL} for \code{design = 'uncontrolled'}.
#' @param Sigma_c_nogo A 2x2 positive-definite numeric matrix giving the
#'        true within-group covariance in the control group under the
#'        NoGo-calibration scenario.  Required for
#'        \code{design = 'controlled'} or \code{'external'};
#'        set to \code{NULL} for \code{design = 'uncontrolled'}.
#' @param target_go A numeric scalar in \code{(0, 1)} giving the upper bound
#'        on the marginal Go probability under the Go-calibration scenario.
#'        The optimal \eqn{\gamma_{\mathrm{go}}} is the smallest grid value
#'        satisfying the constraint.
#' @param target_nogo A numeric scalar in \code{(0, 1)} giving the upper
#'        bound on the marginal NoGo probability under the NoGo-calibration
#'        scenario.  The optimal \eqn{\gamma_{\mathrm{nogo}}} is the smallest
#'        grid value satisfying the constraint.
#' @param n_t A positive integer giving the number of patients in the
#'        treatment group in the PoC trial.
#' @param n_c A positive integer giving the number of patients in the
#'        control group in the PoC trial.  Set to \code{NULL} for
#'        \code{design = 'uncontrolled'}.
#' @param theta_TV1 A numeric scalar giving the TV threshold for
#'        Endpoint 1.  Required when \code{prob = 'posterior'};
#'        otherwise set to \code{NULL}.
#' @param theta_MAV1 A numeric scalar giving the MAV threshold for
#'        Endpoint 1.  Required when \code{prob = 'posterior'};
#'        otherwise set to \code{NULL}.
#' @param theta_TV2 A numeric scalar giving the TV threshold for
#'        Endpoint 2.  Required when \code{prob = 'posterior'};
#'        otherwise set to \code{NULL}.
#' @param theta_MAV2 A numeric scalar giving the MAV threshold for
#'        Endpoint 2.  Required when \code{prob = 'posterior'};
#'        otherwise set to \code{NULL}.
#' @param theta_NULL1 A numeric scalar giving the null hypothesis threshold
#'        for Endpoint 1.  Required when \code{prob = 'predictive'};
#'        otherwise set to \code{NULL}.
#' @param theta_NULL2 A numeric scalar giving the null hypothesis threshold
#'        for Endpoint 2.  Required when \code{prob = 'predictive'};
#'        otherwise set to \code{NULL}.
#' @param m_t A positive integer giving the future sample size for the
#'        treatment group.  Required when \code{prob = 'predictive'};
#'        set to \code{NULL} otherwise.
#' @param m_c A positive integer giving the future sample size for the
#'        control group.  Required when \code{prob = 'predictive'};
#'        set to \code{NULL} otherwise.
#' @param kappa0_t Positive numeric scalar.  NIW prior hyperparameter
#'        \eqn{\kappa_{01}} for the treatment group.  Required when
#'        \code{prior = 'N-Inv-Wishart'}; otherwise \code{NULL}.
#' @param nu0_t Positive numeric scalar.  NIW prior degrees of freedom
#'        \eqn{\nu_{01}} for the treatment group.  Required when
#'        \code{prior = 'N-Inv-Wishart'}; otherwise \code{NULL}.
#' @param mu0_t Length-2 numeric vector.  NIW prior mean \eqn{\mu_{01}}
#'        for the treatment group.  Required when
#'        \code{prior = 'N-Inv-Wishart'}; otherwise \code{NULL}.
#' @param Lambda0_t A 2x2 positive-definite numeric matrix.  NIW prior
#'        scale matrix \eqn{\Lambda_{01}} for the treatment group.  Required
#'        when \code{prior = 'N-Inv-Wishart'}; otherwise \code{NULL}.
#' @param kappa0_c Positive numeric scalar; see \code{kappa0_t}.
#'        For the control group.
#' @param nu0_c Positive numeric scalar; see \code{nu0_t}.
#'        For the control group.
#' @param mu0_c Length-2 numeric vector; see \code{mu0_t}.  For the control
#'        group.  May be required for the vague prior uncontrolled design;
#'        see \code{\link{pbayesdecisionprob2cont}}.
#' @param Lambda0_c A 2x2 matrix; see \code{Lambda0_t}.
#'        For the control group.
#' @param r A positive numeric scalar giving the power prior weight for
#'        the control group when \code{design = 'uncontrolled'} and
#'        \code{prior = 'vague'}.  Otherwise \code{NULL}.
#' @param ne_t A positive integer giving the external treatment sample size.
#'        Required when \code{design = 'external'} and external treatment
#'        data are used; otherwise \code{NULL}.
#' @param ne_c A positive integer giving the external control sample size.
#'        Required when \code{design = 'external'} and external control
#'        data are used; otherwise \code{NULL}.
#' @param alpha0e_t A numeric scalar in \code{(0, 1]} giving the power prior
#'        weight for external treatment data.  Required when external
#'        treatment data are used; otherwise \code{NULL}.
#' @param alpha0e_c A numeric scalar in \code{(0, 1]} giving the power prior
#'        weight for external control data.  Required when external control
#'        data are used; otherwise \code{NULL}.
#' @param bar_ye_t A length-2 numeric vector.  External treatment sample
#'        mean.  Required when external treatment data are used;
#'        otherwise \code{NULL}.
#' @param bar_ye_c A length-2 numeric vector.  External control sample mean.
#'        Required when external control data are used; otherwise \code{NULL}.
#' @param se_t A 2x2 numeric matrix.  External treatment sum-of-squares
#'        matrix.  Required when external treatment data are used;
#'        otherwise \code{NULL}.
#' @param se_c A 2x2 numeric matrix.  External control sum-of-squares
#'        matrix.  Required when external control data are used;
#'        otherwise \code{NULL}.
#' @param nMC A positive integer giving the number of Monte Carlo draws
#'        passed to \code{\link{pbayespostpred2cont}}.  Required when
#'        \code{CalcMethod = 'MC'}.  May be set to \code{NULL} when
#'        \code{CalcMethod = 'MM'} and \eqn{\nu_k > 4}; if
#'        \code{CalcMethod = 'MM'} but \eqn{\nu_k \le 4} causes a fallback
#'        to MC, \code{nMC} must be a positive integer.
#'        Default is \code{NULL}.
#' @param CalcMethod A character string specifying the computation method
#'        passed to \code{\link{pbayespostpred2cont}}.  Must be
#'        \code{'MC'} (default) or \code{'MM'}.
#' @param gamma_go_grid A numeric vector of candidate Go threshold values
#'        in \code{(0, 1)} to search over.  Defaults to
#'        \code{seq(0.01, 0.99, by = 0.01)}.
#' @param gamma_nogo_grid A numeric vector of candidate NoGo threshold
#'        values in \code{(0, 1)} to search over.  Defaults to
#'        \code{seq(0.01, 0.99, by = 0.01)}.
#' @param seed A numeric scalar for reproducible random number generation.
#'        The Go-calibration simulation uses \code{seed} and the
#'        NoGo-calibration simulation uses \code{seed + 1} to ensure
#'        independence between the two scenarios.
#'
#' @return A list of class \code{getgamma2cont} with the following elements:
#' \describe{
#'   \item{gamma_go}{Optimal Go threshold: the smallest value in
#'         \code{gamma_go_grid} for which the marginal
#'         \eqn{\Pr(\mathrm{Go}) < \code{target\_go}} under the
#'         Go-calibration scenario.  \code{NA} if no such value exists.}
#'   \item{gamma_nogo}{Optimal NoGo threshold: the smallest value in
#'         \code{gamma_nogo_grid} for which the marginal
#'         \eqn{\Pr(\mathrm{NoGo}) < \code{target\_nogo}} under the
#'         NoGo-calibration scenario.  \code{NA} if no such value exists.}
#'   \item{PrGo_opt}{Marginal \eqn{\Pr(\mathrm{Go})} at
#'         \code{gamma_go} under the Go-calibration scenario.
#'         \code{NA} if \code{gamma_go} is \code{NA}.}
#'   \item{PrNoGo_opt}{Marginal \eqn{\Pr(\mathrm{NoGo})} at
#'         \code{gamma_nogo} under the NoGo-calibration scenario.
#'         \code{NA} if \code{gamma_nogo} is \code{NA}.}
#'   \item{target_go}{The value of \code{target_go} supplied by the user.}
#'   \item{target_nogo}{The value of \code{target_nogo} supplied by the user.}
#'   \item{grid_results}{A data frame with columns \code{gamma_grid},
#'         \code{PrGo_grid} (marginal Go probability under the Go-calibration
#'         scenario), and \code{PrNoGo_grid} (marginal NoGo probability under
#'         the NoGo-calibration scenario).}
#' }
#'
#' @details
#' The function uses a two-stage simulate-then-sweep strategy:
#'
#' \strong{Stage 1 (simulation and precomputation)}: \code{nsim} bivariate
#' datasets are generated independently for each calibration scenario.
#' For the Go-calibration scenario, datasets are drawn from
#' \eqn{N_2(\mu_{t,\mathrm{go}}, \Sigma_{t,\mathrm{go}})} (and
#' \eqn{N_2(\mu_{c,\mathrm{go}}, \Sigma_{c,\mathrm{go}})} for
#' controlled/external designs); for the NoGo-calibration scenario,
#' the corresponding \code{_nogo} parameters are used.
#' \code{\link{pbayespostpred2cont}} is called once per scenario in
#' vectorised mode to return an \eqn{nsim \times 9} matrix of region
#' probabilities.  The probabilities are summed over \code{GoRegions}
#' (for the Go scenario) and \code{NoGoRegions} (for the NoGo scenario)
#' to obtain \eqn{\hat{g}_{Go,i}} and \eqn{\hat{g}_{NoGo,i}},
#' independent of the decision thresholds.
#'
#' \strong{Stage 2 (gamma sweep)}: For each pair
#' \eqn{(\gamma_{\mathrm{go}}, \gamma_{\mathrm{nogo}})} in the
#' two-dimensional grid, operating characteristics are computed separately
#' under each calibration scenario:
#' \deqn{\Pr(\mathrm{Go}) = \frac{1}{n_{\mathrm{sim}}} \sum_{i=1}^{n_{\mathrm{sim}}}
#'   \mathbf{1}\!\left[\hat{g}_{Go,i} \ge \gamma_{\mathrm{go}},\;
#'   \hat{g}_{NoGo,i} < \gamma_{\mathrm{nogo}}\right]}
#' \deqn{\Pr(\mathrm{NoGo}) = \frac{1}{n_{\mathrm{sim}}} \sum_{i=1}^{n_{\mathrm{sim}}}
#'   \mathbf{1}\!\left[\hat{g}_{NoGo,i} \ge \gamma_{\mathrm{nogo}},\;
#'   \hat{g}_{Go,i} < \gamma_{\mathrm{go}}\right]}
#'
#' \strong{Stage 3 (optimal threshold selection)}: For each candidate
#' \eqn{\gamma_{\mathrm{go}}}, the worst-case \eqn{\Pr(\mathrm{Go})} over
#' all \eqn{\gamma_{\mathrm{nogo}}} in \code{gamma_nogo_grid} is computed;
#' the optimal \eqn{\gamma_{\mathrm{go}}} is the \emph{smallest} grid value
#' for which this worst-case probability is less than \code{target_go}.
#' Analogously, the optimal \eqn{\gamma_{\mathrm{nogo}}} is the
#' \emph{smallest} grid value for which the worst-case
#' \eqn{\Pr(\mathrm{NoGo})} is less than \code{target_nogo}.
#'
#' @examples
#' # Example 1: Controlled design, posterior probability, vague prior
#' # gamma_go  : smallest gamma_go   s.t. max_{gamma_nogo} Pr(Go)   < 0.05 under Null
#' # gamma_nogo: smallest gamma_nogo s.t. max_{gamma_go}   Pr(NoGo) < 0.20 under Alt
#' \donttest{
#' Sigma_null <- matrix(c(6400.0, 15.0, 15.0, 36.0), 2, 2)
#' Sigma_alt  <- matrix(c(6400.0, 15.0, 15.0, 36.0), 2, 2)
#' getgamma2cont(
#'   nsim = 1000L, prob = 'posterior', design = 'controlled',
#'   prior = 'vague',
#'   GoRegions = 1L, NoGoRegions = 9L,
#'   mu_t_go = c(-5.0, 0.0), Sigma_t_go = Sigma_null,
#'   mu_c_go = c(-10.0, -1.0), Sigma_c_go = Sigma_null,
#'   mu_t_nogo = c(5.0, 1.0), Sigma_t_nogo = Sigma_alt,
#'   mu_c_nogo = c(-10.0, -1.0), Sigma_c_nogo = Sigma_alt,
#'   target_go = 0.05, target_nogo = 0.20,
#'   n_t = 30L, n_c = 30L,
#'   theta_TV1 = 10.0, theta_MAV1 = 5.0,
#'   theta_TV2 = 2.0,  theta_MAV2 = 1.0,
#'   theta_NULL1 = NULL, theta_NULL2 = NULL,
#'   m_t = NULL, m_c = NULL,
#'   kappa0_t = NULL, nu0_t = NULL, mu0_t = NULL, Lambda0_t = NULL,
#'   kappa0_c = NULL, nu0_c = NULL, mu0_c = NULL, Lambda0_c = NULL,
#'   r = NULL,
#'   ne_t = NULL, ne_c = NULL, alpha0e_t = NULL, alpha0e_c = NULL,
#'   bar_ye_t = NULL, bar_ye_c = NULL, se_t = NULL, se_c = NULL,
#'   nMC = 500L, CalcMethod = 'MC',
#'   gamma_go_grid = seq(0.01, 0.99, by = 0.01),
#'   gamma_nogo_grid = seq(0.01, 0.99, by = 0.01),
#'   seed = 1L
#' )
#' }
#'
#' # Example 2: Uncontrolled design, posterior probability, vague prior
#' \donttest{
#' Sigma_null <- matrix(c(6400.0, 15.0, 15.0, 36.0), 2, 2)
#' Sigma_alt  <- matrix(c(6400.0, 15.0, 15.0, 36.0), 2, 2)
#' getgamma2cont(
#'   nsim = 1000L, prob = 'posterior', design = 'uncontrolled',
#'   prior = 'vague',
#'   GoRegions = 1L, NoGoRegions = 9L,
#'   mu_t_go = c(-5.0, 0.0), Sigma_t_go = Sigma_null,
#'   mu_c_go = NULL, Sigma_c_go = NULL,
#'   mu_t_nogo = c(5.0, 1.0), Sigma_t_nogo = Sigma_alt,
#'   mu_c_nogo = NULL, Sigma_c_nogo = NULL,
#'   target_go = 0.05, target_nogo = 0.20,
#'   n_t = 30L, n_c = NULL,
#'   theta_TV1 = 10.0, theta_MAV1 = 5.0,
#'   theta_TV2 = 2.0,  theta_MAV2 = 1.0,
#'   theta_NULL1 = NULL, theta_NULL2 = NULL,
#'   m_t = NULL, m_c = NULL,
#'   kappa0_t = NULL, nu0_t = NULL, mu0_t = NULL, Lambda0_t = NULL,
#'   kappa0_c = NULL, nu0_c = NULL, mu0_c = c(-10.0, -1.0), Lambda0_c = NULL,
#'   r = 1.0,
#'   ne_t = NULL, ne_c = NULL, alpha0e_t = NULL, alpha0e_c = NULL,
#'   bar_ye_t = NULL, bar_ye_c = NULL, se_t = NULL, se_c = NULL,
#'   nMC = NULL, CalcMethod = 'MM',
#'   gamma_go_grid = seq(0.01, 0.99, by = 0.01),
#'   gamma_nogo_grid = seq(0.01, 0.99, by = 0.01),
#'   seed = 1L
#' )
#' }
#'
#' # Example 3: External design (control only), posterior probability, NIW prior
#' \donttest{
#' Sigma  <- matrix(c(6400.0, 15.0, 15.0, 36.0), 2, 2)
#' Lambda <- matrix(c(6400.0, 15.0, 15.0, 36.0), 2, 2)
#' se_c   <- matrix(c(6400.0, 15.0, 15.0, 36.0), 2, 2)
#' getgamma2cont(
#'   nsim = 1000L, prob = 'posterior', design = 'external',
#'   prior = 'N-Inv-Wishart',
#'   GoRegions = 1L, NoGoRegions = 9L,
#'   mu_t_go = c(-5.0, 0.0), Sigma_t_go = Sigma,
#'   mu_c_go = c(-10.0, -1.0), Sigma_c_go = Sigma,
#'   mu_t_nogo = c(5.0, 1.0), Sigma_t_nogo = Sigma,
#'   mu_c_nogo = c(-10.0, -1.0), Sigma_c_nogo = Sigma,
#'   target_go = 0.05, target_nogo = 0.20,
#'   n_t = 30L, n_c = 30L,
#'   theta_TV1 = 10.0, theta_MAV1 = 5.0,
#'   theta_TV2 = 2.0,  theta_MAV2 = 1.0,
#'   theta_NULL1 = NULL, theta_NULL2 = NULL,
#'   m_t = NULL, m_c = NULL,
#'   kappa0_t = 0.1, nu0_t = 4.0, mu0_t = c(0.0, 1.0),  Lambda0_t = Lambda,
#'   kappa0_c = 0.1, nu0_c = 4.0, mu0_c = c(-10.0, -1.0), Lambda0_c = Lambda,
#'   r = NULL,
#'   ne_t = NULL, ne_c = 10L, alpha0e_t = NULL, alpha0e_c = 0.5,
#'   bar_ye_t = NULL, bar_ye_c = c(-10.0, -1.0), se_t = NULL, se_c = se_c,
#'   nMC = 500L, CalcMethod = 'MC',
#'   gamma_go_grid = seq(0.01, 0.99, by = 0.01),
#'   gamma_nogo_grid = seq(0.01, 0.99, by = 0.01),
#'   seed = 1L
#' )
#' }
#'
#' # Example 4: Controlled design, predictive probability, vague prior
#' \donttest{
#' Sigma_null <- matrix(c(6400.0, 15.0, 15.0, 36.0), 2, 2)
#' Sigma_alt  <- matrix(c(6400.0, 15.0, 15.0, 36.0), 2, 2)
#' getgamma2cont(
#'   nsim = 1000L, prob = 'predictive', design = 'controlled',
#'   prior = 'vague',
#'   GoRegions = 1L, NoGoRegions = 4L,
#'   mu_t_go = c(-5.0, 0.0), Sigma_t_go = Sigma_null,
#'   mu_c_go = c(-10.0, -1.0), Sigma_c_go = Sigma_null,
#'   mu_t_nogo = c(5.0, 1.0), Sigma_t_nogo = Sigma_alt,
#'   mu_c_nogo = c(-10.0, -1.0), Sigma_c_nogo = Sigma_alt,
#'   target_go = 0.05, target_nogo = 0.20,
#'   n_t = 30L, n_c = 30L,
#'   theta_TV1 = NULL, theta_MAV1 = NULL,
#'   theta_TV2 = NULL, theta_MAV2 = NULL,
#'   theta_NULL1 = 5.0, theta_NULL2 = 1.0,
#'   m_t = 100L, m_c = 100L,
#'   kappa0_t = NULL, nu0_t = NULL, mu0_t = NULL, Lambda0_t = NULL,
#'   kappa0_c = NULL, nu0_c = NULL, mu0_c = NULL, Lambda0_c = NULL,
#'   r = NULL,
#'   ne_t = NULL, ne_c = NULL, alpha0e_t = NULL, alpha0e_c = NULL,
#'   bar_ye_t = NULL, bar_ye_c = NULL, se_t = NULL, se_c = NULL,
#'   nMC = 500L, CalcMethod = 'MC',
#'   gamma_go_grid = seq(0.01, 0.99, by = 0.01),
#'   gamma_nogo_grid = seq(0.01, 0.99, by = 0.01),
#'   seed = 1L
#' )
#' }
#'
#' # Example 5: Uncontrolled design, predictive probability, vague prior
#' \donttest{
#' Sigma_null <- matrix(c(6400.0, 15.0, 15.0, 36.0), 2, 2)
#' Sigma_alt  <- matrix(c(6400.0, 15.0, 15.0, 36.0), 2, 2)
#' getgamma2cont(
#'   nsim = 1000L, prob = 'predictive', design = 'uncontrolled',
#'   prior = 'vague',
#'   GoRegions = 1L, NoGoRegions = 4L,
#'   mu_t_go = c(-5.0, 0.0), Sigma_t_go = Sigma_null,
#'   mu_c_go = NULL, Sigma_c_go = NULL,
#'   mu_t_nogo = c(5.0, 1.0), Sigma_t_nogo = Sigma_alt,
#'   mu_c_nogo = NULL, Sigma_c_nogo = NULL,
#'   target_go = 0.05, target_nogo = 0.20,
#'   n_t = 30L, n_c = NULL,
#'   theta_TV1 = NULL, theta_MAV1 = NULL,
#'   theta_TV2 = NULL, theta_MAV2 = NULL,
#'   theta_NULL1 = 5.0, theta_NULL2 = 1.0,
#'   m_t = 100L, m_c = 100L,
#'   kappa0_t = NULL, nu0_t = NULL, mu0_t = NULL, Lambda0_t = NULL,
#'   kappa0_c = NULL, nu0_c = NULL, mu0_c = c(-10.0, -1.0), Lambda0_c = NULL,
#'   r = 1.0,
#'   ne_t = NULL, ne_c = NULL, alpha0e_t = NULL, alpha0e_c = NULL,
#'   bar_ye_t = NULL, bar_ye_c = NULL, se_t = NULL, se_c = NULL,
#'   nMC = 500L, CalcMethod = 'MC',
#'   gamma_go_grid = seq(0.01, 0.99, by = 0.01),
#'   gamma_nogo_grid = seq(0.01, 0.99, by = 0.01),
#'   seed = 1L
#' )
#' }
#'
#' # Example 6: External design (control only), predictive probability, NIW prior
#' \donttest{
#' Sigma  <- matrix(c(6400.0, 15.0, 15.0, 36.0), 2, 2)
#' Lambda <- matrix(c(6400.0, 15.0, 15.0, 36.0), 2, 2)
#' se_c   <- matrix(c(6400.0, 15.0, 15.0, 36.0), 2, 2)
#' getgamma2cont(
#'   nsim = 1000L, prob = 'predictive', design = 'external',
#'   prior = 'N-Inv-Wishart',
#'   GoRegions = 1L, NoGoRegions = 4L,
#'   mu_t_go = c(-5.0, 0.0), Sigma_t_go = Sigma,
#'   mu_c_go = c(-10.0, -1.0), Sigma_c_go = Sigma,
#'   mu_t_nogo = c(5.0, 1.0), Sigma_t_nogo = Sigma,
#'   mu_c_nogo = c(-10.0, -1.0), Sigma_c_nogo = Sigma,
#'   target_go = 0.05, target_nogo = 0.20,
#'   n_t = 30L, n_c = 30L,
#'   theta_TV1 = NULL, theta_MAV1 = NULL,
#'   theta_TV2 = NULL, theta_MAV2 = NULL,
#'   theta_NULL1 = 5.0, theta_NULL2 = 1.0,
#'   m_t = 100L, m_c = 100L,
#'   kappa0_t = 0.1, nu0_t = 4.0, mu0_t = c(0.0, 1.0),  Lambda0_t = Lambda,
#'   kappa0_c = 0.1, nu0_c = 4.0, mu0_c = c(-10.0, -1.0), Lambda0_c = Lambda,
#'   r = NULL,
#'   ne_t = NULL, ne_c = 10L, alpha0e_t = NULL, alpha0e_c = 0.5,
#'   bar_ye_t = NULL, bar_ye_c = c(-10.0, -1.0), se_t = NULL, se_c = se_c,
#'   nMC = 500L, CalcMethod = 'MC',
#'   gamma_go_grid = seq(0.01, 0.99, by = 0.01),
#'   gamma_nogo_grid = seq(0.01, 0.99, by = 0.01),
#'   seed = 1L
#' )
#' }
#'
#' @importFrom stats rnorm
#' @export
getgamma2cont <- function(nsim        = 10000L,
                          prob        = 'posterior',
                          design      = 'controlled',
                          prior       = 'vague',
                          GoRegions, NoGoRegions,
                          mu_t_go, Sigma_t_go,
                          mu_c_go    = NULL, Sigma_c_go  = NULL,
                          mu_t_nogo, Sigma_t_nogo,
                          mu_c_nogo  = NULL, Sigma_c_nogo = NULL,
                          target_go, target_nogo,
                          n_t, n_c = NULL,
                          theta_TV1   = NULL, theta_MAV1  = NULL,
                          theta_TV2   = NULL, theta_MAV2  = NULL,
                          theta_NULL1 = NULL, theta_NULL2 = NULL,
                          m_t = NULL, m_c = NULL,
                          kappa0_t  = NULL, nu0_t     = NULL,
                          mu0_t     = NULL, Lambda0_t = NULL,
                          kappa0_c  = NULL, nu0_c     = NULL,
                          mu0_c     = NULL, Lambda0_c = NULL,
                          r        = NULL,
                          ne_t = NULL, ne_c = NULL,
                          alpha0e_t = NULL, alpha0e_c = NULL,
                          bar_ye_t  = NULL, bar_ye_c  = NULL,
                          se_t      = NULL, se_c      = NULL,
                          nMC    = NULL,
                          CalcMethod = 'MC',
                          gamma_go_grid = seq(0.01, 0.99, by = 0.01),
                          gamma_nogo_grid = seq(0.01, 0.99, by = 0.01),
                          seed) {

  # ---------------------------------------------------------------------------
  # Input validation
  # ---------------------------------------------------------------------------
  if (!is.numeric(nsim) || length(nsim) != 1L || is.na(nsim) ||
      nsim != floor(nsim) || nsim < 1L) {
    stop("'nsim' must be a single positive integer")
  }
  nsim <- as.integer(nsim)

  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.character(prior) || length(prior) != 1L ||
      !prior %in% c('vague', 'N-Inv-Wishart')) {
    stop("'prior' must be 'vague' or 'N-Inv-Wishart'")
  }

  if (!is.integer(GoRegions) || length(GoRegions) < 1L ||
      any(is.na(GoRegions)) || any(GoRegions < 1L) || any(GoRegions > 9L)) {
    stop("'GoRegions' must be an integer vector with all values in 1:9")
  }
  if (!is.integer(NoGoRegions) || length(NoGoRegions) < 1L ||
      any(is.na(NoGoRegions)) || any(NoGoRegions < 1L) ||
      any(NoGoRegions > 9L)) {
    stop("'NoGoRegions' must be an integer vector with all values in 1:9")
  }
  if (length(intersect(GoRegions, NoGoRegions)) > 0L) {
    stop("'GoRegions' and 'NoGoRegions' must be disjoint")
  }

  # Validate Go-calibration scenario parameters
  if (!is.numeric(mu_t_go) || length(mu_t_go) != 2L || any(is.na(mu_t_go))) {
    stop("'mu_t_go' must be a length-2 numeric vector")
  }
  if (!is.matrix(Sigma_t_go) || !all(dim(Sigma_t_go) == c(2L, 2L)) ||
      !is.numeric(Sigma_t_go) || any(is.na(Sigma_t_go))) {
    stop("'Sigma_t_go' must be a 2x2 numeric matrix")
  }

  # Validate NoGo-calibration scenario parameters
  if (!is.numeric(mu_t_nogo) || length(mu_t_nogo) != 2L || any(is.na(mu_t_nogo))) {
    stop("'mu_t_nogo' must be a length-2 numeric vector")
  }
  if (!is.matrix(Sigma_t_nogo) || !all(dim(Sigma_t_nogo) == c(2L, 2L)) ||
      !is.numeric(Sigma_t_nogo) || any(is.na(Sigma_t_nogo))) {
    stop("'Sigma_t_nogo' must be a 2x2 numeric matrix")
  }

  if (design != 'uncontrolled') {
    if (is.null(mu_c_go) || !is.numeric(mu_c_go) || length(mu_c_go) != 2L ||
        any(is.na(mu_c_go))) {
      stop("'mu_c_go' must be a length-2 numeric vector for controlled or external design")
    }
    if (is.null(Sigma_c_go) || !is.matrix(Sigma_c_go) ||
        !all(dim(Sigma_c_go) == c(2L, 2L)) || !is.numeric(Sigma_c_go) ||
        any(is.na(Sigma_c_go))) {
      stop("'Sigma_c_go' must be a 2x2 numeric matrix for controlled or external design")
    }
    if (is.null(mu_c_nogo) || !is.numeric(mu_c_nogo) || length(mu_c_nogo) != 2L ||
        any(is.na(mu_c_nogo))) {
      stop("'mu_c_nogo' must be a length-2 numeric vector for controlled or external design")
    }
    if (is.null(Sigma_c_nogo) || !is.matrix(Sigma_c_nogo) ||
        !all(dim(Sigma_c_nogo) == c(2L, 2L)) || !is.numeric(Sigma_c_nogo) ||
        any(is.na(Sigma_c_nogo))) {
      stop("'Sigma_c_nogo' must be a 2x2 numeric matrix for controlled or external design")
    }
  }

  for (nm in c("target_go", "target_nogo")) {
    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)"))
    }
  }

  if (!is.numeric(n_t) || length(n_t) != 1L || is.na(n_t) ||
      n_t != floor(n_t) || n_t < 1L) {
    stop("'n_t' must be a single positive integer")
  }
  if (design != 'uncontrolled') {
    if (is.null(n_c) || !is.numeric(n_c) || length(n_c) != 1L || is.na(n_c) ||
        n_c != floor(n_c) || n_c < 1L) {
      stop("'n_c' must be a single positive integer for controlled or external design")
    }
  }

  if (prob == 'posterior') {
    for (nm in c("theta_TV1", "theta_MAV1", "theta_TV2", "theta_MAV2")) {
      if (is.null(get(nm))) {
        stop(paste0("'", nm, "' must be non-NULL when prob = 'posterior'"))
      }
    }
    if (theta_TV1 <= theta_MAV1) {
      stop("'theta_TV1' must be strictly greater than 'theta_MAV1'")
    }
    if (theta_TV2 <= theta_MAV2) {
      stop("'theta_TV2' must be strictly greater than 'theta_MAV2'")
    }
  } else {
    for (nm in c("theta_NULL1", "theta_NULL2")) {
      if (is.null(get(nm))) {
        stop(paste0("'", nm, "' must be non-NULL when prob = 'predictive'"))
      }
    }
    if (is.null(m_t) || !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 when prob = 'predictive'")
    }
    if (is.null(m_c) || !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 when prob = 'predictive'")
    }
  }

  if (!is.character(CalcMethod) || length(CalcMethod) != 1L ||
      !CalcMethod %in% c('MC', 'MM')) {
    stop("'CalcMethod' must be either 'MC' or 'MM'")
  }
  if (CalcMethod == 'MC') {
    if (is.null(nMC) || !is.numeric(nMC) || length(nMC) != 1L ||
        is.na(nMC) || nMC != floor(nMC) || nMC < 1L) {
      stop("'nMC' must be a single positive integer when CalcMethod = 'MC'")
    }
  }

  for (gname in c("gamma_go_grid", "gamma_nogo_grid")) {
    gval <- get(gname)
    if (!is.numeric(gval) || length(gval) < 1L ||
        any(is.na(gval)) || any(gval <= 0) || any(gval >= 1)) {
      stop(paste0("'", gname, "' must be a numeric vector with all values in (0, 1)"))
    }
  }
  gamma_go_grid   <- sort(unique(gamma_go_grid))
  gamma_nogo_grid <- sort(unique(gamma_nogo_grid))
  if (!identical(gamma_go_grid, gamma_nogo_grid)) {
    stop("'gamma_go_grid' and 'gamma_nogo_grid' must be identical vectors")
  }
  gamma_grid <- gamma_go_grid
  ng         <- length(gamma_grid)

  if (!is.numeric(seed) || length(seed) != 1L || is.na(seed)) {
    stop("'seed' must be a single numeric value")
  }

  # ---------------------------------------------------------------------------
  # Internal helper: simulate nsim datasets and return PrGo_vec, PrNoGo_vec
  # ---------------------------------------------------------------------------
  .simulate_g <- function(mu_t_s, Sigma_t_s, mu_c_s, Sigma_c_s, seed_val) {
    set.seed(seed_val)

    n_t_int <- as.integer(n_t)
    R_Sigma_t   <- chol(Sigma_t_s)
    Z_t_raw     <- matrix(rnorm(nsim * n_t_int * 2L),
                          nrow = nsim * n_t_int, ncol = 2L) %*% R_Sigma_t
    block_t     <- rep(seq_len(nsim), each = n_t_int)
    Z_t_colsums <- apply(Z_t_raw, 2L, function(col) tapply(col, block_t, sum))
    Z_t_colmeans_rep <- Z_t_colsums[block_t, ] / n_t_int
    Z_t_centered     <- Z_t_raw - Z_t_colmeans_rep
    S_t_11 <- tapply(Z_t_centered[, 1L] ^ 2,                  block_t, sum)
    S_t_12 <- tapply(Z_t_centered[, 1L] * Z_t_centered[, 2L], block_t, sum)
    S_t_22 <- tapply(Z_t_centered[, 2L] ^ 2,                  block_t, sum)
    ybar_t <- sweep(Z_t_colsums / n_t_int, 2L, mu_t_s, '+')
    S_t_list <- vector('list', nsim)
    for (i in seq_len(nsim)) {
      S_t_list[[i]] <- matrix(c(S_t_11[i], S_t_12[i], S_t_12[i], S_t_22[i]),
                              nrow = 2L, ncol = 2L)
    }

    if (design %in% c('controlled', 'external')) {
      n_c_int <- as.integer(n_c)
      R_Sigma_c   <- chol(Sigma_c_s)
      Z_c_raw     <- matrix(rnorm(nsim * n_c_int * 2L),
                            nrow = nsim * n_c_int, ncol = 2L) %*% R_Sigma_c
      block_c     <- rep(seq_len(nsim), each = n_c_int)
      Z_c_colsums <- apply(Z_c_raw, 2L, function(col) tapply(col, block_c, sum))
      Z_c_colmeans_rep <- Z_c_colsums[block_c, ] / n_c_int
      Z_c_centered     <- Z_c_raw - Z_c_colmeans_rep
      S_c_11 <- tapply(Z_c_centered[, 1L] ^ 2,                  block_c, sum)
      S_c_12 <- tapply(Z_c_centered[, 1L] * Z_c_centered[, 2L], block_c, sum)
      S_c_22 <- tapply(Z_c_centered[, 2L] ^ 2,                  block_c, sum)
      ybar_c <- sweep(Z_c_colsums / n_c_int, 2L, mu_c_s, '+')
      S_c_list <- vector('list', nsim)
      for (i in seq_len(nsim)) {
        S_c_list[[i]] <- matrix(c(S_c_11[i], S_c_12[i], S_c_12[i], S_c_22[i]),
                                nrow = 2L, ncol = 2L)
      }
    } else {
      ybar_c   <- NULL
      S_c_list <- NULL
      n_c_int  <- NULL
    }

    # Vectorised call to pbayespostpred2cont: returns nsim x n_regions matrix
    Pr_R_mat <- pbayespostpred2cont(
      prob        = prob,
      design      = design,
      prior       = prior,
      theta_TV1   = theta_TV1,   theta_MAV1  = theta_MAV1,
      theta_TV2   = theta_TV2,   theta_MAV2  = theta_MAV2,
      theta_NULL1 = theta_NULL1, theta_NULL2 = theta_NULL2,
      n_t = n_t_int, n_c = n_c_int,
      ybar_t = ybar_t, S_t = S_t_list,
      ybar_c = ybar_c, S_c = S_c_list,
      m_t = m_t, m_c = m_c,
      kappa0_t  = kappa0_t,  nu0_t    = nu0_t,
      mu0_t     = mu0_t,     Lambda0_t = Lambda0_t,
      kappa0_c  = kappa0_c,  nu0_c    = nu0_c,
      mu0_c     = mu0_c,     Lambda0_c = Lambda0_c,
      r         = r,
      ne_t = ne_t, ne_c = ne_c,
      alpha0e_t = alpha0e_t, alpha0e_c = alpha0e_c,
      bar_ye_t  = bar_ye_t,  bar_ye_c  = bar_ye_c,
      se_t      = se_t,      se_c      = se_c,
      nMC    = nMC,
      CalcMethod = CalcMethod
    )

    # Sum region probabilities over GoRegions and NoGoRegions
    PrGo_vec   <- rowSums(Pr_R_mat[, GoRegions,   drop = FALSE])
    PrNoGo_vec <- rowSums(Pr_R_mat[, NoGoRegions, drop = FALSE])

    list(PrGo_vec = PrGo_vec, PrNoGo_vec = PrNoGo_vec)
  }

  # ---------------------------------------------------------------------------
  # Stage 1: Simulate nsim datasets for each calibration scenario independently
  # ---------------------------------------------------------------------------
  # Go-calibration scenario (typically Null): use seed
  sim_go   <- .simulate_g(mu_t_go,   Sigma_t_go,   mu_c_go,   Sigma_c_go,
                          seed_val = seed)
  # NoGo-calibration scenario (typically Alternative): use seed + 1
  sim_nogo <- .simulate_g(mu_t_nogo, Sigma_t_nogo, mu_c_nogo, Sigma_c_nogo,
                          seed_val = seed + 1L)

  PrGo_vec_go     <- sim_go$PrGo_vec
  PrNoGo_vec_nogo <- sim_nogo$PrNoGo_vec

  # ---------------------------------------------------------------------------
  # Stage 2: Marginal sweep over gamma_grid
  #
  # Go and NoGo probabilities are computed marginally (independently):
  #   Pr(Go)   = mean( I(PrGo_vec_go   >= g) )
  #   Pr(NoGo) = mean( I(PrNoGo_vec_nogo >= g) )
  # ---------------------------------------------------------------------------
  PrGo_grid   <- numeric(ng)
  PrNoGo_grid <- numeric(ng)

  for (k in seq_len(ng)) {
    g <- gamma_grid[k]
    PrGo_grid[k]   <- mean(PrGo_vec_go   >= g)
    PrNoGo_grid[k] <- mean(PrNoGo_vec_nogo >= g)
  }

  # ---------------------------------------------------------------------------
  # Stage 3: Select optimal (gamma_go, gamma_nogo)
  #
  # gamma_go  : smallest value in gamma_grid s.t. Pr(Go)   < target_go
  # gamma_nogo: smallest value in gamma_grid s.t. Pr(NoGo) < target_nogo
  # Both curves are non-increasing in gamma.
  # ---------------------------------------------------------------------------
  idx_go <- which(PrGo_grid < target_go)
  if (length(idx_go) == 0L) {
    gamma_go <- NA_real_
    PrGo_opt <- NA_real_
  } else {
    opt1     <- min(idx_go)
    gamma_go <- gamma_grid[opt1]
    PrGo_opt <- PrGo_grid[opt1]
  }

  idx_nogo <- which(PrNoGo_grid < target_nogo)
  if (length(idx_nogo) == 0L) {
    gamma_nogo <- NA_real_
    PrNoGo_opt <- NA_real_
  } else {
    opt2       <- min(idx_nogo)
    gamma_nogo <- gamma_grid[opt2]
    PrNoGo_opt <- PrNoGo_grid[opt2]
  }

  # ---------------------------------------------------------------------------
  # Build and return result
  # ---------------------------------------------------------------------------
  result <- list(
    gamma_go     = gamma_go,
    gamma_nogo   = gamma_nogo,
    PrGo_opt     = PrGo_opt,
    PrNoGo_opt   = PrNoGo_opt,
    target_go    = target_go,
    target_nogo  = target_nogo,
    grid_results = data.frame(
      gamma_grid  = gamma_grid,
      PrGo_grid   = PrGo_grid,
      PrNoGo_grid = PrNoGo_grid
    )
  )

  class(result) <- 'getgamma2cont'
  return(result)
}

Try the BayesianQDM package in your browser

Any scripts or data that you put into this service are public.

BayesianQDM documentation built on April 22, 2026, 1:09 a.m.