R/sharpbound.R

Defines functions sharpbound

Documented in sharpbound

#' Sharp bound
#'
#' `sharpbound()` returns a list with the sharp bound. All sensitivity parameters for
#' the population of interest must be set to numbers, and the rest can be left
#' as `NULL`. The sensitivity parameters can be inserted directly or as output
#' from `sensitivityparametersM()`. 
#'
#' @param whichEst Input string. Defining the causal estimand of interest.
#'   Available options are as follows. (1) Risk ratio in the total
#'   population: `"RR_tot"`, (2) Risk difference in the total population:
#'   `"RD_tot"`, (3) Risk ratio in the subpopulation: `"RR_sub"`, (4) Risk
#'   difference in the subpopulation: `"RD_sub"`.
#' @param sens Possible method to input sensitivity parameters. `sens` can 
#'   be the output from sensitivityparametersM(), a data.frame with columns 
#'   'parameter' and 'value', or a name list with correct names (e.g. 
#'   `"RR_UY_T1"`,  `"RR_UY_T0"`, etc.). If not supplied, parameters can be 
#'   entered manually as specified below.
#' @param pY1_T1_S1 Input value. The probability P(Y=1|T=1,I_S=1). Must be
#'   between 0 and 1.
#' @param pY1_T0_S1 Input value. The probability P(Y=1|T=0,I_S=1). Must be
#'   between 0 and 1.
#' @param pT1_S1 Input value. The probability P(T=1|I_S=1). Must be
#'   between 0 and 1. Only needed for the causal estimands in the subpopulation.
#' @param pT0_S1 Input value. The probability P(T=1|I_S=1). Must be
#'   between 0 and 1. Only needed for the causal estimands in the subpopulation.
#' @param pS1_T1 Input value. The probability P(I_S=1|T=1). Must be
#'   between 0 and 1. Can be set to 0 if the value is unknown. Only needed
#'   for the causal estimands in the total population.
#' @param pS1_T0 Input value. The probability P(I_S=1|T=0). Must be
#'   between 0 and 1. Can be set to 0 if the value is unknown. Only needed
#'   for the causal estimands in the total population.
#' @param RR_UY_T1 Possible method to input sensitivity parameter. 
#'   The sensitivity parameter RR_UY|T=1. Must be greater than or equal to 1. 
#'   Used in the bounds for the total population.
#' @param RR_UY_T0 Possible method to input sensitivity parameter. 
#'   The sensitivity parameter RR_UY|T=0. Must be greater than or equal to 1. 
#'   Used in the bounds for the total population.
#' @param RR_SU_11 Possible method to input sensitivity parameter. 
#'   The sensitivity parameter RR_SU|11. Must be greater than or equal to 1. 
#'   Used in the bounds for the total population.
#' @param RR_SU_00 Possible method to input sensitivity parameter. 
#'   The sensitivity parameter RR_SU|00. Must be greater than or equal to 1. 
#'   Used in the bounds for the total population.
#' @param RR_SU_10 Possible method to input sensitivity parameter. 
#'   The sensitivity parameter RR_SU|10. Must be greater than or equal to 1. 
#'   Used in the bounds for the total population.
#' @param RR_SU_01 Possible method to input sensitivity parameter. 
#'   The sensitivity parameter RR_SU|01. Must be greater than or equal to 1. 
#'   Used in the bounds for the total population.
#' @param RR_UY_S1 Possible method to input sensitivity parameter. 
#'   The sensitivity parameter RR_UY|S=1. Must be greater than or equal to 1. 
#'   Used in the bounds for the subpopulation.
#' @param RR_TU_1 Possible method to input sensitivity parameter. 
#'   The sensitivity parameter RR_TU|1. Must be greater than or equal to 1. 
#'   Used in the bounds for the subpopulation.
#' @param RR_TU_0 Possible method to input sensitivity parameter. 
#'   The sensitivity parameter RR_TU|0. Must be greater than or equal to 1. 
#'   Used in the bounds for the subpopulation.
#'   
#' @return A list containing the sharp lower and upper bounds.
#' @export
#'
#' @examples
#' # Example for risk ratio in the total population.
#' sharpbound(whichEst = "RR_tot", pY1_T1_S1 = 0.05, pY1_T0_S1 = 0.01,
#'  pS1_T1 = 0.2, pS1_T0 = 0.7, RR_UY_T1 = 2, RR_UY_T0 = 2, RR_SU_11 = 1.7, 
#'  RR_SU_00 = 1.5, RR_SU_10 = 2.1, RR_SU_01 = 2.3)
#'
#' # Example for risk difference in the total population.
#' sharpbound(whichEst = "RD_tot", pY1_T1_S1 = 0.05, pY1_T0_S1 = 0.01,
#'  pS1_T1 = 0.2, pS1_T0 = 0.7, RR_UY_T1 = 2, RR_UY_T0 = 2, RR_SU_11 = 1.7, 
#'  RR_SU_00 = 1.5, RR_SU_10 = 2.1, RR_SU_01 = 2.3)
#'
#' # Example for risk ratio in the subpopulation.
#' sharpbound(whichEst = "RR_sub", pY1_T1_S1 = 0.05, pY1_T0_S1 = 0.01,
#'  pT1_S1 = 0.2, pT0_S1 = 0.1, RR_UY_S1 = 2.71, RR_TU_1 = 1.91, RR_TU_0 = 2.33)
#'
#' # Example for risk difference in the subpopulation.
#' sharpbound(whichEst = "RD_sub", pY1_T1_S1 = 0.05, pY1_T0_S1 = 0.01,
#'  pT1_S1 = 0.2, pT0_S1 = 0.1, RR_UY_S1 = 2.71, RR_TU_1 = 1.91, RR_TU_0 = 2.33)
#'  
#' # Example specifying the sensitivity parameters from sensitivityparametersM().
#' # Risk ratio in the subpopulation. DGP from the zika example.
#' V = matrix(c(1, 0, 0.85, 0.15), ncol = 2)
#' U = matrix(c(1, 0, 0.5, 0.5), ncol = 2)
#' Tr = c(-6.2, 1.75)
#' Y = c(-5.2, 5.0, -1.0)
#' S = matrix(c(1.2, 2.2, 0.0, 0.5, 2.0, -2.75, -4.0, 0.0), ncol = 4)
#' probT1 = 0.286
#' probT0 = 0.004
#' senspar = sensitivityparametersM(whichEst = "RR_sub", whichBound = "sharp",
#'  Vval = V,  Uval = U, Tcoef = Tr, Ycoef = Y, Scoef = S, Mmodel = "L",
#'  pY1_T1_S1 = probT1, pY1_T0_S1 = probT0)
#'  
#' sharpbound(whichEst = "RR_sub", sens = senspar, pY1_T1_S1 = probT1, 
#'  pY1_T0_S1 = probT0, pT1_S1 = 0.99, pT0_S1 = 0.01)
#'  
#'
#' @references  Zetterstrom S, Sjölander A, Waernbaum I. "Investigations of sharp bounds 
#'   for causal effects under selection bias." Statistical Methods in Medical 
#'   Research (2025).
#'
#'
sharpbound <- function(whichEst, sens = NULL, pY1_T1_S1, pY1_T0_S1, pT1_S1 = NULL, 
                       pT0_S1 = NULL, pS1_T1 = NULL, pS1_T0 = NULL,
                       RR_UY_T1 = NULL, RR_UY_T0 = NULL, RR_SU_11 = NULL,
                       RR_SU_00 = NULL, RR_SU_10 = NULL, RR_SU_01 = NULL,
                       RR_UY_S1 = NULL, RR_TU_1 = NULL, RR_TU_0 = NULL)
{
  
  if (!is.null(sens)) {
    # Case 1: Output from sensitivityparametersM()
    if (inherits(sens, "sensparams")) {
      params <- stats::setNames(as.numeric(sens$value), sens$parameter)
      # Case 2: Data frame with columns 'parameter' and 'value'
    } else if (is.data.frame(sens)) {
      params <- stats::setNames(as.numeric(sens$value), sens$parameter)
      # Case 3: Named list with correct names
    } else if (is.list(sens)) {
      params <- unlist(sens)
    } else {
      stop("'sens' must be a named list (with correct names), data.frame (with columns 'parameter' and 'value'), or 'sensparams' object.")
    }
  }
  
  
  # Check if the estimand is one of the four "RR_tot", "RD_tot", "RR_sub", "RD_sub".
  if(whichEst != "RR_tot" & whichEst != "RD_tot" & whichEst != "RR_sub" & whichEst != "RD_sub")
    stop('The estimand must be "RR_tot", "RD_tot", "RR_sub" or "RD_sub".')
  
  # Check if the probabilities are valid.
  if((pY1_T1_S1 < 0 | pY1_T1_S1 > 1 | pY1_T0_S1 < 0 | pY1_T0_S1 > 1))
    stop("P(Y=1|T=1,I_S=1) and P(Y=1|T=0,I_S=1) cannot be smaller than 0 or larger than 1.")
  
  # Check if the probabilities are valid.
  if(!is.null(pT1_S1) & !is.null(pT0_S1))
  {
    if((pT1_S1 > 1 | pT0_S1 > 1 | pT1_S1 < 0 | pT0_S1 < 0))
      stop("P(T=1|I_S=1) and P(T=0|I_S=1) cannot be smaller than 0 or larger than 1 (if not set to NULL).") 
  }
  
  # Check if the probabilities are valid.
  if(!is.null(pS1_T1) & !is.null(pS1_T0))
  {
    if((pS1_T1 > 1 | pS1_T0 > 1 | pS1_T1 < 0 | pS1_T0 < 0))
      stop("P(I_S=1|T=1) and P(I_S=1|T=0) cannot be smaller than 0 or larger than 1 (if not set to NULL).") 
  }
  
  # The observational estimands.
  RRobs = pY1_T1_S1 / pY1_T0_S1
  RDobs = pY1_T1_S1 - pY1_T0_S1
  
  pS0_T1 = 1 - pS1_T1
  pS0_T0 = 1 - pS1_T0
  
  # Calculations for the total population.
  if(whichEst == "RR_tot" | whichEst == "RD_tot")
  {
    # Extracting the sensitivity parameters from the input list.
    if (!is.null(sens))
    {
      RR_UY_T1 = params[["RR_UY|T=1"]]
      RR_UY_T0 = params[["RR_UY|T=0"]]
      RR_SU_11 = params[["RR_SU|11"]]
      RR_SU_00 = params[["RR_SU|00"]]
      RR_SU_10 = params[["RR_SU|10"]]
      RR_SU_01 = params[["RR_SU|01"]]
    }
    
    # Check if the correct sensitivity parameters are specified.
    if(is.null(RR_UY_T1) | is.null(RR_UY_T0) | is.null(RR_SU_11) | is.null(RR_SU_00) | is.null(RR_SU_10) | is.null(RR_SU_01))
      stop("When the total population is of interest, RR_UY_T1, RR_UY_T0,
           RR_SU_11, RR_SU_00, RR_SU_10, and RR_SU_01 cannot be equal to NULL." )
    
    if(is.null(pS1_T1) & is.null(pS1_T0))
      stop("P(I_S=1|T=1) and P(I_S=1|T=0) cannot be NULL when the total population is of interest. If thery are unknown, they can be set to 0 for conservative bounds.") 
    
    # Check if the sensitivity parameters are valid.
    if(RR_UY_T1 < 1 | RR_UY_T0 < 1 | RR_SU_11 < 1 | RR_SU_00 < 1 | RR_SU_10 < 1 | RR_SU_01 < 1)
      stop("All sensitivity parameters must be greater than or equal to 1.")
    
    # Calculate BF_11, BF_00, BF_10, and BF_01. Used in both "RR_tot" and "RD_tot".
    BF11 = (RR_UY_T1 * RR_SU_11) / (RR_UY_T1 + RR_SU_11 - 1)
    BF00 = (RR_UY_T0 * RR_SU_00) / (RR_UY_T0 + RR_SU_00 - 1)
    BF10 = (RR_UY_T1 * RR_SU_10) / (RR_UY_T1 + RR_SU_10 - 1)
    BF01 = (RR_UY_T0 * RR_SU_01) / (RR_UY_T0 + RR_SU_01 - 1)
    
    lower_pY1_T1 = pY1_T1_S1 * (pS1_T1 + pS0_T1 * BF11) 
    upper_pY1_T1 = pY1_T1_S1 * (pS1_T1 + pS0_T1 * min(BF10, 1 / pY1_T1_S1))
    
    lower_pY1_T0 = pY1_T0_S1 * (pS1_T0 + pS0_T0 * BF01) 
    upper_pY1_T0 = pY1_T0_S1 * (pS1_T0 + pS0_T0 * min(BF00, 1 / pY1_T0_S1))
    
    # Sharp bound for RR in tot pop.
    lowerboundRRtot = round((lower_pY1_T1 / upper_pY1_T0), 2)
    upperboundRRtot = round((upper_pY1_T1 / lower_pY1_T0), 2)
    
    # Sharp bound for RD in tot pop.
    lowerboundRDtot = round((lower_pY1_T1 - upper_pY1_T0), 2)
    upperboundRDtot = round((upper_pY1_T1 - lower_pY1_T0), 2)
    
    
  }else
  { # Calculations for the subpopulation.
    if (!is.null(sens)) 
    {
      # Extracting the sensitivity parameters from the input list.
      RR_UY_S1 = params[["RR_UY|S=1"]]
      RR_TU_1 = params[["RR_TU|1"]]
      RR_TU_0 = params[["RR_TU|0"]]
    }
    
    
    # Check if the correct sensitivity parameters are specified.
    if(is.null(RR_UY_S1) | is.null(RR_TU_1 ) | is.null(RR_TU_0))
      stop("When the subpopulation is of interest, RR_UY_S1, RR_TU_1, and RR_TU_0 cannot be equal to NULL." )
    
    if(is.null(pT1_S1) & is.null(pT0_S1))
      stop("P(T=1|I_S=1) and P(T=0|I_S=1) cannot be NULL when the subpopulation is of interest.") 
    
    # Check if the sensitivity parameters are valid.
    if(RR_UY_S1 < 1 | RR_TU_1 < 1 | RR_TU_0 < 1)
      stop("All sensitivity parameters must be greater than or equal to 1.")
    
    # Calculate BF_1. Used in both "RR_sub" and "RD_sub".
    BF1 = (RR_UY_S1 * RR_TU_1) / (RR_UY_S1 + RR_TU_1 - 1)
    BF0 = (RR_UY_S1 * RR_TU_0) / (RR_UY_S1 + RR_TU_0 - 1)
    
    lower_pY11_S1 = pY1_T1_S1 * (pT1_S1 + pT0_S1 / BF1) 
    upper_pY11_S1 = pY1_T1_S1 * (pT1_S1 + pT0_S1 * min(BF0, (1 / pY1_T1_S1)))
    
    lower_pY01_S1 = pY1_T0_S1 * (pT0_S1 + pT1_S1 * BF0) 
    upper_pY01_S1 = pY1_T0_S1 * (pT0_S1 + pT1_S1 * min(BF1, (1 / pY1_T0_S1)))
    
    # Sharp bound for RR in subpop.
    lowerboundRRs = round((lower_pY11_S1 / upper_pY01_S1), 2)
    upperboundRRs = round((upper_pY11_S1 / lower_pY01_S1), 2)
    
    # Sharp bound for RD in subpop.
    lowerboundRDs = round((lower_pY11_S1 - upper_pY01_S1), 2)
    upperboundRDs = round((upper_pY11_S1 - lower_pY01_S1), 2)

  }
  
  # The return list.
  heading = c("Sharp lower bound", "Sharp upper bound")
  
  if(whichEst == "RR_tot"){
    values = list(lowerboundRRtot, upperboundRRtot)
  }else if(whichEst == "RD_tot"){
    values = list(lowerboundRDtot,upperboundRDtot)
  }else if(whichEst == "RR_sub"){
    values = list(lowerboundRRs, upperboundRRs)
  }else{
    values = list(lowerboundRDs, upperboundRDs)
  }
  
  returnDat = matrix(cbind(heading, values), ncol = 2)
  return(returnDat)
  
  
}

Try the SelectionBias package in your browser

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

SelectionBias documentation built on Nov. 5, 2025, 6:48 p.m.