Events
Add Initial
Events
Events are added below through the add_tte()
function.
We use this function once applying to both interventions. We must define
several arguments: one to indicate the intervention, one to define the
names of the events used, one to define the names of other objects
created that we would like to store (optional, maybe we generate an
intermediate input which is not an event but that we want to save) and
the actual input in which we generate the time to event. Events and
other objects will be automatically initialized to Inf
. We
draw the times to event for the patients. Note: the order of the
evts
argument that appears first will be used as a
reference of the order in which to process events in the case of ties
(so “sick” would be processed before “sicker” if there is a tie in time
to event.)
Note that the model will use the evnets defined in evts
argument to look for the objects both defined in the input list and in
this expression to allocate time to events. If an event is declared in
evts
but not defined elsewhere, then they would be assumed
TTE of Inf
by default.
This chunk is a bit more complex, so it’s worth spending a bit of
time explaining it.
The init_event_list
object is populated by using the
add_tte()
function which applies to both arms, “int”
strategy and “noint” strategy. We first declare the start
time to be 0
. Note this could also be separated by arm if
the user wants to have more clarity using two add_tte
functions (i.e.,
add_tte(arm="noint"...) %>% add_tte(arm="int"...)
).
We then proceed to generate the actual time to event. We use the
draw_tte()
function to generate the time to event, though
one can set this up in any other way (e.g., using rexp()
).
One should always be aware of how the competing risks interact with each
other. While we have abstracted from these type of corrections here, it
is recommended to have an understanding about how these affect the
results and have a look at the competing risks/semi-competing risks
literature.
init_event_list <-
add_tte(arm=c("noint","int"), evts = c("sick","sicker","death") ,input={
sick <- 0
sicker <- draw_tte(1,dist="exp", coef1=coef_noint, beta_tx = ifelse(arm=="int",HR_int,1), seed = random_seed_sicker_i[i]) #this way the value would be the same if it wasn't for the HR, effectively "cloning" patients luck
})
Add Reaction to Those
Events
Once the initial times of the events have been defined, we also need
to declare how events react and affect each other. To do so, we use the
evt_react_list
object and the add_reactevt()
function. This function just needs to state which event is affected, and
the actual reaction (usually setting flags to 1 or 0, or creating
new/adjusting events).
There are a series of objects that can be used in this context to
help with the reactions. Apart from the global objects and flags defined
above, we can also use curtime
for the current event time,
prevtime
for the time of the previous event,
cur_evtlist
for the C++ event queue, arm
for
the current treatment in the loop, evt
for the current
event being processed, i
expresses the patient iteration,
and simulation
the specific simulation (relevant when the
number of simulations is greater than 1). Furthermore, one can also call
any other input/item that has been created before or create new ones.
For example, we could even modify a cost/utility item by changing it
directly, e.g. through assigning it directly
cost.idfs.tx <- 500
.
curtime |
Current event time (numeric) |
prevtime |
Time of the previous event (numeric) |
cur_evtlist |
External pointer of C++ events that is yet to happen for that
patient |
evt |
Current event being processed (character) |
i |
Patient being iterated (numeric) |
arm |
Intervention being iterated (character) |
simulation |
Simulation being iterated (numeric) |
sens |
Sensitivity analysis being iterated (numeric) |
The functions to add/modify events and inputs use named vectors or
lists. Whenever several inputs/events are added or modified, it’s
recommended to group them within one function, as it reduces the
computation cost. So rather than use two modify_event()
with a list of one element, it’s better to group them into a single
modify_event()
with a list of two elements.
The list of relevant functions to be used within
add_reactevt()
are: new_event()
allows to
generate events and add them to the vector of events. It accepts more
than one event but a single event per event type.
modify_event()
allows to modify events (e.g. delay death).
When adding an event, the name of the events and the time of the events
must be defined. When using modify_event
, one must indicate
which events are affected and what are the new times of the events. If
the event specified does not exist or has already occurred, it will
return an error. modify_event
with
create_if_null = TRUE
argument will also generate events if
they don’t exist. remove_event()
will remove an event from
the event queue (could also be modified instead and set to
Inf
). get_event()
will return the TTE of the
specified event name. has_event()
will return a TRUE/FALSE
flag depending on whether the given patient has a specific event in the
queue (will return TRUE even if time is Inf
).
next_event()
will return a list with the next event in the
queue, with time, patient, and event name (patient_id
,
event_name
, and time
).
next_event_pt()
will return a list with the next event in
the queue for a specific patient, with time, patient, and event name
(patient_id
, event_name
, and
time
). queue_empty()
will return TRUE if the
queue of events is empty (no more events to process, but
Inf
events are considered part of the queue)
queue_size()
allows to check the size of the queue of
events, including Inf
events.
Note that one could potentially omit part of the modeling set in
init_event_list
and actually define new events dynamically
through the reactions (we do that below for the "ae"
event). However, this can have an impact in computation time, so if
possible it’s always better to use init_event_list
.
To modify/create items, WARDEN now allows to assign them directly in
the code, which allows the code to run faster.
The model will run until curtime
is set to
Inf
, so the event that terminates the model (in this case,
os
), should modify curtime
and set it to
Inf
.
Finally, note that there could be two different ways of accumulating
continuous outcomes, backwards (i.e., in the example below, we would set
q_default = util.sick
at the sicker event, and modify the
q_default
value in the death event) and forwards (as in the
example below). This option can be modified in the
run_sim()
function using the accum_backwards
argument, which assumes forwards by default.
evt_react_list <-
add_reactevt(name_evt = "sick",
input = {}) %>%
add_reactevt(name_evt = "sicker",
input = {
q_default <- util.sicker
c_default <- cost.sicker + if(arm=="int"){cost.int}else{0}
fl.sick <- 0
}) %>%
add_reactevt(name_evt = "death",
input = {
q_default <- 0
c_default <- 0
curtime <- Inf
})
# Below how it would be set up if using `accum_backwards = TRUE` in `run_sim()` (and will give equal final results)
# Note that we set the value applied in the reaction right up to the event, changing the interpretation of the reaction
# It is also a slower method than the standard approach
#
# evt_react_list <-
# add_reactevt(name_evt = "sick",
# input = {}) %>%
# add_reactevt(name_evt = "sicker",
# input = {
# q_default = util.sick
# c_default = cost.sick + if(arm=="int"){cost.int}else{0}
# fl.sick = 0
# }) %>%
# add_reactevt(name_evt = "death",
# input = {
# c_dis <- if(fl.sick==1){cost.sick}else{cost.sicker}
# q_default = if(fl.sick==1){util.sick}else{util.sicker}
# c_default = c_dis + if(arm=="int"){cost.int}else{0}
# curtime = Inf
# })
Model
Model Execution
The model can be run using the function run_sim()
below.
We must define the number of patients to be simulated, the number of
simulations, whether we want to run a PSA or not, the strategy list, the
inputs, events and reactions defined above, utilities, costs and also if
we want any extra output and the level of ipd data desired to be
exported.
It is worth noting that the psa_bool
argument does not
run a PSA automatically, but is rather an additional input/flag of the
model that we use as a reference to determine whether we want to use a
deterministic or stochastic input. As such, it could also be defined in
common_all_inputs
as the first item to be defined, and the
result would be the same. However, we recommend it to be defined in
run_sim()
.
Note that the distribution chosen, the number of events and the
interaction between events can have a substantial impact on the running
time of the model.
Debugging can be implemented using the argument debug
in
the run_sim()
function.
#Logic is: per patient, per intervention, per event, react to that event.
results <- run_sim(
npats=1000, # number of patients to be simulated
n_sim=1, # number of simulations to run
psa_bool = FALSE, # use PSA or not. If n_sim > 1 and psa_bool = FALSE, then difference in outcomes is due to sampling (number of pats simulated)
arm_list = c("int", "noint"), # intervention list
common_all_inputs = common_all_inputs, # inputs common that do not change within a simulation
common_pt_inputs = common_pt_inputs, # inputs that change within a simulation but are not affected by the intervention
unique_pt_inputs = unique_pt_inputs, # inputs that change within a simulation between interventions
init_event_list = init_event_list, # initial event list
evt_react_list = evt_react_list, # reaction of events
util_ongoing_list = util_ongoing,
cost_ongoing_list = cost_ongoing,
ipd = 1
)
#> Analysis number: 1
#> Simulation number: 1
#> Time to run simulation 1: 0.62s
#> Time to run analysis 1: 0.62s
#> Total time to run: 0.64s
#> Simulation finalized;
Post-processing of
Model Outputs
Summary of
Results
Once the model has been run, we can use the results and summarize
them using the summary_results_det()
to print the results
of the last simulation (if nsim = 1
, it’s the deterministic
case), and summary_results_sim()
to show the PSA results
(with the confidence intervals). We can also use the individual patient
data generated by the simulation, which we collect here to plot in the
psa_ipd
object.
summary_results_det(results[[1]][[1]]) #print first simulation
#> int noint
#> costs 58978.88 51768.23
#> dcosts 0.00 7210.66
#> lys 9.72 9.72
#> dlys 0.00 0.00
#> qalys 6.27 6.08
#> dqalys 0.00 0.19
#> ICER NA Inf
#> ICUR NA 38286.46
#> INMB NA 2206.06
#> costs_undisc 74324.03 65474.81
#> dcosts_undisc 0.00 8849.22
#> lys_undisc 11.99 11.99
#> dlys_undisc 0.00 0.00
#> qalys_undisc 7.62 7.38
#> dqalys_undisc 0.00 0.24
#> ICER_undisc NA Inf
#> ICUR_undisc NA 37557.56
#> INMB_undisc NA 2931.65
#> c_default 58978.88 51768.23
#> dc_default 0.00 7210.66
#> c_default_undisc 74324.03 65474.81
#> dc_default_undisc 0.00 8849.22
#> q_default 6.27 6.08
#> dq_default 0.00 0.19
#> q_default_undisc 7.62 7.38
#> dq_default_undisc 0.00 0.24
summary_results_sim(results[[1]])
#> int noint
#> costs 58,979 (58,979; 58,979) 51,768 (51,768; 51,768)
#> dcosts 0 (0; 0) 7,211 (7,211; 7,211)
#> lys 9.72 (9.72; 9.72) 9.72 (9.72; 9.72)
#> dlys 0 (0; 0) 0 (0; 0)
#> qalys 6.27 (6.27; 6.27) 6.08 (6.08; 6.08)
#> dqalys 0 (0; 0) 0.188 (0.188; 0.188)
#> ICER NaN (NA; NA) Inf (Inf; Inf)
#> ICUR NaN (NA; NA) 38,286 (38,286; 38,286)
#> INMB NaN (NA; NA) 2,206 (2,206; 2,206)
#> costs_undisc 74,324 (74,324; 74,324) 65,475 (65,475; 65,475)
#> dcosts_undisc 0 (0; 0) 8,849 (8,849; 8,849)
#> lys_undisc 12 (12; 12) 12 (12; 12)
#> dlys_undisc 0 (0; 0) 0 (0; 0)
#> qalys_undisc 7.62 (7.62; 7.62) 7.38 (7.38; 7.38)
#> dqalys_undisc 0 (0; 0) 0.236 (0.236; 0.236)
#> ICER_undisc NaN (NA; NA) Inf (Inf; Inf)
#> ICUR_undisc NaN (NA; NA) 37,558 (37,558; 37,558)
#> INMB_undisc NaN (NA; NA) 2,932 (2,932; 2,932)
#> c_default 58,979 (58,979; 58,979) 51,768 (51,768; 51,768)
#> dc_default 0 (0; 0) 7,211 (7,211; 7,211)
#> c_default_undisc 74,324 (74,324; 74,324) 65,475 (65,475; 65,475)
#> dc_default_undisc 0 (0; 0) 8,849 (8,849; 8,849)
#> q_default 6.27 (6.27; 6.27) 6.08 (6.08; 6.08)
#> dq_default 0 (0; 0) 0.188 (0.188; 0.188)
#> q_default_undisc 7.62 (7.62; 7.62) 7.38 (7.38; 7.38)
#> dq_default_undisc 0 (0; 0) 0.236 (0.236; 0.236)
summary_results_sens(results)
#> arm analysis analysis_name variable value
#> <char> <int> <char> <fctr> <char>
#> 1: int 1 costs 58,979 (58,979; 58,979)
#> 2: noint 1 costs 51,768 (51,768; 51,768)
#> 3: int 1 dcosts 0 (0; 0)
#> 4: noint 1 dcosts 7,211 (7,211; 7,211)
#> 5: int 1 lys 9.72 (9.72; 9.72)
#> 6: noint 1 lys 9.72 (9.72; 9.72)
#> 7: int 1 dlys 0 (0; 0)
#> 8: noint 1 dlys 0 (0; 0)
#> 9: int 1 qalys 6.27 (6.27; 6.27)
#> 10: noint 1 qalys 6.08 (6.08; 6.08)
#> 11: int 1 dqalys 0 (0; 0)
#> 12: noint 1 dqalys 0.188 (0.188; 0.188)
#> 13: int 1 ICER NaN (NA; NA)
#> 14: noint 1 ICER Inf (Inf; Inf)
#> 15: int 1 ICUR NaN (NA; NA)
#> 16: noint 1 ICUR 38,286 (38,286; 38,286)
#> 17: int 1 INMB NaN (NA; NA)
#> 18: noint 1 INMB 2,206 (2,206; 2,206)
#> 19: int 1 costs_undisc 74,324 (74,324; 74,324)
#> 20: noint 1 costs_undisc 65,475 (65,475; 65,475)
#> 21: int 1 dcosts_undisc 0 (0; 0)
#> 22: noint 1 dcosts_undisc 8,849 (8,849; 8,849)
#> 23: int 1 lys_undisc 12 (12; 12)
#> 24: noint 1 lys_undisc 12 (12; 12)
#> 25: int 1 dlys_undisc 0 (0; 0)
#> 26: noint 1 dlys_undisc 0 (0; 0)
#> 27: int 1 qalys_undisc 7.62 (7.62; 7.62)
#> 28: noint 1 qalys_undisc 7.38 (7.38; 7.38)
#> 29: int 1 dqalys_undisc 0 (0; 0)
#> 30: noint 1 dqalys_undisc 0.236 (0.236; 0.236)
#> 31: int 1 ICER_undisc NaN (NA; NA)
#> 32: noint 1 ICER_undisc Inf (Inf; Inf)
#> 33: int 1 ICUR_undisc NaN (NA; NA)
#> 34: noint 1 ICUR_undisc 37,558 (37,558; 37,558)
#> 35: int 1 INMB_undisc NaN (NA; NA)
#> 36: noint 1 INMB_undisc 2,932 (2,932; 2,932)
#> 37: int 1 c_default 58,979 (58,979; 58,979)
#> 38: noint 1 c_default 51,768 (51,768; 51,768)
#> 39: int 1 dc_default 0 (0; 0)
#> 40: noint 1 dc_default 7,211 (7,211; 7,211)
#> 41: int 1 c_default_undisc 74,324 (74,324; 74,324)
#> 42: noint 1 c_default_undisc 65,475 (65,475; 65,475)
#> 43: int 1 dc_default_undisc 0 (0; 0)
#> 44: noint 1 dc_default_undisc 8,849 (8,849; 8,849)
#> 45: int 1 q_default 6.27 (6.27; 6.27)
#> 46: noint 1 q_default 6.08 (6.08; 6.08)
#> 47: int 1 dq_default 0 (0; 0)
#> 48: noint 1 dq_default 0.188 (0.188; 0.188)
#> 49: int 1 q_default_undisc 7.62 (7.62; 7.62)
#> 50: noint 1 q_default_undisc 7.38 (7.38; 7.38)
#> 51: int 1 dq_default_undisc 0 (0; 0)
#> 52: noint 1 dq_default_undisc 0.236 (0.236; 0.236)
#> arm analysis analysis_name variable value
psa_ipd <- bind_rows(map(results[[1]], "merged_df"))
psa_ipd[1:10,] %>%
kable() %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive"))
evtname
|
evttime
|
prevtime
|
pat_id
|
arm
|
total_lys
|
total_qalys
|
total_costs
|
total_costs_undisc
|
total_qalys_undisc
|
total_lys_undisc
|
lys
|
qalys
|
costs
|
lys_undisc
|
qalys_undisc
|
costs_undisc
|
c_default
|
q_default
|
c_default_undisc
|
q_default_undisc
|
nexttime
|
simulation
|
sensitivity
|
sick
|
0.000
|
0.000
|
1
|
int
|
10.34
|
8.27
|
41358
|
51112
|
10.22
|
12.78
|
10.339
|
8.272
|
41358
|
12.778
|
10.222
|
51112
|
41358
|
8.272
|
51112
|
10.222
|
12.778
|
1
|
1
|
death
|
12.778
|
0.000
|
1
|
int
|
10.34
|
8.27
|
41358
|
51112
|
10.22
|
12.78
|
0.000
|
0.000
|
0
|
0.000
|
0.000
|
0
|
0
|
0.000
|
0
|
0.000
|
12.778
|
1
|
1
|
sick
|
0.000
|
0.000
|
2
|
int
|
7.75
|
4.14
|
58485
|
68551
|
4.78
|
9.02
|
0.887
|
0.710
|
3549
|
0.901
|
0.721
|
3604
|
3549
|
0.710
|
3604
|
0.721
|
0.901
|
1
|
1
|
sicker
|
0.901
|
0.000
|
2
|
int
|
7.75
|
4.14
|
58485
|
68551
|
4.78
|
9.02
|
6.867
|
3.434
|
54937
|
8.118
|
4.059
|
64947
|
54937
|
3.434
|
64947
|
4.059
|
9.019
|
1
|
1
|
death
|
9.019
|
0.901
|
2
|
int
|
7.75
|
4.14
|
58485
|
68551
|
4.78
|
9.02
|
0.000
|
0.000
|
0
|
0.000
|
0.000
|
0
|
0
|
0.000
|
0
|
0.000
|
9.019
|
1
|
1
|
sick
|
0.000
|
0.000
|
3
|
int
|
10.94
|
5.57
|
86287
|
108582
|
6.96
|
13.73
|
0.317
|
0.253
|
1266
|
0.318
|
0.255
|
1273
|
1266
|
0.253
|
1273
|
0.255
|
0.318
|
1
|
1
|
sicker
|
0.318
|
0.000
|
3
|
int
|
10.94
|
5.57
|
86287
|
108582
|
6.96
|
13.73
|
10.628
|
5.314
|
85020
|
13.414
|
6.707
|
107309
|
85020
|
5.314
|
107309
|
6.707
|
13.732
|
1
|
1
|
death
|
13.732
|
0.318
|
3
|
int
|
10.94
|
5.57
|
86287
|
108582
|
6.96
|
13.73
|
0.000
|
0.000
|
0
|
0.000
|
0.000
|
0
|
0
|
0.000
|
0
|
0.000
|
13.732
|
1
|
1
|
sick
|
0.000
|
0.000
|
4
|
int
|
8.61
|
5.24
|
56449
|
68546
|
6.10
|
10.22
|
3.116
|
2.493
|
12463
|
3.296
|
2.637
|
13183
|
12463
|
2.493
|
13183
|
2.637
|
3.296
|
1
|
1
|
sicker
|
3.296
|
0.000
|
4
|
int
|
8.61
|
5.24
|
56449
|
68546
|
6.10
|
10.22
|
5.498
|
2.749
|
43986
|
6.920
|
3.460
|
55363
|
43986
|
2.749
|
55363
|
3.460
|
10.216
|
1
|
1
|
We can also check what has been the absolute number of events per
strategy.
arm
|
evtname
|
n
|
int
|
death
|
1000
|
int
|
sick
|
1000
|
int
|
sicker
|
827
|
noint
|
death
|
1000
|
noint
|
sick
|
1000
|
noint
|
sicker
|
880
|
Plots
We now use the data output to plot the histograms/densities of the
simulation.
data_plot <- results[[1]][[1]]$merged_df %>%
filter(evtname != "sick") %>%
group_by(arm,evtname,simulation) %>%
mutate(median = median(evttime)) %>%
ungroup()
ggplot(data_plot) +
geom_density(aes(fill = arm, x = evttime),
alpha = 0.7) +
geom_vline(aes(xintercept=median,col=arm)) +
facet_wrap( ~ evtname, scales = "free") +
scale_y_continuous(expand = c(0, 0)) +
scale_x_continuous(expand = c(0, 0)) +
theme_bw()

We can also plot the patient level incremental QALY/costs.
data_qaly_cost<- psa_ipd[,.SD[1],by=.(pat_id,arm,simulation)][,.(arm,qaly=total_qalys,cost=total_costs,pat_id,simulation)]
data_qaly_cost[,ps_id:=paste(pat_id,simulation,sep="_")]
mean_data_qaly_cost <- data_qaly_cost %>% group_by(arm) %>% summarise(across(where(is.numeric),mean))
ggplot(data_qaly_cost,aes(x=qaly, y = cost, col = arm)) +
geom_point(alpha=0.15,shape = 21) +
geom_point(data=mean_data_qaly_cost, aes(x=qaly, y = cost, fill = arm), shape = 21,col="black",size=3) +
scale_y_continuous(expand = c(0, 0)) +
scale_x_continuous(expand = c(0, 0)) +
theme_bw()+
theme(axis.text.x = element_text(angle = 90, vjust = .5))

Sensitivity
Analysis
Inputs
In this case, inputs must be created first to change across
sensitivity analysis. To do so, the item list
sensitivity_inputs
can be used. In this case, we also use
pick_val_v()
which allows the model to automatically pick
the relevant value (no PSA, PSA or sensitivity analysis) based on the
corresponding boolean flags of psa_bool and sensitivity_bool. In this
case we also use the sens
iterator for each sensitivity
analysis and the n_sensitivity
which is an argument in
run_sim()
.
Note that we have then just changed how the inputs are handled in
common_all_inputs, but the same could be done with unique_pt_inputs, but
in those cases, as the inputs change per patient, the
pick_val_v()
function should be applied within
unique_pt_inputs to make sure they are evaluated when it correspond.
Note that for the psa we are directly calling the distributions and
passing the parameters.Note also that the sens_name_used
is
automatically computed by the engine and is accessible to the user (it’s
the name of the sensitivity analysis, e.g., “scenario 1”).
The indicator parameter in pick_val_v()
is used to
determine which parameters are left “as is” and which ones are to be
substituted with the sensitivity value. There are two ways to do this,
either by setting it in a binary way (1 or 0), or by using the indicator
as the number of the parameter values to be varied (useful when several
parameters are varied at the same time, or only specific values of a
vector are varied). This can be set by using
indicator_sens_binary
argument.
Note that pick_val_v()
can be directly loaded as
parameters (in fact, a named list will be loaded directly by R). A small
tweak is needed if it’s the first item added, in which the item list
must be initiated by using add_item()
(see below). Note
that one can use a list of lists in the case where the base_value or any
of the other parameters are vectors instead of elements of length 1. In
this case, we showcase a list but it could also use a data.frame.
pick_psa
can be used to select the correct PSA
distributions.
#Load some data
list_par <- list(parameter_name = list("util.sick","util.sicker","cost.sick","cost.sicker","cost.int","coef_noint","HR_int"),
base_value = list(0.8,0.5,3000,7000,1000,log(0.2),0.8),
DSA_min = list(0.6,0.3,1000,5000,800,log(0.1),0.5),
DSA_max = list(0.9,0.7,5000,9000,2000,log(0.4),0.9),
PSA_dist = list("rnorm","rbeta_mse","rgamma_mse","rgamma_mse","rgamma_mse","rnorm","rlnorm"),
a=list(0.8,0.5,3000,7000,1000,log(0.2),log(0.8)),
b=lapply(list(0.8,0.5,3000,7000,1000,log(0.2),log(0.8)), function(x) abs(x/5)),
scenario_1=list(0.6,0.3,1000,5000,800,log(0.1),0.5),
scenario_2=list(0.9,0.7,5000,9000,2000,log(0.4),0.9)
)
sensitivity_inputs <-add_item(
indicators = if(sensitivity_bool){ create_indicators(sens,n_sensitivity*length(sensitivity_names),rep(1,length(list_par[[1]])))}else{
rep(1,length(list_par[[1]]))} #vector of indicators, value 0 everywhere except at sens, where it takes value 1 (for dsa_min and dsa_max, if not sensitivity analysis, then we activate all of them, i.e., in a PSA)
)
common_all_inputs <- add_item(
pick_val_v(base = list_par[["base_value"]],
psa = pick_psa(list_par[["PSA_dist"]],rep(1,length(list_par[["PSA_dist"]])),list_par[["a"]],list_par[["b"]]),
sens = list_par[[sens_name_used]],
psa_ind = psa_bool,
sens_ind = sensitivity_bool,
indicator = indicators,
names_out = list_par[["parameter_name"]]
)
) %>%
add_item(random_seed_sicker_i = sample(1:1000,1000,replace = FALSE)) #we don't add this variable ot the sensitivity analysis
Model Execution
The model is executed as before, just adding the
sensitivity_inputs
, sensitivity_names
,
sensitivity_bool
and n_sensitivity
arguments.
Note that the total number of sensitivity iterations is given not by
n_sensitivity, but by n_sensitivity * length(sensitivity_names), so in
this case it will be 2 x n_sensitivity, or 2 x 7 = 14. For two scenario
analysis it would be 2 x 1 = 2, with the indicators
variable defined in the previous section taking value 1 for all the
variables altered in the scenario, and 0 otherwise.
results <- run_sim(
npats=100, # number of patients to be simulated
n_sim=1, # number of simulations to run
psa_bool = FALSE, # use PSA or not. If n_sim > 1 and psa_bool = FALSE, then difference in outcomes is due to sampling (number of pats simulated)
arm_list = c("int", "noint"), # intervention list
common_all_inputs = common_all_inputs, # inputs common that do not change within a simulation
common_pt_inputs = common_pt_inputs, # inputs that change within a simulation but are not affected by the intervention
unique_pt_inputs = unique_pt_inputs, # inputs that change within a simulation between interventions
init_event_list = init_event_list, # initial event list
evt_react_list = evt_react_list, # reaction of events
util_ongoing_list = util_ongoing,
cost_ongoing_list = cost_ongoing,
sensitivity_inputs = sensitivity_inputs,
sensitivity_names = c("DSA_min","DSA_max"),
sensitivity_bool = TRUE,
n_sensitivity = length(list_par[[1]]),
input_out = unlist(list_par[["parameter_name"]])
)
#> Analysis number: 1
#> Simulation number: 1
#> Time to run simulation 1: 0.12s
#> Time to run analysis 1: 0.12s
#> Analysis number: 2
#> Simulation number: 1
#> Time to run simulation 1: 0.13s
#> Time to run analysis 2: 0.13s
#> Analysis number: 3
#> Simulation number: 1
#> Time to run simulation 1: 0.12s
#> Time to run analysis 3: 0.12s
#> Analysis number: 4
#> Simulation number: 1
#> Time to run simulation 1: 0.13s
#> Time to run analysis 4: 0.13s
#> Analysis number: 5
#> Simulation number: 1
#> Time to run simulation 1: 0.12s
#> Time to run analysis 5: 0.12s
#> Analysis number: 6
#> Simulation number: 1
#> Time to run simulation 1: 0.11s
#> Time to run analysis 6: 0.11s
#> Analysis number: 7
#> Simulation number: 1
#> Time to run simulation 1: 0.13s
#> Time to run analysis 7: 0.13s
#> Analysis number: 8
#> Simulation number: 1
#> Time to run simulation 1: 0.12s
#> Time to run analysis 8: 0.12s
#> Analysis number: 9
#> Simulation number: 1
#> Time to run simulation 1: 0.13s
#> Time to run analysis 9: 0.13s
#> Analysis number: 10
#> Simulation number: 1
#> Time to run simulation 1: 0.12s
#> Time to run analysis 10: 0.12s
#> Analysis number: 11
#> Simulation number: 1
#> Time to run simulation 1: 0.09s
#> Time to run analysis 11: 0.09s
#> Analysis number: 12
#> Simulation number: 1
#> Time to run simulation 1: 0.1s
#> Time to run analysis 12: 0.12s
#> Analysis number: 13
#> Simulation number: 1
#> Time to run simulation 1: 0.13s
#> Time to run analysis 13: 0.13s
#> Analysis number: 14
#> Simulation number: 1
#> Time to run simulation 1: 0.12s
#> Time to run analysis 14: 0.12s
#> Total time to run: 1.71s
#> Simulation finalized;
Check results
We briefly check below that indeed the engine has been changing the
corresponding parameter value.
data_sensitivity <- bind_rows(map_depth(results,2, "merged_df"))
#Check mean value across iterations as PSA is off
data_sensitivity %>% group_by(sensitivity) %>% summarise_at(c("util.sick","util.sicker","cost.sick","cost.sicker","cost.int","coef_noint","HR_int"),mean)
#> # A tibble: 14 × 8
#> sensitivity util.sick util.sicker cost.sick cost.sicker cost.int coef_noint
#> <int> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 1 0.6 0.5 3000 7000 1000 -1.61
#> 2 2 0.8 0.3 3000 7000 1000 -1.61
#> 3 3 0.8 0.5 1000 7000 1000 -1.61
#> 4 4 0.8 0.5 3000 5000 1000 -1.61
#> 5 5 0.8 0.5 3000 7000 800 -1.61
#> 6 6 0.8 0.5 3000 7000 1000 -2.30
#> 7 7 0.8 0.5 3000 7000 1000 -1.61
#> 8 8 0.8 0.5 3000 7000 1000 -1.61
#> 9 9 0.8 0.5 3000 7000 1000 -1.61
#> 10 10 0.8 0.5 3000 7000 1000 -1.61
#> 11 11 0.8 0.5 3000 7000 1000 -1.61
#> 12 12 0.8 0.5 3000 7000 1000 -1.61
#> 13 13 0.8 0.5 3000 7000 1000 -1.61
#> 14 14 0.8 0.5 3000 7000 1000 -1.61
#> # ℹ 1 more variable: HR_int <dbl>
Model Execution,
probabilistic DSA
The model is executed as before, just activating the psa_bool
option
results <- run_sim(
npats=100,
n_sim=6,
psa_bool = TRUE,
arm_list = c("int", "noint"),
common_all_inputs = common_all_inputs,
common_pt_inputs = common_pt_inputs,
unique_pt_inputs = unique_pt_inputs,
init_event_list = init_event_list,
evt_react_list = evt_react_list,
util_ongoing_list = util_ongoing,
cost_ongoing_list = cost_ongoing,
sensitivity_inputs = sensitivity_inputs,
sensitivity_names = c("DSA_min","DSA_max"),
sensitivity_bool = TRUE,
n_sensitivity = length(list_par[[1]]),
input_out = unlist(list_par[["parameter_name"]])
)
#> Analysis number: 1
#> Simulation number: 1
#> Time to run simulation 1: 0.22s
#> Simulation number: 2
#> Time to run simulation 2: 0.14s
#> Simulation number: 3
#> Time to run simulation 3: 0.11s
#> Simulation number: 4
#> Time to run simulation 4: 0.12s
#> Simulation number: 5
#> Time to run simulation 5: 0.11s
#> Simulation number: 6
#> Time to run simulation 6: 0.11s
#> Time to run analysis 1: 0.83s
#> Analysis number: 2
#> Simulation number: 1
#> Time to run simulation 1: 0.14s
#> Simulation number: 2
#> Time to run simulation 2: 0.11s
#> Simulation number: 3
#> Time to run simulation 3: 0.11s
#> Simulation number: 4
#> Time to run simulation 4: 0.12s
#> Simulation number: 5
#> Time to run simulation 5: 0.13s
#> Simulation number: 6
#> Time to run simulation 6: 0.12s
#> Time to run analysis 2: 0.73s
#> Analysis number: 3
#> Simulation number: 1
#> Time to run simulation 1: 0.11s
#> Simulation number: 2
#> Time to run simulation 2: 0.11s
#> Simulation number: 3
#> Time to run simulation 3: 0.12s
#> Simulation number: 4
#> Time to run simulation 4: 0.13s
#> Simulation number: 5
#> Time to run simulation 5: 0.11s
#> Simulation number: 6
#> Time to run simulation 6: 0.12s
#> Time to run analysis 3: 0.72s
#> Analysis number: 4
#> Simulation number: 1
#> Time to run simulation 1: 0.13s
#> Simulation number: 2
#> Time to run simulation 2: 0.12s
#> Simulation number: 3
#> Time to run simulation 3: 0.11s
#> Simulation number: 4
#> Time to run simulation 4: 0.13s
#> Simulation number: 5
#> Time to run simulation 5: 0.12s
#> Simulation number: 6
#> Time to run simulation 6: 0.13s
#> Time to run analysis 4: 0.74s
#> Analysis number: 5
#> Simulation number: 1
#> Time to run simulation 1: 0.12s
#> Simulation number: 2
#> Time to run simulation 2: 0.13s
#> Simulation number: 3
#> Time to run simulation 3: 0.12s
#> Simulation number: 4
#> Time to run simulation 4: 0.11s
#> Simulation number: 5
#> Time to run simulation 5: 0.13s
#> Simulation number: 6
#> Time to run simulation 6: 0.12s
#> Time to run analysis 5: 0.73s
#> Analysis number: 6
#> Simulation number: 1
#> Time to run simulation 1: 0.11s
#> Simulation number: 2
#> Time to run simulation 2: 0.11s
#> Simulation number: 3
#> Time to run simulation 3: 0.12s
#> Simulation number: 4
#> Time to run simulation 4: 0.13s
#> Simulation number: 5
#> Time to run simulation 5: 0.14s
#> Simulation number: 6
#> Time to run simulation 6: 0.12s
#> Time to run analysis 6: 0.75s
#> Analysis number: 7
#> Simulation number: 1
#> Time to run simulation 1: 0.13s
#> Simulation number: 2
#> Time to run simulation 2: 0.14s
#> Simulation number: 3
#> Time to run simulation 3: 0.11s
#> Simulation number: 4
#> Time to run simulation 4: 0.12s
#> Simulation number: 5
#> Time to run simulation 5: 0.13s
#> Simulation number: 6
#> Time to run simulation 6: 0.12s
#> Time to run analysis 7: 0.75s
#> Analysis number: 8
#> Simulation number: 1
#> Time to run simulation 1: 0.14s
#> Simulation number: 2
#> Time to run simulation 2: 0.13s
#> Simulation number: 3
#> Time to run simulation 3: 0.12s
#> Simulation number: 4
#> Time to run simulation 4: 0.13s
#> Simulation number: 5
#> Time to run simulation 5: 0.12s
#> Simulation number: 6
#> Time to run simulation 6: 0.13s
#> Time to run analysis 8: 0.77s
#> Analysis number: 9
#> Simulation number: 1
#> Time to run simulation 1: 0.12s
#> Simulation number: 2
#> Time to run simulation 2: 0.13s
#> Simulation number: 3
#> Time to run simulation 3: 0.12s
#> Simulation number: 4
#> Time to run simulation 4: 0.13s
#> Simulation number: 5
#> Time to run simulation 5: 0.12s
#> Simulation number: 6
#> Time to run simulation 6: 0.13s
#> Time to run analysis 9: 0.75s
#> Analysis number: 10
#> Simulation number: 1
#> Time to run simulation 1: 0.14s
#> Simulation number: 2
#> Time to run simulation 2: 0.12s
#> Simulation number: 3
#> Time to run simulation 3: 0.13s
#> Simulation number: 4
#> Time to run simulation 4: 0.12s
#> Simulation number: 5
#> Time to run simulation 5: 0.13s
#> Simulation number: 6
#> Time to run simulation 6: 0.14s
#> Time to run analysis 10: 0.78s
#> Analysis number: 11
#> Simulation number: 1
#> Time to run simulation 1: 0.12s
#> Simulation number: 2
#> Time to run simulation 2: 0.13s
#> Simulation number: 3
#> Time to run simulation 3: 0.12s
#> Simulation number: 4
#> Time to run simulation 4: 0.14s
#> Simulation number: 5
#> Time to run simulation 5: 0.13s
#> Simulation number: 6
#> Time to run simulation 6: 0.12s
#> Time to run analysis 11: 0.76s
#> Analysis number: 12
#> Simulation number: 1
#> Time to run simulation 1: 0.14s
#> Simulation number: 2
#> Time to run simulation 2: 0.11s
#> Simulation number: 3
#> Time to run simulation 3: 0.14s
#> Simulation number: 4
#> Time to run simulation 4: 0.21s
#> Simulation number: 5
#> Time to run simulation 5: 0.12s
#> Simulation number: 6
#> Time to run simulation 6: 0.11s
#> Time to run analysis 12: 0.83s
#> Analysis number: 13
#> Simulation number: 1
#> Time to run simulation 1: 0.13s
#> Simulation number: 2
#> Time to run simulation 2: 0.12s
#> Simulation number: 3
#> Time to run simulation 3: 0.13s
#> Simulation number: 4
#> Time to run simulation 4: 0.12s
#> Simulation number: 5
#> Time to run simulation 5: 0.13s
#> Simulation number: 6
#> Time to run simulation 6: 0.11s
#> Time to run analysis 13: 0.74s
#> Analysis number: 14
#> Simulation number: 1
#> Time to run simulation 1: 0.12s
#> Simulation number: 2
#> Time to run simulation 2: 0.13s
#> Simulation number: 3
#> Time to run simulation 3: 0.12s
#> Simulation number: 4
#> Time to run simulation 4: 0.13s
#> Simulation number: 5
#> Time to run simulation 5: 0.11s
#> Simulation number: 6
#> Time to run simulation 6: 0.12s
#> Time to run analysis 14: 0.73s
#> Total time to run: 10.61s
#> Simulation finalized;
Check results
We briefly check below that indeed the engine has been changing the
corresponding parameter value.
data_sensitivity <- bind_rows(map_depth(results,2, "merged_df"))
#Check mean value across iterations as PSA is off
data_sensitivity %>% group_by(sensitivity) %>% summarise_at(c("util.sick","util.sicker","cost.sick","cost.sicker","cost.int","coef_noint","HR_int"),mean)
#> # A tibble: 14 × 8
#> sensitivity util.sick util.sicker cost.sick cost.sicker cost.int coef_noint
#> <int> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 1 0.6 0.580 3156. 7974. 1061. -1.61
#> 2 2 0.722 0.3 3156. 7974. 1061. -1.61
#> 3 3 0.722 0.580 1000 7974. 1061. -1.61
#> 4 4 0.722 0.580 3156. 5000 1061. -1.61
#> 5 5 0.722 0.580 3156. 7974. 800 -1.61
#> 6 6 0.725 0.579 3140. 7957. 1065. -2.30
#> 7 7 0.722 0.580 3155. 7973. 1061. -1.62
#> 8 8 0.722 0.580 3156. 7974. 1061. -1.61
#> 9 9 0.722 0.580 3156. 7974. 1061. -1.61
#> 10 10 0.722 0.580 3156. 7974. 1061. -1.61
#> 11 11 0.722 0.580 3156. 7974. 1061. -1.61
#> 12 12 0.722 0.580 3156. 7974. 1061. -1.61
#> 13 13 0.722 0.580 3156. 7974. 1061. -1.61
#> 14 14 0.722 0.580 3156. 7974. 1061. -1.61
#> # ℹ 1 more variable: HR_int <dbl>
Model Execution,
Simple PSA
The model is executed as before, just activating the psa_bool option
and deactivating the sensitivity_bool and removing sensitivity_names and
setting n_sensitivity = 1
results <- run_sim(
npats=100,
n_sim=10,
psa_bool = TRUE,
arm_list = c("int", "noint"),
common_all_inputs = common_all_inputs,
common_pt_inputs = common_pt_inputs,
unique_pt_inputs = unique_pt_inputs,
init_event_list = init_event_list,
evt_react_list = evt_react_list,
util_ongoing_list = util_ongoing,
cost_ongoing_list = cost_ongoing,
sensitivity_inputs = sensitivity_inputs,
sensitivity_bool = FALSE,
n_sensitivity = 1,
input_out = unlist(list_par[["parameter_name"]])
)
#> Analysis number: 1
#> Simulation number: 1
#> Time to run simulation 1: 0.14s
#> Simulation number: 2
#> Time to run simulation 2: 0.13s
#> Simulation number: 3
#> Time to run simulation 3: 0.12s
#> Simulation number: 4
#> Time to run simulation 4: 0.13s
#> Simulation number: 5
#> Time to run simulation 5: 0.12s
#> Simulation number: 6
#> Time to run simulation 6: 0.13s
#> Simulation number: 7
#> Time to run simulation 7: 0.11s
#> Simulation number: 8
#> Time to run simulation 8: 0.12s
#> Simulation number: 9
#> Time to run simulation 9: 0.14s
#> Simulation number: 10
#> Time to run simulation 10: 0.11s
#> Time to run analysis 1: 1.25s
#> Total time to run: 1.25s
#> Simulation finalized;
Check results
We briefly check below that indeed the engine has been changing the
corresponding parameter values.
data_simulation <- bind_rows(map_depth(results,2, "merged_df"))
#Check mean value across iterations as PSA is off
data_simulation %>% group_by(simulation) %>% summarise_at(c("util.sick","util.sicker","cost.sick","cost.sicker","cost.int","coef_noint","HR_int"),mean)
#> # A tibble: 10 × 8
#> simulation util.sick util.sicker cost.sick cost.sicker cost.int coef_noint
#> <int> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 1 0.528 0.563 5010. 9352. 1073. -1.43
#> 2 2 0.666 0.544 3065. 7914. 808. -1.07
#> 3 3 0.742 0.526 2393. 7690. 1323. -1.99
#> 4 4 0.727 0.767 2491. 8069. 1009. -1.34
#> 5 5 0.808 0.570 4021. 7834. 1026. -1.93
#> 6 6 0.874 0.501 1881. 6913. 1158. -2.01
#> 7 7 0.859 0.457 2730. 9349. 1264. -1.70
#> 8 8 0.935 0.438 3954. 6225. 813. -1.35
#> 9 9 0.886 0.648 3104. 7909. 695. -1.75
#> 10 10 0.870 0.576 3952. 6264. 1176. -1.52
#> # ℹ 1 more variable: HR_int <dbl>