├── .gitignore ├── LICENSE ├── R ├── Functions.R └── Functions_cSTM_time_indep.R ├── README.Rmd ├── README.md ├── analysis └── cSTM_time_indep.R ├── cohort-modeling-tutorial-intro.Rproj └── manuscript ├── Appendix_matrix algebra.pdf ├── Appendix_table.Rmd ├── Appendix_table.pdf ├── Appendix_table.tex ├── WorkingPapers-CohortModelsR.bib ├── cSTM_Tutorial_Intro.R ├── cSTM_Tutorial_Intro.Rmd ├── cSTM_Tutorial_Intro.aux ├── cSTM_Tutorial_Intro.pdf ├── cSTM_Tutorial_Intro.tex ├── cSTM_Tutorial_Intro.toc ├── cSTM_Tutorial_Intro.xwm ├── figs ├── CE-scatter-1.jpeg ├── CE-scatter-1.pdf ├── CE-scatter-1.png ├── CEAC-1.jpeg ├── CEAC-1.pdf ├── CEAC-1.png ├── ELC-1.jpeg ├── ELC-1.pdf ├── ELC-1.png ├── Sick-Sicker-CEA-1.jpeg ├── Sick-Sicker-CEA-1.pdf ├── Sick-Sicker-CEA-1.png ├── Sick-Sicker-Trace-TimeHom-1.jpeg ├── Sick-Sicker-Trace-TimeHom-1.pdf ├── Sick-Sicker-Trace-TimeHom-1.png └── Sick-Sicker.png ├── markdown_to_R.R ├── matrixAlgebraSlides for markov tutorials.pptx ├── sage-vancouver.csl └── vancouver-superscript.csl /.gitignore: -------------------------------------------------------------------------------- 1 | # History files 2 | .Rhistory 3 | .Rapp.history 4 | 5 | # Session Data files 6 | .RData 7 | 8 | # User-specific files 9 | .Ruserdata 10 | 11 | # Example code in package build process 12 | *-Ex.R 13 | 14 | # Output files from R CMD build 15 | /*.tar.gz 16 | 17 | # Output files from R CMD check 18 | /*.Rcheck/ 19 | 20 | # RStudio files 21 | .Rproj.user/ 22 | 23 | # produced vignettes 24 | vignettes/*.html 25 | vignettes/*.pdf 26 | 27 | # OAuth2 token, see https://github.com/hadley/httr/releases/tag/v0.3 28 | .httr-oauth 29 | 30 | # knitr and R markdown default cache directories 31 | *_cache/ 32 | /cache/ 33 | 34 | # Temporary files created by R markdown 35 | *.utf8.md 36 | *.knit.md 37 | 38 | # R Environment Variables 39 | .Renviron 40 | 41 | # Mac OS Environment Variables 42 | .DS_Store 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Decision Analysis in R for Technologies in Health (DARTH) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /R/Functions_cSTM_time_indep.R: -------------------------------------------------------------------------------- 1 | #* Script name: Functions_cSTM_time_indep.R 2 | #------------------------------------------------------------------------------# 3 | #### Decision Model #### 4 | #------------------------------------------------------------------------------# 5 | #' Decision Model 6 | #' 7 | #' \code{decision_model} implements the decision model used. 8 | #' 9 | #' @param l_params_all List with all parameters of decision model 10 | #' @param verbose Logical variable to indicate print out of messages 11 | #' @return The transition probability array and the cohort trace matrix. 12 | #' @export 13 | decision_model <- function(l_params_all, verbose = FALSE) { 14 | with(as.list(l_params_all), { 15 | ########################### Process model inputs ########################### 16 | ### Process model inputs 17 | ## Number of cycles 18 | n_cycles <- (n_age_max - n_age_init)/cycle_length # time horizon, number of cycles 19 | ## Cycle-specific transition probabilities to the Dead state 20 | # compute mortality rates 21 | r_S1D <- r_HD * hr_S1 # annual mortality rate in the Sick state 22 | r_S2D <- r_HD * hr_S2 # annual mortality rate in the Sicker state 23 | # transform rates to probabilities 24 | p_HS1 <- rate_to_prob(r = r_HS1, t = cycle_length) # constant annual probability of becoming Sick when Healthy conditional on surviving 25 | p_S1H <- rate_to_prob(r = r_S1H, t = cycle_length) # constant annual probability of becoming Healthy when Sick conditional on surviving 26 | p_S1S2 <- rate_to_prob(r = r_S1S2, t = cycle_length)# constant annual probability of becoming Sicker when Sick conditional on surviving 27 | p_HD <- rate_to_prob(r = r_HD, t = cycle_length) # annual mortality risk in the Healthy state 28 | p_S1D <- rate_to_prob(r = r_S1D, t = cycle_length) # annual mortality risk in the Sick state 29 | p_S2D <- rate_to_prob(r = r_S2D, t = cycle_length) # annual mortality risk in the Sicker state 30 | 31 | ## Annual transition probability of becoming Sicker when Sick for treatment B 32 | # apply hazard ratio to rate to obtain transition rate of becoming Sicker when Sick for treatment B 33 | r_S1S2_trtB <- r_S1S2 * hr_S1S2_trtB 34 | # transform rate to probability 35 | # probability to become Sicker when Sick 36 | # under treatment B conditional on surviving 37 | p_S1S2_trtB <- rate_to_prob(r = r_S1S2_trtB, t = cycle_length) 38 | 39 | ##################### Construct state-transition models #################### 40 | ## Initial state vector 41 | # All starting healthy 42 | v_m_init <- c(H = 1, S1 = 0, S2 = 0, D = 0) # initial state vector 43 | # Number of health states 44 | n_states <- length(v_m_init) 45 | # Health state names 46 | v_names_states <- names(v_m_init) 47 | 48 | ## Initialize cohort trace for SoC 49 | m_M <- matrix(NA, 50 | nrow = (n_cycles + 1), ncol = n_states, 51 | dimnames = list(0:n_cycles, v_names_states)) 52 | # Store the initial state vector in the first row of the cohort trace 53 | m_M[1, ] <- v_m_init 54 | ## Initialize cohort trace for strategies A, B, and AB 55 | # Structure and initial states are the same as for SoC 56 | m_M_strA <- m_M # Strategy A 57 | m_M_strB <- m_M # Strategy B 58 | m_M_strAB <- m_M # Strategy AB 59 | 60 | ## Initialize transition probability matrix for strategy SoC 61 | # all transitions to a non-death state are assumed to be conditional on survival 62 | m_P <- matrix(0, 63 | nrow = n_states, ncol = n_states, 64 | dimnames = list(v_names_states, 65 | v_names_states)) # define row and column names 66 | ## Fill in matrix 67 | # From H 68 | m_P["H", "H"] <- (1 - p_HD) * (1 - p_HS1) 69 | m_P["H", "S1"] <- (1 - p_HD) * p_HS1 70 | m_P["H", "D"] <- p_HD 71 | # From S1 72 | m_P["S1", "H"] <- (1 - p_S1D) * p_S1H 73 | m_P["S1", "S1"] <- (1 - p_S1D) * (1 - (p_S1H + p_S1S2)) 74 | m_P["S1", "S2"] <- (1 - p_S1D) * p_S1S2 75 | m_P["S1", "D"] <- p_S1D 76 | # From S2 77 | m_P["S2", "S2"] <- 1 - p_S2D 78 | m_P["S2", "D"] <- p_S2D 79 | # From D 80 | m_P["D", "D"] <- 1 81 | 82 | ## Initialize transition probability matrix for strategy A as a copy of SoC's 83 | m_P_strA <- m_P 84 | 85 | ## Initialize transition probability matrix for strategy B 86 | m_P_strB <- m_P 87 | # Update only transition probabilities from S1 involving p_S1S2 88 | m_P_strB["S1", "S1"] <- (1 - p_S1D) * (1 - (p_S1H + p_S1S2_trtB)) 89 | m_P_strB["S1", "S2"] <- (1 - p_S1D) * p_S1S2_trtB 90 | 91 | ## Initialize transition probability matrix for strategy AB as a copy of B's 92 | m_P_strAB <- m_P_strB 93 | 94 | ### Check if transition probability matrices are valid 95 | ## Check that transition probabilities are [0, 1] 96 | check_transition_probability(m_P, verbose = TRUE) 97 | check_transition_probability(m_P_strA, verbose = TRUE) 98 | check_transition_probability(m_P_strB, verbose = TRUE) 99 | check_transition_probability(m_P_strAB, verbose = TRUE) 100 | ## Check that all rows sum to 1 101 | check_sum_of_transition_array(m_P, n_states = n_states, n_cycles = n_cycles, verbose = TRUE) 102 | check_sum_of_transition_array(m_P_strA, n_states = n_states, n_cycles = n_cycles, verbose = TRUE) 103 | check_sum_of_transition_array(m_P_strB, n_states = n_states, n_cycles = n_cycles, verbose = TRUE) 104 | check_sum_of_transition_array(m_P_strAB, n_states = n_states, n_cycles = n_cycles, verbose = TRUE) 105 | 106 | #### Run Markov model #### 107 | # Iterative solution of time-independent cSTM 108 | for(t in 1:n_cycles){ 109 | # For SoC 110 | m_M[t + 1, ] <- m_M[t, ] %*% m_P 111 | # For strategy A 112 | m_M_strA[t + 1, ] <- m_M_strA[t, ] %*% m_P_strA 113 | # For strategy B 114 | m_M_strB[t + 1, ] <- m_M_strB[t, ] %*% m_P_strB 115 | # For strategy AB 116 | m_M_strAB[t + 1, ] <- m_M_strAB[t, ] %*% m_P_strAB 117 | } 118 | 119 | ## Strategy names 120 | v_names_str <- c("Standard of care", # store the strategy names 121 | "Strategy A", 122 | "Strategy B", 123 | "Strategy AB") 124 | n_str <- length(v_names_str) # number of strategies 125 | 126 | ## Store the cohort traces in a list 127 | l_m_M <- list(m_M, 128 | m_M, 129 | m_M_strB, 130 | m_M_strB) 131 | names(l_m_M) <- v_names_str 132 | 133 | ###################### RETURN OUTPUT ###################### 134 | return(l_m_M) 135 | } 136 | ) 137 | } 138 | 139 | #------------------------------------------------------------------------------# 140 | #### Calculate cost-effectiveness outcomes #### 141 | #------------------------------------------------------------------------------# 142 | #' Calculate cost-effectiveness outcomes 143 | #' 144 | #' \code{calculate_ce_out} calculates costs and effects for a given vector of parameters using a simulation model. 145 | #' @param l_params_all List with all parameters of decision model 146 | #' @param n_wtp Willingness-to-pay threshold to compute net benefits 147 | #' @return A data frame with discounted costs, effectiveness and NMB. 148 | #' @export 149 | calculate_ce_out <- function(l_params_all, n_wtp = 100000){ # User defined 150 | with(as.list(l_params_all), { 151 | 152 | #### Run Markov Model #### 153 | ## Cohort traces 154 | l_m_M <- decision_model(l_params_all = l_params_all) 155 | 156 | ## Strategy names 157 | v_names_str <- c("Standard of care", # store the strategy names 158 | "Strategy A", 159 | "Strategy B", 160 | "Strategy AB") 161 | n_str <- length(v_names_str) # number of strategies 162 | 163 | #### State Rewards #### 164 | ## Vector of state utilities under strategy SoC 165 | v_u_SoC <- c(H = u_H, 166 | S1 = u_S1, 167 | S2 = u_S2, 168 | D = u_D) * cycle_length 169 | ## Vector of state costs under strategy SoC 170 | v_c_SoC <- c(H = c_H, 171 | S1 = c_S1, 172 | S2 = c_S2, 173 | D = c_D) * cycle_length 174 | ## Vector of state utilities under strategy A 175 | v_u_strA <- c(H = u_H, 176 | S1 = u_trtA, 177 | S2 = u_S2, 178 | D = u_D) * cycle_length 179 | ## Vector of state costs under strategy A 180 | v_c_strA <- c(H = c_H, 181 | S1 = c_S1 + c_trtA, 182 | S2 = c_S2 + c_trtA, 183 | D = c_D) * cycle_length 184 | ## Vector of state utilities under strategy B 185 | v_u_strB <- c(H = u_H, 186 | S1 = u_S1, 187 | S2 = u_S2, 188 | D = u_D) * cycle_length 189 | ## Vector of state costs under strategy B 190 | v_c_strB <- c(H = c_H, 191 | S1 = c_S1 + c_trtB, 192 | S2 = c_S2 + c_trtB, 193 | D = c_D) * cycle_length 194 | ## Vector of state utilities under strategy AB 195 | v_u_strAB <- c(H = u_H, 196 | S1 = u_trtA, 197 | S2 = u_S2, 198 | D = u_D) * cycle_length 199 | ## Vector of state costs under strategy AB 200 | v_c_strAB <- c(H = c_H, 201 | S1 = c_S1 + (c_trtA + c_trtB), 202 | S2 = c_S2 + (c_trtA + c_trtB), 203 | D = c_D) * cycle_length 204 | 205 | ## Store the vectors of state utilities for each strategy in a list 206 | l_u <- list(SQ = v_u_SoC, 207 | A = v_u_strA, 208 | B = v_u_strB, 209 | AB = v_u_strAB) 210 | ## Store the vectors of state cost for each strategy in a list 211 | l_c <- list(SQ = v_c_SoC, 212 | A = v_c_strA, 213 | B = v_c_strB, 214 | AB = v_c_strAB) 215 | 216 | # assign strategy names to matching items in the lists 217 | names(l_u) <- names(l_c) <- v_names_str 218 | 219 | ## create empty vectors to store total utilities and costs 220 | v_tot_qaly <- v_tot_cost <- vector(mode = "numeric", length = n_str) 221 | names(v_tot_qaly) <- names(v_tot_cost) <- v_names_str 222 | 223 | ## Number of cycles 224 | n_cycles <- (n_age_max - n_age_init)/cycle_length # time horizon, number of cycles 225 | 226 | ## Discount weight for costs and effects 227 | v_dwc <- 1 / ((1 + d_e * cycle_length) ^ (0:n_cycles)) 228 | v_dwe <- 1 / ((1 + d_c * cycle_length) ^ (0:n_cycles)) 229 | 230 | ## Within-cycle correction (WCC) using Simpson's 1/3 rule 231 | v_wcc <- darthtools::gen_wcc(n_cycles = n_cycles, 232 | method = "Simpson1/3") # vector of wcc 233 | 234 | #### Loop through each strategy and calculate total utilities and costs #### 235 | for (i in 1:n_str) { 236 | v_u_str <- l_u[[i]] # select the vector of state utilities for the i-th strategy 237 | v_c_str <- l_c[[i]] # select the vector of state costs for the i-th strategy 238 | 239 | #### Expected QALYs and costs per cycle #### 240 | ### Vector of QALYs and Costs 241 | ## Apply state rewards ### 242 | v_qaly_str <- l_m_M[[i]] %*% v_u_str # sum the utilities of all states for each cycle 243 | v_cost_str <- l_m_M[[i]] %*% v_c_str # sum the costs of all states for each cycle 244 | 245 | #### Discounted total expected QALYs and Costs per strategy and apply half-cycle correction if applicable #### 246 | ## QALYs 247 | v_tot_qaly[i] <- t(v_qaly_str) %*% (v_dwe * v_wcc) 248 | ## Costs 249 | v_tot_cost[i] <- t(v_cost_str) %*% (v_dwc * v_wcc) 250 | } 251 | 252 | ## Vector with discounted net monetary benefits (NMB) 253 | v_nmb <- v_tot_qaly * n_wtp - v_tot_cost 254 | 255 | ## data.frame with discounted costs, effectiveness and NMB 256 | df_ce <- data.frame(Strategy = v_names_str, 257 | Cost = v_tot_cost, 258 | Effect = v_tot_qaly, 259 | NMB = v_nmb) 260 | 261 | return(df_ce) 262 | } 263 | ) 264 | } 265 | 266 | #------------------------------------------------------------------------------# 267 | #### Generate a PSA input parameter dataset #### 268 | #------------------------------------------------------------------------------# 269 | #' Generate parameter sets for the probabilistic sensitivity analysis (PSA) 270 | #' 271 | #' \code{generate_psa_params} generates a PSA dataset of the parameters of the 272 | #' cost-effectiveness analysis. 273 | #' @param n_sim Number of parameter sets for the PSA dataset 274 | #' @param seed Seed for the random number generation 275 | #' @return A data.frame with a PSA dataset of he parameters of the 276 | #' cost-effectiveness analysis 277 | #' @export 278 | generate_psa_params <- function(n_sim = 1000, seed = 071818){ 279 | set.seed(seed) # set a seed to be able to reproduce the same results 280 | df_psa <- data.frame( 281 | # Transition probabilities (per cycle), hazard ratios 282 | r_HD = rgamma(n_sim, shape = 20, rate = 10000), # constant rate of dying when Healthy (all-cause mortality) 283 | r_HS1 = rgamma(n_sim, shape = 30, rate = 170 + 30), # constant rate of becoming Sick when Healthy conditional on surviving 284 | r_S1H = rgamma(n_sim, shape = 60, rate = 60 + 60), # constant rate of becoming Healthy when Sick conditional on surviving 285 | r_S1S2 = rgamma(n_sim, shape = 84, rate = 716 + 84), # constant rate of becoming Sicker when Sick conditional on surviving 286 | hr_S1 = rlnorm(n_sim, meanlog = log(3), sdlog = 0.01), # hazard ratio of death in Sick vs Healthy 287 | hr_S2 = rlnorm(n_sim, meanlog = log(10), sdlog = 0.02), # hazard ratio of death in Sicker vs Healthy 288 | 289 | # Effectiveness of treatment B 290 | hr_S1S2_trtB = rlnorm(n_sim, meanlog = log(0.6), sdlog = 0.02), # hazard ratio of becoming Sicker when Sick under treatment B 291 | 292 | # State rewards 293 | # Costs 294 | c_H = rgamma(n_sim, shape = 100, scale = 20), # cost of remaining one cycle in Healthy 295 | c_S1 = rgamma(n_sim, shape = 177.8, scale = 22.5), # cost of remaining one cycle in Sick 296 | c_S2 = rgamma(n_sim, shape = 225, scale = 66.7), # cost of remaining one cycle in Sicker 297 | c_D = 0, # cost of being dead (per cycle) 298 | c_trtA = rgamma(n_sim, shape = 73.5, scale = 163.3), # cost of treatment A 299 | c_trtB = rgamma(n_sim, shape = 86.2, scale = 150.8), # cost of treatment B 300 | 301 | # Utilities 302 | u_H = rbeta(n_sim, shape1 = 200, shape2 = 3), # utility when Healthy 303 | u_S1 = rbeta(n_sim, shape1 = 130, shape2 = 45), # utility when Sick 304 | u_S2 = rbeta(n_sim, shape1 = 230, shape2 = 230), # utility when Sicker 305 | u_D = 0, # utility when Dead 306 | u_trtA = rbeta(n_sim, shape1 = 300, shape2 = 15) # utility when being treated with A 307 | ) 308 | return(df_psa) 309 | } 310 | 311 | #' Update parameters 312 | #' 313 | #' \code{update_param_list} is used to update list of all parameters with new 314 | #' values for specific parameters. 315 | #' 316 | #' @param l_params_all List with all parameters of decision model 317 | #' @param params_updated Parameters for which values need to be updated 318 | #' @return 319 | #' A list with all parameters updated. 320 | #' @export 321 | update_param_list <- function(l_params_all, params_updated){ 322 | 323 | if (typeof(params_updated)!="list"){ 324 | params_updated <- split(unname(params_updated),names(params_updated)) #converte the named vector to a list 325 | } 326 | l_params_all <- modifyList(l_params_all, params_updated) #update the values 327 | return(l_params_all) 328 | } -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: github_document 3 | --- 4 | 5 | 6 | [![DOI](https://zenodo.org/badge/357362984.svg)](https://zenodo.org/badge/latestdoi/357362984) 7 | 8 | # An Introductory Tutorial on Cohort State-Transition Models in R Using a Cost-Effectiveness Analysis Example 9 | 10 | This GitHub repository provides the code of the tutorial on how to implement time-independent cohort state-transition models (cSTMs) in R using a cost-effectiveness analysis (CEA) example, explained in the following manuscript: 11 | 12 | - Alarid-Escudero F, Krijkamp EM, Enns EA, Yang A, Hunink MGM, Pechlivanoglou P, Jalal H. [An Introductory Tutorial on Cohort State-Transition Models in R Using a Cost-Effectiveness Analysis Example](https://journals.sagepub.com/doi/full/10.1177/0272989X221103163). [Medical Decision Making](https://journals.sagepub.com/home/mdm), 2023;43(1):3-20. https://doi.org/10.1177/0272989X221103163 13 | 14 | The release that accompanies the published article has been archived in zenodo: https://zenodo.org/badge/latestdoi/357362984 15 | 16 | The [`R`](https://github.com/DARTH-git/cohort-modeling-tutorial-intro/tree/main/R) folder includes two different scripts corresponding to functions used to synthesize cSTMs outputs and conduct several sensitivity analyses: 17 | 18 | - [`Funtions.R`](https://github.com/DARTH-git/cohort-modeling-tutorial-intro/blob/main/R/Functions.R): Functions that generate epidemiological measures from time-independent cSTMs and compute within-cycle correction, parameter transformation, matrix checks, and CEA and PSA visualization. 19 | - [`Functions_cSTM_time_indep.R`](https://github.com/DARTH-git/cohort-modeling-tutorial-intro/blob/main/R/Functions_cSTM_time_indep.R): These functions wrap the time-independent cSTM, compute CEA measures, and generate probabilistic sensitivity analysis (PSA) input datasets. 20 | 21 | ## How to cite this R code in your article 22 | 23 | You can cite the R code in this repository like this "we based our analysis using the R code from Alarid-Escudero F et al. (2022)". Here is the full bibliographic reference to include in your reference list for the manuscript and the R code (don't forget to update the 'last accessed' date): 24 | 25 | > Alarid-Escudero F, Krijkamp EM, Enns EA, Yang A, Hunink MGM, Pechlivanoglou P, Jalal H. An Introductory Tutorial on Cohort State-Transition Models in R Using a Cost-Effectiveness Analysis Example. Medical Decision Making, 2023;43(1):3-20. 26 | 27 | > Alarid-Escudero F, Krijkamp EM, Enns EA, Yang A, Hunink MGM, Pechlivanoglou P, Jalal H (2022). R Code for An Introductory Tutorial on Cohort State-Transition Models in R Using a Cost-Effectiveness Analysis Example (Version v0.2.1). Zenodo. [10.5281/zenodo.5223093](https://www.doi.org/10.5281/zenodo.5223093). Last accessed 30 March 2022. 28 | 29 | If you adapted the code, you should indicate "Adapted from:" or "Based on" so it is understood that you modified the code. For more information on how to cite computer code, we refer the user to review [Writing Code (from MIT Research Guide)](https://integrity.mit.edu/handbook/writing-code), which provides examples of how and when to cite computer code. 30 | 31 | ## Advanced state-transition cohort modeling 32 | To learn more on on how to implement time-dependent cohort state-transition models (cSTMs) in R using a cost-effectiveness analysis (CEA) example, we refer the reader to the following manuscript: 33 | 34 | - Alarid-Escudero F, Krijkamp EM, Enns EA, Yang A, Hunink MGM, Pechlivanoglou P, Jalal H. [A Tutorial on Time-Dependent Cohort State-Transition Models in R using a Cost-Effectiveness Analysis Example](https://journals.sagepub.com/doi/full/10.1177/0272989X221121747). [Medical Decision Making](https://journals.sagepub.com/home/mdm). 2023;43(1):21-41. https://doi.org/10.1177/0272989X221121747 35 | 36 | ## Preliminaries 37 | - Install [RStudio](https://www.rstudio.com/products/rstudio/download/) 38 | - Install [`dampack`](https://cran.r-project.org/web/packages/dampack/index.html) R package from CRAN 39 | ```{r, eval=FALSE} 40 | # Install release version from CRAN 41 | install.packages("dampack") 42 | 43 | # Or install development version from GitHub 44 | # devtools::install_github("DARTH-git/dampack") 45 | ``` 46 | - Install `devtools` to install [`darthtools`](https://github.com/DARTH-git/darthtools) R package from [DARTH's GitHub](https://github.com/DARTH-git) 47 | ```{r, eval=FALSE} 48 | # Install release version from CRAN 49 | install.packages("devtools") 50 | 51 | # Or install development version from GitHub 52 | # devtools::install_github("r-lib/devtools") 53 | ``` 54 | - Install `darthtools` using `devtools` 55 | ```{r, eval=FALSE} 56 | # Install development version from GitHub 57 | devtools::install_github("DARTH-git/darthtools") 58 | ``` 59 | We recommend familiarizing with the [DARTH](http://darthworkgroup.com) coding framework described in 60 | 61 | - Alarid-Escudero F, Krijkamp EM, Pechlivanoglou P, Jalal HJ, Kao SYZ, Yang A, Enns EA. [A Need for Change! A Coding Framework for Improving Transparency in Decision Modeling](https://link.springer.com/article/10.1007/s40273-019-00837-x). [PharmacoEconomics](https://www.springer.com/journal/40273), 2190;37(11):1329–1339. https://doi.org/10.1007/s40273-019-00837-x 62 | 63 | To run the CEA, you require [`dampack`: Decision-Analytic Modeling Package](https://cran.r-project.org/web/packages/dampack/index.html), an R package for analyzing and visualizing the health economic outputs of decision models. 64 | 65 | ## Use repository as a regular coding template 66 | 1. On the [tutorial's GitHub repository](https://github.com/DARTH-git/cohort-modeling-tutorial-intro), navigate to the main page of the repository (https://github.com/DARTH-git/cohort-modeling-tutorial-intro). 67 | 2. Above the file list, click **Clone or download** and select either 68 | a. **Open in desktop**, which requires the user to have a GitHub desktop installed, or 69 | b. **Download zip** that will ask the user to download the whole repository as a .zip file. 70 | 3. Open the RStudio project `cohort-modeling-tutorial-intro.Rproj`. 71 | 4. Install all the required packages (as mentioned above) 72 | - [`dampack`](https://cran.r-project.org/web/packages/dampack/index.html) 73 | - [`darthtools`](https://github.com/DARTH-git/darthtools) 74 | 5. Run the scripts in the analysis folder. 75 | 6. Modify or adapt these scripts as needed for your project or analysis. 76 | 77 | ## Full list of Contributors: 78 | 79 | * [Fernando Alarid-Escudero](https://github.com/feralaes) 80 | 81 | * [Eline Krijkamp](https://github.com/krijkamp) 82 | 83 | * [Eva Enns](https://github.com/evaenns) 84 | 85 | * [Alan Yang](https://github.com/alanyang0924) 86 | 87 | * [Myriam Hunink](http://www.erasmus-epidemiology.nl/people/profile.php?id=45) 88 | 89 | * [Petros Pechlivanoglou](https://github.com/ppehli) 90 | 91 | * [Hawre Jalal](https://github.com/hjalal) 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | [![DOI](https://zenodo.org/badge/357362984.svg)](https://zenodo.org/badge/latestdoi/357362984) 5 | 6 | # An Introductory Tutorial on Cohort State-Transition Models in R Using a Cost-Effectiveness Analysis Example 7 | 8 | This GitHub repository provides the code of the tutorial on how to 9 | implement time-independent cohort state-transition models (cSTMs) in R 10 | using a cost-effectiveness analysis (CEA) example, explained in the 11 | following manuscript: 12 | 13 | - Alarid-Escudero F, Krijkamp EM, Enns EA, Yang A, Hunink MGM, 14 | Pechlivanoglou P, Jalal H. [An Introductory Tutorial on Cohort 15 | State-Transition Models in R Using a Cost-Effectiveness Analysis 16 | Example](https://journals.sagepub.com/doi/full/10.1177/0272989X221103163). 17 | [Medical Decision Making](https://journals.sagepub.com/home/mdm), 18 | 2023;43(1):3-20. 19 | 20 | The release that accompanies the published article has been archived in 21 | zenodo: 22 | 23 | The 24 | [`R`](https://github.com/DARTH-git/cohort-modeling-tutorial-intro/tree/main/R) 25 | folder includes two different scripts corresponding to functions used to 26 | synthesize cSTMs outputs and conduct several sensitivity analyses: 27 | 28 | - [`Funtions.R`](https://github.com/DARTH-git/cohort-modeling-tutorial-intro/blob/main/R/Functions.R): 29 | Functions that generate epidemiological measures from time-independent 30 | cSTMs and compute within-cycle correction, parameter transformation, 31 | matrix checks, and CEA and PSA visualization. 32 | - [`Functions_cSTM_time_indep.R`](https://github.com/DARTH-git/cohort-modeling-tutorial-intro/blob/main/R/Functions_cSTM_time_indep.R): 33 | These functions wrap the time-independent cSTM, compute CEA measures, 34 | and generate probabilistic sensitivity analysis (PSA) input datasets. 35 | 36 | ## How to cite this R code in your article 37 | 38 | You can cite the R code in this repository like this “we based our 39 | analysis using the R code from Alarid-Escudero F et al. (2022)”. Here is 40 | the full bibliographic reference to include in your reference list for 41 | the manuscript and the R code (don’t forget to update the ‘last 42 | accessed’ date): 43 | 44 | > Alarid-Escudero F, Krijkamp EM, Enns EA, Yang A, Hunink MGM, 45 | > Pechlivanoglou P, Jalal H. An Introductory Tutorial on Cohort 46 | > State-Transition Models in R Using a Cost-Effectiveness Analysis 47 | > Example. Medical Decision Making, 2023;43(1):3-20. 48 | 49 | > Alarid-Escudero F, Krijkamp EM, Enns EA, Yang A, Hunink MGM, 50 | > Pechlivanoglou P, Jalal H (2022). R Code for An Introductory Tutorial 51 | > on Cohort State-Transition Models in R Using a Cost-Effectiveness 52 | > Analysis Example (Version v0.2.1). Zenodo. 53 | > [10.5281/zenodo.5223093](https://www.doi.org/10.5281/zenodo.5223093). 54 | > Last accessed 30 March 2022. 55 | 56 | If you adapted the code, you should indicate “Adapted from:” or “Based 57 | on” so it is understood that you modified the code. For more information 58 | on how to cite computer code, we refer the user to review [Writing Code 59 | (from MIT Research 60 | Guide)](https://integrity.mit.edu/handbook/writing-code), which provides 61 | examples of how and when to cite computer code. 62 | 63 | ## Advanced state-transition cohort modeling 64 | 65 | To learn more on on how to implement time-dependent cohort 66 | state-transition models (cSTMs) in R using a cost-effectiveness analysis 67 | (CEA) example, we refer the reader to the following manuscript: 68 | 69 | - Alarid-Escudero F, Krijkamp EM, Enns EA, Yang A, Hunink MGM, 70 | Pechlivanoglou P, Jalal H. [A Tutorial on Time-Dependent Cohort 71 | State-Transition Models in R using a Cost-Effectiveness Analysis 72 | Example](https://journals.sagepub.com/doi/full/10.1177/0272989X221121747). 73 | [Medical Decision Making](https://journals.sagepub.com/home/mdm). 74 | 2023;43(1):21-41. 75 | 76 | ## Preliminaries 77 | 78 | - Install [RStudio](https://www.rstudio.com/products/rstudio/download/) 79 | - Install 80 | [`dampack`](https://cran.r-project.org/web/packages/dampack/index.html) 81 | R package from CRAN 82 | 83 | ``` r 84 | # Install release version from CRAN 85 | install.packages("dampack") 86 | 87 | # Or install development version from GitHub 88 | # devtools::install_github("DARTH-git/dampack") 89 | ``` 90 | 91 | - Install `devtools` to install 92 | [`darthtools`](https://github.com/DARTH-git/darthtools) R package from 93 | [DARTH’s GitHub](https://github.com/DARTH-git) 94 | 95 | ``` r 96 | # Install release version from CRAN 97 | install.packages("devtools") 98 | 99 | # Or install development version from GitHub 100 | # devtools::install_github("r-lib/devtools") 101 | ``` 102 | 103 | - Install `darthtools` using `devtools` 104 | 105 | ``` r 106 | # Install development version from GitHub 107 | devtools::install_github("DARTH-git/darthtools") 108 | ``` 109 | 110 | We recommend familiarizing with the [DARTH](http://darthworkgroup.com) 111 | coding framework described in 112 | 113 | - Alarid-Escudero F, Krijkamp EM, Pechlivanoglou P, Jalal HJ, Kao SYZ, 114 | Yang A, Enns EA. [A Need for Change! A Coding Framework for Improving 115 | Transparency in Decision 116 | Modeling](https://link.springer.com/article/10.1007/s40273-019-00837-x). 117 | [PharmacoEconomics](https://www.springer.com/journal/40273), 118 | 2190;37(11):1329–1339. 119 | 120 | To run the CEA, you require [`dampack`: Decision-Analytic Modeling 121 | Package](https://cran.r-project.org/web/packages/dampack/index.html), an 122 | R package for analyzing and visualizing the health economic outputs of 123 | decision models. 124 | 125 | ## Use repository as a regular coding template 126 | 127 | 1. On the [tutorial’s GitHub 128 | repository](https://github.com/DARTH-git/cohort-modeling-tutorial-intro), 129 | navigate to the main page of the repository 130 | (). 131 | 2. Above the file list, click **Clone or download** and select either 132 | 1. **Open in desktop**, which requires the user to have a GitHub 133 | desktop installed, or 134 | 2. **Download zip** that will ask the user to download the whole 135 | repository as a .zip file. 136 | 3. Open the RStudio project `cohort-modeling-tutorial-intro.Rproj`. 137 | 4. Install all the required packages (as mentioned above) 138 | - [`dampack`](https://cran.r-project.org/web/packages/dampack/index.html) 139 | - [`darthtools`](https://github.com/DARTH-git/darthtools) 140 | 5. Run the scripts in the analysis folder. 141 | 6. Modify or adapt these scripts as needed for your project or 142 | analysis. 143 | 144 | ## Full list of Contributors: 145 | 146 | - [Fernando Alarid-Escudero](https://github.com/feralaes) 147 | 148 | - [Eline Krijkamp](https://github.com/krijkamp) 149 | 150 | - [Eva Enns](https://github.com/evaenns) 151 | 152 | - [Alan Yang](https://github.com/alanyang0924) 153 | 154 | - [Myriam 155 | Hunink](http://www.erasmus-epidemiology.nl/people/profile.php?id=45) 156 | 157 | - [Petros Pechlivanoglou](https://github.com/ppehli) 158 | 159 | - [Hawre Jalal](https://github.com/hjalal) 160 | -------------------------------------------------------------------------------- /analysis/cSTM_time_indep.R: -------------------------------------------------------------------------------- 1 | #* Script name: cSTM_time_indep.R 2 | # Appendix code to time-independent cSTMs in R ---- 3 | 4 | #* This code forms the basis for the state-transition model of the tutorial: 5 | #* 'An Introductory Tutorial to Cohort State-Transition Models in R for 6 | #* Cost-Effectiveness Analysis' 7 | #* Authors: 8 | #* - Fernando Alarid-Escudero 9 | #* - Eline Krijkamp 10 | #* - Eva A. Enns 11 | #* - Alan Yang 12 | #* - M.G. Myriam Hunink 13 | #* - Petros Pechlivanoglou 14 | #* - Hawre Jalal 15 | #* Please cite the article when using this code 16 | #* 17 | #* To program this tutorial we used: 18 | #* R version 4.0.5 (2021-03-31) 19 | #* Platform: 64-bit operating system, x64-based processor 20 | #* Running under: Mac OS 12.2.1 21 | #* RStudio: Version 1.4.1717 2009-2021 RStudio, Inc 22 | 23 | #* Implements a time-independent Sick-Sicker cSTM model that evaluates four 24 | #* strategies: 25 | #* - Standard of Care (SoC): best available care for the patients with the 26 | #* disease. This scenario reflects the natural history of the disease 27 | #* progression. 28 | #* - Strategy A: treatment A is given to patients in the Sick and Sicker states, 29 | #* but does only improves the quality of life of those in the Sick state. 30 | #* - Strategy B: treatment B is given to all sick patients and reduces disease 31 | #* progression from the Sick to Sicker state. 32 | #* - Strategy AB: This strategy combines treatment A and treatment B. The disease 33 | #* progression is reduced and individuals in the Sick state have an improved 34 | #* quality of life. 35 | 36 | #******************************************************************************# 37 | # Initial setup ---- 38 | rm(list = ls()) # remove any variables in R's memory 39 | 40 | ## Install required packages ---- 41 | # install.packages("dplyr") # to manipulate data 42 | # install.packages("tidyr") # to manipulate data 43 | # install.packages("reshape2") # to manipulate data 44 | # install.packages("ggplot2") # to visualize data 45 | # install.packages("ggrepel") # to visualize data 46 | # install.packages("ellipse") # to visualize data 47 | # install.packages("scales") # for dollar signs and commas 48 | # install.packages("dampack") # for CEA and calculate ICERs 49 | # install.packages("devtools") # to install packages from GitHub 50 | # devtools::install_github("DARTH-git/darthtools") # to install darthtools from GitHub using devtools 51 | # install.packages("doParallel") # to handle parallel processing 52 | 53 | ## Load packages ---- 54 | library(dplyr) 55 | library(tidyr) 56 | library(reshape2) # For melting data 57 | library(ggplot2) # For plotting 58 | library(ggrepel) # For plotting 59 | library(ellipse) # For plotting 60 | library(scales) # For dollar signs and commas 61 | # library(dampack) # Uncomment to use CEA and PSA visualization functionality from dampack instead of the functions included in this repository 62 | # library(darthtools) # Uncomment to use WCC, parameter transformation, and matrix checks from darthtools instead of the functions included in this repository 63 | # library(doParallel) # For running PSA in parallel 64 | 65 | ## Load supplementary functions ---- 66 | source("R/Functions.R") 67 | 68 | # Model input ---- 69 | ## General setup ---- 70 | cycle_length <- 1 # cycle length equal to one year (use 1/12 for monthly) 71 | n_age_init <- 25 # age at baseline 72 | n_age_max <- 100 # maximum age of follow up 73 | n_cycles <- (n_age_max - n_age_init)/cycle_length # time horizon, number of cycles 74 | v_names_states <- c("H", # the 4 health states of the model: 75 | "S1", # Healthy (H), Sick (S1), Sicker (S2), Dead (D) 76 | "S2", 77 | "D") 78 | 79 | n_states <- length(v_names_states) # number of health states 80 | 81 | ### Discounting factors ---- 82 | d_c <- 0.03 # annual discount rate for costs 83 | d_e <- 0.03 # annual discount rate for QALYs 84 | 85 | ### Strategies ---- 86 | v_names_str <- c("Standard of care", # store the strategy names 87 | "Strategy A", 88 | "Strategy B", 89 | "Strategy AB") 90 | n_str <- length(v_names_str) # number of strategies 91 | 92 | ## Within-cycle correction (WCC) using Simpson's 1/3 rule ---- 93 | v_wcc <- gen_wcc(n_cycles = n_cycles, # Function included in "R/Functions.R". The latest version can be found in `darthtools` package 94 | method = "Simpson1/3") # vector of wcc 95 | 96 | ### Transition rates (annual), and hazard ratios (HRs) ---- 97 | r_HD <- 0.002 # constant annual rate of dying when Healthy (all-cause mortality) 98 | r_HS1 <- 0.15 # constant annual rate of becoming Sick when Healthy 99 | r_S1H <- 0.5 # constant annual rate of becoming Healthy when Sick 100 | r_S1S2 <- 0.105 # constant annual rate of becoming Sicker when Sick 101 | hr_S1 <- 3 # hazard ratio of death in Sick vs Healthy 102 | hr_S2 <- 10 # hazard ratio of death in Sicker vs Healthy 103 | 104 | ### Effectiveness of treatment B ---- 105 | hr_S1S2_trtB <- 0.6 # hazard ratio of becoming Sicker when Sick under treatment B 106 | 107 | ### State rewards ---- 108 | #### Costs ---- 109 | c_H <- 2000 # annual cost of being Healthy 110 | c_S1 <- 4000 # annual cost of being Sick 111 | c_S2 <- 15000 # annual cost of being Sicker 112 | c_D <- 0 # annual cost of being dead 113 | c_trtA <- 12000 # annual cost of receiving treatment A 114 | c_trtB <- 13000 # annual cost of receiving treatment B 115 | #### Utilities ---- 116 | u_H <- 1 # annual utility of being Healthy 117 | u_S1 <- 0.75 # annual utility of being Sick 118 | u_S2 <- 0.5 # annual utility of being Sicker 119 | u_D <- 0 # annual utility of being dead 120 | u_trtA <- 0.95 # annual utility when receiving treatment A 121 | 122 | ### Discount weight for costs and effects ---- 123 | v_dwc <- 1 / ((1 + (d_c * cycle_length)) ^ (0:n_cycles)) 124 | v_dwe <- 1 / ((1 + (d_e * cycle_length)) ^ (0:n_cycles)) 125 | 126 | # Process model inputs ---- 127 | ## Cycle-specific transition probabilities to the Dead state ---- 128 | #* compute mortality rates 129 | r_S1D <- r_HD * hr_S1 # annual mortality rate in the Sick state 130 | r_S2D <- r_HD * hr_S2 # annual mortality rate in the Sicker state 131 | #* transform rates to probabilities 132 | #* Function included in "R/Functions.R". The latest version can be found in `darthtools` package 133 | p_HS1 <- rate_to_prob(r = r_HS1, t = cycle_length) # constant annual probability of becoming Sick when Healthy conditional on surviving 134 | p_S1H <- rate_to_prob(r = r_S1H, t = cycle_length) # constant annual probability of becoming Healthy when Sick conditional on surviving 135 | p_S1S2 <- rate_to_prob(r = r_S1S2, t = cycle_length)# constant annual probability of becoming Sicker when Sick conditional on surviving 136 | p_HD <- rate_to_prob(r = r_HD, t = cycle_length) # annual mortality risk in the Healthy state 137 | p_S1D <- rate_to_prob(r = r_S1D, t = cycle_length) # annual mortality risk in the Sick state 138 | p_S2D <- rate_to_prob(r = r_S2D, t = cycle_length) # annual mortality risk in the Sicker state 139 | 140 | ## Annual transition probability of becoming Sicker when Sick for treatment B ---- 141 | #* Apply hazard ratio to rate to obtain transition rate of becoming Sicker when 142 | #* Sick for treatment B 143 | r_S1S2_trtB <- r_S1S2 * hr_S1S2_trtB 144 | #* Transform rate to probability to become Sicker when Sick under treatment B 145 | #* conditional on surviving 146 | #* (Function included in "R/Functions.R". The latest version can be found in 147 | #* `darthtools` package) 148 | p_S1S2_trtB <- rate_to_prob(r = r_S1S2_trtB, t = cycle_length) 149 | 150 | # Construct state-transition models ---- 151 | ## Initial state vector ---- 152 | #* All starting healthy 153 | v_m_init <- c(H = 1, S1 = 0, S2 = 0, D = 0) # initial state vector 154 | v_m_init 155 | 156 | ## Initialize cohort traces ---- 157 | ### Initialize cohort trace for SoC ---- 158 | m_M <- matrix(NA, 159 | nrow = (n_cycles + 1), ncol = n_states, 160 | dimnames = list(0:n_cycles, v_names_states)) 161 | #* Store the initial state vector in the first row of the cohort trace 162 | m_M[1, ] <- v_m_init 163 | 164 | ### Initialize cohort trace for strategies A, B, and AB ---- 165 | #* Structure and initial states are the same as for SoC 166 | m_M_strA <- m_M # Strategy A 167 | m_M_strB <- m_M # Strategy B 168 | m_M_strAB <- m_M # Strategy AB 169 | 170 | ## Create transition probability matrices for strategy SoC ---- 171 | ### Initialize transition probability matrix for strategy SoC ---- 172 | #* All transitions to a non-death state are assumed to be conditional on survival 173 | m_P <- matrix(0, 174 | nrow = n_states, ncol = n_states, 175 | dimnames = list(v_names_states, 176 | v_names_states)) # define row and column names 177 | ### Fill in matrix ---- 178 | #* From H 179 | m_P["H", "H"] <- (1 - p_HD) * (1 - p_HS1) 180 | m_P["H", "S1"] <- (1 - p_HD) * p_HS1 181 | m_P["H", "D"] <- p_HD 182 | #* From S1 183 | m_P["S1", "H"] <- (1 - p_S1D) * p_S1H 184 | m_P["S1", "S1"] <- (1 - p_S1D) * (1 - (p_S1H + p_S1S2)) 185 | m_P["S1", "S2"] <- (1 - p_S1D) * p_S1S2 186 | m_P["S1", "D"] <- p_S1D 187 | #* From S2 188 | m_P["S2", "S2"] <- 1 - p_S2D 189 | m_P["S2", "D"] <- p_S2D 190 | #* From D 191 | m_P["D", "D"] <- 1 192 | 193 | ### Initialize transition probability matrix for strategy A as a copy of SoC's ---- 194 | m_P_strA <- m_P 195 | 196 | ### Initialize transition probability matrix for strategy B ---- 197 | m_P_strB <- m_P 198 | #* Update only transition probabilities from S1 involving p_S1S2 199 | m_P_strB["S1", "S1"] <- (1 - p_S1D) * (1 - (p_S1H + p_S1S2_trtB)) 200 | m_P_strB["S1", "S2"] <- (1 - p_S1D) * p_S1S2_trtB 201 | 202 | ### Initialize transition probability matrix for strategy AB as a copy of B's ---- 203 | m_P_strAB <- m_P_strB 204 | 205 | ## Check if transition probability matrices are valid ---- 206 | #* Functions included in "R/Functions.R". The latest version can be found in `darthtools` package 207 | ### Check that transition probabilities are [0, 1] ---- 208 | check_transition_probability(m_P, verbose = TRUE) # m_P >= 0 && m_P <= 1 209 | check_transition_probability(m_P_strA, verbose = TRUE) # m_P_strA >= 0 && m_P_strA <= 1 210 | check_transition_probability(m_P_strB, verbose = TRUE) # m_P_strB >= 0 && m_P_strB <= 1 211 | check_transition_probability(m_P_strAB, verbose = TRUE) # m_P_strAB >= 0 && m_P_strAB <= 1 212 | ### Check that all rows sum to 1 ---- 213 | check_sum_of_transition_array(m_P, n_states = n_states, n_cycles = n_cycles, verbose = TRUE) # rowSums(m_P) == 1 214 | check_sum_of_transition_array(m_P_strA, n_states = n_states, n_cycles = n_cycles, verbose = TRUE) # rowSums(m_P_strA) == 1 215 | check_sum_of_transition_array(m_P_strB, n_states = n_states, n_cycles = n_cycles, verbose = TRUE) # rowSums(m_P_strB) == 1 216 | check_sum_of_transition_array(m_P_strAB, n_states = n_states, n_cycles = n_cycles, verbose = TRUE) # rowSums(m_P_strAB) == 1 217 | 218 | # Run Markov model ---- 219 | #* Iterative solution of time-independent cSTM 220 | for(t in 1:n_cycles){ 221 | # For SoC 222 | m_M[t + 1, ] <- m_M[t, ] %*% m_P 223 | # For strategy A 224 | m_M_strA[t + 1, ] <- m_M_strA[t, ] %*% m_P_strA 225 | # For strategy B 226 | m_M_strB[t + 1, ] <- m_M_strB[t, ] %*% m_P_strB 227 | # For strategy AB 228 | m_M_strAB[t + 1, ] <- m_M_strAB[t, ] %*% m_P_strAB 229 | } 230 | 231 | ## Store the cohort traces in a list ---- 232 | l_m_M <- list(m_M, 233 | m_M_strA, 234 | m_M_strB, 235 | m_M_strAB) 236 | names(l_m_M) <- v_names_str 237 | 238 | # Plot Outputs ---- 239 | #* Plot the cohort trace for strategies SoC and A 240 | #* (Function included in "R/Functions.R"; depends on the `ggplot2` package) 241 | plot_trace(m_M) 242 | 243 | # State Rewards ---- 244 | ## Scale by the cycle length ---- 245 | #* Vector of state utilities under strategy SoC 246 | v_u_SoC <- c(H = u_H, 247 | S1 = u_S1, 248 | S2 = u_S2, 249 | D = u_D) * cycle_length 250 | #* Vector of state costs under strategy SoC 251 | v_c_SoC <- c(H = c_H, 252 | S1 = c_S1, 253 | S2 = c_S2, 254 | D = c_D) * cycle_length 255 | #* Vector of state utilities under strategy A 256 | v_u_strA <- c(H = u_H, 257 | S1 = u_trtA, 258 | S2 = u_S2, 259 | D = u_D) * cycle_length 260 | #* Vector of state costs under strategy A 261 | v_c_strA <- c(H = c_H, 262 | S1 = c_S1 + c_trtA, 263 | S2 = c_S2 + c_trtA, 264 | D = c_D) 265 | #* Vector of state utilities under strategy B 266 | v_u_strB <- c(H = u_H, 267 | S1 = u_S1, 268 | S2 = u_S2, 269 | D = u_D) * cycle_length 270 | #* Vector of state costs under strategy B 271 | v_c_strB <- c(H = c_H, 272 | S1 = c_S1 + c_trtB, 273 | S2 = c_S2 + c_trtB, 274 | D = c_D) * cycle_length 275 | #* Vector of state utilities under strategy AB 276 | v_u_strAB <- c(H = u_H, 277 | S1 = u_trtA, 278 | S2 = u_S2, 279 | D = u_D) * cycle_length 280 | #* Vector of state costs under strategy AB 281 | v_c_strAB <- c(H = c_H, 282 | S1 = c_S1 + (c_trtA + c_trtB), 283 | S2 = c_S2 + (c_trtA + c_trtB), 284 | D = c_D) * cycle_length 285 | 286 | ## Store state rewards ---- 287 | #* Store the vectors of state utilities for each strategy in a list 288 | l_u <- list(SQ = v_u_SoC, 289 | A = v_u_strA, 290 | B = v_u_strB, 291 | AB = v_u_strAB) 292 | #* Store the vectors of state cost for each strategy in a list 293 | l_c <- list(SQ = v_c_SoC, 294 | A = v_c_strA, 295 | B = v_c_strB, 296 | AB = v_c_strAB) 297 | 298 | #* assign strategy names to matching items in the lists 299 | names(l_u) <- names(l_c) <- v_names_str 300 | 301 | # Compute expected outcomes ---- 302 | #* Create empty vectors to store total utilities and costs 303 | v_tot_qaly <- v_tot_cost <- vector(mode = "numeric", length = n_str) 304 | names(v_tot_qaly) <- names(v_tot_cost) <- v_names_str 305 | 306 | ## Loop through each strategy and calculate total utilities and costs ---- 307 | for (i in 1:n_str) { 308 | v_u_str <- l_u[[i]] # select the vector of state utilities for the i-th strategy 309 | v_c_str <- l_c[[i]] # select the vector of state costs for the i-th strategy 310 | 311 | ###* Expected QALYs and costs per cycle 312 | ##* Vector of QALYs and Costs 313 | #* Apply state rewards 314 | v_qaly_str <- l_m_M[[i]] %*% v_u_str # sum the utilities of all states for each cycle 315 | v_cost_str <- l_m_M[[i]] %*% v_c_str # sum the costs of all states for each cycle 316 | 317 | ###* Discounted total expected QALYs and Costs per strategy and apply within-cycle correction if applicable 318 | #* QALYs 319 | v_tot_qaly[i] <- t(v_qaly_str) %*% (v_dwe * v_wcc) 320 | #* Costs 321 | v_tot_cost[i] <- t(v_cost_str) %*% (v_dwc * v_wcc) 322 | } 323 | 324 | # Cost-effectiveness analysis (CEA) ---- 325 | ## Incremental cost-effectiveness ratios (ICERs) ---- 326 | #* Function included in "R/Functions.R"; depends on the `dplyr` package 327 | #* The latest version can be found in `dampack` package 328 | df_cea <- calculate_icers(cost = v_tot_cost, 329 | effect = v_tot_qaly, 330 | strategies = v_names_str) 331 | df_cea 332 | 333 | ## CEA table in proper format ---- 334 | table_cea <- format_table_cea(df_cea) # Function included in "R/Functions.R"; depends on the `scales` package 335 | table_cea 336 | 337 | ## CEA frontier ----- 338 | #* Function included in "R/Functions.R"; depends on the `ggplot2` and `ggrepel` packages. 339 | #* The latest version can be found in `dampack` package 340 | plot(df_cea, label = "all", txtsize = 16) + 341 | expand_limits(x = max(table_cea$QALYs) + 0.1) + 342 | theme(legend.position = c(0.8, 0.2)) 343 | 344 | #******************************************************************************# 345 | # Probabilistic Sensitivity Analysis (PSA) ----- 346 | ## Load model, CEA and PSA functions ---- 347 | source("R/Functions_cSTM_time_indep.R") 348 | source("R/Functions.R") 349 | 350 | ## List of input parameters ----- 351 | l_params_all <- list( 352 | # Transition probabilities (per cycle), hazard ratios 353 | r_HD = 0.002, # constant rate of dying when Healthy (all-cause mortality) 354 | r_HS1 = 0.15, # probability to become Sick when Healthy conditional on surviving 355 | r_S1H = 0.5, # probability to become Healthy when Sick conditional on surviving 356 | r_S1S2 = 0.105, # probability to become Sicker when Sick conditional on surviving 357 | hr_S1 = 3, # hazard ratio of death in Sick vs Healthy 358 | hr_S2 = 10, # hazard ratio of death in Sicker vs Healthy 359 | # Effectiveness of treatment B 360 | hr_S1S2_trtB = 0.6, # hazard ratio of becoming Sicker when Sick under treatment B 361 | ## State rewards 362 | # Costs 363 | c_H = 2000, # cost of remaining one cycle in Healthy 364 | c_S1 = 4000, # cost of remaining one cycle in Sick 365 | c_S2 = 15000, # cost of remaining one cycle in Sicker 366 | c_D = 0, # cost of being dead (per cycle) 367 | c_trtA = 12000, # cost of treatment A 368 | c_trtB = 13000, # cost of treatment B 369 | # Utilities 370 | u_H = 1, # utility when Healthy 371 | u_S1 = 0.75, # utility when Sick 372 | u_S2 = 0.5, # utility when Sicker 373 | u_D = 0, # utility when Dead 374 | u_trtA = 0.95, # utility when being treated with A 375 | # Initial and maximum ages 376 | n_age_init = 25, 377 | n_age_max = 100, 378 | # Discount rates 379 | d_c = 0.03, # annual discount rate for costs 380 | d_e = 0.03, # annual discount rate for QALYs, 381 | # Cycle length 382 | cycle_length = 1 383 | ) 384 | 385 | #* Store the parameter names into a vector 386 | v_names_params <- names(l_params_all) 387 | 388 | ## Test functions to generate CE outcomes and PSA dataset ---- 389 | #* Test function to compute CE outcomes 390 | calculate_ce_out(l_params_all) # Function included in "R/Functions_cSTM_time_indep.R" 391 | 392 | #* Test function to generate PSA input dataset 393 | generate_psa_params(10) # Function included in "R/Functions_cSTM_time_indep.R" 394 | 395 | ## Generate PSA dataset ---- 396 | #* Number of simulations 397 | n_sim <- 1000 398 | 399 | #* Generate PSA input dataset 400 | df_psa_input <- generate_psa_params(n_sim = n_sim) 401 | #* First six observations 402 | head(df_psa_input) 403 | 404 | ### Histogram of PSA dataset ---- 405 | ggplot(melt(df_psa_input, variable.name = "Parameter"), 406 | aes(x = value)) + 407 | facet_wrap(~Parameter, scales = "free") + 408 | geom_histogram(aes(y = ..density..)) + 409 | scale_x_continuous(breaks = number_ticks(4)) + 410 | ylab("") + 411 | theme_bw(base_size = 16) + 412 | theme(axis.text = element_text(size = 6), 413 | axis.title.x = element_blank(), 414 | axis.title.y = element_blank(), 415 | axis.text.y = element_blank(), 416 | axis.ticks.y = element_blank()) 417 | 418 | ## Run PSA ---- 419 | #* Initialize data.frames with PSA output 420 | #* data.frame of costs 421 | df_c <- as.data.frame(matrix(0, 422 | nrow = n_sim, 423 | ncol = n_str)) 424 | colnames(df_c) <- v_names_str 425 | #* data.frame of effectiveness 426 | df_e <- as.data.frame(matrix(0, 427 | nrow = n_sim, 428 | ncol = n_str)) 429 | colnames(df_e) <- v_names_str 430 | 431 | #* Conduct probabilistic sensitivity analysis 432 | #* Run Markov model on each parameter set of PSA input dataset 433 | n_time_init_psa_series <- Sys.time() 434 | for(i in 1:n_sim){ 435 | l_psa_input <- update_param_list(l_params_all, df_psa_input[i,]) 436 | l_out_temp <- calculate_ce_out(l_psa_input) 437 | df_c[i, ] <- l_out_temp$Cost 438 | df_e[i, ] <- l_out_temp$Effect 439 | # Display simulation progress 440 | if(i/(n_sim/10) == round(i/(n_sim/10), 0)) { # display progress every 10% 441 | cat('\r', paste(i/n_sim * 100, "% done", sep = " ")) 442 | } 443 | } 444 | n_time_end_psa_series <- Sys.time() 445 | n_time_total_psa_series <- n_time_end_psa_series - n_time_init_psa_series 446 | print(paste0("PSA with ", scales::comma(n_sim), " simulations run in series in ", 447 | round(n_time_total_psa_series, 2), " ", 448 | units(n_time_total_psa_series))) 449 | 450 | ### Run Markov model on each parameter set of PSA input dataset in parallel 451 | # ## Get OS 452 | # os <- get_os() 453 | # print(paste0("Parallelized PSA on ", os)) 454 | # 455 | # no_cores <- parallel::detectCores() - 1 456 | # 457 | # n_time_init_psa <- Sys.time() 458 | # 459 | # ## Run parallelized PSA based on OS 460 | # if(os == "osx"){ 461 | # # Initialize cluster object 462 | # cl <- parallel::makeForkCluster(no_cores) 463 | # # Register clusters 464 | # doParallel::registerDoParallel(cl) 465 | # # Run parallelized PSA 466 | # df_ce <- foreach::foreach(i = 1:n_sim, .combine = rbind) %dopar% { 467 | # l_out_temp <- calculate_ce_out(df_psa_input[i, ]) 468 | # df_ce <- c(l_out_temp$Cost, l_out_temp$Effect) 469 | # } 470 | # # Extract costs and effects from the PSA dataset 471 | # df_c <- df_ce[, 1:n_str] 472 | # df_e <- df_ce[, (n_str+1):(2*n_str)] 473 | # # Register end time of parallelized PSA 474 | # n_time_end_psa <- Sys.time() 475 | # } 476 | # if(os == "windows"){ 477 | # # Initialize cluster object 478 | # cl <- parallel::makeCluster(no_cores) 479 | # # Register clusters 480 | # doParallel::registerDoParallel(cl) 481 | # opts <- list(attachExportEnv = TRUE) 482 | # # Run parallelized PSA 483 | # df_ce <- foreach::foreach(i = 1:n_samp, .combine = rbind, 484 | # .export = ls(globalenv()), 485 | # .packages=c("dampack"), 486 | # .options.snow = opts) %dopar% { 487 | # l_out_temp <- calculate_ce_out(df_psa_input[i, ]) 488 | # df_ce <- c(l_out_temp$Cost, l_out_temp$Effect) 489 | # } 490 | # # Extract costs and effects from the PSA dataset 491 | # df_c <- df_ce[, 1:n_str] 492 | # df_e <- df_ce[, (n_str+1):(2*n_str)] 493 | # # Register end time of parallelized PSA 494 | # n_time_end_psa <- Sys.time() 495 | # } 496 | # if(os == "linux"){ 497 | # # Initialize cluster object 498 | # cl <- parallel::makeCluster(no_cores) 499 | # # Register clusters 500 | # doParallel::registerDoMC(cl) 501 | # # Run parallelized PSA 502 | # df_ce <- foreach::foreach(i = 1:n_sim, .combine = rbind) %dopar% { 503 | # l_out_temp <- calculate_ce_out(df_psa_input[i, ]) 504 | # df_ce <- c(l_out_temp$Cost, l_out_temp$Effect) 505 | # } 506 | # # Extract costs and effects from the PSA dataset 507 | # df_c <- df_ce[, 1:n_str] 508 | # df_e <- df_ce[, (n_str+1):(2*n_str)] 509 | # # Register end time of parallelized PSA 510 | # n_time_end_psa <- Sys.time() 511 | # } 512 | # # Stop clusters 513 | # stopCluster(cl) 514 | # n_time_total_psa <- n_time_end_psa - n_time_init_psa 515 | # print(paste0("PSA with ", scales:: comma(n_sim), " simulations run in series in ", 516 | # round(n_time_total_psa, 2), " ", 517 | # units(n_time_total_psa_series))) 518 | 519 | ## Visualize PSA results and CEA ---- 520 | ### Create PSA object ---- 521 | #* Function included in "R/Functions.R" The latest version can be found in `dampack` package 522 | l_psa <- make_psa_obj(cost = df_c, 523 | effectiveness = df_e, 524 | parameters = df_psa_input, 525 | strategies = v_names_str) 526 | l_psa$strategies <- v_names_str 527 | colnames(l_psa$effectiveness)<- v_names_str 528 | colnames(l_psa$cost)<- v_names_str 529 | 530 | #* Vector with willingness-to-pay (WTP) thresholds. 531 | v_wtp <- seq(0, 200000, by = 5000) 532 | 533 | ### Cost-Effectiveness Scatter plot ---- 534 | #* Function included in "R/Functions.R"; depends on `tidyr` and `ellipse` packages. 535 | #* The latest version can be found in `dampack` package 536 | plot.psa(l_psa) + 537 | ggthemes::scale_color_colorblind() + 538 | ggthemes::scale_fill_colorblind() + 539 | xlab("Effectiveness (QALYs)") + 540 | guides(col = guide_legend(nrow = 2)) + 541 | theme(legend.position = "bottom") 542 | 543 | ### Incremental cost-effectiveness ratios (ICERs) with probabilistic output ---- 544 | #* Compute expected costs and effects for each strategy from the PSA 545 | #* Function included in "R/Functions.R". The latest version can be found in `dampack` package 546 | df_out_ce_psa <- summary.psa(l_psa) 547 | 548 | #* Function included in "R/Functions.R"; depends on the `dplyr` package 549 | #* The latest version can be found in `dampack` package 550 | df_cea_psa <- calculate_icers(cost = df_out_ce_psa$meanCost, 551 | effect = df_out_ce_psa$meanEffect, 552 | strategies = df_out_ce_psa$Strategy) 553 | df_cea_psa 554 | 555 | ### Plot cost-effectiveness frontier with probabilistic output ---- 556 | #* Function included in "R/Functions.R"; depends on the `ggplot2` and `ggrepel` packages. 557 | #* The latest version can be found in `dampack` package 558 | plot.icers(df_cea_psa) 559 | 560 | ## Cost-effectiveness acceptability curves (CEACs) and frontier (CEAF) --- 561 | #* Functions included in "R/Functions.R". The latest versions can be found in `dampack` package 562 | ceac_obj <- ceac(wtp = v_wtp, psa = l_psa) 563 | #* Regions of highest probability of cost-effectiveness for each strategy 564 | summary.ceac(ceac_obj) 565 | #* CEAC & CEAF plot 566 | plot.ceac(ceac_obj) + 567 | ggthemes::scale_color_colorblind() + 568 | ggthemes::scale_fill_colorblind() + 569 | theme(legend.position = c(0.82, 0.5)) 570 | 571 | ## Expected Loss Curves (ELCs) ---- 572 | #* Function included in "R/Functions.R".The latest version can be found in `dampack` package 573 | elc_obj <- calc_exp_loss(wtp = v_wtp, psa = l_psa) 574 | elc_obj 575 | #* ELC plot 576 | plot.exp_loss(elc_obj, log_y = FALSE, 577 | txtsize = 16, xlim = c(0, NA), n_x_ticks = 14, 578 | col = "full") + 579 | ggthemes::scale_color_colorblind() + 580 | ggthemes::scale_fill_colorblind() + 581 | # geom_point(aes(shape = as.name("Strategy"))) + 582 | scale_y_continuous("Expected Loss (Thousand $)", 583 | breaks = number_ticks(10), 584 | labels = function(x) x/1000) + 585 | theme(legend.position = c(0.4, 0.7)) 586 | 587 | ## Expected value of perfect information (EVPI) ---- 588 | #* Function included in "R/Functions.R". The latest version can be found in `dampack` package 589 | evpi <- calc_evpi(wtp = v_wtp, psa = l_psa) 590 | #* EVPI plot 591 | plot.evpi(evpi, effect_units = "QALY") 592 | -------------------------------------------------------------------------------- /cohort-modeling-tutorial-intro.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: knitr 13 | LaTeX: pdfLaTeX 14 | -------------------------------------------------------------------------------- /manuscript/Appendix_matrix algebra.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DARTH-git/cohort-modeling-tutorial-intro/d3c5497fb77879c79322e1925e9c8ccc38ef76c4/manuscript/Appendix_matrix algebra.pdf -------------------------------------------------------------------------------- /manuscript/Appendix_table.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "An Introductory Tutorial on Cohort State-Transition Models in R Using a Cost-Effectiveness Analysis Example" 3 | subtitle: "Appendix" 4 | author: 5 | - Fernando Alarid-Escudero, PhD^[Division of Public Administration, Center for Research and Teaching in Economics (CIDE), Aguascalientes, AGS, Mexico] 6 | - Eline Krijkamp, MSc^[Department of Epidemiology and Department of Radiology, Erasmus University Medical Center, Rotterdam, The Netherlands] 7 | - Eva A. Enns, PhD^[Division of Health Policy and Management, University of Minnesota School of Public Health, Minneapolis, MN, USA] 8 | - Alan Yang, MSc^[The Hospital for Sick Children, Toronto] 9 | - Myriam G.M. Hunink, PhD$^\dagger$^[Center for Health Decision Sciences, Harvard T.H. Chan School of Public Health, Boston, USA] 10 | - Petros Pechlivanoglou, PhD^[The Hospital for Sick Children, Toronto and University of Toronto, Toronto, Ontario, Canada] 11 | - Hawre Jalal, MD, PhD^[University of Pittsburgh, Pittsburgh, PA, USA] 12 | date: '`r Sys.Date()`' 13 | output: 14 | pdf_document: 15 | keep_tex: yes 16 | classoption: landscape 17 | --- 18 | 19 | ```{r setup, include=FALSE} 20 | knitr::opts_chunk$set(echo = TRUE) 21 | ``` 22 | 23 | ## Cohort tutorial model components 24 | ### Table I 25 | This table contains an overview of the key model components used in the code for the Sick-Sicker example from the [DARTH](http://darthworkgroup.com/) manuscript: [“An Introductory Tutorial to Cohort State-Transition Models in R”](https://arxiv.org/abs/2001.07824). The first column gives the mathematical notation for some of the model components that are used in the equations in the manuscript. The second column gives a description of the model component with the R name in the third column. The forth gives the data structure, e.g. scalar, list, vector, matrix etc, with the according dimensions of this data structure in the fifth column. The final column indicated the type of data that is stored in the data structure, e.g. numeric (5.2,6.3,7.4), category (A,B,C), integer (5,6,7), logical (TRUE, FALSE). 26 | 27 | 28 | | Parameter | Description | R name | Data structure | Dimensions | Data type | 29 | |-----------|-------------------------------------|---------------|----------------|-------------------|-------------| 30 | | $n_t$ | Time horizon | `n_cycles` | scalar | | numeric | 31 | | | Cycle length | `cycle_length`| scalar | | numeric | 32 | | $v_s$ | Names of the health states | `v_names_states`| vector | `n_states` x 1 | character | 33 | | $n_s$ | Number of health states | `n_states` | scalar | | numeric | 34 | | $v_{str}$ | Names of the strategies | `v_names_str`| scalar | | character | 35 | | $n_{str}$ | Number of strategies | `n_str` | scalar | | character | 36 | | \(d_c\) | Discount rate for costs | `d_c` | scalar | | numeric | 37 | | \(d_e\) | Discount rate for effects | `d_e` | scalar | | numeric | 38 | | \(\mathbf{d_c}\) | Discount weights vector for costs | `v_dwc` | vector | (`n_t` x 1 ) + 1 | numeric | 39 | | \(\mathbf{d_e}\) | Discount weights vector for effects| `v_dwe` | vector | (`n_t` x 1 ) + 1 | numeric | 40 | | | Sequence of cycle numbers | `v_cycles` | vector | (`n_t` x 1 ) + 1 | numeric | 41 | |\(\mathbf{wcc}\) | Within-cycle correction weights | `v_wcc` | vector | (`n_t` x 1 ) + 1 | numeric | 42 | | $age_{_0}$ | Age at baseline | `n_age_init` | scalar | | numeric | 43 | | $age$ | Maximum age of follow up | `n_age_max` | scalar | | numeric | 44 | | \(M\) | Cohort trace | `m_M` | matrix | (`n_t` + 1) x `n_states` | numeric | 45 | | \(m_0\) | Initial state vector | `v_m_init` | vector | 1 x `n_states` | numeric | 46 | | \(m_t\) | State vector in cycle $t$ | `v_mt` | vector | 1 x `n_states` | numeric | 47 | | | | | | | | 48 | | | **Transition probabilities and rates** | | | | | 49 | | $p_{[H,S1]}$ | From Healthy to Sick conditional on surviving | `p_HS1` | scalar | | numeric | 50 | | $p_{[S1,H]}$ | From Sick to Healthy conditional on surviving | `p_S1H` | scalar | | numeric | 51 | | $p_{[S1,S2]}$ | From Sick to Sicker conditional on surviving | `p_S1S2` | scalar | | numeric | 52 | | $p_{[S1,S2]_{trtB}}$ | From Sicker to Sick under treatment B conditional on surviving | `p_S1S2_trtB` | scalar | | numeric | 53 | | $r_{[H,D]}$ | Constant rate of dying when Healthy (all-cause mortality rate)| `r_HD` | scalar | | numeric | 54 | | $r_{[S1,S2]}$ | Constant rate of becoming Sicker when Sick | `r_S1S2` | scalar | | numeric | 55 | | $r_{[S1,S2]_{trtB}}$ | Constant rate of becoming Sicker when Sick for treatment B| `r_S1S2_trtB` | scalar | | numeric | 56 | | $hr_{[S1,H]}$ | Hazard ratio of death in Sick vs Healthy | `hr_S1` | scalar | | numeric | 57 | | $hr_{[S2,H]}$ | Hazard ratio of death in Sicker vs Healthy | `hr_S2` | scalar | | numeric | 58 | | $hr_{[S1,S2]_{trtB}}$ | Hazard ratio of becoming Sicker when Sick under treatment B | `hr_S1S2_trtB`| scalar | | numeric | 59 | | \(P\) | Time-independent transition probability matrix* | `m_P` | matrix | `n_states` x `n_states` | numeric | 60 | | | * `_trtX` is used to specify for which strategy the transition probability matrix is | | | | | 61 | | | | | | | | 62 | | | **Annual costs** | | | | | 63 | | | Healthy individuals | `c_H` | scalar | | numeric | 64 | | | Sick individuals in Sick | `c_S1` | scalar | | numeric | 65 | | | Sick individuals in Sicker | `c_S2` | scalar | | numeric | 66 | | | Dead individuals | `c_D` | scalar | | numeric | 67 | | | Additional costs treatment A | `c_trtA` | scalar | | numeric | 68 | | | Additional costs treatment B | `c_trtB` | scalar | | numeric | 69 | | | Vector of state costs for a strategy | `v_c_str` | vector | 1 x `n_states` | numeric | 70 | | | list that stores the vectors of state costs for each strategy | `l_c` | list | | numeric | 71 | | | | | | | | 72 | | | **Utility weights** | | | | | 73 | | | Healthy individuals | `u_H` | scalar | | numeric | 74 | | | Sick individuals in Sick | `u_S1` | scalar | | numeric | 75 | | | Sick individuals in Sicker | `u_S2` | scalar | | numeric | 76 | | | Dead individuals | `u_D` | scalar | | numeric | 77 | | | Treated with treatment A | `u_trtA` | scalar | | numeric | 78 | | | Vector of state utilities for a strategy | `v_u_str` | vector | 1 x `n_states` | numeric | 79 | | | List that stores the vectors of state utilities for each strategy | `l_u` | list | | numeric | 80 | | | | | | | | 81 | | | **Outcome structures** | | | | | 82 | | | Expected QALYs per cycle under a strategy | `v_qaly_str` | vector | 1 x (`n_t` + 1) | numeric | 83 | | | Expected costs per cycle under a strategy | `v_cost_str` | vector | 1 x (`n_t` + 1) | numeric | 84 | | | Vector of expected discounted QALYs for each strategy | `v_tot_qaly` | vector | 1 x `n_states` | numeric | 85 | | | Vector of expected discounted costs for each strategy | `v_tot_cost`| vector | 1 x `n_states` | numeric | 86 | | | Summary matrix with costs and QALYS per strategy| `m_outcomes` | table | `n_states` x 2 | | 87 | | | Summary of the model outcomes | `df_cea` | data frame | | | 88 | | | Summary of the model outcomes | `table_cea` | table | | | 89 | | | | | | | | 90 | | | **Probabilistic analysis structures** | | | | | 91 | | | Number of PSA iterations | `n_sim` | scalar | | numeric | 92 | | | List that stores all the values of the input parameters | `l_params_all` | list | | numeric | 93 | | | Data frame with the parameter values for each PSA iteration | `df_psa_input` | data frame | | numeric | 94 | | | Vector with the names of all the input parameters | `v_names_params` | vector | | character | 95 | | | List with the model outcomes of the PSA for all strategies | `l_psa` | list | | numeric | 96 | | | Vector with a sequence of relevant willingness-to-pay values | `v_wtp` | vector | | numeric | 97 | | | Data frame to store expected costs and effects for each strategy from the PSA | `df_out_ce_psa` | data frame | | numeric | 98 | | | Data frame to store incremental cost-effectiveness ratios (ICERs) from the PSA | `df_cea_psa` | data frame | | numeric | 99 | | | For more details about the PSA structures read `dampack`'s vignettes | | | | | 100 | -------------------------------------------------------------------------------- /manuscript/Appendix_table.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DARTH-git/cohort-modeling-tutorial-intro/d3c5497fb77879c79322e1925e9c8ccc38ef76c4/manuscript/Appendix_table.pdf -------------------------------------------------------------------------------- /manuscript/Appendix_table.tex: -------------------------------------------------------------------------------- 1 | % Options for packages loaded elsewhere 2 | \PassOptionsToPackage{unicode}{hyperref} 3 | \PassOptionsToPackage{hyphens}{url} 4 | % 5 | \documentclass[ 6 | landscape]{article} 7 | \usepackage{amsmath,amssymb} 8 | \usepackage{lmodern} 9 | \usepackage{ifxetex,ifluatex} 10 | \ifnum 0\ifxetex 1\fi\ifluatex 1\fi=0 % if pdftex 11 | \usepackage[T1]{fontenc} 12 | \usepackage[utf8]{inputenc} 13 | \usepackage{textcomp} % provide euro and other symbols 14 | \else % if luatex or xetex 15 | \usepackage{unicode-math} 16 | \defaultfontfeatures{Scale=MatchLowercase} 17 | \defaultfontfeatures[\rmfamily]{Ligatures=TeX,Scale=1} 18 | \fi 19 | % Use upquote if available, for straight quotes in verbatim environments 20 | \IfFileExists{upquote.sty}{\usepackage{upquote}}{} 21 | \IfFileExists{microtype.sty}{% use microtype if available 22 | \usepackage[]{microtype} 23 | \UseMicrotypeSet[protrusion]{basicmath} % disable protrusion for tt fonts 24 | }{} 25 | \makeatletter 26 | \@ifundefined{KOMAClassName}{% if non-KOMA class 27 | \IfFileExists{parskip.sty}{% 28 | \usepackage{parskip} 29 | }{% else 30 | \setlength{\parindent}{0pt} 31 | \setlength{\parskip}{6pt plus 2pt minus 1pt}} 32 | }{% if KOMA class 33 | \KOMAoptions{parskip=half}} 34 | \makeatother 35 | \usepackage{xcolor} 36 | \IfFileExists{xurl.sty}{\usepackage{xurl}}{} % add URL line breaks if available 37 | \IfFileExists{bookmark.sty}{\usepackage{bookmark}}{\usepackage{hyperref}} 38 | \hypersetup{ 39 | pdftitle={An Introductory Tutorial on Cohort State-Transition Models in R Using a Cost-Effectiveness Analysis Example}, 40 | pdfauthor={Fernando Alarid-Escudero, PhD; Eline Krijkamp, MSc; Eva A. Enns, PhD; Alan Yang, MSc; Myriam G.M. Hunink, PhD\^{}\textbackslash dagger; Petros Pechlivanoglou, PhD; Hawre Jalal, MD, PhD}, 41 | hidelinks, 42 | pdfcreator={LaTeX via pandoc}} 43 | \urlstyle{same} % disable monospaced font for URLs 44 | \usepackage[margin=1in]{geometry} 45 | \usepackage{longtable,booktabs,array} 46 | \usepackage{calc} % for calculating minipage widths 47 | % Correct order of tables after \paragraph or \subparagraph 48 | \usepackage{etoolbox} 49 | \makeatletter 50 | \patchcmd\longtable{\par}{\if@noskipsec\mbox{}\fi\par}{}{} 51 | \makeatother 52 | % Allow footnotes in longtable head/foot 53 | \IfFileExists{footnotehyper.sty}{\usepackage{footnotehyper}}{\usepackage{footnote}} 54 | \makesavenoteenv{longtable} 55 | \usepackage{graphicx} 56 | \makeatletter 57 | \def\maxwidth{\ifdim\Gin@nat@width>\linewidth\linewidth\else\Gin@nat@width\fi} 58 | \def\maxheight{\ifdim\Gin@nat@height>\textheight\textheight\else\Gin@nat@height\fi} 59 | \makeatother 60 | % Scale images if necessary, so that they will not overflow the page 61 | % margins by default, and it is still possible to overwrite the defaults 62 | % using explicit options in \includegraphics[width, height, ...]{} 63 | \setkeys{Gin}{width=\maxwidth,height=\maxheight,keepaspectratio} 64 | % Set default figure placement to htbp 65 | \makeatletter 66 | \def\fps@figure{htbp} 67 | \makeatother 68 | \setlength{\emergencystretch}{3em} % prevent overfull lines 69 | \providecommand{\tightlist}{% 70 | \setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}} 71 | \setcounter{secnumdepth}{-\maxdimen} % remove section numbering 72 | \ifluatex 73 | \usepackage{selnolig} % disable illegal ligatures 74 | \fi 75 | 76 | \title{An Introductory Tutorial on Cohort State-Transition Models in R 77 | Using a Cost-Effectiveness Analysis Example} 78 | \usepackage{etoolbox} 79 | \makeatletter 80 | \providecommand{\subtitle}[1]{% add subtitle to \maketitle 81 | \apptocmd{\@title}{\par {\large #1 \par}}{}{} 82 | } 83 | \makeatother 84 | \subtitle{Appendix} 85 | \author{Fernando Alarid-Escudero, PhD\footnote{Division of Public 86 | Administration, Center for Research and Teaching in Economics (CIDE), 87 | Aguascalientes, AGS, Mexico} \and Eline Krijkamp, 88 | MSc\footnote{Department of Epidemiology and Department of Radiology, 89 | Erasmus University Medical Center, Rotterdam, The Netherlands} \and Eva 90 | A. Enns, PhD\footnote{Division of Health Policy and Management, 91 | University of Minnesota School of Public Health, Minneapolis, MN, USA} \and Alan 92 | Yang, MSc\footnote{The Hospital for Sick Children, Toronto} \and Myriam 93 | G.M. Hunink, PhD\(^\dagger\)\footnote{Center for Health Decision 94 | Sciences, Harvard T.H. Chan School of Public Health, Boston, USA} \and Petros 95 | Pechlivanoglou, PhD\footnote{The Hospital for Sick Children, Toronto and 96 | University of Toronto, Toronto, Ontario, Canada} \and Hawre Jalal, MD, 97 | PhD\footnote{University of Pittsburgh, Pittsburgh, PA, USA}} 98 | \date{2021-09-01} 99 | 100 | \begin{document} 101 | \maketitle 102 | 103 | \hypertarget{cohort-tutorial-model-components}{% 104 | \subsection{Cohort tutorial model 105 | components}\label{cohort-tutorial-model-components}} 106 | 107 | \hypertarget{table-i}{% 108 | \subsubsection{Table I}\label{table-i}} 109 | 110 | This table contains an overview of the key model components used in the 111 | code for the Sick-Sicker example from the 112 | \href{http://darthworkgroup.com/}{DARTH} manuscript: 113 | \href{https://arxiv.org/abs/2001.07824}{``An Introductory Tutorial to 114 | Cohort State-Transition Models in R''}. The first column gives the 115 | mathematical notation for some of the model components that are used in 116 | the equations in the manuscript. The second column gives a description 117 | of the model component with the R name in the third column. The forth 118 | gives the data structure, e.g.~scalar, list, vector, matrix etc, with 119 | the according dimensions of this data structure in the fifth column. The 120 | final column indicated the type of data that is stored in the data 121 | structure, e.g.~numeric (5.2,6.3,7.4), category (A,B,C), integer 122 | (5,6,7), logical (TRUE, FALSE). 123 | 124 | \begin{longtable}[]{@{} 125 | >{\raggedright\arraybackslash}p{(\columnwidth - 10\tabcolsep) * \real{0.10}} 126 | >{\raggedright\arraybackslash}p{(\columnwidth - 10\tabcolsep) * \real{0.33}} 127 | >{\raggedright\arraybackslash}p{(\columnwidth - 10\tabcolsep) * \real{0.14}} 128 | >{\raggedright\arraybackslash}p{(\columnwidth - 10\tabcolsep) * \real{0.14}} 129 | >{\raggedright\arraybackslash}p{(\columnwidth - 10\tabcolsep) * \real{0.17}} 130 | >{\raggedright\arraybackslash}p{(\columnwidth - 10\tabcolsep) * \real{0.12}}@{}} 131 | \toprule 132 | Parameter & Description & R name & Data structure & Dimensions & Data 133 | type \\ 134 | \midrule 135 | \endhead 136 | \(n_t\) & Time horizon & \texttt{n\_cycles} & scalar & & numeric \\ 137 | & Cycle length & \texttt{cycle\_length} & scalar & & numeric \\ 138 | \(v_s\) & Names of the health states & \texttt{v\_names\_states} & 139 | vector & \texttt{n\_states} x 1 & character \\ 140 | \(n_s\) & Number of health states & \texttt{n\_states} & scalar & & 141 | numeric \\ 142 | \(v_{str}\) & Names of the strategies & \texttt{v\_names\_str} & scalar 143 | & & character \\ 144 | \(n_{str}\) & Number of strategies & \texttt{n\_str} & scalar & & 145 | character \\ 146 | \(d_c\) & Discount rate for costs & \texttt{d\_c} & scalar & & 147 | numeric \\ 148 | \(d_e\) & Discount rate for effects & \texttt{d\_e} & scalar & & 149 | numeric \\ 150 | \(\mathbf{d_c}\) & Discount weights vector for costs & \texttt{v\_dwc} & 151 | vector & (\texttt{n\_t} x 1 ) + 1 & numeric \\ 152 | \(\mathbf{d_e}\) & Discount weights vector for effects & \texttt{v\_dwe} 153 | & vector & (\texttt{n\_t} x 1 ) + 1 & numeric \\ 154 | & Sequence of cycle numbers & \texttt{v\_cycles} & vector & 155 | (\texttt{n\_t} x 1 ) + 1 & numeric \\ 156 | \(\mathbf{wcc}\) & Within-cycle correction weights & \texttt{v\_wcc} & 157 | vector & (\texttt{n\_t} x 1 ) + 1 & numeric \\ 158 | \(age_{_0}\) & Age at baseline & \texttt{n\_age\_init} & scalar & & 159 | numeric \\ 160 | \(age\) & Maximum age of follow up & \texttt{n\_age\_max} & scalar & & 161 | numeric \\ 162 | \(M\) & Cohort trace & \texttt{m\_M} & matrix & (\texttt{n\_t} + 1) x 163 | \texttt{n\_states} & numeric \\ 164 | \(m_0\) & Initial state vector & \texttt{v\_m\_init} & vector & 1 x 165 | \texttt{n\_states} & numeric \\ 166 | \(m_t\) & State vector in cycle \(t\) & \texttt{v\_mt} & vector & 1 x 167 | \texttt{n\_states} & numeric \\ 168 | & & & & & \\ 169 | & \textbf{Transition probabilities and rates} & & & & \\ 170 | \(p_{[H,S1]}\) & From Healthy to Sick conditional on surviving & 171 | \texttt{p\_HS1} & scalar & & numeric \\ 172 | \(p_{[S1,H]}\) & From Sick to Healthy conditional on surviving & 173 | \texttt{p\_S1H} & scalar & & numeric \\ 174 | \(p_{[S1,S2]}\) & From Sick to Sicker conditional on surviving & 175 | \texttt{p\_S1S2} & scalar & & numeric \\ 176 | \(p_{[S1,S2]_{trtB}}\) & From Sicker to Sick under treatment B 177 | conditional on surviving & \texttt{p\_S1S2\_trtB} & scalar & & 178 | numeric \\ 179 | \(r_{[H,D]}\) & Constant rate of dying when Healthy (all-cause mortality 180 | rate) & \texttt{r\_HD} & scalar & & numeric \\ 181 | \(r_{[S1,S2]}\) & Constant rate of becoming Sicker when Sick & 182 | \texttt{r\_S1S2} & scalar & & numeric \\ 183 | \(r_{[S1,S2]_{trtB}}\) & Constant rate of becoming Sicker when Sick for 184 | treatment B & \texttt{r\_S1S2\_trtB} & scalar & & numeric \\ 185 | \(hr_{[S1,H]}\) & Hazard ratio of death in Sick vs Healthy & 186 | \texttt{hr\_S1} & scalar & & numeric \\ 187 | \(hr_{[S2,H]}\) & Hazard ratio of death in Sicker vs Healthy & 188 | \texttt{hr\_S2} & scalar & & numeric \\ 189 | \(hr_{[S1,S2]_{trtB}}\) & Hazard ratio of becoming Sicker when Sick 190 | under treatment B & \texttt{hr\_S1S2\_trtB} & scalar & & numeric \\ 191 | \(P\) & Time-independent transition probability matrix* & \texttt{m\_P} 192 | & matrix & \texttt{n\_states} x \texttt{n\_states} & numeric \\ 193 | & * \texttt{\_trtX} is used to specify for which strategy the transition 194 | probability matrix is & & & & \\ 195 | & & & & & \\ 196 | & \textbf{Annual costs} & & & & \\ 197 | & Healthy individuals & \texttt{c\_H} & scalar & & numeric \\ 198 | & Sick individuals in Sick & \texttt{c\_S1} & scalar & & numeric \\ 199 | & Sick individuals in Sicker & \texttt{c\_S2} & scalar & & numeric \\ 200 | & Dead individuals & \texttt{c\_D} & scalar & & numeric \\ 201 | & Additional costs treatment A & \texttt{c\_trtA} & scalar & & 202 | numeric \\ 203 | & Additional costs treatment B & \texttt{c\_trtB} & scalar & & 204 | numeric \\ 205 | & Vector of state costs for a strategy & \texttt{v\_c\_str} & vector & 1 206 | x \texttt{n\_states} & numeric \\ 207 | & list that stores the vectors of state costs for each strategy & 208 | \texttt{l\_c} & list & & numeric \\ 209 | & & & & & \\ 210 | & \textbf{Utility weights} & & & & \\ 211 | & Healthy individuals & \texttt{u\_H} & scalar & & numeric \\ 212 | & Sick individuals in Sick & \texttt{u\_S1} & scalar & & numeric \\ 213 | & Sick individuals in Sicker & \texttt{u\_S2} & scalar & & numeric \\ 214 | & Dead individuals & \texttt{u\_D} & scalar & & numeric \\ 215 | & Treated with treatment A & \texttt{u\_trtA} & scalar & & numeric \\ 216 | & Vector of state utilities for a strategy & \texttt{v\_u\_str} & vector 217 | & 1 x \texttt{n\_states} & numeric \\ 218 | & List that stores the vectors of state utilities for each strategy & 219 | \texttt{l\_u} & list & & numeric \\ 220 | & & & & & \\ 221 | & \textbf{Outcome structures} & & & & \\ 222 | & Expected QALYs per cycle under a strategy & \texttt{v\_qaly\_str} & 223 | vector & 1 x (\texttt{n\_t} + 1) & numeric \\ 224 | & Expected costs per cycle under a strategy & \texttt{v\_cost\_str} & 225 | vector & 1 x (\texttt{n\_t} + 1) & numeric \\ 226 | & Vector of expected discounted QALYs for each strategy & 227 | \texttt{v\_tot\_qaly} & vector & 1 x \texttt{n\_states} & numeric \\ 228 | & Vector of expected discounted costs for each strategy & 229 | \texttt{v\_tot\_cost} & vector & 1 x \texttt{n\_states} & numeric \\ 230 | & Summary matrix with costs and QALYS per strategy & 231 | \texttt{m\_outcomes} & table & \texttt{n\_states} x 2 & \\ 232 | & Summary of the model outcomes & \texttt{df\_cea} & data frame & & \\ 233 | & Summary of the model outcomes & \texttt{table\_cea} & table & & \\ 234 | & & & & & \\ 235 | & \textbf{Probabilistic analysis structures} & & & & \\ 236 | & Number of PSA iterations & \texttt{n\_sim} & scalar & & numeric \\ 237 | & List that stores all the values of the input parameters & 238 | \texttt{l\_params\_all} & list & & numeric \\ 239 | & Data frame with the parameter values for each PSA iteration & 240 | \texttt{df\_psa\_input} & data frame & & numeric \\ 241 | & Vector with the names of all the input parameters & 242 | \texttt{v\_names\_params} & vector & & character \\ 243 | & List with the model outcomes of the PSA for all strategies & 244 | \texttt{l\_psa} & list & & numeric \\ 245 | & Vector with a sequence of relevant willingness-to-pay values & 246 | \texttt{v\_wtp} & vector & & numeric \\ 247 | & Data frame to store expected costs and effects for each strategy from 248 | the PSA & \texttt{df\_out\_ce\_psa} & data frame & & numeric \\ 249 | & Data frame to store incremental cost-effectiveness ratios (ICERs) from 250 | the PSA & \texttt{df\_cea\_psa} & data frame & & numeric \\ 251 | & For more details about the PSA structures read \texttt{dampack}'s 252 | vignettes & & & & \\ 253 | \bottomrule 254 | \end{longtable} 255 | 256 | \end{document} 257 | -------------------------------------------------------------------------------- /manuscript/cSTM_Tutorial_Intro.R: -------------------------------------------------------------------------------- 1 | ## ---- echo = FALSE----------------------------------------------------------------------------------------------- 2 | ## Journal Abbreviations 3 | # library(RJSONIO) 4 | # if(!file.exists("abbreviations.json")){ 5 | # download.file("https://ndownloader.figshare.com/files/5212423","wos_abbrev_table.csv") 6 | # abbrev <- read.csv("wos_abbrev_table.csv", sep = ";", header = TRUE, stringsAsFactors = FALSE) 7 | # abbrev$full <- gsub("\\", "\\\\",abbrev$full, fixed = TRUE) 8 | # abbrev.list <- list('default' = list('container-title' = abbrev$abbrev.dots)) 9 | # names(abbrev.list$default$`container-title`) = abbrev$full 10 | # write(toJSON(abbrev.list), "abbreviations.json") 11 | # rm(abbrev) 12 | # rm(abbrev.list) 13 | # } 14 | 15 | 16 | ## ----setup, include=FALSE---------------------------------------------------------------------------------------- 17 | #install.packages(c("kableExtra", "scales", "tensorA" )) 18 | library(knitr) 19 | library(kableExtra) # https://haozhu233.github.io/kableExtra/awesome_table_in_html.html 20 | library(dplyr) 21 | library(reshape2) 22 | library(ggplot2) 23 | library(scales) # For dollar signs and commas 24 | library(boot) 25 | # devtools::install_github("DARTH-git/dampack") 26 | library(dampack) 27 | # devtools::install_github("DARTH-git/darthtools") 28 | library(darthtools) 29 | knitr::opts_chunk$set(echo = TRUE) 30 | doc_type <- knitr::opts_knit$get('rmarkdown.pandoc.to') 31 | 32 | # Load costumed functions 33 | source("../R/Functions STM_01.R") 34 | # source("../R/Functions.R") 35 | 36 | 37 | # Define parameters 38 | cycle_length <- 1 # cycle length equal one year 39 | n_age_init <- 25 # age at baseline 40 | n_age_max <- 100 # maximum age of follow up 41 | n_cycles <- n_age_max - n_age_init # time horizon, number of cycles 42 | 43 | ## General setup 44 | v_names_states <- c("H", "S1", "S2", "D") # the 4 health states of the model: 45 | # Healthy (H), Sick (S1), Sicker (S2), Dead (D) 46 | n_states <- length(v_names_states) # number of health states 47 | d_c <- d_e <- 0.03 # equal discount of costs and QALYs by 3% 48 | v_names_str <- c("Standard of care", # store the strategy names 49 | "Strategy A", 50 | "Strategy B", 51 | "Strategy AB") 52 | n_str <- length(v_names_str) 53 | 54 | ## Transition probabilities (per cycle) 55 | r_HD <- 0.002 # constant rate of dying when Healthy (all-cause mortality rate) 56 | p_HS1 <- 0.15 # probability of becoming Sick when Healthy 57 | p_S1H <- 0.5 # probability of becoming Healthy when Sick 58 | p_S1S2 <- 0.105 # probability of becoming Sicker when Sick 59 | hr_S1 <- 3 # hazard ratio of death in Sick vs Healthy 60 | hr_S2 <- 10 # hazard ratio of death in Sicker vs Healthy 61 | 62 | # Effectiveness of treatment B 63 | hr_S1S2_trtB <- 0.6 # hazard ratio of becoming Sicker when Sick under treatment B 64 | 65 | ## Transition probability of becoming Sicker when Sick for treatment B 66 | # transform probability to rate 67 | r_S1S2 <- -log(1-p_S1S2) 68 | # apply hazard ratio to rate to obtain transition rate of becoming Sicker when Sick for treatment B 69 | r_S1S2_trtB <- r_S1S2 * hr_S1S2_trtB 70 | # transform rate to probability 71 | p_S1S2_trtB <- 1-exp(-r_S1S2_trtB*cycle_length) # probability to become Sicker when Sick 72 | # under treatment B conditional on surviving 73 | 74 | ## Cost and utility inputs 75 | # State rewards 76 | c_H <- 2000 # cost of being Healthy for one cycle 77 | c_S1 <- 4000 # cost of being Sick for one cycle 78 | c_S2 <- 15000 # cost of being Sicker for one cycle 79 | c_D <- 0 # cost of being dead for one cycle 80 | c_trtA <- 12000 # cost of treatment A for one cycle 81 | c_trtB <- 13000 # cost of treatment B for one cycle 82 | 83 | u_H <- 1 # utility of being Healthy for one cycle 84 | u_S1 <- 0.75 # utility of being Sick for one cycle 85 | u_S2 <- 0.5 # utility of being Sicker for one cycle 86 | u_D <- 0 # utility of being dead for one cycle 87 | u_trtA <- 0.95 # utility when being treated for one cycle 88 | 89 | # PSA parameters 90 | n_sim <- 1000 # Number of PSA samples 91 | 92 | 93 | ## ----figure-setup, echo=FALSE, include=FALSE--------------------------------------------------------------------- 94 | ## chunk will ensure that: 95 | library(formatR) 96 | # indent = 2: two spaces of indentation. 97 | # tidy=TRUE puts formatR to work to produce a beautiful and standardized layout code. 98 | if(!knitr:::is_html_output()) 99 | { 100 | # options("width"=56) 101 | knitr::opts_chunk$set(tidy.opts=list(indent = 1.5)) # width.cutoff=56, tidy = TRUE 102 | knitr::opts_chunk$set(fig.pos = 'H') 103 | } 104 | 105 | ## chunk will ensure that: 106 | # all the figures generated by the report will be placed in the figs/sub-directory 107 | # all the figures will be 6.5 x 4 inches and centered in the text. 108 | knitr::opts_chunk$set(fig.path="figs/", fig.width=8, fig.height=6, fig.align="center") 109 | 110 | 111 | ## ----STD-Sick-Sicker, echo=FALSE, fig.cap="State-transition diagram of the time-independent Sick-Sicker cohort state-transition model with the name of the health states and possible transitions with their corresponding transition probabilities.", fig.pos="H"---- 112 | knitr::include_graphics("figs/Sick-Sicker.png") 113 | 114 | 115 | ## ----Model-Params, eval=FALSE------------------------------------------------------------------------------------ 116 | ## ## General setup 117 | ## cycle_length <- 1 # cycle length equal one year 118 | ## n_age_init <- 25 # age at baseline 119 | ## n_age_max <- 100 # maximum age of follow up 120 | ## n_cycles <- n_age_max - n_age_init # number of cycles 121 | ## v_names_states <- c("H", "S1", "S2", "D") # the 4 health states of the model: 122 | ## # Healthy (H), Sick (S1), Sicker (S2), Dead (D) 123 | ## n_states <- length(v_names_states) # number of health states 124 | ## d_e <- 0.03 # discount rate for QALYs of 3% per cycle 125 | ## d_c <- 0.03 # discount rate for costs of 3% per cycle 126 | ## v_names_str <- c("Standard of care", # store the strategy names 127 | ## "Strategy A", 128 | ## "Strategy B", 129 | ## "Strategy AB") 130 | ## 131 | ## ## Transition probabilities (per cycle), hazard ratios and odds ratio (OR) 132 | ## r_HD <- 0.002 # constant rate of dying when Healthy (all-cause mortality rate) 133 | ## p_HS1 <- 0.15 # probability of becoming Sick when Healthy 134 | ## p_S1H <- 0.5 # probability of becoming Healthy when Sick 135 | ## p_S1S2 <- 0.105 # probability of becoming Sicker when Sick 136 | ## hr_S1 <- 3 # hazard ratio of death in Sick vs Healthy 137 | ## hr_S2 <- 10 # hazard ratio of death in Sicker vs Healthy 138 | ## 139 | ## # Effectiveness of treatment B 140 | ## hr_S1S2_trtB <- 0.6 # hazard ratio of becoming Sicker when Sick under treatment B 141 | ## 142 | ## ## State rewards 143 | ## ## Costs 144 | ## c_H <- 2000 # cost of being Healthy for one cycle 145 | ## c_S1 <- 4000 # cost of being Sick for one cycle 146 | ## c_S2 <- 15000 # cost of being Sicker for one cycle 147 | ## c_D <- 0 # cost of being dead for one cycle 148 | ## c_trtA <- 12000 # cost of receiving treatment A for one cycle 149 | ## c_trtB <- 13000 # cost of receiving treatment B for one cycle 150 | ## # Utilities 151 | ## u_H <- 1 # utility of being Healthy for one cycle 152 | ## u_S1 <- 0.75 # utility of being Sick for one cycle 153 | ## u_S2 <- 0.5 # utility of being Sicker for one cycle 154 | ## u_D <- 0 # utility of being dead for one cycle 155 | ## u_trtA <- 0.95 # utility when receiving treatment A for one cycle 156 | 157 | 158 | ## ----Sick-Sicker-Params, eval=TRUE------------------------------------------------------------------------------- 159 | ## Mortality rates 160 | r_S1D <- r_HD * hr_S1 # rate of dying when Sick 161 | r_S2D <- r_HD * hr_S2 # rate of dying when Sicker 162 | ## Probabilities of dying 163 | cycle_length <- 1 164 | p_HD <- 1 - exp(-r_HD*cycle_length) # background mortality risk (i.e., probability) 165 | p_S1D <- 1 - exp(-r_S1D*cycle_length) # probability of dying when Sick 166 | p_S2D <- 1 - exp(-r_S2D*cycle_length) # probability of dying when Sicker 167 | 168 | 169 | ## ----New-Treatment-2-Effectiveness, eval=TRUE-------------------------------------------------------------------- 170 | ## Transition probability of becoming Sicker when Sick for treatment B 171 | # transform probability to rate 172 | r_S1S2 <- -log(1-p_S1S2)/cycle_length 173 | # apply hazard ratio to rate to obtain transition rate of becoming Sicker when Sick 174 | # for treatment B 175 | r_S1S2_trtB <- r_S1S2 * hr_S1S2_trtB 176 | # transform rate to probability 177 | p_S1S2_trtB <- 1-exp(-r_S1S2_trtB*cycle_length) # probability to become Sicker when Sick 178 | # under treatment B conditional on surviving 179 | 180 | 181 | ## ----Sick-Sicker-s0---------------------------------------------------------------------------------------------- 182 | v_s_init <- c(H = 1, S1 = 0, S2 = 0, D = 0) # initial state vector 183 | v_s_init 184 | 185 | 186 | ## ----Sick-Sicker-M----------------------------------------------------------------------------------------------- 187 | ## Initialize cohort trace for SoC 188 | m_M <- matrix(NA, 189 | nrow = (n_cycles + 1), ncol = n_states, 190 | dimnames = list(0:n_cycles, v_names_states)) 191 | # Store the initial state vector in the first row of the cohort trace 192 | m_M[1, ] <- v_s_init 193 | ## Initialize cohort trace under treatment B 194 | m_M_trtB <- m_M # structure and initial states remain the same. 195 | 196 | 197 | ## ----Sick-Sicker-P2---------------------------------------------------------------------------------------------- 198 | ## Initialize transition probability matrix 199 | m_P <- matrix(0, 200 | nrow = n_states, ncol = n_states, 201 | dimnames = list(v_names_states, v_names_states)) # row and column names 202 | ## Fill in matrix 203 | # From H 204 | m_P["H", "H"] <- (1 - p_HD) * (1 - p_HS1) 205 | m_P["H", "S1"] <- (1 - p_HD) * p_HS1 206 | m_P["H", "D"] <- p_HD 207 | # From S1 208 | m_P["S1", "H"] <- (1 - p_S1D) * p_S1H 209 | m_P["S1", "S1"] <- (1 - p_S1D) * (1 - (p_S1H + p_S1S2)) 210 | m_P["S1", "S2"] <- (1 - p_S1D) * p_S1S2 211 | m_P["S1", "D"] <- p_S1D 212 | # From S2 213 | m_P["S2", "S2"] <- 1 - p_S2D 214 | m_P["S2", "D"] <- p_S2D 215 | # From D 216 | m_P["D", "D"] <- 1 217 | 218 | 219 | ## ----Sick-Sicker-Time-independent-New-Treatment2----------------------------------------------------------------- 220 | ## Initialize transition probability matrix for treatment B 221 | m_P_trtB <- m_P 222 | ## Update only transition probabilities from S1 involving p_S1S2 223 | m_P_trtB["S1", "S1"] <- (1 - p_S1D) * (1 - (p_S1H + p_S1S2_trtB)) 224 | m_P_trtB["S1", "S2"] <- (1 - p_S1D) * p_S1S2_trtB 225 | 226 | 227 | ## ---------------------------------------------------------------------------------------------------------------- 228 | ### Check if transition probability matrices are valid 229 | ## Check that transition probabilities are [0, 1] 230 | check_transition_probability(m_P) 231 | check_transition_probability(m_P_trtB) 232 | ## Check that all rows sum to 1 233 | check_sum_of_transition_array(m_P, n_states = n_states, n_cycles = n_cycles) 234 | check_sum_of_transition_array(m_P_trtB, n_states = n_states, n_cycles = n_cycles) 235 | 236 | 237 | ## ----Sick-Sicker-TimeHomogeneous-Solution------------------------------------------------------------------------ 238 | # Iterative solution of time-independent cSTM 239 | for(t in 1:n_cycles){ 240 | # For SoC 241 | m_M[t + 1, ] <- m_M[t, ] %*% m_P 242 | # For treatment B 243 | m_M_trtB[t + 1, ] <- m_M_trtB[t, ] %*% m_P_trtB 244 | } 245 | 246 | 247 | 248 | 249 | ## ----Sick-Sicker-Trace-TimeHom, echo=FALSE, fig.cap='Cohort trace of the time-independent cSTM', message=FALSE, warning=FALSE, fig.pos="H"---- 250 | cols <- get_DARTH_cols() 251 | lty <- c("H" = 1, "S1" = 2, "S2" = 4, "D" = 3) 252 | ggplot(melt(m_M), aes(x = Var1, y = value, 253 | color = Var2, linetype = Var2)) + 254 | geom_line(size = 1) + 255 | scale_colour_manual(name = "Health state", 256 | values = cols) + 257 | scale_linetype_manual(name = "Health state", 258 | values = lty) + 259 | scale_x_continuous(breaks = number_ticks(8)) + 260 | xlab("Cycle") + 261 | ylab("Proportion of the cohort") + 262 | theme_bw(base_size = 16) + 263 | theme(legend.position = "bottom",#c(0.7, 0.75), 264 | legend.background = element_rect(fill = NA)) 265 | 266 | 267 | ## ----State-rewards-UC-------------------------------------------------------------------------------------------- 268 | # Vector of state utilities under SOC 269 | v_u_SoC <- c(H = u_H, S1 = u_S1, S2 = u_S2, D = u_D) 270 | # Vector of state costs under SoC 271 | v_c_SoC <- c(H = c_H, S1 = c_S1, S2 = c_S2, D = c_D) 272 | 273 | 274 | ## ----State-rewards-U-Tr------------------------------------------------------------------------------------------ 275 | # Vector of state utilities for strategy A 276 | v_u_strA <- c(H = u_H, S1 = u_trtA, S2 = u_S2, D = u_D) 277 | # Vector of state utilities for strategy B 278 | v_u_strB <- v_u_SoC 279 | # Vector of state utilities for strategy AB 280 | v_u_strAB <- v_u_strA 281 | 282 | 283 | ## ----State-rewards-C-Tr------------------------------------------------------------------------------------------ 284 | # Vector of state costs for strategy A 285 | v_c_strA <- c(H = c_H, 286 | S1 = c_S1 + c_trtA, 287 | S2 = c_S2 + c_trtA, 288 | D = c_D) 289 | # Vector of state costs for strategy B 290 | v_c_strB <- c(H = c_H, 291 | S1 = c_S1 + c_trtB, 292 | S2 = c_S2 + c_trtB, 293 | D = c_D) 294 | # Vector of state costs for strategy AB 295 | v_c_strAB <- c(H = c_H, 296 | S1 = c_S1 + (c_trtA + c_trtB), 297 | S2 = c_S2 + (c_trtA + c_trtB), 298 | D = c_D) 299 | 300 | 301 | ## ----Expected-outcomes-each-cycle-------------------------------------------------------------------------------- 302 | # Vector of QALYs under SoC 303 | v_qaly_SoC <- m_M %*% v_u_SoC 304 | # Vector of costs under SoC 305 | v_cost_SoC <- m_M %*% v_c_SoC 306 | # Vector of QALYs for strategy A 307 | v_qaly_strA <- m_M %*% v_u_strA 308 | # Vector of costs for strategy A 309 | v_cost_strA <- m_M %*% v_c_strA 310 | # Vector of QALYs for strategy B 311 | v_qaly_strB <- m_M_trtB %*% v_u_strB 312 | # Vector of costs for strategy B 313 | v_cost_strB <- m_M_trtB %*% v_c_strB 314 | # Vector of QALYs for strategy AB 315 | v_qaly_strAB <- m_M_trtB %*% v_u_strAB 316 | # Vector of costs for strategy AB 317 | v_cost_strAB <- m_M_trtB %*% v_c_strAB 318 | 319 | 320 | ## ----within-cycle-vector----------------------------------------------------------------------------------------- 321 | ## Vector with cycles 322 | v_cycles <- seq(1, n_cycles+1) 323 | ## Generate 2/3 and 4/3 multipliers for even and odd entries, respectively 324 | v_wcc <- ((v_cycles %% 2)==0)*(2/3) + ((v_cycles %% 2)!=0)*(4/3) 325 | ## Substitute 1/3 in first and last entries 326 | v_wcc[1] <- v_wcc[n_cycles + 1] <- 1/3 327 | 328 | 329 | ## ----Discount vectors-------------------------------------------------------------------------------------------- 330 | # Discount weight for effects 331 | v_dwe <- 1 / ((1 + d_e) ^ (0:(n_cycles))) 332 | # Discount weight for costs 333 | v_dwc <- 1 / ((1 + d_c) ^ (0:(n_cycles))) 334 | 335 | 336 | ## ----Expected-outcomes-all-cycles-------------------------------------------------------------------------------- 337 | ## Expected discounted QALYs under SoC 338 | n_tot_qaly_SoC <- t(v_qaly_SoC) %*% (v_dwe * v_wcc) 339 | ## Expected discounted costs under SoC 340 | n_tot_cost_SoC <- t(v_cost_SoC) %*% (v_dwc * v_wcc) 341 | ## Expected discounted QALYs for strategy A 342 | n_tot_qaly_strA <- t(v_qaly_strA) %*% (v_dwe * v_wcc) 343 | ## Expected discounted costs for strategy A 344 | n_tot_cost_strA <- t(v_cost_strA) %*% (v_dwc * v_wcc) 345 | ## Expected discounted QALYs for strategy B 346 | n_tot_qaly_strB <- t(v_qaly_strB) %*% (v_dwe * v_wcc) 347 | ## Expected discounted costs for strategy B 348 | n_tot_cost_strB <- t(v_cost_strB) %*% (v_dwc * v_wcc) 349 | ## Expected discounted QALYs for strategy AB 350 | n_tot_qaly_strAB <- t(v_qaly_strAB) %*% (v_dwe * v_wcc) 351 | ## Expected discounted costs for strategy AB 352 | n_tot_cost_strAB <- t(v_cost_strAB) %*% (v_dwc * v_wcc) 353 | 354 | 355 | ## ----Expected-outcomes, echo=FALSE, message=FALSE, warning=FALSE------------------------------------------------- 356 | m_outcomes <- matrix(c(dollar(c(n_tot_cost_SoC, n_tot_cost_strA, 357 | n_tot_cost_strB, n_tot_cost_strAB)), 358 | format(round(c(n_tot_qaly_SoC, n_tot_qaly_strA, 359 | n_tot_qaly_strB, n_tot_qaly_strAB), 3), nsmall = 3)), 360 | ncol = 2, nrow = length(v_names_str), 361 | dimnames = list(v_names_str, 362 | c("Costs", "QALYs"))) 363 | 364 | 365 | 366 | 367 | ## ----CEA-analysis------------------------------------------------------------------------------------------------ 368 | ### Vector of costs 369 | v_cost_str <- c(n_tot_cost_SoC, n_tot_cost_strA, n_tot_cost_strB, n_tot_cost_strAB) 370 | ### Vector of effectiveness 371 | v_qaly_str <- c(n_tot_qaly_SoC, n_tot_qaly_strA, n_tot_qaly_strB, n_tot_qaly_strAB) 372 | 373 | ### Calculate incremental cost-effectiveness ratios (ICERs) 374 | df_cea <- dampack::calculate_icers(cost = v_cost_str, 375 | effect = v_qaly_str, 376 | strategies = v_names_str) 377 | 378 | 379 | ## ----CEA-table, echo=FALSE--------------------------------------------------------------------------------------- 380 | table_cea <- df_cea 381 | ## Format column names 382 | colnames(table_cea)[2:6] <- c("Costs ($)", "QALYs", 383 | "Incremental Costs ($)", "Incremental QALYs", 384 | "ICER ($/QALY)") # name the columns 385 | table_cea$`Costs ($)` <- comma(round(table_cea$`Costs ($)`, 0)) 386 | table_cea$`Incremental Costs ($)` <- comma(round(table_cea$`Incremental Costs ($)`, 0)) 387 | table_cea$QALYs <- round(table_cea$QALYs, 3) 388 | table_cea$`Incremental QALYs` <- round(table_cea$`Incremental QALYs`, 3) 389 | table_cea$`ICER ($/QALY)` <- comma(round(table_cea$`ICER ($/QALY)`, 0)) 390 | 391 | 392 | 393 | 394 | ## ----Sick-Sicker-CEA, echo=FALSE, fig.cap='Cost-effectiveness efficient frontier of all four strategies for the Sick-Sicker model.', message=FALSE, warning=FALSE, fig.pos="H"---- 395 | plot(df_cea, label = "all", txtsize = 16) + 396 | expand_limits(x = c(NA, 21.8)) + 397 | theme(legend.position = c(0.8, 0.2)) 398 | 399 | 400 | ## ----PSA-setup, eval=TRUE, echo=FALSE---------------------------------------------------------------------------- 401 | # Number of PSA samples 402 | n_sim <- 1000 403 | # Generate PSA input dataset 404 | df_psa_input <- generate_psa_params(n_sim = n_sim) 405 | 406 | # Initialize matrices with PSA output 407 | # data.frame of costs 408 | df_c <- as.data.frame(matrix(0, 409 | nrow = n_sim, 410 | ncol = n_str)) 411 | colnames(df_c) <- v_names_str 412 | # data.frame of effectiveness 413 | df_e <- as.data.frame(matrix(0, 414 | nrow = n_sim, 415 | ncol = n_str)) 416 | colnames(df_e) <- v_names_str 417 | 418 | 419 | ## ----PSA-run, eval=TRUE, echo=FALSE, cache=TRUE, message=FALSE, warning=FALSE------------------------------------ 420 | ## Conduct probabilistic sensitivity analysis 421 | # Run Markov model on each parameter set of PSA input dataset 422 | for(i in 1:n_sim){ 423 | l_out_temp <- calculate_ce_out(l_params_all = df_psa_input[i, ]) 424 | df_c[i, ] <- l_out_temp$Cost 425 | df_e[i, ] <- l_out_temp$Effect 426 | # # Display simulation progress 427 | # if(i/(n_sim/100) == round(i/(n_sim/100), 0)) { # display progress every 5% 428 | # cat('\r', paste(i/n_sim * 100, "% done", sep = " ")) 429 | # } 430 | } 431 | 432 | 433 | ## ----Generate-PSA-object, eval=TRUE, echo=FALSE, message=FALSE, warning=FALSE------------------------------------ 434 | # Create PSA object for dampack 435 | l_psa <- make_psa_obj(cost = df_c, 436 | effectiveness = df_e, 437 | parameters = df_psa_input, 438 | strategies = v_names_str) 439 | l_psa$strategies <- v_names_str 440 | colnames(l_psa$effectiveness)<- v_names_str 441 | colnames(l_psa$cost)<- v_names_str 442 | 443 | # Vector with willingness-to-pay (WTP) thresholds. 444 | v_wtp <- seq(0, 200000, by = 5000) 445 | 446 | ## Cost-effectiveness acceptability curves (CEACs) and frontier (CEAF) 447 | ceac_obj <- ceac(wtp = v_wtp, psa = l_psa) 448 | ceac_obj$Strategy <- ordered(ceac_obj$Strategy, v_names_str) 449 | 450 | ## Expected Loss Curves (ELCs) 451 | elc_obj <- calc_exp_loss(wtp = v_wtp, psa = l_psa) 452 | 453 | 454 | ## ----CEAC, echo=FALSE, fig.cap='Cost-effectiveness acceptability curves (CEACs) and frontier (CEAF).', message=FALSE, warning=FALSE, fig.pos="H"---- 455 | # CEAC & CEAF plot 456 | plot(ceac_obj, txtsize = 16, xlim = c(0, NA), n_x_ticks = 14) + 457 | theme(legend.position = c(0.82, 0.5)) 458 | 459 | 460 | ## ----ELC, echo=FALSE, fig.cap='Expected loss curves (ELCs) and expected value of perfect information (EVPI).', message=FALSE, warning=FALSE, fig.pos="H"---- 461 | # ELC plot 462 | plot(elc_obj, log_y = FALSE, txtsize = 16, xlim = c(0, NA), n_x_ticks = 14, 463 | col = "full") + 464 | # geom_point(aes(shape = as.name("Strategy"))) + 465 | scale_y_continuous("Expected Loss (Thousand $)", 466 | breaks = number_ticks(10), 467 | labels = function(x) x/1000) + 468 | theme(legend.position = c(0.4, 0.7)) 469 | 470 | -------------------------------------------------------------------------------- /manuscript/cSTM_Tutorial_Intro.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'An Introductory Tutorial on Cohort State-Transition Models in R Using a Cost-Effectiveness Analysis Example' 3 | author: 4 | - Fernando Alarid-Escudero, PhD^[Division of Public Administration, Center for Research and Teaching in Economics (CIDE), Aguascalientes, AGS, Mexico] 5 | - Eline Krijkamp, MSc^[Department of Epidemiology and Department of Radiology, Erasmus University Medical Center, Rotterdam, The Netherlands] 6 | - Eva A. Enns, PhD^[Division of Health Policy and Management, University of Minnesota School of Public Health, Minneapolis, MN, USA] 7 | - Alan Yang, MSc^[The Hospital for Sick Children, Toronto] 8 | - Myriam G.M. Hunink, PhD$^\dagger$^[Center for Health Decision Sciences, Harvard T.H. Chan School of Public Health, Boston, USA] 9 | - Petros Pechlivanoglou, PhD^[The Hospital for Sick Children, Toronto and University of Toronto, Toronto, Ontario, Canada] 10 | - Hawre Jalal, MD, PhD^[School of Epidemiology and Public Health, Faculty of Medicine, University of Ottawa, Ottawa, ON, Canada] 11 | date: '`r Sys.Date()`' 12 | documentclass: "article" 13 | output: 14 | bookdown::pdf_book: 15 | fig_caption: yes 16 | keep_tex: yes 17 | always_allow_html: yes 18 | bibliography: WorkingPapers-CohortModelsR.bib 19 | geometry: margin=1in 20 | header-includes: 21 | - \usepackage{amsmath} 22 | - \usepackage{float} 23 | - \usepackage{setspace}\onehalfspacing 24 | # - \usepackage[printwatermark]{xwatermark} 25 | # - \newwatermark[allpages,color=gray!20,angle=45,scale=2,xpos=0,ypos=0]{DRAFT, Do Not Share} 26 | - \renewcommand{\contentsname}{}\vspace{-.5cm} # Removes heading of Contents 27 | keywords: "Markov models, state-transition models, decision models, Tutorial, R" 28 | link-citations: yes 29 | csl: vancouver-superscript.csl 30 | site: bookdown::bookdown_site 31 | tags: 32 | - Markov models 33 | - state-transition models 34 | - decision models 35 | - Tutorial 36 | - R 37 | abstract: | 38 | Decision models can combine information from different sources to simulate the long-term consequences of alternative strategies in the presence of uncertainty. A cohort state-transition model (cSTM) is a decision model commonly used in medical decision-making to simulate the transitions of a hypothetical cohort among various health states over time. This tutorial focuses on time-independent cSTM, where transition probabilities among health states remain constant over time. We implement time-independent cSTM in R, an open-source mathematical and statistical programming language. We illustrate time-independent cSTMs using a previously published decision model, calculate costs and effectiveness outcomes, conduct a cost-effectiveness analysis of multiple strategies, including a probabilistic sensitivity analysis. We provide open-source code in R to facilitate wider adoption. In a second, more advanced tutorial, we illustrate time-dependent cSTMs. 39 | --- 40 | ```{r, echo = FALSE} 41 | ## Journal Abbreviations 42 | # library(RJSONIO) 43 | # if(!file.exists("abbreviations.json")){ 44 | # download.file("https://ndownloader.figshare.com/files/5212423","wos_abbrev_table.csv") 45 | # abbrev <- read.csv("wos_abbrev_table.csv", sep = ";", header = TRUE, stringsAsFactors = FALSE) 46 | # abbrev$full <- gsub("\\", "\\\\",abbrev$full, fixed = TRUE) 47 | # abbrev.list <- list('default' = list('container-title' = abbrev$abbrev.dots)) 48 | # names(abbrev.list$default$`container-title`) = abbrev$full 49 | # write(toJSON(abbrev.list), "abbreviations.json") 50 | # rm(abbrev) 51 | # rm(abbrev.list) 52 | # } 53 | ``` 54 | 55 | ```{r setup, include=FALSE} 56 | #install.packages(c("kableExtra", "scales", "dampack")) 57 | library(knitr) 58 | library(kableExtra) # https://haozhu233.github.io/kableExtra/awesome_table_in_html.html 59 | library(dplyr) 60 | library(reshape2) 61 | library(ggplot2) 62 | library(ggthemes) # For colorblind palettes 63 | library(ggrepel) # For geom_label_repel 64 | library(scales) # For dollar signs and commas 65 | library(ellipse) 66 | library(tidyr) 67 | library(boot) 68 | library(dampack) # For CEA and PSA visualization functionality 69 | # devtools::install_github("DARTH-git/darthtools") 70 | library(darthtools) 71 | knitr::opts_chunk$set(echo = TRUE) 72 | doc_type <- knitr::opts_knit$get('rmarkdown.pandoc.to') 73 | 74 | # Load cSTM and CEA functions 75 | source("../R/Functions_cSTM_time_indep.R") 76 | source("../R/Functions.R") 77 | 78 | 79 | # Define parameters 80 | cycle_length <- 1 # cycle length equal one year 81 | n_age_init <- 25 # age at baseline 82 | n_age_max <- 100 # maximum age of follow up 83 | n_cycles <- n_age_max - n_age_init # time horizon, number of cycles 84 | 85 | ## General setup 86 | v_names_states <- c("H", "S1", "S2", "D") # the 4 health states of the model: 87 | # Healthy (H), Sick (S1), Sicker (S2), Dead (D) 88 | n_states <- length(v_names_states) # number of health states 89 | d_c <- d_e <- 0.03 # equal discount of costs and QALYs by 3% 90 | v_names_str <- c("Standard of care", # store the strategy names 91 | "Strategy A", 92 | "Strategy B", 93 | "Strategy AB") 94 | n_str <- length(v_names_str) 95 | 96 | ## Transition probabilities (per cycle) 97 | r_HD <- 0.002 # constant rate of dying when Healthy (all-cause mortality rate) 98 | r_HS1 <- 0.15 # constant rate of becoming Sick when Healthy 99 | r_S1H <- 0.5 # constant rate of becoming Healthy when Sick 100 | r_S1S2 <- 0.105 # constant rate of becoming Sicker when Sick 101 | hr_S1 <- 3 # hazard ratio of death in Sick vs Healthy 102 | hr_S2 <- 10 # hazard ratio of death in Sicker vs Healthy 103 | 104 | # Effectiveness of treatment B 105 | hr_S1S2_trtB <- 0.6 # hazard ratio of becoming Sicker when Sick under treatment B 106 | 107 | ### Process model inputs 108 | ## Transition probability of becoming Sick when Healthy 109 | # transform rate to probability 110 | p_HS1 <- 1 - exp(-r_HS1 * cycle_length) 111 | ## Transition probability of becoming Healthy when Sick 112 | # transform rate to probability 113 | p_S1H <- 1 - exp(-r_S1H * cycle_length) 114 | ## Transition probability of becoming Sicker when Sick 115 | # transform rate to probability 116 | p_S1S2 <- 1 - exp(-r_S1S2 * cycle_length) 117 | ## Transition probability of becoming Sicker when Sick for treatment B 118 | # apply hazard ratio to rate to obtain transition rate of becoming Sicker when Sick for treatment B 119 | r_S1S2_trtB <- r_S1S2 * hr_S1S2_trtB 120 | # transform rate to probability 121 | p_S1S2_trtB <- 1-exp(-r_S1S2_trtB * cycle_length) # probability to become Sicker when Sick 122 | # under treatment B 123 | 124 | ## Cost and utility inputs 125 | # State rewards 126 | c_H <- 2000 # cost of being Healthy for one cycle 127 | c_S1 <- 4000 # cost of being Sick for one cycle 128 | c_S2 <- 15000 # cost of being Sicker for one cycle 129 | c_D <- 0 # cost of being dead for one cycle 130 | c_trtA <- 12000 # cost of treatment A for one cycle 131 | c_trtB <- 13000 # cost of treatment B for one cycle 132 | 133 | u_H <- 1 # utility of being Healthy for one cycle 134 | u_S1 <- 0.75 # utility of being Sick for one cycle 135 | u_S2 <- 0.5 # utility of being Sicker for one cycle 136 | u_D <- 0 # utility of being dead for one cycle 137 | u_trtA <- 0.95 # utility when being treated for one cycle 138 | 139 | # PSA parameters 140 | n_sim <- 1000 # Number of PSA samples 141 | 142 | # List of input parameters 143 | l_params_all <- list( 144 | # Transition probabilities (per cycle), hazard ratios 145 | r_HD = r_HD , # constant rate of dying when Healthy (all-cause mortality) 146 | r_HS1 = r_HS1 , # probability to become Sick when Healthy 147 | r_S1H = r_S1H , # probability to become Healthy when Sick 148 | r_S1S2 = r_S1S2, # probability to become Sicker when Sick 149 | hr_S1 = hr_S1 , # hazard ratio of death in Sick vs Healthy 150 | hr_S2 = hr_S2 , # hazard ratio of death in Sicker vs Healthy 151 | # Effectiveness of treatment B 152 | hr_S1S2_trtB = hr_S1S2_trtB, # hazard ratio of becoming Sicker when Sick under treatment B 153 | ## State rewards 154 | # Costs 155 | c_H = c_H, # cost of remaining one cycle in Healthy 156 | c_S1 = c_S1, # cost of remaining one cycle in Sick 157 | c_S2 = c_S2, # cost of remaining one cycle in Sicker 158 | c_D = c_D, # cost of being dead (per cycle) 159 | c_trtA = c_trtA, # cost of treatment A 160 | c_trtB = c_trtB, # cost of treatment B 161 | # Utilities 162 | u_H = u_H, # utility when Healthy 163 | u_S1 = u_S1, # utility when Sick 164 | u_S2 = u_S2, # utility when Sicker 165 | u_D = u_D, # utility when Dead 166 | u_trtA = u_trtA, # utility when being treated with A 167 | # Initial and maximum ages 168 | n_age_init = n_age_init, 169 | n_age_max = n_age_max, 170 | # Discount rates 171 | d_c = d_c, # annual discount rate for costs 172 | d_e = d_e, # annual discount rate for QALYs, 173 | # Cycle length 174 | cycle_length = cycle_length 175 | ) 176 | ``` 177 | 178 | ```{r figure-setup, echo=FALSE, include=FALSE} 179 | ## chunk will ensure that: 180 | library(formatR) 181 | # indent = 2: two spaces of indentation. 182 | # tidy=TRUE puts formatR to work to produce a beautiful and standardized layout code. 183 | if(!knitr:::is_html_output()) 184 | { 185 | # options("width"=56) 186 | knitr::opts_chunk$set(tidy.opts=list(indent = 1.5)) # width.cutoff=56, tidy = TRUE 187 | knitr::opts_chunk$set(fig.pos = 'H') 188 | } 189 | 190 | ## chunk will ensure that: 191 | # all the figures generated by the report will be placed in the figs/sub-directory 192 | # all the figures will be 6.5 x 4 inches and centered in the text. 193 | knitr::opts_chunk$set(fig.path="figs/", fig.width=8, fig.height=6, fig.align="center") 194 | ``` 195 | 196 | # Introduction 197 | 198 | Policymakers are often tasked with allocating limited healthcare resources under constrained budgets and uncertainty about future outcomes. Health economic evaluations might inform their final decisions. These economic evaluations often rely on decision models to synthesize evidence from different sources and project long-term outcomes of various alternative strategies. A commonly used decision model is the discrete-time cohort state-transition model (cSTM), often referred to as a Markov model.[@Kuntz2017] 199 | 200 | A cSTM is a dynamic mathematical model in which a hypothetical cohort of individuals transition between different health states over time. A cSTM is most appropriate when the decision problem has a dynamic component (e.g., the disease process can vary over time) and can be described using a reasonable number of health states. cSTMs are often used because of their transparency, efficiency, ease of development, and debugging. cSTMs are usually computationally less demanding than individual-based state-transition model (iSTMs), providing the ability to conduct PSA and value-of-information (VOI) analyses that otherwise might not be computationally feasible with iSTMs.[@Siebert2012c] cSTMs have been used to evaluate screening and surveillance programs,[@Suijkerbuijk2018; @Sathianathen2018a] diagnostic procedures,[@Lu2018b] disease management programs,[@Djatche2018] interventions,[@Smith-Spangler2010] and policies.[@Pershing2014] 201 | 202 | In a recent review, we illustrated the increased use of R's statistical programming framework in health decision sciences. We provided a summary of available resources to apply to medical decision making.[@Jalal2017b] Many packages have been explicitly developed to estimate and construct cSTMs in R. For example, the `markovchain`[@Spedicato2017] and `heemod`[@Filipovic-Pierucci2017] packages are designed to build cSTMs using a pre-defined structure. The `markovchain` package simulates time-independent, and time-dependent Markov chains but is not designed to conduct economic evaluations. `heemod` is a well-structured R package for economic evaluations. However, these packages are necessarily stylized and require users to specify the structure and inputs of their cSTM in a particular way potentially without fully understanding how cSTMs work. Using these packages can be challenging if the desired cSTM does not fit within this structure. 203 | 204 | This tutorial demonstrates how to conduct a full cost-effectiveness analysis (CEA) comparing multiple interventions and implementing probabilistic sensitivity analysis (PSA) without needing a specialized cSTM package. We first describe each of the components of a time-independent cSTM. Then, we illustrate the implementation of these components with an example. Our general conceptualization should apply to other programming languages (e.g., MATLAB, Python, C++, and Julia). The reader can find the most up-to-date R code of the time-independent cSTM and the R code to create the tutorial graphs in the accompanying GitHub repository (https://github.com/DARTH-git/cohort-modeling-tutorial-intro) to replicate and modify the example to fit their needs. We assume that the reader is familiar with the basics of decision modeling and has a basic understanding of programming. Thus, a prior introduction to R, "for" loops, and linear algebra for decision modelers is recommended. The linear algebra concepts used throughout the code are explained in more detail in the Supplementary Material. 205 | 206 | This introductory tutorial aims to (1) conceptualize time-independent cSTMs for implementation in a programming language and (2) provide a template for implementing these cSTMs in _base_ R. We focus on using R _base_ packages, ensuring modelers understand the concept and structure of cSTMs and avoid the limitation of constructing cSTMs in a package-specific structure. We used previously developed R packages for visualizing CEA results and checking cSTMs are correctly specified. 207 | 208 | 209 | # Cohort state-transition models (cSTMs) 210 | 211 | A cSTM consists of a set of $n_S$ mutually exclusive and collectively exhaustive health states. The cohort is assumed to be homogeneous within each health state. Individuals in the cohort residing in a particular health state are assumed to have the same characteristics and are indistinguishable from one another. The cohort could transition between health states with defined probabilities, which are called "transition probabilities". A transition probability represents the chance that individuals in the cohort residing in a state in a given cycle transition to another state or remain in the same state. In a cSTM, a one-cycle transition probability reflects a conditional probability of transitioning during the cycle, given that the person is alive at the beginning of the cycle.[@Miller1994] 212 | 213 | In a cSTM, the transition probabilities only depend on the current health state in a given cycle, meaning that the transition probabilities do not depend on the history of past transitions or time spent in a given state. This property is often referred to as the "Markovian assumption."[@Kuntz2001; @Sonnenberg1993; @Beck1983] This tutorial focuses on time-independent cSTM, meaning that model parameters, such as transition probabilities or reward (e.g., costs or utilities associated with being in a particular health state), do not vary with time. We discuss time-dependence in cSTMs in an accompanying advanced tutorial.[@Alarid-Escudero2021b] 214 | 215 | ## Rates versus probabilities 216 | 217 | In discrete-time cSTMs, cohort dynamics are described by the probability of transitioning between states. However, these transitions might be reported in terms of rates in the literature, or probabilities may not always be available in the desired cycle length. For example, transition probabilities might be available from published literature in one time period (e.g., annual) and might differ from the model's cycle length scale (e.g., monthly). Below, we illustrate a simple approach to converting from rates to probabilities and using rates to convert probabilities from one time scale to another. 218 | 219 | While probabilities and rates are often numerically similar in practice, there is a subtle but important conceptual difference between them. A rate represents the \textit{instantaneous} force of an event occurrence per unit time, while a probability represents the cumulative risk of an event over a defined period. 220 | 221 | To illustrate this difference further, let us assume that after 10,000 person-years of observation of healthy individuals (e.g., 10,000 individuals observed for an average of 1 year, or 5,000 individuals observed for an average of 2 years, etc.), we observe 500 events of interest (e.g., becoming sick from some disease). The annual event rate of becoming sick, $\mu_{yearly}$, is then equal to $\mu_{yearly}=500 / 10,000=0.05$. 222 | 223 | If we then wanted to know what proportion of an initially healthy cohort becomes sick at the end of the year, we can convert the annual rate of becoming sick into an annual probability of becoming sick using the following equation: 224 | \begin{equation} 225 | p_{yearly} = 1-\exp{\left(-\mu_{yearly} \right)}, 226 | (\#eq:rate-to-prob-ann) 227 | \end{equation} 228 | resulting in $p_{yearly} = 1-\exp{(-0.05)}=$ `r round(1-exp(-0.05), 4)`. Equation \@ref(eq:rate-to-prob-ann) assumes that the rate of becoming sick is constant over the year, implying that the time until a healthy person becomes sick is exponentially distributed. The parameter $p_{yearly}$ is the transition probability from healthy to sick in a cSTM when using an annual cycle length. 229 | 230 | If we were concerned that an annual cycle length was too long to capture disease dynamics accurately, we could use a monthly cycle length. To calculate monthly rates, we divide the annual rate by 12: 231 | \begin{equation} 232 | \mu_{monthly} = \mu_{yearly} / 12. 233 | (\#eq:rate-ann-to-month) 234 | \end{equation} 235 | 236 | To convert to monthly transition probabilities, we apply equation \@ref(eq:rate-to-prob-ann): 237 | \begin{equation} 238 | p_{monthly} = 1-\exp{\left(-\mu_{monthly}\right)}. 239 | (\#eq:rate-to-prob-month) 240 | \end{equation} 241 | 242 | We divide by 12 because of the number of months (desired cycle length) in a year (cycle length of the given data). If the original or desired cycle length differed, we would divide by a different factor (e.g., annual to weekly: 52; monthly to annual: 1/12; annual to daily: 365.25, etc.). 243 | 244 | These equations are also helpful for computing probabilities when studies (e.g., survival analyses) provide rates rather than transition probabilities assuming exponentially distributed transition times. 245 | 246 | # Time-independent cSTM dynamics 247 | A cSTM consists of three core components: (1) a state vector, $\mathbf{m}_t$, that stores the distribution of the cohort across all health states in cycle $t$ where $t = 0,\ldots, n_T$; (2) the cohort trace matrix, $M$, that stacks $\mathbf{m}_t$ for all $t$ and represents the distribution of the cohort in the various states over time; and (3) a transition probability matrix, $P$.[@Iskandar2018a] If the cSTM is comprised of $n_S$ discrete health states, $\mathbf{m}_t$ is a $1 \times n_S$ vector and $P$ is a $n_S \times n_S$ matrix. The $i$-th element of $\mathbf{m}_t$, where $i = 1,\ldots, n_S$, represents the proportion of the cohort in the $i$-th health state in cycle $t$, referred to as $m_{[t,i]}$. Thus, $\mathbf{m}_t$ is written as: 248 | $$ 249 | \mathbf{m}_t = 250 | \begin{bmatrix} 251 | m_{[t,1]} & m_{[t,2]} & \cdots & m_{[t,n_S]} 252 | \end{bmatrix}. 253 | $$ 254 | The elements of $P$ are the transition probabilities of moving from state $i$ to state $j$, $p_{[i,j]}$, where $\{i,j\} = 1,\ldots, n_S$ and all should have values between 0 and 1. 255 | $$ 256 | P = 257 | \begin{bmatrix} 258 | p_{[1,1]} & p_{[1,2]} & \cdots & p_{[1,n_S]} \\ 259 | p_{[2,1]} & p_{[2,2]} & \cdots & p_{[2,n_S]} \\ 260 | \vdots & \vdots & \ddots & \vdots \\ 261 | p_{[n_S,1]} & p_{[n_S,2]} & \cdots & p_{[n_S,n_S]} \\ 262 | \end{bmatrix}. 263 | $$ 264 | For $P$ to be a correctly specified transition probability matrix, each row of the transition probability matrix must sum to one, $\sum_{j=1}^{n_S}{p_{[i,j]}} = 1$ for all $i = 1,\ldots,n_S$. 265 | 266 | The state vector at cycle $t+1$ ($\mathbf{m}_{t+1}$) is then calculated as the matrix product of the state vector at cycle $t$, $\mathbf{m}_{t}$, and the transition probability matrix, $P$, such that 267 | 268 | $$ 269 | \mathbf{m}_{t+1} = \mathbf{m}_{t} P \text{ for } t = 0,\ldots, (n_T - 1), 270 | $$ 271 | where $\mathbf{m}_1$ is computed from $\mathbf{m}_{0}$, the initial state vector with the distribution of the cohort across all health states at the start of the simulation (cycle 0). Then, we iteratively apply this equation through $t = \left(n_T-1 \right)$. 272 | 273 | The cohort trace matrix, $M$, is a matrix of dimensions $(n_T+1) \times n_S$ where each row is a state vector $(-\mathbf{m}_{t}-)$, such that 274 | 275 | $$ 276 | M = 277 | \begin{bmatrix} 278 | - \mathbf{m}_0 - \\ 279 | - \mathbf{m}_1 - \\ 280 | \vdots \\ 281 | - \mathbf{m}_{n_T} - 282 | \end{bmatrix}. 283 | $$ 284 | 285 | Note that the initial cycle (i.e., cycle 0) corresponds to $t=0$, which is on the first row of $M$. Thus, $M$ stores the output of the cSTM, which could be used to compute various epidemiological, and economic outcomes, such as life expectancy, prevalence, cumulative resource use, and costs, etc. Table \@ref(tab:cSTM-components-table) describes the elements related to the core components of cSTM and their suggested R code names. For a more detailed description of the variable types, data structure, R name for all cSTM elements, please see the Supplementary Material. 286 | 287 | Table: (\#tab:cSTM-components-table) Components of a cSTM with their R name. 288 | 289 | | Element | Description | R name | | 290 | |---------|---------------------------|:------:|---| 291 | | $n_S$ | Number of states | `n_states`| | 292 | | $\mathbf{m}_0$ | Initial state vector | `v_m_init` | | 293 | | $\mathbf{m}_t$ | State vector in cycle $t$ | `v_mt` | | 294 | | $M$ | Cohort trace matrix | `m_M` | | 295 | | $P$ | Time-independent transition probability matrix| `m_P` | | 296 | 297 | 298 | # Case study: Sick-Sicker model 299 | 300 | Here, we use the previously published 4-state "Sick-Sicker" model for conducting a CEA of multiple strategies to illustrate the various aspects of cSTM implementation in R.[@Enns2015e;@Krijkamp2018] Figure \@ref(fig:STD-Sick-Sicker) represents the state-transition diagram of the Sick-Sicker model. 301 | 302 | ```{r STD-Sick-Sicker, echo=FALSE, fig.cap="State-transition diagram of the time-independent Sick-Sicker cohort state-transition model, showing all possible states (labeled with state names) and transitions (labeled with transition probability variable names).", fig.pos="H"} 303 | knitr::include_graphics("figs/Sick-Sicker.png") 304 | ``` 305 | 306 | The model simulates a cohort at risk of a hypothetical disease with two stages, "Sick" and "Sicker", to compute the expected costs and quality-adjusted life years (QALYs) of the cohort over time. 307 | 308 | All the parameters of the Sick-Sicker model and the corresponding R variable names are presented in Table \@ref(tab:param-table). The naming of these parameters and variables follows the notation described in the DARTH coding framework.[@Alarid-Escudero2019e] Briefly, we define variables by `__`, where `x` is the prefix that indicates the data type (e.g., scalar (no prefix), `v` for vector, `m` for matrix, `a` for array, `df` for data frame, etc.), `y` is the prefix indicating variable type (e.g., `p` for probability, `r` for rate, `hr` for hazard ratio, `c` for cost `c`, `u` for utility, etc.), and `var_name` is some description of the variable presented separated by underscores. For example, `v_p_HD` denotes the vector of transition probabilities from health state "H" to health state "D". In later sections we will define and name all the other parameters. 309 | 310 | In this model, we simulate a hypothetical cohort of 25-year-olds in the "Healthy" state (denoted "H") until they reach a maximum age of 100 years. We will simulate the cohort dynamics in annual cycle lengths, requiring a total of `r n_cycles` one-year cycles. The total number of cycles is denoted as $n_T$ and defined in R as `n_cycles`. The model setup is as follows. Healthy individuals are at risk of developing the disease when they transition to the "Sick" state (denoted by "S1") with an annual rate of `r_HS1`. Sick individuals are at risk of further progressing to a more severe disease stage, the "Sicker" health state (denoted by "S2") with an annual rate of `r_S1S2`. Individuals in S1 can recover and return to H, as depicted in Figure \@ref(fig:STD-Sick-Sicker) by the arc labeled `p_S1H`. However, once individuals reach S2, they cannot recover; the rate of transitioning to S1 or H from S2 is zero. Individuals in H face a constant background mortality (labeled `p_HD` in Figure \@ref(fig:STD-Sick-Sicker)) due to other causes of death. Individuals in S1 and S2 face an increased hazard of death, compared to healthy individuals, in the form of a hazard ratio (HR) of 3 and 10, respectively, relative to the background mortality rate. We transform all transition rates to probabilities following the Section "Rates versus probabilities" approach. All transitions between non-death states are assumed to be conditional on surviving each cycle. Individuals in S1 and S2 also experience increased health care costs and reduced quality of life (QoL) compared to individuals in H. When individuals die, they transition to the absorbing "Dead" state (denoted by "D"), meaning that once the proportion of the cohort arrives in that state, they remain. We discount both costs and QALYs at an annual rate of `r percent(d_c)`. 311 | 312 | We are interested in evaluating the cost-effectiveness of four strategies: the standard of care (strategy SoC), strategy A, strategy B, and a combination of strategies A and B (strategy AB). Strategy A involves administering treatment A that increases the QoL of individuals in S1 from `r u_S1` (utility without treatment, `u_S1`) to `r u_trtA` (utility with treatment A, `u_trtA`). Treatment A costs \$`r comma(c_trtA)` per year (`c_trtA`).[@Krijkamp2018] This strategy does not impact the QoL of individuals in S2, nor does it change the risk of becoming sick or progressing through the sick states. Strategy B uses treatment B to reduce only the rate of Sick individuals progressing to the Sicker state by `r percent(1-hr_S1S2_trtB)` (i.e., a hazard ratio (HR) of `r hr_S1S2_trtB`, `hr_S1S2_trtB`), costs \$`r comma(c_trtB)` per year (`c_trtB`), and does not affect QoL. Strategy AB involves administering both treatments A and B. 313 | 314 | We assume that it is not possible to distinguish between Sick and Sicker patients; therefore, individuals in both disease states receive the treatment under the treatment strategies. After comparing the four strategies in terms of expected QALYs and costs, we calculate the incremental cost per QALY gained between non-dominated strategies. 315 | 316 | 317 | 318 | Table: (\#tab:param-table) Description of parameters, their R variable name, base-case values and distribution. 319 | 320 | | **Parameter** | **R name** | **Base-case** |**Distribution**| 321 | |:-----------------------------------|:-----------:|:-------------:|:--------------:| 322 | | Number of cycles ($n_{T}$) | `n_cycles` | `r n_cycles` years | - | 323 | | Names of health states ($n$) | `v_names_states` | H, S1, S2, D | - | 324 | | Annual discount rate for costs | `d_c` | 3% | - | 325 | | Annual discount rate for QALYs | `d_e` | 3% | - | 326 | | Number of PSA samples ($K$) | `n_sim` | 1,000 | - | 327 | | Annual constant transition rates | | | | 328 | | - Disease onset (H to S1) | `r_HS1` | 0.150 | gamma(30, 200) | 329 | | - Recovery (S1 to H) | `r_S1H` | 0.500 | gamma(60, 120) | 330 | | - Disease progression (S1 to S2) | `r_S1S2` | 0.105 | gamma(84, 800) | 331 | | Annual mortality | | | | 332 | | - Background mortality rate (H to D)| `r_HD` | 0.002 | gamma(20, 10000) | 333 | | - Hazard ratio of death in S1 vs H | `hr_S1` | 3.0 | lognormal(log(3.0), 0.01) | 334 | | - Hazard ratio of death in S2 vs H | `hr_S2` | 10.0 | lognormal(log(10.0), 0.02) | 335 | | Annual costs | | | | 336 | | - Healthy individuals | `c_H` | $2,000 | gamma(100.0, 20.0) | 337 | | - Sick individuals in S1 | `c_S1` | $4,000 | gamma(177.8, 22.5) | 338 | | - Sick individuals in S2 | `c_S2` | $15,000 | gamma(225.0, 66.7) | 339 | | - Dead individuals | `c_D` | $0 | - | 340 | | Utility weights | | | | 341 | | - Healthy individuals | `u_H` | 1.00 | beta(200, 3) | 342 | | - Sick individuals in S1 | `u_S1` | 0.75 | beta(130, 45) | 343 | | - Sick individuals in S2 | `u_S2` | 0.50 | beta(230, 230) | 344 | | - Dead individuals | `u_D` | 0.00 | - | 345 | | Treatment A cost and effectiveness | | | | 346 | | - Cost of treatment A, additional to state-specific health care costs | `c_trtA` | $12,000 |gamma(73.5, 163.3) | 347 | | - Utility for treated individuals in S1 | `u_trtA` | 0.95 | beta(300, 15) | 348 | | Treatment B cost and effectiveness | | | | 349 | | - Cost of treatment B, additional to state-specific health care costs | `c_trtB` | $12,000 |gamma(86.2, 150.8) | 350 | | - Reduction in rate of disease progression (S1 to S2) as hazard ratio (HR) | `hr_S1S2_trtB` | log(0.6) | lognormal(log(0.6), 0.02) | 351 | 352 | The following sections include R code snippets. All R code is stored as a GitHub repository and can be accessed at https://github.com/DARTH-git/cohort-modeling-tutorial-intro. We initialize the input parameters in the R code below by setting the variables to their base-case values. We do this process as the first coding step, all in one place, so the updated value will carry through the rest of the code when a parameter value changes. 353 | 354 | ```{r Model-Params, eval=FALSE} 355 | ## General setup 356 | cycle_length <- 1 # cycle length equal one year (use 1/12 for monthly) 357 | n_age_init <- 25 # age at baseline 358 | n_age_max <- 100 # maximum age of follow up 359 | n_cycles <- (n_age_max - n_age_init)/cycle_length # time horizon, number of cycles 360 | v_names_states <- c("H", "S1", "S2", "D") # the 4 health states of the model: 361 | # Healthy (H), Sick (S1), Sicker (S2), Dead (D) 362 | n_states <- length(v_names_states) # number of health states 363 | d_e <- 0.03 # annual discount rate for QALYs of 3% 364 | d_c <- 0.03 # annual discount rate for costs of 3% 365 | v_names_str <- c("Standard of care", # store the strategy names 366 | "Strategy A", 367 | "Strategy B", 368 | "Strategy AB") 369 | 370 | ## Transition probabilities (annual), and hazard ratios (HRs) 371 | r_HD <- 0.002 # constant annual rate of dying when Healthy (all-cause mortality rate) 372 | r_HS1 <- 0.15 # constant annual rate of becoming Sick when Healthy 373 | r_S1H <- 0.5 # constant annual rate of becoming Healthy when Sick 374 | r_S1S2 <- 0.105 # constant annual rate of becoming Sicker when Sick 375 | hr_S1 <- 3 # hazard ratio of death in Sick vs Healthy 376 | hr_S2 <- 10 # hazard ratio of death in Sicker vs Healthy 377 | 378 | ### Process model inputs 379 | ## Constant transition probability of becoming Sick when Healthy 380 | # transform rate to probability and scale by the cycle length 381 | p_HS1 <- 1 - exp(-r_HS1 * cycle_length) 382 | ## Constant transition probability of becoming Healthy when Sick 383 | # transform rate to probability and scale by the cycle length 384 | p_S1H <- 1 - exp(-r_S1H * cycle_length) 385 | ## Constant transition probability of becoming Sicker when Sick 386 | # transform rate to probability and scale by the cycle length 387 | p_S1S2 <- 1 - exp(-r_S1S2 * cycle_length) 388 | 389 | # Effectiveness of treatment B 390 | hr_S1S2_trtB <- 0.6 # hazard ratio of becoming Sicker when Sick under treatment B 391 | 392 | ## State rewards 393 | ## Costs 394 | c_H <- 2000 # annual cost of being Healthy 395 | c_S1 <- 4000 # annual cost of being Sick 396 | c_S2 <- 15000 # annual cost of being Sicker 397 | c_D <- 0 # annual cost of being dead 398 | c_trtA <- 12000 # annual cost of receiving treatment A 399 | c_trtB <- 13000 # annual cost of receiving treatment B 400 | # Utilities 401 | u_H <- 1 # annual utility of being Healthy 402 | u_S1 <- 0.75 # annual utility of being Sick 403 | u_S2 <- 0.5 # annual utility of being Sicker 404 | u_D <- 0 # annual utility of being dead 405 | u_trtA <- 0.95 # annual utility when receiving treatment A 406 | ``` 407 | 408 | To compute the background mortality risk, `p_HD`, from the background mortality rate for the same cycle length (i.e., `cycle_length = 1`), we apply Equation \@ref(eq:rate-to-prob-ann) to `r_HD`. To compute the mortality risks of the cohort in S1 and S2, we multiply the background mortality rate `r_HD` by the hazard ratios `hr_S1` and `hr_S2`, respectively, and then convert back to probabilities using Equation \@ref(eq:rate-to-prob-ann). These calculations are required because hazard ratios only apply to rates and not to probabilities. The code below performs the computation in R. In the `darthtools` package (https://github.com/DARTH-git/darthtools), we provide R functions that compute transformations between rates and probabilities since these transformations are frequently used. 409 | 410 | ```{r Sick-Sicker-Params, eval=TRUE} 411 | ## Mortality rates 412 | r_S1D <- r_HD * hr_S1 # annual rate of dying when Sick 413 | r_S2D <- r_HD * hr_S2 # annual rate of dying when Sicker 414 | ## Cycle-specific probabilities of dying 415 | p_HD <- 1 - exp(-r_HD * cycle_length) # annual background mortality risk (i.e., probability) 416 | p_S1D <- 1 - exp(-r_S1D * cycle_length) # annual probability of dying when Sick 417 | p_S2D <- 1 - exp(-r_S2D * cycle_length) # annual probability of dying when Sicker 418 | ``` 419 | 420 | To compute the risk of progression from S1 to S2 under treatment B, we multiply the hazard ratio of treatment B by the rate of progressing from S1 to S2 and transform it to probability by applying Equation \@ref(eq:rate-to-prob-ann). 421 | 422 | ```{r New-Treatment-2-Effectiveness, eval=TRUE} 423 | ## Transition probability of becoming Sicker when Sick for treatment B 424 | # apply hazard ratio to rate to obtain transition rate of becoming Sicker when Sick 425 | # for treatment B 426 | r_S1S2_trtB <- r_S1S2 * hr_S1S2_trtB 427 | # transform rate to probability 428 | # probability to become Sicker when Sick under treatment B 429 | p_S1S2_trtB <- 1 - exp(-r_S1S2_trtB * cycle_length) 430 | ``` 431 | 432 | For the Sick-Sicker model, the entire cohort starts in the H state. Therefore, we create the $1 \times n_S$ initial state vector `v_m_init` with all of the cohort assigned to the H state: 433 | ```{r Sick-Sicker-s0} 434 | v_m_init <- c(H = 1, S1 = 0, S2 = 0, D = 0) # initial state vector 435 | ``` 436 | 437 | The variable `v_m_init` is used to initialize $M$ represented by `m_M` for the cohort under strategy SoC. We also create a trace for each of the other treatment-based strategies. 438 | 439 | ```{r Sick-Sicker-M} 440 | ## Initialize cohort trace for SoC 441 | m_M <- matrix(NA, 442 | nrow = (n_cycles + 1), ncol = n_states, 443 | dimnames = list(0:n_cycles, v_names_states)) 444 | # Store the initial state vector in the first row of the cohort trace 445 | m_M[1, ] <- v_m_init 446 | ## Initialize cohort trace for strategies A, B, and AB 447 | # Structure and initial states are the same as for SoC 448 | m_M_strA <- m_M # Strategy A 449 | m_M_strB <- m_M # Strategy B 450 | m_M_strAB <- m_M # Strategy AB 451 | ``` 452 | 453 | Note that the initial state vector, `v_m_init`, can be modified to account for the cohort's distribution across the states at the start of the simulation. This distribution can also vary by strategy if needed. 454 | 455 | Since the Sick-Sicker model consists of `r n_states` states, we create a `r n_states` $\times$ `r n_states` transition probability matrix for strategy SoC, `m_P`. We initialize the matrix with default values of zero for all transition probabilities and then populate it with the corresponding transition probabilities. To access an element of `m_P`, we specify first the row name (or number) and then the column name (or number) separated by a comma. For example, we could access the transition probability from state Healthy (H) to state Sick (S1) using the corresponding row or column state-names as characters `m_P["H", "S1"]`. We assume that all transitions to non-death states are conditional on surviving to the end of a cycle. Thus, we first condition on surviving by multiplying the transition probabilities times `1 - p_HD`, the probability of surviving a cycle. For example, to obtain the probability of transitioning from H to S1, we multiply the transition probability from H to S1 conditional on being alive, `p_HS1` by `1 - p_HD`. We create the transition probability matrix for strategy A as a copy of the SoC's transition probability matrix because treatment A does not alter the cohort's transition probabilities. 456 | 457 | ```{r Sick-Sicker-P2} 458 | ## Initialize transition probability matrix for strategy SoC 459 | m_P <- matrix(0, 460 | nrow = n_states, ncol = n_states, 461 | dimnames = list(v_names_states, v_names_states)) # row and column names 462 | ## Fill in matrix 463 | # From H 464 | m_P["H", "H"] <- (1 - p_HD) * (1 - p_HS1) 465 | m_P["H", "S1"] <- (1 - p_HD) * p_HS1 466 | m_P["H", "D"] <- p_HD 467 | # From S1 468 | m_P["S1", "H"] <- (1 - p_S1D) * p_S1H 469 | m_P["S1", "S1"] <- (1 - p_S1D) * (1 - (p_S1H + p_S1S2)) 470 | m_P["S1", "S2"] <- (1 - p_S1D) * p_S1S2 471 | m_P["S1", "D"] <- p_S1D 472 | # From S2 473 | m_P["S2", "S2"] <- 1 - p_S2D 474 | m_P["S2", "D"] <- p_S2D 475 | # From D 476 | m_P["D", "D"] <- 1 477 | 478 | ## Initialize transition probability matrix for strategy A as a copy of SoC's 479 | m_P_strA <- m_P 480 | ``` 481 | 482 | Because treatment B alters progression from S1 to S2, we created a different transition probability matrix to model this treatment, `m_P_strB`. We initialize `m_P_strB` as a copy of `m_P` and update only the transition probabilities from S1 to S2 (i.e., `p_S1S2` is replaced with `p_S1S2_trtB`). Strategy AB also alters progression from S1 to S2 because it uses treatment B, so we create this strategy's transition probability matrix as a copy of the transition probability matrix of strategy B. 483 | 484 | ```{r Sick-Sicker-Time-independent-New-Treatment2} 485 | ## Initialize transition probability matrix for strategy B 486 | m_P_strB <- m_P 487 | ## Update only transition probabilities from S1 involving p_S1S2 488 | m_P_strB["S1", "S1"] <- (1 - p_S1D) * (1 - (p_S1H + p_S1S2_trtB)) 489 | m_P_strB["S1", "S2"] <- (1 - p_S1D) * p_S1S2_trtB 490 | 491 | ## Initialize transition probability matrix for strategy AB as a copy of B's 492 | m_P_strAB <- m_P_strB 493 | ``` 494 | 495 | Once all transition matrices are created, we verify they are valid by checking that each row sums to one and that each entry is between 0 and 1. In the `darthtools` package (https://github.com/DARTH-git/darthtools), we provide R functions that do these checks, which have been described previously.[@Alarid-Escudero2019e] 496 | 497 | ```{r, results='hide'} 498 | ### Check if transition probability matrices are valid 499 | ## Check that transition probabilities are [0, 1] 500 | m_P >= 0 && m_P <= 1 501 | m_P_strA >= 0 && m_P_strA <= 1 502 | m_P_strB >= 0 && m_P_strB <= 1 503 | m_P_strAB >= 0 && m_P_strAB <= 1 504 | ## Check that all rows sum to 1 505 | rowSums(m_P) == 1 506 | rowSums(m_P_strA) == 1 507 | rowSums(m_P_strB) == 1 508 | rowSums(m_P_strAB) == 1 509 | ``` 510 | 511 | Next, we obtain the cohort distribution across the `r n_states` states over `r n_cycles` cycles using a time-independent cSTM under all four strategies. To achieve this, we iteratively compute the matrix product between each of the rows of `m_M` and `m_P`, and between `m_M_strB` and `m_P_strB`, respectively, using the `%*%` symbol in R at each cycle using a `for` loop 512 | 513 | ```{r Sick-Sicker-TimeHomogeneous-Solution} 514 | # Iterative solution of time-independent cSTM 515 | for(t in 1:n_cycles){ 516 | # For SoC 517 | m_M[t + 1, ] <- m_M[t, ] %*% m_P 518 | # For strategy A 519 | m_M_strA[t + 1, ] <- m_M_strA[t, ] %*% m_P_strA 520 | # For strategy B 521 | m_M_strB[t + 1, ] <- m_M_strB[t, ] %*% m_P_strB 522 | # For strategy AB 523 | m_M_strAB[t + 1, ] <- m_M_strAB[t, ] %*% m_P_strAB 524 | } 525 | ``` 526 | 527 | Table \@ref(tab:Trace) shows the cohort trace matrix $M$ of the Sick-Sicker model under strategies SoC and A for the first six cycles. The table could easily be displayed using the `head()` function. The whole cohort starts in the H state and transitions to the rest of the states over time. Given that the D state is absorbing, the proportion in this state increases over time. A graphical representation of the cohort trace for all the cycles is shown in Figure \@ref(fig:Sick-Sicker-Trace-TimeHom). 528 | 529 | ```{r Trace, echo=FALSE, message=FALSE, warning=FALSE, purl=FALSE} 530 | kable(head(round(cbind(Cycle = as.numeric(rownames(m_M)), m_M), 3)), 531 | format = ifelse(doc_type == "docx", "markdown", "latex"), 532 | row.names = FALSE, 533 | booktabs = TRUE, 534 | caption = "The distribution of the cohort under strategies SoC and A for the first six cycles of the time-independent Sick-Sicker model. The first row, labeled with cycle 0, contains the distribution of the cohort at time zero.", 535 | align = c("c", "c", "c", "c", "c")) %>% 536 | kableExtra::kable_styling(latex_options = "hold_position") 537 | ``` 538 | 539 | ```{r Sick-Sicker-Trace-TimeHom, echo=FALSE, fig.cap='Cohort trace of the time-independent cSTM under strategies SoC and A.', message=FALSE, warning=FALSE, fig.pos="H", dev=c('pdf', 'jpeg'), dpi =900} 540 | cols <- get_DARTH_cols() 541 | lty <- c("H" = 1, "S1" = 2, "S2" = 4, "D" = 3) 542 | ggplot(reshape2::melt(m_M), aes(x = Var1, y = value, 543 | color = Var2, linetype = Var2)) + 544 | geom_line(size = 1) + 545 | scale_colour_manual(name = "Health state", 546 | values = cols) + 547 | scale_linetype_manual(name = "Health state", 548 | values = lty) + 549 | scale_x_continuous(breaks = number_ticks(8)) + 550 | xlab("Cycle") + 551 | ylab("Proportion of the cohort") + 552 | theme_bw(base_size = 16) + 553 | theme(legend.position = "bottom",#c(0.7, 0.75), 554 | legend.background = element_rect(fill = NA)) 555 | ``` 556 | 557 | # Cost and effectiveness outcomes 558 | We are interested in computing the total QALYs and costs accrued by the cohort over a predefined time horizon for a CEA. In the advanced cSTM tutorial,[@Alarid-Escudero2021b] we describe how to compute epidemiological outcomes from cSTMs, such as survival, prevalence, and life expectancy.[@Siebert2012c] These epidemiological outcomes are often used to produce other measures of interest for model calibration and validation. 559 | 560 | ## State rewards 561 | A state reward refers to a value assigned to individuals for being in a given health state. These could be either utilities or costs associated with remaining in a specific health state for one cycle in a CEA context. The column vector $\mathbf{y}$ of size $n_T+1$ can represent the total expected reward of an outcome of interest for the entire cohort at each cycle. To calculate $\mathbf{y}$, we compute the matrix product of the cohort trace matrix times a *vector* of state rewards $\mathbf{r}$ of the same dimension as the number of states ($n_S$), such that 562 | \begin{equation} 563 | \mathbf{y} = M\mathbf{r}. 564 | (\#eq:exp-rew-cycle) 565 | \end{equation} 566 | 567 | For the Sick-Sicker model, we create a vector of utilities and costs for each of the four strategies considered. The vectors of utilities and costs in R, `v_u_SoC` and `v_c_SoC`, respectively, represent the utilities and costs in each of the four health states under SoC, scaled by the cycle length (values are shown in Table \@ref(tab:param-table)). 568 | 569 | ```{r State-rewards-UC} 570 | # Vector of state utilities under SOC 571 | v_u_SoC <- c(H = u_H, S1 = u_S1, S2 = u_S2, D = u_D) * cycle_length 572 | # Vector of state costs under SoC 573 | v_c_SoC <- c(H = c_H, S1 = c_S1, S2 = c_S2, D = c_D) * cycle_length 574 | ``` 575 | 576 | We account for the benefits and costs of both treatments individually and their combination to create the state-reward vectors under treatments A and B (strategies A and B, respectively) and when applied jointly (strategy AB). Only treatment A affects QoL, so we create a vector of utilities specific to strategies involving treatment A (strategies A and AB), `v_u_strA` and `v_u_strAB`. These vectors will have the same utility weights as for strategy SoC except for being in S1. We assign the utility associated with the benefit of treatment A in that state, `u_trtA`. Treatment B does not affect QoL, so the vector of utilities for strategy involving treatment B, `v_u_strB`, is the same as for SoC. 577 | 578 | ```{r State-rewards-U-Tr} 579 | # Vector of state utilities for strategy A 580 | v_u_strA <- c(H = u_H, S1 = u_trtA, S2 = u_S2, D = u_D) * cycle_length 581 | # Vector of state utilities for strategy B 582 | v_u_strB <- c(H = u_H, S1 = u_S1, S2 = u_S2, D = u_D) * cycle_length 583 | # Vector of state utilities for strategy AB 584 | v_u_strAB <- c(H = u_H, S1 = u_trtA, S2 = u_S2, D = u_D) * cycle_length 585 | ``` 586 | 587 | Both treatments A and B incur a cost. To create the vector of state costs for strategy A, `v_c_strA`, we add the cost of treatment A, `c_trtA`, to the state costs of S1 and S2. Similarly, when constructing the vector of state costs for strategy B, `v_c_strB`, we add the cost of treatment B, `c_trtB`, to the state costs of S1 and S2. Finally, for the vector of state costs for strategy AB, `v_c_strAB`, we add both treatment costs to the state costs of S1 and S2. 588 | 589 | ```{r State-rewards-C-Tr} 590 | # Vector of state costs for strategy A 591 | v_c_strA <- c(H = c_H, 592 | S1 = c_S1 + c_trtA, 593 | S2 = c_S2 + c_trtA, 594 | D = c_D) * cycle_length 595 | # Vector of state costs for strategy B 596 | v_c_strB <- c(H = c_H, 597 | S1 = c_S1 + c_trtB, 598 | S2 = c_S2 + c_trtB, 599 | D = c_D) * cycle_length 600 | # Vector of state costs for strategy AB 601 | v_c_strAB <- c(H = c_H, 602 | S1 = c_S1 + (c_trtA + c_trtB), 603 | S2 = c_S2 + (c_trtA + c_trtB), 604 | D = c_D) * cycle_length 605 | ``` 606 | 607 | To compute the expected QALYs and costs for the Sick-Sicker model under SoC and strategy A, we apply Equation \@ref(eq:exp-rew-cycle) by multiplying the cohort trace matrix, `m_M`, times the corresponding strategy-specific state vectors of rewards. Similarly, to compute the expected rewards for strategies B and AB, we multiply the cohort trace matrix accounting for the effectiveness of treatment B, `m_M_strB`, times their corresponding state vectors of rewards. 608 | 609 | ```{r Expected-outcomes-each-cycle} 610 | # Vector of QALYs under SoC 611 | v_qaly_SoC <- m_M %*% v_u_SoC 612 | # Vector of costs under SoC 613 | v_cost_SoC <- m_M %*% v_c_SoC 614 | # Vector of QALYs for strategy A 615 | v_qaly_strA <- m_M_strA %*% v_u_strA 616 | # Vector of costs for strategy A 617 | v_cost_strA <- m_M_strA %*% v_c_strA 618 | # Vector of QALYs for strategy B 619 | v_qaly_strB <- m_M_strB %*% v_u_strB 620 | # Vector of costs for strategy B 621 | v_cost_strB <- m_M_strB %*% v_c_strB 622 | # Vector of QALYs for strategy AB 623 | v_qaly_strAB <- m_M_strAB %*% v_u_strAB 624 | # Vector of costs for strategy AB 625 | v_cost_strAB <- m_M_strAB %*% v_c_strAB 626 | ``` 627 | 628 | ## Within-cycle correction 629 | A discrete-time cSTM involves an approximation of continuous-time dynamics to discrete points in time. The discretization might introduce biases when estimating outcomes based on state occupancy.[@VanRosmalen2013] One approach to reducing these biases is to shorten the cycle length, requiring simulating the model for a larger number of cycles, which can be computationally burdensome. Another approach is to use within-cycle corrections (WCC).[@Siebert2012c;@Hunink2014] In this tutorial, we use Simpson's 1/3rd rule by multiplying the rewards (e.g., costs and effectiveness) by $1/3$ in the first and last cycles, by $4/3$ for the odd cycles, and by $2/3$ for the even cycles.[@Elbasha2016;@Elbasha2016a] We implement the WCC by generating a column vector $\mathbf{wcc}$ of size $n_T+1$ with values corresponding to the first, $t=0$, and last cycle, $t= n_T$, equal to $1/3$, and the entries corresponding to the even and odd cycles with $2/3$ and $4/3$, respectively. 630 | 631 | \[ 632 | \mathbf{wcc} = \left[\frac{1}{3}, \frac{2}{3}, \frac{4}{3}, \cdots, \frac{1}{3}\right] 633 | \] 634 | 635 | Since the WCC vector is the same for costs and QALYs, we only require one vector (`v_wcc`). We create `v_wcc` by defining two indicator functions that tell us whether the vector entries are even or odd, filled with the corresponding factors given by Simpson's 1/3rd rule. We used the `function` command that reads a vector `x` and applies the modulo operation `%%` that returns the remainder of dividing each of the vector entries by 2. If the remainder of the $i-th$ entry is 0, the entry is even, and it is odd if the remainder is 1. 636 | ```{r within-cycle-vector} 637 | # First, we define two functions to identify if a number is even or odd 638 | is_even <- function(x) x %% 2 == 0 639 | is_odd <- function(x) x %% 2 != 0 640 | ## Vector with cycles 641 | v_cycles <- seq(1, n_cycles + 1) 642 | ## Generate 2/3 and 4/3 multipliers for even and odd entries, respectively 643 | v_wcc <- is_even(v_cycles)*(2/3) + is_odd(v_cycles)*(4/3) 644 | ## Substitute 1/3 in first and last entries 645 | v_wcc[1] <- v_wcc[n_cycles + 1] <- 1/3 646 | ``` 647 | 648 | ## Discounting future rewards 649 | We often discount future costs and benefits by a specific rate to calculate the net present value of these rewards. We then use this rate to generate a column vector with cycle-specific discount weights $\mathbf{d}$ of size $n_T+1$ where its $t$-th entry represents the discounting for cycle $t$ 650 | 651 | $$ 652 | \mathbf{d} = \left[1, \frac{1}{(1+d)^{1}}, \frac{1}{(1+d)^{2}}, \cdots, \frac{1}{(1+d)^{n_T}}\right], 653 | $$ 654 | where $d$ is the cycle-length discount rate. At the end of the simulation, we multiply the vector of expected rewards, $\mathbf{y}$, by a discounting column vector. The total expected discounted outcome summed over the $n_T$ cycles, $y$, is obtained by the inner product between $\mathbf{y}$ transposed, $\mathbf{y'}$, and $\mathbf{d}$, 655 | \begin{equation} 656 | y = \mathbf{y'} \mathbf{d}. 657 | (\#eq:tot-exp-disc-rewd) 658 | \end{equation} 659 | 660 | The discount vectors for costs and QALYs for the Sick-Sicker model with annual cycles, `v_dwc` and `v_dwe`, respectively, scaled by the cycle length, are 661 | 662 | ```{r Discount vectors} 663 | # Discount weight for effects 664 | v_dwe <- 1 / ((1 + (d_e * cycle_length)) ^ (0:n_cycles)) 665 | # Discount weight for costs 666 | v_dwc <- 1 / ((1 + (d_c * cycle_length)) ^ (0:n_cycles)) 667 | ``` 668 | 669 | The functions and code for creating the WCC and discounting vectors above are single lines of code that affect the entire vectors of rewards used to compute the health and economic outputs of the model. To compute the total expected discounted QALYs and costs under all four strategies accounting for both discounting and WCC, we incorporate $\mathbf{wcc}$ in equation \@ref(eq:tot-exp-disc-rewd) using an element-wise multiplication with $\mathbf{d}$, indicated by the $\odot$ sign. The element-wise multiplication computes a new vector with elements that are the products of the corresponding elements of $\mathbf{wcc}$ and $\mathbf{d}$. 670 | \begin{equation} 671 | y = \mathbf{y}^{'} \left(\mathbf{d} \odot \mathbf{wcc}\right). 672 | (\#eq:tot-exp-disc-rewd-wcc) 673 | \end{equation} 674 | 675 | To compute the total expected discounted and WCC-corrected QALYs under all four strategies in R, we apply Equation \@ref(eq:tot-exp-disc-rewd-wcc) to the reward vectors of each strategy. 676 | 677 | ```{r Expected-outcomes-all-cycles} 678 | ## Expected discounted QALYs under SoC 679 | n_tot_qaly_SoC <- t(v_qaly_SoC) %*% (v_dwe * v_wcc) 680 | ## Expected discounted costs under SoC 681 | n_tot_cost_SoC <- t(v_cost_SoC) %*% (v_dwc * v_wcc) 682 | ## Expected discounted QALYs for strategy A 683 | n_tot_qaly_strA <- t(v_qaly_strA) %*% (v_dwe * v_wcc) 684 | ## Expected discounted costs for strategy A 685 | n_tot_cost_strA <- t(v_cost_strA) %*% (v_dwc * v_wcc) 686 | ## Expected discounted QALYs for strategy B 687 | n_tot_qaly_strB <- t(v_qaly_strB) %*% (v_dwe * v_wcc) 688 | ## Expected discounted costs for strategy B 689 | n_tot_cost_strB <- t(v_cost_strB) %*% (v_dwc * v_wcc) 690 | ## Expected discounted QALYs for strategy AB 691 | n_tot_qaly_strAB <- t(v_qaly_strAB) %*% (v_dwe * v_wcc) 692 | ## Expected discounted costs for strategy AB 693 | n_tot_cost_strAB <- t(v_cost_strAB) %*% (v_dwc * v_wcc) 694 | ``` 695 | 696 | ```{r Expected-outcomes, echo=FALSE, message=FALSE, warning=FALSE} 697 | m_outcomes <- matrix(c(dollar(c(n_tot_cost_SoC, n_tot_cost_strA, 698 | n_tot_cost_strB, n_tot_cost_strAB)), 699 | format(round(c(n_tot_qaly_SoC, n_tot_qaly_strA, 700 | n_tot_qaly_strB, n_tot_qaly_strAB), 3), nsmall = 3)), 701 | ncol = 2, nrow = length(v_names_str), 702 | dimnames = list(v_names_str, 703 | c("Costs", "QALYs"))) 704 | ``` 705 | 706 | ```{r Expected-outcomes-table, echo=FALSE, message=FALSE, warning=FALSE, purl=FALSE} 707 | kable(m_outcomes, 708 | format = ifelse(doc_type == "docx", "markdown", "latex"), 709 | booktabs = TRUE, 710 | caption = "Total expected discounted QALYs and costs per average individual in the cohort of the Sick-Sicker model by strategy accounting for within-cycle correction.", 711 | align = c("l", "c", "c")) %>% 712 | kable_styling(latex_options = "hold_position") 713 | ``` 714 | 715 | The total expected discounted QALYs and costs for the Sick-Sicker model under the four strategies accounting for within-cycle correction are shown in Table \@ref(tab:Expected-outcomes-table). 716 | 717 | # Incremental cost-effectiveness ratios (ICERs) 718 | We combine the total expected discounted costs and QALYs for all four strategies into outcome-specific vectors, `v_cost_str` for costs and `v_qaly_str` for QALYs. So far, we have used *base* R to create and simulate cSTMs. For the CEA, we use the R package `dampack` (https://cran.r-project.org/web/packages/dampack/)[@Alarid-Escudero2021] to calculate the incremental costs and effectiveness and the incremental cost-effectiveness ratio (ICER) between non-dominated strategies and create the data frame `df_cea` with this information. These outcomes are required inputs to conduct a CEA. We also use `dampack` for the probabilistic sensitivity analysis (PSA) below. 719 | 720 | ```{r CEA-analysis} 721 | ### Vector of costs 722 | v_cost_str <- c(n_tot_cost_SoC, n_tot_cost_strA, n_tot_cost_strB, n_tot_cost_strAB) 723 | ### Vector of effectiveness 724 | v_qaly_str <- c(n_tot_qaly_SoC, n_tot_qaly_strA, n_tot_qaly_strB, n_tot_qaly_strAB) 725 | 726 | ### Calculate incremental cost-effectiveness ratios (ICERs) 727 | df_cea <- dampack::calculate_icers(cost = v_cost_str, 728 | effect = v_qaly_str, 729 | strategies = v_names_str) 730 | ``` 731 | 732 | ```{r CEA-table, echo=FALSE} 733 | table_cea <- df_cea 734 | ## Format column names 735 | colnames(table_cea)[2:6] <- c("Costs ($)", "QALYs", 736 | "Incremental Costs ($)", "Incremental QALYs", 737 | "ICER ($/QALY)") # name the columns 738 | table_cea$`Costs ($)` <- comma(round(table_cea$`Costs ($)`, 0)) 739 | table_cea$`Incremental Costs ($)` <- comma(round(table_cea$`Incremental Costs ($)`, 0)) 740 | table_cea$QALYs <- round(table_cea$QALYs, 3) 741 | table_cea$`Incremental QALYs` <- round(table_cea$`Incremental QALYs`, 3) 742 | table_cea$`ICER ($/QALY)` <- comma(round(table_cea$`ICER ($/QALY)`, 0)) 743 | ``` 744 | 745 | SoC is the least costly and effective strategy, followed by Strategy B producing an expected incremental benefit of `r round(table_cea[2, 5], 3)` QALYs per individual for an additional expected cost of \$`r table_cea[2, 4]` with an ICER of \$`r table_cea[2, 6]`/QALY followed by Strategy AB with an ICER \$`r table_cea[3, 6]`/QALY. Strategy A is a dominated strategy (Table \@ref(tab:table-cea)). Strategies SoC, B and AB form the cost-effectiveness efficient frontier (Figure \@ref(fig:Sick-Sicker-CEA)). 746 | 747 | ```{r table-cea, echo=FALSE, message=FALSE, warning=FALSE, purl=FALSE} 748 | kable(table_cea, 749 | format = ifelse(doc_type == "docx", "markdown", "latex"), 750 | booktabs = TRUE, 751 | caption = "Cost-effectiveness analysis results for the Sick-Sicker model. ND: Non-dominated strategy; D: Dominated strategy.", 752 | align = c("r", "c", "c", "c", "c", "c", "c")) %>% 753 | kable_styling(latex_options = "hold_position") 754 | ``` 755 | 756 | ```{r Sick-Sicker-CEA, echo=FALSE, fig.cap='Cost-effectiveness efficient frontier of the cost-effectiveness analysis based on the time-independent Sick-Sicker model', message=FALSE, warning=FALSE, fig.pos="H", dev=c('pdf', 'jpeg'), dpi =900} 757 | plot(df_cea, label = "all", txtsize = 16) + 758 | expand_limits(x = c(NA, 21.8)) + 759 | theme(legend.position = c(0.8, 0.2)) 760 | ``` 761 | 762 | # Probabilistic sensitivity analysis 763 | To quantify the effect of model parameter uncertainty on cost-effectiveness outcomes, we conducted a PSA by randomly drawing $K$ parameter sets (`n_sim`) from distributions that reflect the current uncertainty in model parameter estimates.[@Briggs2012] The distribution for all the parameters and their values are described in Table \@ref(tab:param-table) and in more detail in the Supplementary Material. We compute model outcomes for each sampled set of parameter values (e.g., total discounted cost and QALYs) for each strategy. In a previously published manuscript, we describe the implementation of these steps in R.[@Alarid-Escudero2019e] Briefly, to conduct the PSA, we create three R functions: 764 | 765 | ```{r PSA-setup, eval=TRUE, echo=FALSE} 766 | # Number of PSA samples 767 | n_sim <- 1000 768 | # Generate PSA input dataset 769 | df_psa_input <- generate_psa_params(n_sim = n_sim) 770 | 771 | # Initialize matrices with PSA output 772 | # data.frame of costs 773 | df_c <- as.data.frame(matrix(0, 774 | nrow = n_sim, 775 | ncol = n_str)) 776 | colnames(df_c) <- v_names_str 777 | # data.frame of effectiveness 778 | df_e <- as.data.frame(matrix(0, 779 | nrow = n_sim, 780 | ncol = n_str)) 781 | colnames(df_e) <- v_names_str 782 | ``` 783 | 784 | 1. `generate_psa_params(n_sim, seed)`: a function that generates a sample of size `n_sim` for the model parameters, `df_psa_input`, from their distributions defined in Table \@ref(tab:param-table). The function input `seed` sets the seed of the pseudo-random number generator used in sampling parameter values, which ensures reproducibility of the PSA results. 785 | 786 | 2. `decision_model(l_params_all, verbose = FALSE)`: a function that wraps the R code of the time-independent cSTM described in section \@ref(time-independent-cstm-dynamics). This function requires inputting a list of all model parameter values, `l_params_all` and whether the user wants print messages on whether transition probability matrices are valid via the `verbose` parameter. 787 | 3. `calculate_ce_out(l_params_all, n_wtp = 100000)`: a function that calculates total discounted costs and QALYs based on the `decision_model` function output. This function also computes the net monetary benefit (NMB) for a given willingness-to-pay threshold, specified by the argument `n_wtp`. 788 | These functions are provided in the accompanying GitHub repository of this manuscript. 789 | 790 | ```{r PSA-run, eval=TRUE, echo=FALSE, cache=TRUE, message=FALSE, warning=FALSE} 791 | ## Conduct probabilistic sensitivity analysis 792 | # Run Markov model on each parameter set of PSA input dataset 793 | for(i in 1:n_sim){ 794 | l_psa_input <- update_param_list(l_params_all, df_psa_input[i,]) 795 | l_out_temp <- calculate_ce_out(l_psa_input) 796 | df_c[i, ] <- l_out_temp$Cost 797 | df_e[i, ] <- l_out_temp$Effect 798 | # # Display simulation progress 799 | # if(i/(n_sim/100) == round(i/(n_sim/100), 0)) { # display progress every 5% 800 | # cat('\r', paste(i/n_sim * 100, "% done", sep = " ")) 801 | # } 802 | } 803 | ``` 804 | 805 | ```{r Generate-PSA-object, eval=TRUE, echo=FALSE, message=FALSE, warning=FALSE} 806 | # Create PSA object for dampack 807 | l_psa <- make_psa_obj(cost = df_c, 808 | effectiveness = df_e, 809 | parameters = df_psa_input, 810 | strategies = v_names_str) 811 | l_psa$strategies <- v_names_str 812 | colnames(l_psa$effectiveness)<- v_names_str 813 | colnames(l_psa$cost)<- v_names_str 814 | 815 | # Vector with willingness-to-pay (WTP) thresholds. 816 | v_wtp <- seq(0, 200000, by = 5000) 817 | 818 | ## Cost-effectiveness acceptability curves (CEACs) and frontier (CEAF) 819 | ceac_obj <- ceac(wtp = v_wtp, psa = l_psa) 820 | ceac_obj$Strategy <- ordered(ceac_obj$Strategy, v_names_str) 821 | 822 | ## Expected Loss Curves (ELCs) 823 | elc_obj <- calc_exp_loss(wtp = v_wtp, psa = l_psa) 824 | ``` 825 | 826 | To conduct the PSA of the CEA using the time-independent Sick-Sicker cSTM, we sampled `r comma(n_sim)` parameter sets from their distributions. We assumed commonly used distributions to describe their uncertainty for each type of parameter. For example, gamma for transition rates, lognormal for hazard ratios, and beta for utility weights.[@Parmigiani1997;@Parmigiani2002a;@Briggs2002;@Hunink2014] For each sampled parameter set, we simulated the cost and effectiveness of each strategy. Results from a PSA can be represented in various ways. For example, the joint distribution, 95% confidence ellipse, and the expected values of the total discounted costs and QALYs for each strategy can be plotted in a cost-effectiveness scatter plot (Figure \@ref(fig:CE-scatter)),[@Briggs2002] where each of the `r comma(n_sim)` simulations are plotted as a point in the graph. The CE scatter plot for CEA using the time-independent model shows that strategy AB has the highest expected costs and QALYs. Standard of care has the lowest expected cost and QALYs. Strategy B is more effective and least costly than Strategy A. Strategy A is a strongly dominated strategy. 827 | 828 | ```{r CE-scatter, echo=FALSE, fig.cap='Cost-effectiveness scatter plot.', message=FALSE, warning=FALSE, fig.pos="H", dev=c('png', 'pdf', 'jpeg'), dpi =900} 829 | # CEAC & CEAF plot 830 | plot(l_psa, txtsize = 16) + 831 | ggthemes::scale_color_colorblind() + 832 | ggthemes::scale_fill_colorblind() + 833 | xlab("Effectiveness (QALYs)") + 834 | guides(col = guide_legend(nrow = 2)) + 835 | theme(legend.position = "bottom") 836 | ``` 837 | 838 | Figure \@ref(fig:CEAC) presents the cost-effectiveness acceptability curves (CEACs), which show the probability that each strategy is cost-effective, and the cost-effectiveness frontier (CEAF), which shows the strategy with the highest expected net monetary benefit (NMB), over a range of willingness-to-pay (WTP) thresholds. Each strategy's NMB is computed using $\text{NMB} = \text{QALY} \times \text{WTP} - \text{Cost}$[@Stinnett1998b] for each PSA sample. At WTP thresholds less than `r dollar(summary(ceac_obj)[summary(ceac_obj)[, "cost_eff_strat"]=="Standard of care", 2], accuracy = 1)` per QALY gained, strategy SoC has both the highest probability of being cost-effective and the highest expected NMB. This switches to strategy B for WTP thresholds between `r dollar(summary(ceac_obj)[summary(ceac_obj)[, "cost_eff_strat"]=="Standard of care", 2], accuracy = 1)` and `r dollar(summary(ceac_obj)[summary(ceac_obj)[, "cost_eff_strat"]=="Strategy B", 2][1], accuracy = 1)` per QALY gained and to strategy AB for WTP thresholds greater than or equal to `r dollar(summary(ceac_obj)[summary(ceac_obj)[, "cost_eff_strat"]=="Strategy B", 2][1], accuracy = 1)` per QALY gained. 839 | 840 | ```{r CEAC, echo=FALSE, fig.cap='Cost-effectiveness acceptability curves (CEACs) and frontier (CEAF).', message=FALSE, warning=FALSE, fig.pos="H", dev=c( 'png', 'pdf', 'jpeg'), dpi =900} 841 | # CEAC & CEAF plot 842 | plot(ceac_obj, txtsize = 16, xlim = c(0, NA), n_x_ticks = 14) + 843 | ggthemes::scale_color_colorblind() + 844 | ggthemes::scale_fill_colorblind() + 845 | theme(legend.position = c(0.82, 0.5)) 846 | ``` 847 | 848 | The CEAC and CEAF do not show the magnitude of the expected net benefit lost (i.e., expected loss) when the chosen strategy is not the cost-effective strategy in all the samples of the PSA. To complement these results, we quantify expected loss from each strategy over a range of WTP thresholds with the expected loss curves (ELCs). These curves quantify the expected loss from each strategy over a range of WTP thresholds (Figure \@ref(fig:ELC)). The expected loss considers both the probability of making the wrong decision and the magnitude of the loss due to this decision, representing the foregone benefits of choosing a suboptimal strategy. The expected loss of the optimal strategy represents the lowest envelope of the ELCs because, given current information, the loss cannot be minimized further. The lower bound of the ELCs represents the expected value of perfect information (EVPI), which quantifies the value of eliminating parameter uncertainty. We refer the reader to previously published literature for a more detailed description of CEAC, CEAF, ELC, and EVPI interpretations and the R code to generate them.[@Alarid-Escudero2019] 849 | 850 | 851 | ```{r ELC, echo=FALSE, fig.cap='Expected loss curves (ELCs) and expected value of perfect information (EVPI).', message=FALSE, warning=FALSE, fig.pos="H", dev=c('png', 'pdf', 'jpeg'), dpi =900} 852 | # ELC plot 853 | plot(elc_obj, log_y = FALSE, 854 | txtsize = 16, xlim = c(0, NA), n_x_ticks = 14, 855 | col = "full") + 856 | ggthemes::scale_color_colorblind() + 857 | ggthemes::scale_fill_colorblind() + 858 | # geom_point(aes(shape = as.name("Strategy"))) + 859 | scale_y_continuous("Expected Loss (Thousand $)", 860 | breaks = number_ticks(10), 861 | labels = function(x) x/1000) + 862 | theme(legend.position = c(0.4, 0.7)) 863 | ``` 864 | 865 | # Discussion 866 | In this introductory tutorial, we provided a step-by-step mathematical conceptualization of time-independent cSTMs and a walk-through of their implementation in R using a hypothetical disease example with accompanying code throughout the tutorial. While some of the presented implementation details are specific to the R programming language, much of the code structure shown in this tutorial would be similar in other programming languages. Thus, readers may use this tutorial as a template for coding cSTMs more generally in different programming languages. 867 | 868 | The parameterization of our example model assumes all parameters are known, or at least, the characterization of their uncertainty is known (i.e., we know their distributions). However, to construct a real-world cSTM, modelers must conduct a thorough synthesis of current evidence to determine these appropriate structures and inform all parameters based on the current evidence. For example, literature must be carefully considered when determining whether transitions between non-death health states are estimated conditional on being alive or are estimated as competing risks along with mortality risks.[@Briggs2012] Similarly, our PSA analysis simplifies reality where all model parameters are assumed to be independent of each other. However, parameters could be correlated or have a rank order, and appropriate statistical methods that simulate these correlations or rank order might be needed.[@Goldhaber-Fiebert2015] We encourage modelers to use appropriate statistical methods to accurately synthesize and quantify model parameters' uncertainty. For example, for the PSA of our case study, we used distributions based on the type of parameters following standard recommendations. For a more detailed description of how to choose distributions, we refer the reader to other literature.[@Briggs2002;@Briggs2003] In addition, modelers should appropriately specify all model parameters for the cycle length of the model.[@Hunink2014] 869 | 870 | In general, cSTMs are recommended when the number of states is considered "not too large".[@Siebert2012c] This recommendation arises because it becomes more challenging to keep track of their construction as the number of states increases. It is possible to build reasonably complex cSTMs in R as long as the computer's RAM can store the size of the transition probability matrix and outputs of interest. For time-independent cSTMs, in general, this should not be a problem with the capacity of current RAM in personal computers. An alternative to reduce the explosion of disease states is iSTMs, a type of STM where simulated individuals transition between health states over time.[@Siebert2012c] We have previously published a tutorial on the implementation of iSTM in R.[@Krijkamp2018] 871 | 872 | With increasing model complexity and interdependency of functions to conduct various analyses like PSA, it is essential to ensure all code and functions work as expected and all elements of the cSTM are valid. We can achieve this by creating functions that help with model debugging, validation, and thorough unit testing. In the accompanying GitHub repository, we provide functions to check that transition probability matrices and their elements are valid. These functions are an example of a broader standard practice in software development called unit testing that requires building functions to test and check that the model and model-based analysis perform as intended.[@Wickham2021] However, unit testing is beyond the scope of this tutorial. We refer the reader to a previously published manuscript that describes unit testing in more detail and provides accompanying code.[@Alarid-Escudero2019e] 873 | 874 | In this tutorial, we implemented a cSTM using a (discrete-time) transition matrix. However, cSTM can also be implemented via (discrete-time) difference equations or (continuous-time) differential equations in R.[@Grimmett2014;@Axler2005] We refer readers interested in learning more on continuous-time cSTMs to previously published manuscripts[@Cao2016;@VanRosmalen2013;@Begun2013;@Soares2012] and a tutorial using R.[@Frederix2013a] Finally, the variable names used in this paper reflect our coding style. While we provide standardized variable names, adopting these conventions is ultimately a personal preference. 875 | 876 | In summary, this tutorial provides a conceptualization of time-independent cSTMs and a step-by-step guide to implement them in R. We aim to add to the current body of literature and material on building this type of decision model so that health decision scientists and health economists can develop cSTMs in a more flexible, efficient, open-source manner and to encourage increased transparency and reproducibility. In the advanced cSTM tutorial, we explore generalizing this framework to time-dependent cSTM, generating epidemiological outcomes, and incorporating transition rewards. 877 | 878 | # Acknowledgements 879 | Dr. Alarid-Escudero was supported by grants U01-CA199335 and U01-CA253913 from the National Cancer Institute (NCI) as part of the Cancer Intervention and Surveillance Modeling Network (CISNET), and a grant by the Gordon and Betty Moore Foundation. Miss Krijkamp was supported by the Society for Medical Decision Making (SMDM) fellowship through a grant by the Gordon and Betty Moore Foundation (GBMF7853). Dr. Enns was supported by a grant from the National Institute of Allergy and Infectious Diseases of the National Institutes of Health under award no. K25AI118476. Dr. Hunink received research funding from the American Diabetes Association, the Netherlands Organization for Health Research and Development, the German Innovation Fund, Netherlands Educational Grant ("Studie Voorschot Middelen"), and the Gordon and Betty Moore Foundation. Dr. Jalal was supported by a grant from the National Institute on Drug Abuse of the National Institute of Health under award no. K01DA048985. The content is solely the responsibility of the authors and does not necessarily represent the official views of the National Institutes of Health. The funding agencies had no role in the design of the study, interpretation of results, or writing of the manuscript. The funding agreement ensured the authors’ independence in designing the study, interpreting the data, writing, and publishing the report. We also want to thank the anonymous reviewers of *Medical Decision Making* for their valuable suggestions and the students who took our classes where we refined these materials. 880 | 881 | # References 882 | 883 | -------------------------------------------------------------------------------- /manuscript/cSTM_Tutorial_Intro.aux: -------------------------------------------------------------------------------- 1 | \relax 2 | \providecommand\hyper@newdestlabel[2]{} 3 | \providecommand\HyperFirstAtBeginDocument{\AtBeginDocument} 4 | \HyperFirstAtBeginDocument{\ifx\hyper@anchor\@undefined 5 | \global\let\oldcontentsline\contentsline 6 | \gdef\contentsline#1#2#3#4{\oldcontentsline{#1}{#2}{#3}} 7 | \global\let\oldnewlabel\newlabel 8 | \gdef\newlabel#1#2{\newlabelxx{#1}#2} 9 | \gdef\newlabelxx#1#2#3#4#5#6{\oldnewlabel{#1}{{#2}{#3}}} 10 | \AtEndDocument{\ifx\hyper@anchor\@undefined 11 | \let\contentsline\oldcontentsline 12 | \let\newlabel\oldnewlabel 13 | \fi} 14 | \fi} 15 | \global\let\hyper@last\relax 16 | \gdef\HyperFirstAtBeginDocument#1{#1} 17 | \providecommand\HyField@AuxAddToFields[1]{} 18 | \providecommand\HyField@AuxAddToCoFields[2]{} 19 | \providecommand\BKM@entry[2]{} 20 | \BKM@entry{id=1,dest={73656374696F6E2E31},srcline={170}}{5C3337365C3337375C303030495C3030306E5C303030745C303030725C3030306F5C303030645C303030755C303030635C303030745C303030695C3030306F5C3030306E} 21 | \@writefile{toc}{\contentsline {section}{\numberline {1}Introduction}{2}{section.1}\protected@file@percent } 22 | \newlabel{introduction}{{1}{2}{Introduction}{section.1}{}} 23 | \BKM@entry{id=2,dest={73656374696F6E2E32},srcline={183}}{5C3337365C3337375C303030435C3030306F5C303030685C3030306F5C303030725C303030745C3030305C3034305C303030735C303030745C303030615C303030745C303030655C3030302D5C303030745C303030725C303030615C3030306E5C303030735C303030695C303030745C303030695C3030306F5C3030306E5C3030305C3034305C3030306D5C3030306F5C303030645C303030655C3030306C5C303030735C3030305C3034305C3030305C3035305C303030635C303030535C303030545C3030304D5C303030735C3030305C303531} 24 | \BKM@entry{id=3,dest={73756273656374696F6E2E322E31},srcline={190}}{5C3337365C3337375C303030525C303030615C303030745C303030655C303030735C3030305C3034305C303030765C303030655C303030725C303030735C303030755C303030735C3030305C3034305C303030705C303030725C3030306F5C303030625C303030615C303030625C303030695C3030306C5C303030695C303030745C303030695C303030655C30303073} 25 | \@writefile{toc}{\contentsline {section}{\numberline {2}Cohort state-transition models (cSTMs)}{3}{section.2}\protected@file@percent } 26 | \newlabel{cohort-state-transition-models-cstms}{{2}{3}{Cohort state-transition models (cSTMs)}{section.2}{}} 27 | \@writefile{toc}{\contentsline {subsection}{\numberline {2.1}Rates versus probabilities}{3}{subsection.2.1}\protected@file@percent } 28 | \newlabel{rates-versus-probabilities}{{2.1}{3}{Rates versus probabilities}{subsection.2.1}{}} 29 | \BKM@entry{id=4,dest={73656374696F6E2E33},srcline={222}}{5C3337365C3337375C303030545C303030695C3030306D5C303030655C3030302D5C303030695C3030306E5C303030645C303030655C303030705C303030655C3030306E5C303030645C303030655C3030306E5C303030745C3030305C3034305C303030635C303030535C303030545C3030304D5C3030305C3034305C303030645C303030795C3030306E5C303030615C3030306D5C303030695C303030635C30303073} 30 | \newlabel{eq:rate-to-prob-ann}{{1}{4}{Rates versus probabilities}{equation.2.1}{}} 31 | \newlabel{eq:rate-ann-to-month}{{2}{4}{Rates versus probabilities}{equation.2.2}{}} 32 | \newlabel{eq:rate-to-prob-month}{{3}{4}{Rates versus probabilities}{equation.2.3}{}} 33 | \@writefile{toc}{\contentsline {section}{\numberline {3}Time-independent cSTM dynamics}{4}{section.3}\protected@file@percent } 34 | \newlabel{time-independent-cstm-dynamics}{{3}{4}{Time-independent cSTM dynamics}{section.3}{}} 35 | \gdef \LT@i {\LT@entry 36 | {1}{41.97197pt}\LT@entry 37 | {3}{220.74954pt}\LT@entry 38 | {3}{53.99988pt}\LT@entry 39 | {1}{6.0pt}} 40 | \BKM@entry{id=5,dest={73656374696F6E2E34},srcline={283}}{5C3337365C3337375C303030435C303030615C303030735C303030655C3030305C3034305C303030735C303030745C303030755C303030645C303030795C3030303A5C3030305C3034305C303030535C303030695C303030635C3030306B5C3030302D5C303030535C303030695C303030635C3030306B5C303030655C303030725C3030305C3034305C3030306D5C3030306F5C303030645C303030655C3030306C} 41 | \newlabel{tab:cSTM-components-table}{{1}{5}{Time-independent cSTM dynamics}{table.1}{}} 42 | \@writefile{lot}{\contentsline {table}{\numberline {1}{ Components of a cSTM with their R name.}}{5}{table.1}\protected@file@percent } 43 | \@writefile{toc}{\contentsline {section}{\numberline {4}Case study: Sick-Sicker model}{5}{section.4}\protected@file@percent } 44 | \newlabel{case-study-sick-sicker-model}{{4}{5}{Case study: Sick-Sicker model}{section.4}{}} 45 | \@writefile{lof}{\contentsline {figure}{\numberline {1}{\ignorespaces State-transition diagram of the time-independent Sick-Sicker cohort state-transition model, showing all possible states (labeled with state names) and transitions (labeled with transition probability variable names).}}{6}{figure.1}\protected@file@percent } 46 | \newlabel{fig:STD-Sick-Sicker}{{1}{6}{State-transition diagram of the time-independent Sick-Sicker cohort state-transition model, showing all possible states (labeled with state names) and transitions (labeled with transition probability variable names)}{figure.1}{}} 47 | \newlabel{tab:param-table}{{2}{7}{Case study: Sick-Sicker model}{table.2}{}} 48 | \@writefile{lot}{\contentsline {table}{\numberline {2}{ Description of parameters, their R variable name, base-case values and distribution.}}{7}{table.2}\protected@file@percent } 49 | \gdef \LT@ii {\LT@entry 50 | {1}{201.18843pt}\LT@entry 51 | {1}{81.40239pt}\LT@entry 52 | {1}{94.4145pt}\LT@entry 53 | {1}{92.74968pt}} 54 | \@writefile{lot}{\contentsline {table}{\numberline {3}{\ignorespaces The distribution of the cohort under strategies SoC and A for the first six cycles of the time-independent Sick-Sicker model. The first row, labeled with cycle 0, contains the distribution of the cohort at time zero.}}{12}{table.3}\protected@file@percent } 55 | \newlabel{tab:Trace}{{3}{12}{The distribution of the cohort under strategies SoC and A for the first six cycles of the time-independent Sick-Sicker model. The first row, labeled with cycle 0, contains the distribution of the cohort at time zero}{table.3}{}} 56 | \BKM@entry{id=6,dest={73656374696F6E2E35},srcline={577}}{5C3337365C3337375C303030435C3030306F5C303030735C303030745C3030305C3034305C303030615C3030306E5C303030645C3030305C3034305C303030655C303030665C303030665C303030655C303030635C303030745C303030695C303030765C303030655C3030306E5C303030655C303030735C303030735C3030305C3034305C3030306F5C303030755C303030745C303030635C3030306F5C3030306D5C303030655C30303073} 57 | \BKM@entry{id=7,dest={73756273656374696F6E2E352E31},srcline={582}}{5C3337365C3337375C303030535C303030745C303030615C303030745C303030655C3030305C3034305C303030725C303030655C303030775C303030615C303030725C303030645C30303073} 58 | \@writefile{lof}{\contentsline {figure}{\numberline {2}{\ignorespaces Cohort trace of the time-independent cSTM under strategies SoC and A.}}{13}{figure.2}\protected@file@percent } 59 | \newlabel{fig:Sick-Sicker-Trace-TimeHom}{{2}{13}{Cohort trace of the time-independent cSTM under strategies SoC and A}{figure.2}{}} 60 | \@writefile{toc}{\contentsline {section}{\numberline {5}Cost and effectiveness outcomes}{13}{section.5}\protected@file@percent } 61 | \newlabel{cost-and-effectiveness-outcomes}{{5}{13}{Cost and effectiveness outcomes}{section.5}{}} 62 | \@writefile{toc}{\contentsline {subsection}{\numberline {5.1}State rewards}{13}{subsection.5.1}\protected@file@percent } 63 | \newlabel{state-rewards}{{5.1}{13}{State rewards}{subsection.5.1}{}} 64 | \newlabel{eq:exp-rew-cycle}{{4}{13}{State rewards}{equation.5.4}{}} 65 | \BKM@entry{id=8,dest={73756273656374696F6E2E352E32},srcline={660}}{5C3337365C3337375C303030575C303030695C303030745C303030685C303030695C3030306E5C3030302D5C303030635C303030795C303030635C3030306C5C303030655C3030305C3034305C303030635C3030306F5C303030725C303030725C303030655C303030635C303030745C303030695C3030306F5C3030306E} 66 | \@writefile{toc}{\contentsline {subsection}{\numberline {5.2}Within-cycle correction}{15}{subsection.5.2}\protected@file@percent } 67 | \newlabel{within-cycle-correction}{{5.2}{15}{Within-cycle correction}{subsection.5.2}{}} 68 | \BKM@entry{id=9,dest={73756273656374696F6E2E352E33},srcline={685}}{5C3337365C3337375C303030445C303030695C303030735C303030635C3030306F5C303030755C3030306E5C303030745C303030695C3030306E5C303030675C3030305C3034305C303030665C303030755C303030745C303030755C303030725C303030655C3030305C3034305C303030725C303030655C303030775C303030615C303030725C303030645C30303073} 69 | \@writefile{toc}{\contentsline {subsection}{\numberline {5.3}Discounting future rewards}{16}{subsection.5.3}\protected@file@percent } 70 | \newlabel{discounting-future-rewards}{{5.3}{16}{Discounting future rewards}{subsection.5.3}{}} 71 | \newlabel{eq:tot-exp-disc-rewd}{{5}{16}{Discounting future rewards}{equation.5.5}{}} 72 | \newlabel{eq:tot-exp-disc-rewd-wcc}{{6}{16}{Discounting future rewards}{equation.5.6}{}} 73 | \BKM@entry{id=10,dest={73656374696F6E2E36},srcline={757}}{5C3337365C3337375C303030495C3030306E5C303030635C303030725C303030655C3030306D5C303030655C3030306E5C303030745C303030615C3030306C5C3030305C3034305C303030635C3030306F5C303030735C303030745C3030302D5C303030655C303030665C303030665C303030655C303030635C303030745C303030695C303030765C303030655C3030306E5C303030655C303030735C303030735C3030305C3034305C303030725C303030615C303030745C303030695C3030306F5C303030735C3030305C3034305C3030305C3035305C303030495C303030435C303030455C303030525C303030735C3030305C303531} 74 | \@writefile{lot}{\contentsline {table}{\numberline {4}{\ignorespaces Total expected discounted QALYs and costs per average individual in the cohort of the Sick-Sicker model by strategy accounting for within-cycle correction.}}{17}{table.4}\protected@file@percent } 75 | \newlabel{tab:Expected-outcomes-table}{{4}{17}{Total expected discounted QALYs and costs per average individual in the cohort of the Sick-Sicker model by strategy accounting for within-cycle correction}{table.4}{}} 76 | \@writefile{toc}{\contentsline {section}{\numberline {6}Incremental cost-effectiveness ratios (ICERs)}{17}{section.6}\protected@file@percent } 77 | \newlabel{incremental-cost-effectiveness-ratios-icers}{{6}{17}{Incremental cost-effectiveness ratios (ICERs)}{section.6}{}} 78 | \BKM@entry{id=11,dest={73656374696F6E2E37},srcline={803}}{5C3337365C3337375C303030505C303030725C3030306F5C303030625C303030615C303030625C303030695C3030306C5C303030695C303030735C303030745C303030695C303030635C3030305C3034305C303030735C303030655C3030306E5C303030735C303030695C303030745C303030695C303030765C303030695C303030745C303030795C3030305C3034305C303030615C3030306E5C303030615C3030306C5C303030795C303030735C303030695C30303073} 79 | \@writefile{lot}{\contentsline {table}{\numberline {5}{\ignorespaces Cost-effectiveness analysis results for the Sick-Sicker model. ND: Non-dominated strategy; D: Dominated strategy.}}{18}{table.5}\protected@file@percent } 80 | \newlabel{tab:table-cea}{{5}{18}{Cost-effectiveness analysis results for the Sick-Sicker model. ND: Non-dominated strategy; D: Dominated strategy}{table.5}{}} 81 | \@writefile{lof}{\contentsline {figure}{\numberline {3}{\ignorespaces Cost-effectiveness efficient frontier of the cost-effectiveness analysis based on the time-independent Sick-Sicker model}}{18}{figure.3}\protected@file@percent } 82 | \newlabel{fig:Sick-Sicker-CEA}{{3}{18}{Cost-effectiveness efficient frontier of the cost-effectiveness analysis based on the time-independent Sick-Sicker model}{figure.3}{}} 83 | \@writefile{toc}{\contentsline {section}{\numberline {7}Probabilistic sensitivity analysis}{19}{section.7}\protected@file@percent } 84 | \newlabel{probabilistic-sensitivity-analysis}{{7}{19}{Probabilistic sensitivity analysis}{section.7}{}} 85 | \@writefile{lof}{\contentsline {figure}{\numberline {4}{\ignorespaces Cost-effectiveness scatter plot.}}{20}{figure.4}\protected@file@percent } 86 | \newlabel{fig:CE-scatter}{{4}{20}{Cost-effectiveness scatter plot}{figure.4}{}} 87 | \@writefile{lof}{\contentsline {figure}{\numberline {5}{\ignorespaces Cost-effectiveness acceptability curves (CEACs) and frontier (CEAF).}}{21}{figure.5}\protected@file@percent } 88 | \newlabel{fig:CEAC}{{5}{21}{Cost-effectiveness acceptability curves (CEACs) and frontier (CEAF)}{figure.5}{}} 89 | \BKM@entry{id=12,dest={73656374696F6E2E38},srcline={852}}{5C3337365C3337375C303030445C303030695C303030735C303030635C303030755C303030735C303030735C303030695C3030306F5C3030306E} 90 | \@writefile{lof}{\contentsline {figure}{\numberline {6}{\ignorespaces Expected loss curves (ELCs) and expected value of perfect information (EVPI).}}{22}{figure.6}\protected@file@percent } 91 | \newlabel{fig:ELC}{{6}{22}{Expected loss curves (ELCs) and expected value of perfect information (EVPI)}{figure.6}{}} 92 | \@writefile{toc}{\contentsline {section}{\numberline {8}Discussion}{22}{section.8}\protected@file@percent } 93 | \newlabel{discussion}{{8}{22}{Discussion}{section.8}{}} 94 | \BKM@entry{id=13,dest={73656374696F6E2E39},srcline={867}}{5C3337365C3337375C303030415C303030635C3030306B5C3030306E5C3030306F5C303030775C3030306C5C303030655C303030645C303030675C303030655C3030306D5C303030655C3030306E5C303030745C30303073} 95 | \@writefile{toc}{\contentsline {section}{\numberline {9}Acknowledgements}{23}{section.9}\protected@file@percent } 96 | \newlabel{acknowledgements}{{9}{23}{Acknowledgements}{section.9}{}} 97 | \BKM@entry{id=14,dest={73656374696F6E2A2E32},srcline={873}}{5C3337365C3337375C303030525C303030655C303030665C303030655C303030725C303030655C3030306E5C303030635C303030655C30303073} 98 | \newlabel{references}{{9}{24}{References}{section*.2}{}} 99 | \@writefile{toc}{\contentsline {section}{References}{24}{section*.2}\protected@file@percent } 100 | \gdef \@abspage@last{27} 101 | -------------------------------------------------------------------------------- /manuscript/cSTM_Tutorial_Intro.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DARTH-git/cohort-modeling-tutorial-intro/d3c5497fb77879c79322e1925e9c8ccc38ef76c4/manuscript/cSTM_Tutorial_Intro.pdf -------------------------------------------------------------------------------- /manuscript/cSTM_Tutorial_Intro.toc: -------------------------------------------------------------------------------- 1 | \contentsline {section}{\numberline {1}Introduction}{2}{section.1}% 2 | \contentsline {section}{\numberline {2}Cohort state-transition models (cSTMs)}{3}{section.2}% 3 | \contentsline {subsection}{\numberline {2.1}Rates versus probabilities}{3}{subsection.2.1}% 4 | \contentsline {section}{\numberline {3}Time-independent cSTM dynamics}{4}{section.3}% 5 | \contentsline {section}{\numberline {4}Case study: Sick-Sicker model}{5}{section.4}% 6 | \contentsline {section}{\numberline {5}Cost and effectiveness outcomes}{13}{section.5}% 7 | \contentsline {subsection}{\numberline {5.1}State rewards}{13}{subsection.5.1}% 8 | \contentsline {subsection}{\numberline {5.2}Within-cycle correction}{15}{subsection.5.2}% 9 | \contentsline {subsection}{\numberline {5.3}Discounting future rewards}{16}{subsection.5.3}% 10 | \contentsline {section}{\numberline {6}Incremental cost-effectiveness ratios (ICERs)}{17}{section.6}% 11 | \contentsline {section}{\numberline {7}Probabilistic sensitivity analysis}{19}{section.7}% 12 | \contentsline {section}{\numberline {8}Discussion}{22}{section.8}% 13 | \contentsline {section}{\numberline {9}Acknowledgements}{23}{section.9}% 14 | \contentsline {section}{References}{24}{section*.2}% 15 | -------------------------------------------------------------------------------- /manuscript/cSTM_Tutorial_Intro.xwm: -------------------------------------------------------------------------------- 1 | \relax 2 | -------------------------------------------------------------------------------- /manuscript/figs/CE-scatter-1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DARTH-git/cohort-modeling-tutorial-intro/d3c5497fb77879c79322e1925e9c8ccc38ef76c4/manuscript/figs/CE-scatter-1.jpeg -------------------------------------------------------------------------------- /manuscript/figs/CE-scatter-1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DARTH-git/cohort-modeling-tutorial-intro/d3c5497fb77879c79322e1925e9c8ccc38ef76c4/manuscript/figs/CE-scatter-1.pdf -------------------------------------------------------------------------------- /manuscript/figs/CE-scatter-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DARTH-git/cohort-modeling-tutorial-intro/d3c5497fb77879c79322e1925e9c8ccc38ef76c4/manuscript/figs/CE-scatter-1.png -------------------------------------------------------------------------------- /manuscript/figs/CEAC-1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DARTH-git/cohort-modeling-tutorial-intro/d3c5497fb77879c79322e1925e9c8ccc38ef76c4/manuscript/figs/CEAC-1.jpeg -------------------------------------------------------------------------------- /manuscript/figs/CEAC-1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DARTH-git/cohort-modeling-tutorial-intro/d3c5497fb77879c79322e1925e9c8ccc38ef76c4/manuscript/figs/CEAC-1.pdf -------------------------------------------------------------------------------- /manuscript/figs/CEAC-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DARTH-git/cohort-modeling-tutorial-intro/d3c5497fb77879c79322e1925e9c8ccc38ef76c4/manuscript/figs/CEAC-1.png -------------------------------------------------------------------------------- /manuscript/figs/ELC-1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DARTH-git/cohort-modeling-tutorial-intro/d3c5497fb77879c79322e1925e9c8ccc38ef76c4/manuscript/figs/ELC-1.jpeg -------------------------------------------------------------------------------- /manuscript/figs/ELC-1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DARTH-git/cohort-modeling-tutorial-intro/d3c5497fb77879c79322e1925e9c8ccc38ef76c4/manuscript/figs/ELC-1.pdf -------------------------------------------------------------------------------- /manuscript/figs/ELC-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DARTH-git/cohort-modeling-tutorial-intro/d3c5497fb77879c79322e1925e9c8ccc38ef76c4/manuscript/figs/ELC-1.png -------------------------------------------------------------------------------- /manuscript/figs/Sick-Sicker-CEA-1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DARTH-git/cohort-modeling-tutorial-intro/d3c5497fb77879c79322e1925e9c8ccc38ef76c4/manuscript/figs/Sick-Sicker-CEA-1.jpeg -------------------------------------------------------------------------------- /manuscript/figs/Sick-Sicker-CEA-1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DARTH-git/cohort-modeling-tutorial-intro/d3c5497fb77879c79322e1925e9c8ccc38ef76c4/manuscript/figs/Sick-Sicker-CEA-1.pdf -------------------------------------------------------------------------------- /manuscript/figs/Sick-Sicker-CEA-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DARTH-git/cohort-modeling-tutorial-intro/d3c5497fb77879c79322e1925e9c8ccc38ef76c4/manuscript/figs/Sick-Sicker-CEA-1.png -------------------------------------------------------------------------------- /manuscript/figs/Sick-Sicker-Trace-TimeHom-1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DARTH-git/cohort-modeling-tutorial-intro/d3c5497fb77879c79322e1925e9c8ccc38ef76c4/manuscript/figs/Sick-Sicker-Trace-TimeHom-1.jpeg -------------------------------------------------------------------------------- /manuscript/figs/Sick-Sicker-Trace-TimeHom-1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DARTH-git/cohort-modeling-tutorial-intro/d3c5497fb77879c79322e1925e9c8ccc38ef76c4/manuscript/figs/Sick-Sicker-Trace-TimeHom-1.pdf -------------------------------------------------------------------------------- /manuscript/figs/Sick-Sicker-Trace-TimeHom-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DARTH-git/cohort-modeling-tutorial-intro/d3c5497fb77879c79322e1925e9c8ccc38ef76c4/manuscript/figs/Sick-Sicker-Trace-TimeHom-1.png -------------------------------------------------------------------------------- /manuscript/figs/Sick-Sicker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DARTH-git/cohort-modeling-tutorial-intro/d3c5497fb77879c79322e1925e9c8ccc38ef76c4/manuscript/figs/Sick-Sicker.png -------------------------------------------------------------------------------- /manuscript/markdown_to_R.R: -------------------------------------------------------------------------------- 1 | knitr::purl(input = "Manuscripts/Markov_Tutorial_Part1.Rmd", 2 | output = "Manuscripts/Markov_Tutorial_Part1.R") 3 | knitr::purl(input = "Manuscripts/Markov_Tutorial_Part2.Rmd", 4 | output = "Manuscripts/Markov_Tutorial_Part2.R") 5 | -------------------------------------------------------------------------------- /manuscript/matrixAlgebraSlides for markov tutorials.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DARTH-git/cohort-modeling-tutorial-intro/d3c5497fb77879c79322e1925e9c8ccc38ef76c4/manuscript/matrixAlgebraSlides for markov tutorials.pptx -------------------------------------------------------------------------------- /manuscript/sage-vancouver.csl: -------------------------------------------------------------------------------- 1 | 2 | 190 | -------------------------------------------------------------------------------- /manuscript/vancouver-superscript.csl: -------------------------------------------------------------------------------- 1 | 2 | 352 | --------------------------------------------------------------------------------