Skip to content

Instantly share code, notes, and snippets.

@n8thangreen
Last active October 28, 2021 14:42
Show Gist options
  • Save n8thangreen/fd317603292d7ab28261866bd8184a55 to your computer and use it in GitHub Desktop.
Save n8thangreen/fd317603292d7ab28261866bd8184a55 to your computer and use it in GitHub Desktop.
################
# Markov model #
################
## Model set-up ----
t_names <- c("without_drug", "with_drug")
n_treatments <- length(t_names)
s_names <- c("Asymptomatic_disease", "Progressive_disease", "Dead")
n_states <- length(s_names)
n_cycles <- 46
Initial_age <- 55
cAsymp <- 500
cDeath <- 1000
cDrug <- 1000
cProg <- 3000
uAsymp <- 0.95
uProg <- 0.75
oDr <- 0.06
cDr <- 0.06
tpDcm <- 0.15
state_c_matrix <-
matrix(c(cAsymp, cProg, 0,
cAsymp + cDrug, cProg, 0),
byrow = TRUE,
nrow = n_treatments,
dimnames = list(t_names,
s_names))
state_q_matrix <-
matrix(c(uAsymp, uProg, 0,
uAsymp, uProg, 0),
byrow = TRUE,
nrow = n_treatments,
dimnames = list(t_names,
s_names))
# same for both treatments
trans_c_matrix <-
matrix(c(0, 0, 0,
0, 0, cDeath,
0, 0, 0),
byrow = TRUE,
nrow = n_states,
dimnames = list(from = s_names,
to = s_names))
# Transition probabilities
p_matrix <- array(data = 0,
dim = c(n_states, n_states, n_treatments),
dimnames = list(from = s_names,
to = s_names,
t_names))
# Store population output for each cycle
pop <- array(data = NA,
dim = c(n_states, n_cycles, n_treatments),
dimnames = list(state = s_names,
cycle = NULL,
treatment = t_names))
pop["Asymptomatic_disease", cycle = 1, ] <- 1000
pop["Progressive_disease", cycle = 1, ] <- 0
pop["Dead", cycle = 1, ] <- 0
trans <- array(data = NA,
dim = c(n_states, n_cycles, n_treatments),
dimnames = list(state = s_names,
cycle = NULL,
treatment = t_names))
trans[, cycle = 1, ] <- 0
# Sum costs and QALYs for each cycle at a time for each drug
cycle_empty_array <-
array(NA,
dim = c(n_treatments, n_cycles),
dimnames = list(treatment = t_names,
cycle = NULL))
cycle_state_costs <- cycle_trans_costs <- cycle_empty_array
cycle_costs <- cycle_QALYs <- cycle_empty_array
LE <- LYs <- cycle_empty_array
cycle_QALE <- cycle_empty_array
total_costs <- setNames(c(NA, NA), t_names)
total_QALYs <- setNames(c(NA, NA), t_names)
# Time-dependent probability matrix ----
p_matrix_cycle <- function(p_matrix, age, cycle,
tpProg = 0.01,
tpDcm = 0.15,
effect = 0.5) {
tpDn_lookup <-
c("(34,44]" = 0.0017,
"(44,54]" = 0.0044,
"(54,64]" = 0.0138,
"(64,74]" = 0.0379,
"(74,84]" = 0.0912,
"(84,100]" = 0.1958)
age_grp <- cut(age, breaks = c(34,44,54,64,74,84,100))
tpDn <- tpDn_lookup[age_grp]
# Matrix containing transition probabilities for without_drug
p_matrix["Asymptomatic_disease", "Progressive_disease", "without_drug"] <- tpProg*cycle
p_matrix["Asymptomatic_disease", "Dead", "without_drug"] <- tpDn
p_matrix["Asymptomatic_disease", "Asymptomatic_disease", "without_drug"] <- 1 - tpProg*cycle - tpDn
p_matrix["Progressive_disease", "Dead", "without_drug"] <- tpDcm + tpDn
p_matrix["Progressive_disease", "Progressive_disease", "without_drug"] <- 1 - tpDcm - tpDn
p_matrix["Dead", "Dead", "without_drug"] <- 1
# Matrix containing transition probabilities for with_drug
p_matrix["Asymptomatic_disease", "Progressive_disease", "with_drug"] <- tpProg*(1 - effect)*cycle
p_matrix["Asymptomatic_disease", "Dead", "with_drug"] <- tpDn
p_matrix["Asymptomatic_disease", "Asymptomatic_disease", "with_drug"] <-
1 - tpProg*(1 - effect)*cycle - tpDn
p_matrix["Progressive_disease", "Dead", "with_drug"] <- tpDcm + tpDn
p_matrix["Progressive_disease", "Progressive_disease", "with_drug"] <- 1 - tpDcm - tpDn
p_matrix["Dead", "Dead", "with_drug"] <- 1
return(p_matrix)
}
## Run model ----
for (i in 1:n_treatments) {
age <- Initial_age
for (j in 2:n_cycles) {
p_matrix <- p_matrix_cycle(p_matrix, age, j - 1)
pop[, cycle = j, treatment = i] <-
pop[, cycle = j - 1, treatment = i] %*% p_matrix[, , treatment = i]
trans[, cycle = j, treatment = i] <-
pop[, cycle = j - 1, treatment = i] %*% (trans_c_matrix * p_matrix[, , treatment = i])
age <- age + 1
}
cycle_state_costs[i, ] <-
(state_c_matrix[treatment = i, ] %*% pop[, , treatment = i]) * 1/(1 + cDr)^(1:n_cycles - 1)
# discounting at previous cycle
cycle_trans_costs[i, ] <-
(c(1,1,1) %*% trans[, , treatment = i]) * 1/(1 + cDr)^(1:n_cycles - 2)
cycle_costs[i, ] <- cycle_state_costs[i, ] + cycle_trans_costs[i, ]
LE[i, ] <- c(1,1,0) %*% pop[, , treatment = i]
LYs[i, ] <- LE[i, ] * 1/(1 + oDr)^(1:n_cycles - 1)
cycle_QALE[i, ] <-
state_q_matrix[treatment = i, ] %*% pop[, , treatment = i]
cycle_QALYs[i, ] <- cycle_QALE[i, ] * 1/(1 + oDr)^(1:n_cycles - 1)
total_costs[i] <- sum(cycle_costs[treatment = i, -1])
total_QALYs[i] <- sum(cycle_QALYs[treatment = i, -1])
}
## Plot results ----
# Incremental costs and QALYs of with_drug vs to without_drug
c_incr <- total_costs["with_drug"] - total_costs["without_drug"]
q_incr <- total_QALYs["with_drug"] - total_QALYs["without_drug"]
# Incremental cost effectiveness ratio
ICER <- c_incr/q_incr
plot(x = q_incr, y = c_incr,
xlim = c(0, 1100),
ylim = c(0, 10e6),
pch = 16, cex = 1.5,
xlab = "QALY difference",
ylab = "Cost difference (£)",
frame.plot = FALSE)
abline(a = 0, b = 30000) # Willingness-to-pay threshold
#############################################
# Probability Sensitivity Analysis (PSA)
ce_markov <- function(start_pop,
p_matrix,
state_c_matrix,
trans_c_matrix,
state_q_matrix,
n_cycles = 46,
init_age = 55,
s_names = NULL,
t_names = NULL) {
n_states <- length(start_pop)
n_treat <- dim(p_matrix)[3]
pop <- array(data = NA,
dim = c(n_states, n_cycles, n_treat),
dimnames = list(state = s_names,
cycle = NULL,
treatment = t_names))
trans <- array(data = NA,
dim = c(n_states, n_cycles, n_treat),
dimnames = list(state = s_names,
cycle = NULL,
treatment = t_names))
for (i in 1:n_states) {
pop[i, cycle = 1, ] <- start_pop[i]
}
cycle_costs <- array(NA,
dim = c(n_treat, n_cycles),
dimnames = list(treatment = t_names,
cycle = NULL))
cycle_QALYs <- array(NA,
dim = c(n_treat, n_cycles),
dimnames = list(treatment = t_names,
cycle = NULL))
total_costs <- setNames(rep(NA, n_treat), t_names)
total_QALYs <- setNames(rep(NA, n_treat), t_names)
for (i in 1:n_treat) {
age <- init_age
for (j in 2:n_cycles) {
# difference from point estimate case
# pass in functions for random sample
p_matrix <- p_matrix_cycle(p_matrix, age, j - 1,
tpProg = tpProg(),
tpDcm = tpDcm(),
effect = effect())
# Matrix multiplication
pop[, cycle = j, treatment = i] <-
pop[, cycle = j - 1, treatment = i] %*% p_matrix[, , treatment = i]
trans[, cycle = j, treatment = i] <-
pop[, cycle = j - 1, treatment = i] %*% (trans_c_matrix * p_matrix[, , treatment = i])
age <- age + 1
}
cycle_state_costs[i, ] <-
(state_c_matrix[treatment = i, ] %*% pop[, , treatment = i]) * 1/(1 + cDr)^(1:n_cycles - 1)
cycle_trans_costs[i, ] <-
(c(1,1,1) %*% trans[, , treatment = i]) * 1/(1 + cDr)^(1:n_cycles - 2)
cycle_costs[i, ] <- cycle_state_costs[i, ] + cycle_trans_costs[i, ]
LE[i, ] <- c(1,1,0) %*% pop[, , treatment = i]
LYs[i, ] <- LE[i, ] * 1/(1 + oDr)^(1:n_cycles - 1)
cycle_QALE[i, ] <-
state_q_matrix[treatment = i, ] %*% pop[, , treatment = i]
cycle_QALYs[i, ] <- cycle_QALE[i, ] * 1/(1 + oDr)^(1:n_cycles - 1)
total_costs[i] <- sum(cycle_costs[treatment = i, -1])
total_QALYs[i] <- sum(cycle_QALYs[treatment = i, -1])
}
list(pop = pop,
cycle_costs = cycle_costs,
cycle_QALYs = cycle_QALYs,
total_costs = total_costs,
total_QALYs = total_QALYs)
}
# replace point values with functions to random sample
cAsymp <- function() rnorm(1, 500, 127.55)
cDeath <- function() rnorm(1, 1000, 255.11)
cDrug <- function() rnorm(1, 1000, 102.04)
cProg <- function() rnorm(1, 3000, 510.21)
effect <- function() rnorm(1, 0.5, 0.051)
tpDcm <- function() rbeta(1, 29, 167)
tpProg <- function() rbeta(1, 15, 1506)
uAsymp <- function() rbeta(1, 69, 4)
uProg <- function() rbeta(1, 24, 8)
# Define cost and QALYs as functions
state_c_matrix <- function() {
matrix(c(cAsymp(), cProg(), 0, # without drug
cAsymp() + cDrug(), cProg(), 0), # with drug
byrow = TRUE,
nrow = n_treatments,
dimnames = list(t_names,
s_names))
}
state_q_matrix <- function() {
matrix(c(uAsymp(), uProg(), 0, # without drug
uAsymp(), uProg(), 0), # with drug
byrow = TRUE,
nrow = n_treatments,
dimnames = list(t_names,
s_names))
}
trans_c_matrix <- function() {
matrix(c(0, 0, 0, # Asymptomatic_disease
0, 0, cDeath(), # Progressive_disease
0, 0, 0), # Dead
byrow = TRUE,
nrow = n_states,
dimnames = list(from = s_names,
to = s_names))
}
## Run PSA analysis ----
n_trials <- 500
n_pop <- 1000
costs <- matrix(, nrow = n_trials, ncol = n_treatments, dimnames = list(NULL, t_names))
qalys <- matrix(, nrow = n_trials, ncol = n_treatments, dimnames = list(NULL, t_names))
for (i in 1:n_trials) {
ce_res <- ce_markov(start_pop = c(n_pop, 0, 0),
p_matrix,
state_c_matrix(),
trans_c_matrix(),
state_q_matrix())
costs[i, ] <- ce_res$total_costs
qalys[i, ] <- ce_res$total_QALYs
}
## Plot results ----
# incremental costs and QALYs of with_drug vs to without_drug
c_incr_psa <- costs[, "with_drug"] - costs[, "without_drug"]
q_incr_psa <- qalys[, "with_drug"] - qalys[, "without_drug"]
plot(x = q_incr_psa/n_pop, y = c_incr_psa/n_pop,
xlim = c(0, 2),
ylim = c(0, 15e3),
pch = 16, cex = 1.2,
col = "grey",
xlab = "QALY difference",
ylab = "Cost difference (£)",
frame.plot = FALSE)
abline(a = 0, b = 30000, lwd = 2) # Willingness-to-pay threshold £30,000/QALY
points(x = q_incr/n_pop, y = c_incr/n_pop,
col = "red",
pch = 16, cex = 1.5)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment