|
| 1 | +############################################################################### |
| 2 | +### Implementation of cohort state-transition models in R: ## |
| 3 | +### From conceptualization to implementation 2020 ## |
| 4 | +############################################################################### |
| 5 | +# This code forms the basis for the state-transition model of the article: |
| 6 | +# 'Implementation of cohort state-transition models in R' |
| 7 | +# Authors: |
| 8 | +# - Fernando Alarid-Escudero <[email protected]> |
| 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 made use of |
| 18 | +# R version 4.0.2 (2020-06-22) |
| 19 | +# Platform: 64-bit operating system, x64-based processor |
| 20 | +# Running under: Windows 10 |
| 21 | +# RStudio: Version 1.3.1073 2009-2020 RStudio, Inc |
| 22 | + |
| 23 | +############################################################################### |
| 24 | +################# Code of Appendix ########################################## |
| 25 | +############################################################################### |
| 26 | +# Implements a time-independent Sick-Sicker cSTM model # |
| 27 | +# Usual care: best available care for the patients with the disease. This scenario reflects the natural history of the disease progressions |
| 28 | +# New treatment 1: this new treatment is given to all sick patients, patients in sick and sicker, but does only improves the utility of those being sick. |
| 29 | +# New treatment 2: the new treatment reduces disease progression from sick to sicker. However, it is not possible to distinguish those sick from sicker and therefore all individuals in one of the two sick states get the treatment. |
| 30 | +# New treatment 1 & new treatment 2”: This strategy combines the new treatment 1 and new treatment 2. The disease progression is reduced and Sick individuals has an improved utility. |
| 31 | +# This is the simplified version of the base model (without transition rewards) |
| 32 | + |
| 33 | +##################################### Initial setup ########################### |
| 34 | +rm(list = ls()) # remove any variables in R's memory |
| 35 | + |
| 36 | +### Install packages |
| 37 | +# install.packages("dplyr") # to manipulate data |
| 38 | +# install.packages("reshape2") # to transform data |
| 39 | +# install.packages("ggplot2") # to visualize data |
| 40 | +# install.packages("scales") # for dollar signs and commas |
| 41 | +# install.packages("boot") # to handle log odds and log odds ratios |
| 42 | +# install.packages("devtools") # to ensure compatibility among packages |
| 43 | +# devtools::install_github("DARTH-git/dampack") # to install dampack form GitHub, for CEA and calculate ICERs |
| 44 | + |
| 45 | +### Load packages |
| 46 | +library(dplyr) |
| 47 | +library(reshape2) |
| 48 | +library(ggplot2) |
| 49 | +library(scales) |
| 50 | +library(boot) |
| 51 | +library(dampack) |
| 52 | + |
| 53 | +### Load functions |
| 54 | +source("functions/Functions.R") |
| 55 | + |
| 56 | +##################################### Model input ######################################### |
| 57 | +## General setup |
| 58 | +n_age_init <- 25 # age at baseline |
| 59 | +n_age_max <- 110 # maximum age of follow up |
| 60 | +n_t <- n_age_max - n_age_init # time horizon, number of cycles |
| 61 | + |
| 62 | +v_n <- c("H", "S1", "S2", "D") # the 4 health states of the model: |
| 63 | + # Healthy (H), Sick (S1), Sicker (S2), Dead (D) |
| 64 | +n_states <- length(v_n) # number of health states |
| 65 | +v_hcc <- rep(1, n_t + 1) # vector of half-cycle correction |
| 66 | +v_hcc[1] <- v_hcc[n_t + 1] <- 0.5 # half-cycle correction weight |
| 67 | +d_c <- 0.03 # discount rate for costs |
| 68 | +d_e <- 0.03 # discount rate for QALYs |
| 69 | +v_names_str <- c("Usual care", "New treatment 1", "New treatment 2", "New treatments 1 & 2") # store the strategy names |
| 70 | + |
| 71 | +## Transition probabilities (per cycle) and hazard ratios |
| 72 | +p_HD <- 0.002 # constant probability of dying when Healthy (all-cause mortality) |
| 73 | +p_HS1 <- 0.15 # probability to become Sick when Healthy |
| 74 | +p_S1H <- 0.5 # probability to become Healthy when Sick |
| 75 | +p_S1S2 <- 0.105 # probability to become Sicker when Sick |
| 76 | +hr_S1 <- 3 # hazard ratio of death in Sick vs Healthy |
| 77 | +hr_S2 <- 10 # hazard ratio of death in Sicker vs Healthy |
| 78 | +p_S1D <- 1 - exp(log(1 - p_HD) * hr_S1) # probability of dying in Sick |
| 79 | +p_S2D <- 1 - exp(log(1 - p_HD) * hr_S2) # probability of dying in Sicker |
| 80 | +# For New treatment 2 |
| 81 | +or_S1S2 <- 0.7 # odds ratio of becoming Sicker when Sick under New treatment 2 compared to Usual care |
| 82 | +lor_S1S2 <- log(or_S1S2) # log-odds ratio of becoming Sicker when Sick |
| 83 | +logit_S1S2 <- logit(p_S1S2) # log-odds of becoming Sicker when Sick |
| 84 | +p_S1S2_trt2 <- inv.logit(logit_S1S2 + lor_S1S2) # probability to become Sicker when Sick under New treatment 2 |
| 85 | + |
| 86 | +## State rewards |
| 87 | +# Costs |
| 88 | +c_H <- 2000 # cost of remaining one cycle Healthy |
| 89 | +c_S1 <- 4000 # cost of remaining one cycle Sick |
| 90 | +c_S2 <- 15000 # cost of remaining one cycle Sicker |
| 91 | +c_D <- 0 # cost of being dead (per cycle) |
| 92 | +c_trt1 <- 12000 # cost of New treatment 1 (per cycle) |
| 93 | +c_trt2 <- 13000 # cost of New treatment 2 (per cycle) |
| 94 | +# Utilities |
| 95 | +u_H <- 1 # utility when Healthy |
| 96 | +u_S1 <- 0.75 # utility when Sick |
| 97 | +u_S2 <- 0.5 # utility when Sicker |
| 98 | +u_D <- 0 # utility when Healthy |
| 99 | +u_trt1 <- 0.95 # utility when being treated |
| 100 | + |
| 101 | +# Discount weight (equal discounting is assumed for costs and effects) |
| 102 | +v_dwc <- 1 / ((1 + d_e) ^ (0:(n_t))) |
| 103 | +v_dwe <- 1 / ((1 + d_c) ^ (0:(n_t))) |
| 104 | + |
| 105 | +############################# Construct state-transition models ################ |
| 106 | +## Initial state vector |
| 107 | +# All starting healthy |
| 108 | +v_s_init <- c(H = 1, S1 = 0, S2 = 0, D = 0) # initial state vector |
| 109 | +v_s_init |
| 110 | + |
| 111 | +## Initialize cohort trace |
| 112 | +m_M <- matrix(0, |
| 113 | + nrow = (n_t + 1), ncol = n_states, |
| 114 | + dimnames = list(0:n_t, v_n)) |
| 115 | +# Store the initial state vector in the first row of the cohort trace |
| 116 | +m_M[1, ] <- v_s_init |
| 117 | +# For treatment 2 |
| 118 | +m_M_trt2 <- m_M |
| 119 | + |
| 120 | +## Initialize transition probability matrix |
| 121 | +m_P <- matrix(0, |
| 122 | + nrow = n_states, ncol = n_states, |
| 123 | + dimnames = list(v_n, v_n)) # define row and column names |
| 124 | +## Fill in matrix |
| 125 | +# From H |
| 126 | +m_P["H", "H"] <- 1 - (p_HS1 + p_HD) |
| 127 | +m_P["H", "S1"] <- p_HS1 |
| 128 | +m_P["H", "D"] <- p_HD |
| 129 | +# From S1 |
| 130 | +m_P["S1", "H"] <- p_S1H |
| 131 | +m_P["S1", "S1"] <- 1 - (p_S1H + p_S1S2 + p_S1D) |
| 132 | +m_P["S1", "S2"] <- p_S1S2 |
| 133 | +m_P["S1", "D"] <- p_S1D |
| 134 | +# From S2 |
| 135 | +m_P["S2", "S2"] <- 1 - p_S2D |
| 136 | +m_P["S2", "D"] <- p_S2D |
| 137 | +# From D |
| 138 | +m_P["D", "D"] <- 1 |
| 139 | + |
| 140 | +# For New treatment 2 |
| 141 | +# Only need to update the probabilities involving p_S1S2 |
| 142 | +m_P_trt2 <- m_P |
| 143 | +m_P_trt2["S1", "S1"] <- 1 - (p_S1H + p_S1S2_trt2 + p_S1D) |
| 144 | +m_P_trt2["S1", "S2"] <- p_S1S2_trt2 |
| 145 | + |
| 146 | +### Check if transition matrix is valid (i.e., each row should add up to 1) |
| 147 | +valid <- rowSums(m_P) # sum the rows |
| 148 | +if (!isTRUE(all.equal(as.numeric((valid)), as.numeric(rep(1, n_states))))) { #check if the rows are all equal to one |
| 149 | + stop("This is not a valid transition Matrix") |
| 150 | +} |
| 151 | +valid2 <- rowSums(m_P_trt2) # sum the rows |
| 152 | +if (!isTRUE(all.equal(as.numeric((valid2)), as.numeric(rep(1, n_states))))) { #check if the rows are all equal to one |
| 153 | + stop("This is not a valid transition Matrix") |
| 154 | +} |
| 155 | + |
| 156 | +## Initialize 3D transition dynamics array |
| 157 | +a_A <- array(0, |
| 158 | + dim = c(n_states, n_states, n_t + 1), |
| 159 | + dimnames = list(v_n, v_n, 0:n_t)) |
| 160 | +# Set first slice of A with the initial state vector in its diagonal |
| 161 | +diag(a_A[, , 1]) <- v_s_init |
| 162 | +# For New treatment 2, the array structure and initial state is identical to Usual care and New treatment 1 |
| 163 | +a_A_trt2 <- a_A |
| 164 | + |
| 165 | +#### Run Markov model #### |
| 166 | +# Iterative solution of time-independent cSTM |
| 167 | +for(t in 1:n_t){ |
| 168 | + # Fill in cohort trace |
| 169 | + m_M[t + 1, ] <- m_M[t, ] %*% m_P |
| 170 | + m_M_trt2[t + 1, ] <- m_M_trt2[t, ] %*% m_P_trt2 |
| 171 | + # Fill in transition dynamics array |
| 172 | + a_A[, , t + 1] <- m_M[t, ] * m_P |
| 173 | + a_A_trt2[, , t + 1] <- m_M_trt2[t, ] * m_P_trt2 |
| 174 | +} |
| 175 | + |
| 176 | +#### Plot Outputs #### |
| 177 | +## Plot the cohort trace for scenarios Usual care and New treatment 1 |
| 178 | +plot_trace(m_M) |
| 179 | +## Plot the cohort trace for scenarios New treatment 2 and New treatments 1 & 2 |
| 180 | +plot_trace(m_M_trt2) |
| 181 | + |
| 182 | +#### State Rewards #### |
| 183 | +## Vector of state utilities under Usual Care |
| 184 | +v_u_UC <- c(H = u_H, S1 = u_S1, S2 = u_S2, D = u_D) |
| 185 | +## Vector of state costs per cycle under Usual Care |
| 186 | +v_c_UC <- c(H = c_H, S1 = c_S1, S2 = c_S2, D = c_D) |
| 187 | + |
| 188 | +## Vector of state utilities under New treatment 1 |
| 189 | +v_u_trt1 <- c(H = u_H, S1 = u_trt1, S2 = u_S2, D = u_D) |
| 190 | +## Vector of state costs per cycle under New treatment 1 |
| 191 | +v_c_trt1 <- c(H = c_H, S1 = c_S1 + c_trt1, S2 = c_S2 + c_trt1, D = c_D) |
| 192 | + |
| 193 | +## Vector of state utilities under New treatment 2 |
| 194 | +v_u_trt2 <- c(H = u_H, S1 = u_S1, S2 = u_S2, D = u_D) |
| 195 | +## Vector of state costs per cycle under New treatment 2 |
| 196 | +v_c_trt2 <- c(H = c_H, S1 = c_S1 + c_trt2, S2 = c_S2 + c_trt2, D = c_D) |
| 197 | + |
| 198 | +## Vector of state utilities under New treatment 1 & 2 |
| 199 | +v_u_trt1_2 <- c(H = u_H, S1 = u_trt1, S2 = u_S2, D = u_D) |
| 200 | +## Vector of state costs per cycle under New treatment 1 & 2 |
| 201 | +v_c_trt1_2 <- c(H = c_H, S1 = c_S1 + (c_trt1 + c_trt2), S2 = c_S2 + (c_trt1 + c_trt2), D = c_D) |
| 202 | + |
| 203 | +#### Expected QALYs and Costs per cycle #### |
| 204 | +## Vector of qalys under Usual Care |
| 205 | +v_qaly_UC <- m_M %*% v_u_UC |
| 206 | +## Vector of costs under Usual Care |
| 207 | +v_cost_UC <- m_M %*% v_c_UC |
| 208 | + |
| 209 | +## Vector of qalys under New Treatment 1 |
| 210 | +v_qaly_trt1 <- m_M %*% v_u_trt1 |
| 211 | +## Vector of costs under New Treatment 1 |
| 212 | +v_cost_trt1 <- m_M %*% v_c_trt1 |
| 213 | + |
| 214 | +## Vector of qalys under New Treatment 2 |
| 215 | +v_qaly_trt2 <- m_M_trt2 %*% v_u_trt2 |
| 216 | +## Vector of costs under New Treatment 2 |
| 217 | +v_cost_trt2 <- m_M_trt2 %*% v_c_trt2 |
| 218 | + |
| 219 | +## Vector of qalys under New Treatment 1 & 2 |
| 220 | +v_qaly_trt1_2 <- m_M_trt2 %*% v_u_trt1_2 |
| 221 | +## Vector of costs under New Treatment 1 & 2 |
| 222 | +v_cost_trt1_2 <- m_M_trt2 %*% v_c_trt1_2 |
| 223 | + |
| 224 | +#### Discounted total expected QALYs and Costs per strategy and apply half-cycle correction if applicable #### |
| 225 | +### For Usual Care |
| 226 | +## QALYs |
| 227 | +n_totqaly_UC <- t(v_qaly_UC) %*% (v_dwe * v_hcc) |
| 228 | +## Costs |
| 229 | +n_totcost_UC <- t(v_cost_UC) %*% (v_dwc * v_hcc) |
| 230 | + |
| 231 | +### For New treatment 1 |
| 232 | +## QALYs |
| 233 | +n_totqaly_trt1 <- t(v_qaly_trt1) %*% (v_dwe * v_hcc) |
| 234 | +## Costs |
| 235 | +n_totcost_trt1 <- t(v_cost_trt1) %*% (v_dwc * v_hcc) |
| 236 | + |
| 237 | +### For New treatment 2 |
| 238 | +## QALYs |
| 239 | +n_totqaly_trt2 <- t(v_qaly_trt2) %*% (v_dwe * v_hcc) |
| 240 | +## Costs |
| 241 | +n_totcost_trt2 <- t(v_cost_trt2) %*% (v_dwc * v_hcc) |
| 242 | + |
| 243 | +### For New treatment 1 & 2 |
| 244 | +## QALYs |
| 245 | +n_totqaly_trt1_2 <- t(v_qaly_trt1_2) %*% (v_dwe * v_hcc) |
| 246 | +## Costs |
| 247 | +n_totcost_trt1_2 <- t(v_cost_trt1_2) %*% (v_dwc * v_hcc) |
| 248 | + |
| 249 | +########################### Cost-effectiveness analysis ####################### |
| 250 | +### Vector of total costs for all strategies |
| 251 | +v_ted_cost <- c(n_totcost_UC, n_totcost_trt1, n_totcost_trt2, n_totcost_trt1_2) |
| 252 | +### Vector of effectiveness for all strategies |
| 253 | +v_ted_qaly <- c(n_totqaly_UC, n_totqaly_trt1, n_totqaly_trt2, n_totqaly_trt1_2) |
| 254 | + |
| 255 | +### Calculate incremental cost-effectiveness ratios (ICERs) |
| 256 | +df_cea <- calculate_icers(cost = v_ted_cost, |
| 257 | + effect = v_ted_qaly, |
| 258 | + strategies = v_names_str) |
| 259 | +df_cea |
| 260 | +### Create CEA table with proper formatting |
| 261 | +table_cea <- format_table_cea(df_cea) |
| 262 | +table_cea |
| 263 | + |
| 264 | +### CEA frontier |
| 265 | +plot(df_cea, label = "all") + |
| 266 | + expand_limits(x = max(table_cea$QALYs + 0.5)) |
| 267 | + |
0 commit comments