|
| 1 | +#-----------------------------------------# |
| 2 | +#### Decision Model #### |
| 3 | +#-----------------------------------------# |
| 4 | +#' Decision Model |
| 5 | +#' |
| 6 | +#' \code{decision_model} implements the decision model used. |
| 7 | +#' |
| 8 | +#' @param l_params_all List with all parameters of decision model |
| 9 | +#' @param verbose Logical variable to indicate print out of messages |
| 10 | +#' @return The transition probability array and the cohort trace matrix. |
| 11 | +#' |
| 12 | +decision_model <- function(l_params_all, verbose = FALSE) { |
| 13 | + with(as.list(l_params_all), { |
| 14 | + |
| 15 | + ###################### Process model inputs ###################### |
| 16 | + ## Age-specific transition probabilities to the Dead state |
| 17 | + # compute mortality rates |
| 18 | + v_r_S1Dage <- v_r_HDage * hr_S1 # Age-specific mortality rate in the Sick state |
| 19 | + v_r_S2Dage <- v_r_HDage * hr_S2 # Age-specific mortality rate in the Sicker state |
| 20 | + # transform rates to probabilities |
| 21 | + v_p_S1Dage <- rate_to_prob(v_r_S1Dage) # Age-specific mortality risk in the Sick state |
| 22 | + v_p_S2Dage <- rate_to_prob(v_r_S2Dage) # Age-specific mortality risk in the Sicker state |
| 23 | + |
| 24 | + ## Transition probability of becoming Sicker when Sick for B |
| 25 | + # transform odds ratios to probabilites |
| 26 | + logit_S1S2 <- boot::logit(p_S1S2) # log-odds of becoming Sicker when Sick |
| 27 | + p_S1S2_trtB <- boot::inv.logit(logit_S1S2 + |
| 28 | + lor_S1S2) # probability to become Sicker when Sick |
| 29 | + # under B conditional on surviving |
| 30 | + |
| 31 | + ###################### Construct state-transition models ##################### |
| 32 | + #### Create transition arrays #### |
| 33 | + # Initialize 3-D array |
| 34 | + a_P <- array(0, dim = c(n_states, n_states, n_t), |
| 35 | + dimnames = list(v_names_states, v_names_states, 0:(n_t - 1))) |
| 36 | + ### Fill in array |
| 37 | + ## From H |
| 38 | + a_P["H", "H", ] <- (1 - v_p_HDage) * (1 - p_HS1) |
| 39 | + a_P["H", "S1", ] <- (1 - v_p_HDage) * p_HS1 |
| 40 | + a_P["H", "D", ] <- v_p_HDage |
| 41 | + ## From S1 |
| 42 | + a_P["S1", "H", ] <- (1 - v_p_S1Dage) * p_S1H |
| 43 | + a_P["S1", "S1", ] <- (1 - v_p_S1Dage) * (1 - (p_S1H + p_S1S2)) |
| 44 | + a_P["S1", "S2", ] <- (1 - v_p_S1Dage) * p_S1S2 |
| 45 | + a_P["S1", "D", ] <- v_p_S1Dage |
| 46 | + ## From S2 |
| 47 | + a_P["S2", "S2", ] <- 1 - v_p_S2Dage |
| 48 | + a_P["S2", "D", ] <- v_p_S2Dage |
| 49 | + ## From D |
| 50 | + a_P["D", "D", ] <- 1 |
| 51 | + |
| 52 | + ### For strategies B and AB |
| 53 | + ## Initialize transition probability array for strategies B and AB |
| 54 | + a_P_strB <- a_P |
| 55 | + ## Only need to update the probabilities involving the transition from Sick to Sicker, p_S1S2 |
| 56 | + # From S1 |
| 57 | + a_P_strB["S1", "S1", ] <- (1 - v_p_S1Dage) * (1 - (p_S1H + p_S1S2_trtB)) |
| 58 | + a_P_strB["S1", "S2", ] <- (1 - v_p_S1Dage) * p_S1S2_trtB |
| 59 | + |
| 60 | + ### Check if transition probability matrix is valid (i.e., elements cannot < 0 or > 1) |
| 61 | + check_transition_probability(a_P, verbose = TRUE) |
| 62 | + check_transition_probability(a_P_strB, verbose = TRUE) |
| 63 | + ### Check if transition probability matrix sum to 1 (i.e., each row should sum to 1) |
| 64 | + check_sum_of_transition_array(a_P, n_states = n_states, n_t = n_t, verbose = TRUE) |
| 65 | + check_sum_of_transition_array(a_P_strB, n_states = n_states, n_t = n_t, verbose = TRUE) |
| 66 | + |
| 67 | + #### Run Markov model #### |
| 68 | + ## Initial state vector |
| 69 | + # All starting healthy |
| 70 | + v_s_init <- c(H = 1, S1 = 0, S2 = 0, D = 0) # initial state vector |
| 71 | + v_s_init |
| 72 | + |
| 73 | + ## Initialize cohort trace for age-dependent (ad) cSTM for srategies SoC and A |
| 74 | + m_M_ad <- matrix(0, |
| 75 | + nrow = (n_t + 1), ncol = n_states, |
| 76 | + dimnames = list(0:n_t, v_names_states)) |
| 77 | + # Store the initial state vector in the first row of the cohort trace |
| 78 | + m_M_ad[1, ] <- v_s_init |
| 79 | + ## Initialize cohort trace for srategies B and AB |
| 80 | + m_M_ad_strB <- m_M_ad # structure and initial states remain the same. |
| 81 | + |
| 82 | + ## Initialize transition array which will capture transitions from each state to another over time |
| 83 | + # for srategies SoC and A |
| 84 | + a_A <- array(0, |
| 85 | + dim = c(n_states, n_states, n_t + 1), |
| 86 | + dimnames = list(v_names_states, v_names_states, 0:n_t)) |
| 87 | + # Set first slice of a_A with the initial state vector in its diagonal |
| 88 | + diag(a_A[, , 1]) <- v_s_init |
| 89 | + # For srategies B and AB, the array structure and initial state are identical |
| 90 | + a_A_strB <- a_A |
| 91 | + |
| 92 | + ## Iterative solution of age-dependent cSTM |
| 93 | + for(t in 1:n_t){ |
| 94 | + ## Fill in cohort trace |
| 95 | + # For srategies SoC and A |
| 96 | + m_M_ad[t + 1, ] <- m_M_ad[t, ] %*% a_P[, , t] |
| 97 | + # For srategies B and AB |
| 98 | + m_M_ad_strB[t + 1, ] <- m_M_ad_strB[t, ] %*% a_P_strB[, , t] |
| 99 | + |
| 100 | + ## Fill in transition-dynamics array |
| 101 | + # For srategies SoC and A |
| 102 | + a_A[, , t + 1] <- m_M_ad[t, ] * a_P[, , t] |
| 103 | + # For srategies B and AB |
| 104 | + a_A_strB[, , t + 1] <- m_M_ad_strB[t, ] * a_P_strB[, , t] |
| 105 | + } |
| 106 | + |
| 107 | + ## Store the cohort traces in a list |
| 108 | + l_m_M <- list(m_M_ad, |
| 109 | + m_M_ad, |
| 110 | + m_M_ad_strB, |
| 111 | + m_M_ad_strB) |
| 112 | + names(l_m_M) <- v_names_str |
| 113 | + |
| 114 | + ## Store the transition array for each strategy in a list |
| 115 | + l_a_A <- list(a_A, |
| 116 | + a_A, |
| 117 | + a_A_strB, |
| 118 | + a_A_strB) |
| 119 | + names(l_m_M) <- names(l_a_A) <- v_names_str |
| 120 | + |
| 121 | + ########################################## RETURN OUTPUT ########################################## |
| 122 | + out <- list(l_m_M = l_m_M, |
| 123 | + l_a_A = l_a_A) |
| 124 | + |
| 125 | + return(out) |
| 126 | + } |
| 127 | + ) |
| 128 | +} |
| 129 | + |
| 130 | +#---------------------------------------------# |
| 131 | +#### Calculate cost-effectiveness outcomes #### |
| 132 | +#---------------------------------------------# |
| 133 | +#' Calculate cost-effectiveness outcomes |
| 134 | +#' |
| 135 | +#' \code{calculate_ce_out} calculates costs and effects for a given vector of parameters using a simulation model. |
| 136 | +#' @param l_params_all List with all parameters of decision model |
| 137 | +#' @param n_wtp Willingness-to-pay threshold to compute net benefits |
| 138 | +#' @return A data frame with discounted costs, effectiveness and NMB. |
| 139 | +#' |
| 140 | +calculate_ce_out <- function(l_params_all, n_wtp = 100000){ # User defined |
| 141 | + with(as.list(l_params_all), { |
| 142 | + |
| 143 | + ### Run decision model to get transition dynamics array |
| 144 | + model <- decision_model(l_params_all = l_params_all) |
| 145 | + l_a_A <- model[["l_a_A"]] |
| 146 | + |
| 147 | + #### State Rewards #### |
| 148 | + ## Vector of state utilities under strategy SoC |
| 149 | + v_u_SoC <- c(H = u_H, |
| 150 | + S1 = u_S1, |
| 151 | + S2 = u_S2, |
| 152 | + D = u_D) |
| 153 | + ## Vector of state costs under strategy SoC |
| 154 | + v_c_SoC <- c(H = c_H, |
| 155 | + S1 = c_S1, |
| 156 | + S2 = c_S2, |
| 157 | + D = c_D) |
| 158 | + ## Vector of state utilities under strategy A |
| 159 | + v_u_strA <- c(H = u_H, |
| 160 | + S1 = u_trtA, |
| 161 | + S2 = u_S2, |
| 162 | + D = u_D) |
| 163 | + ## Vector of state costs under strategy A |
| 164 | + v_c_strA <- c(H = c_H, |
| 165 | + S1 = c_S1 + c_trtA, |
| 166 | + S2 = c_S2 + c_trtA, |
| 167 | + D = c_D) |
| 168 | + ## Vector of state utilities under strategy B |
| 169 | + v_u_strB <- c(H = u_H, |
| 170 | + S1 = u_S1, |
| 171 | + S2 = u_S2, |
| 172 | + D = u_D) |
| 173 | + ## Vector of state costs under strategy B |
| 174 | + v_c_strB <- c(H = c_H, |
| 175 | + S1 = c_S1 + c_trtB, |
| 176 | + S2 = c_S2 + c_trtB, |
| 177 | + D = c_D) |
| 178 | + ## Vector of state utilities under strategy AB |
| 179 | + v_u_strAB <- c(H = u_H, |
| 180 | + S1 = u_trtA, |
| 181 | + S2 = u_S2, |
| 182 | + D = u_D) |
| 183 | + ## Vector of state costs under strategy AB |
| 184 | + v_c_strAB <- c(H = c_H, |
| 185 | + S1 = c_S1 + (c_trtA + c_trtB), |
| 186 | + S2 = c_S2 + (c_trtA + c_trtB), |
| 187 | + D = c_D) |
| 188 | + |
| 189 | + ## Store the vectors of state utilities for each strategy in a list |
| 190 | + l_u <- list(SQ = v_u_SoC, |
| 191 | + A = v_u_strA, |
| 192 | + B = v_u_strB, |
| 193 | + AB = v_u_strAB) |
| 194 | + ## Store the vectors of state cost for each strategy in a list |
| 195 | + l_c <- list(SQ = v_c_SoC, |
| 196 | + A = v_c_strA, |
| 197 | + B = v_c_strB, |
| 198 | + AB = v_c_strAB) |
| 199 | + ## Store the transition array for each strategy in a list |
| 200 | + l_a_A <- list(SQ = a_A, |
| 201 | + A = a_A, |
| 202 | + B = a_A_strB, |
| 203 | + AB = a_A_strB) |
| 204 | + |
| 205 | + # assign strategy names to matching items in the lists |
| 206 | + names(l_u) <- names(l_c) <- names(l_a_A) <- v_names_str |
| 207 | + |
| 208 | + ## create empty vectors to store total utilities and costs |
| 209 | + v_tot_qaly <- v_tot_cost <- vector(mode = "numeric", length = n_str) |
| 210 | + names(v_tot_qaly) <- names(v_tot_cost) <- v_names_str |
| 211 | + |
| 212 | + #### Loop through each strategy and calculate total utilities and costs #### |
| 213 | + for (i in 1:n_str) { |
| 214 | + v_u_str <- l_u[[i]] # select the vector of state utilities for the ith strategy |
| 215 | + v_c_str <- l_c[[i]] # select the vector of state costs for the ith strategy |
| 216 | + a_A_str <- l_a_A[[i]] # select the transition array for the ith strategy |
| 217 | + |
| 218 | + #### Array of state rewards #### |
| 219 | + # Create transition matrices of state utilities and state costs for the ith strategy |
| 220 | + m_u_str <- matrix(v_u_str, nrow = n_states, ncol = n_states, byrow = T) |
| 221 | + m_c_str <- matrix(v_c_str, nrow = n_states, ncol = n_states, byrow = T) |
| 222 | + # Expand the transition matrix of state utilities across cycles to form a transition array of state utilities |
| 223 | + a_R_u_str <- array(m_u_str, |
| 224 | + dim = c(n_states, n_states, n_t + 1), |
| 225 | + dimnames = list(v_names_states, v_names_states, 0:n_t)) |
| 226 | + # Expand the transition matrix of state costs across cycles to form a transition array of state costs |
| 227 | + a_R_c_str <- array(m_c_str, |
| 228 | + dim = c(n_states, n_states, n_t + 1), |
| 229 | + dimnames = list(v_names_states, v_names_states, 0:n_t)) |
| 230 | + |
| 231 | + #### Apply transition rewards #### |
| 232 | + # Apply disutility due to transition from H to S1 |
| 233 | + a_R_u_str["H", "S1", ] <- a_R_u_str["H", "S1", ] - du_HS1 |
| 234 | + # Add transition cost per cycle due to transition from H to S1 |
| 235 | + a_R_c_str["H", "S1", ] <- a_R_c_str["H", "S1", ] + ic_HS1 |
| 236 | + # Add transition cost per cycle of dying from all non-dead states |
| 237 | + a_R_c_str[-n_states, "D", ] <- a_R_c_str[- n_states, "D", ] + ic_D |
| 238 | + |
| 239 | + #### Expected QALYs and Costs for all transitions per cycle #### |
| 240 | + # QALYs = life years x QoL |
| 241 | + # Note: all parameters are annual in our example. In case your own case example is different make sure you correctly apply . |
| 242 | + a_Y_c_str <- a_A_str * a_R_c_str |
| 243 | + a_Y_u_str <- a_A_str * a_R_u_str |
| 244 | + |
| 245 | + #### Expected QALYs and Costs per cycle #### |
| 246 | + ## Vector of QALYs and Costs |
| 247 | + v_qaly_str <- apply(a_Y_u_str, 3, sum) # sum the proportion of the cohort across transitions |
| 248 | + v_cost_str <- apply(a_Y_c_str, 3, sum) # sum the proportion of the cohort across transitions |
| 249 | + |
| 250 | + #### Discounted total expected QALYs and Costs per strategy and apply half-cycle correction if applicable #### |
| 251 | + ## QALYs |
| 252 | + v_tot_qaly[i] <- t(v_qaly_str) %*% (v_dwe * v_hcc) |
| 253 | + ## Costs |
| 254 | + v_tot_cost[i] <- t(v_cost_str) %*% (v_dwc * v_hcc) |
| 255 | + } |
| 256 | + |
| 257 | + |
| 258 | + ########################## Cost-effectiveness analysis ####################### |
| 259 | + ### Calculate incremental cost-effectiveness ratios (ICERs) |
| 260 | + df_cea <- calculate_icers(cost = v_tot_cost, |
| 261 | + effect = v_tot_qaly, |
| 262 | + strategies = v_names_str) |
| 263 | + df_cea <- df_cea[match(v_names_str, df_cea$Strategy), ] |
| 264 | + return(df_cea) |
| 265 | + } |
| 266 | + ) |
| 267 | +} |
0 commit comments