Skip to content

Commit b1a0be2

Browse files
committed
Merge branch 'conditional-probs'
2 parents 1cb5157 + adc2c4b commit b1a0be2

28 files changed

+1374
-6176
lines changed

.DS_Store

4 KB
Binary file not shown.

Cohort-modeling-tutorial.Rproj

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
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
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

R/Functions STM_02.R

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

0 commit comments

Comments
 (0)