library(simmer)
library(simmer.bricks)
suppressMessages(library(simmer.plot))
library(tibble)
library(ggplot2)
suppressMessages(library(RCurl))
suppressMessages(library(Rlab))
suppressMessages(library(dplyr))
suppressMessages(library(tidyr))
options(dplyr.summarise.inform = FALSE)
Model code
1. Imports
Note: we are calculating KPIs using our own code here, but you can also use
simmer.plot
. Help with install ofsimmer.plot
(igraph
installation is the actual issue) on Linux based systems: https://r.igraph.org/articles/installation-troubleshooting#cannot-compile-igraph-from-sources-on-linuxIf you make use of conda environments (via Anaconda/mini-conda/mini-forge/mamba), remember to
conda deactivate
before installation.
2. Default values and constants
2.1 Distribution parameters
#' Mean and Variance of the underlying Normal Distribution
#'
#' @description
#' `normal_moments_from_lognormal` calculates the mu and sigma
#' of the normal distribution underlying a lognormal
#' mean and standard
#'
#' @details
#' `rlnorm` from `stats` is designed to sample from the lognormal distribution.
#' The parameters is expects are moments of the underlying normal distribution
#' Using sample mean and standard deviation this function calculates
#' the mu and sigma of the normal distribution. source: https://blogs.sas.com/content/iml/2014/06/04/simulate-lognormal-data-with-specified-mean-and-variance.html
#'
#' @param mean A number. Sample mean.
#' @param stdev A number. Sample standard deviation
#' @returns A list
<- function(mean, std){
normal_moments_from_lognormal <- sqrt(std^2 + mean^2)
phi <- log(mean**2/phi)
mu <- sqrt(log(phi^2/mean^2))
sigma return(list("mu" = mu, "sigma" = sigma))
}
# sign-in/triage parameters
<- 3.0
DEFAULT_TRIAGE_MEAN
# registration parameters (lognormal distribution)
<- normal_moments_from_lognormal(5.0, sqrt(2.0))
DEFAULT_REG_PARAMS
# examination parameters
= list(mean=16.0, var=3.0)
DEFAULT_EXAM_PARAMS
# trauma/stabilisation
<- 90.0
DEFAULT_TRAUMA_MEAN
# Trauma treatment (lognormal distribution)
<- normal_moments_from_lognormal(30.0, sqrt(4.0))
DEFAULT_TRAUMA_TREATMENT_PARAMS
# Non trauma treatment (lognormal distribution)
<- normal_moments_from_lognormal(13.3, sqrt(2.0))
DEFAULT_NON_TRAUMA_TREATMENT_PARAMS
# prob patient requires treatment given trauma
<- 0.60
DEFAULT_NON_TRAUMA_TREAT_P
# proportion of patients triaged as trauma
<- 0.12 DEFAULT_PROB_TRAUMA
2.2 Time dependent arrival rate data
The data for arrival rates varies between clinic opening at 6am and closure at 12am.
# data are held in the Github repo and loaded from there.
= 'https://raw.githubusercontent.com/TomMonks/open-science-for-sim/main/src/notebooks/01_foss_sim/data/ed_arrivals.csv'
NSPP_PATH
<- getURL(NSPP_PATH)
csv_data <- read.csv(text=csv_data)
df
# lock in order of time of day for bar chart display
$period <- factor(df$period, levels = df$period)
df
ggplot(data=df, aes(x=period, y=arrival_rate)) +
geom_bar(stat="identity", fill="steelblue") +
theme(axis.text.x = element_text(angle = 90,
vjust = 0.5,
hjust=1)) +
xlab("Hour of day") +
ylab("Mean arrivals (patients/hr)")
2.3 Resource Counts
Integer count variables representing the number of resources at each activity in the process
<- 1
DEFAULT_N_TRIAGE <- 1
DEFAULT_N_REG <- 3
DEFAULT_N_EXAM
# stabilisation rooms
<- 1
DEFAULT_N_TRAUMA
# Non-trauma cubicles
<- 1
DEFAULT_NON_TRAUMA_CUBICLES
# trauma pathway cubicles
<- 1 DEFAULT_TRAUMA_CUBICLES
2.4 Simulation model run settings
# Random seed - this will be investigated for CRN
<- 42
SEED
# default results collection period
<- 60 * 19
DEFAULT_RESULTS_COLLECTION_PERIOD
# number of replications.
<- 5
DEFAULT_N_REPS
# Show the a trace of simulated events
# 1 = show, 0 = do not show.
<- 1 LOG_LEVEL
3. Functions
Load and format data
<- function(path=NSPP_PATH){
load_arrival_data <- getURL(NSPP_PATH)
csv_data <- read.csv(text=csv_data)
df
# arrivals per minute...
$arrival_rate2 <- df$arrival_rate/60.0
df
# create 60 minute increments for period
$period = seq(0, (nrow(df)-1)*60, by=60)
dfreturn(df)
}
#' Sample a patient type
#'
#' @description
#' `sample_arrival_type` samples if a patient type is trauma or non-trauma
#' with a given probability.
#'
#' @details
#' The function uses the Bernouli distribution (Rlab) to sample
#' if a patient is Trauma or Non-Trauma. The return values are
#' 1 = Trauma, 2 = Non-trauma.
#' @param p A number: the probability a patient has trauma on arrival
<- function(p, n=1){
sample_arrival_type ifelse(rbern(n, prob = DEFAULT_PROB_TRAUMA) == 1, 1, 2)
}
#' Sample a if a non-trauma patient requires treatment
#'
#' @description
#' `sample_nt_trauma_treatment` samples if a non-trauma patient
#' requires cubicle treatment
#'
#' @details
#' The function uses the Bernouli distribution (Rlab) to sample
#' if a patient is requires treatment or not. The return values are
#' 1 = Treatment, 0 = No treatment
#' @param p A number: The probability the patient requires treatment
<- function(p){
sample_nt_trauma_treatment ifelse(rbern(1, prob = p) == 1, 1, 0)
}
Sampling from a non-stationary poisson process using thinning
<- function(simulation_time, data, debug=FALSE){
nspp_thinning
# calc time interval: assumes intervals are of equal length
<- data$period[2] - data$period[1]
interval
# maximum arrival rate (smallest time between arrivals)
<- max(data$arrival_rate2)
lambda_max
while(TRUE){
# get time bucket (row of dataframe to use)
<- floor(simulation_time / interval) %% nrow(data) + 1
t <- data$arrival_rate2[t]
lambda_t
# set to a large number so that at least 1 sample is taken
<- Inf
u <- -1
rejects
# running total of time until next arrival
<- 0.0
inter_arrival_time
# reject proportionate to lambda_t / lambda_max
<- lambda_t / lambda_max
ratio while(u >= ratio){
<- rejects + 1
rejects # sample using max arrival rate
<- inter_arrival_time + rexp(1, lambda_max)
inter_arrival_time <- runif(1, 0.0, 1.0)
u
}
if(debug){
print({paste("Time:", simulation_time,
" Rejections:", rejects,
" t:", t,
" lambda_t:", lambda_t,
" IAT:", inter_arrival_time)})
}
return(inter_arrival_time)
} }
4. Model parameterisation
The model is setup to be created from a set of functions that return trajectories. Each function accepts a list that contains all parameters to configure the simulation model. Here we create the list and pre-populate it using default values.
<- function(n_triage_bays=DEFAULT_N_TRIAGE,
create_experiment n_reg_clerks=DEFAULT_N_REG,
n_exam_rooms=DEFAULT_N_EXAM,
n_trauma_rooms=DEFAULT_N_TRAUMA,
n_non_trauma_cubicles=DEFAULT_NON_TRAUMA_CUBICLES,
n_trauma_cubicles=DEFAULT_TRAUMA_CUBICLES,
triage_mean=DEFAULT_TRIAGE_MEAN,
stabilisation_mean=DEFAULT_TRAUMA_MEAN,
trauma_treat_params=DEFAULT_TRAUMA_TREATMENT_PARAMS,
reg_params=DEFAULT_REG_PARAMS,
exam_params=DEFAULT_EXAM_PARAMS,
prob_non_trauma_treat=DEFAULT_NON_TRAUMA_TREAT_P,
nontrauma_treat_params=DEFAULT_NON_TRAUMA_TREATMENT_PARAMS,
prob_trauma=DEFAULT_PROB_TRAUMA,
arrival_data_path=NSPP_PATH,
log_level=LOG_LEVEL) {
# load arrival data
<- load_arrival_data(path=arrival_data_path)
arrival_data
# create list of parameters
<- list(n_triage_bays=n_triage_bays,
experiment n_reg_clerks=n_reg_clerks,
n_exam_rooms=n_exam_rooms,
n_trauma_rooms=n_trauma_rooms,
n_non_trauma_cubicles=n_non_trauma_cubicles,
n_trauma_cubicles=n_trauma_cubicles,
triage_mean=triage_mean,
stabilisation_mean=stabilisation_mean,
trauma_treat_params=trauma_treat_params,
reg_params=reg_params,
exam_params=exam_params,
prob_non_trauma_treat=prob_non_trauma_treat,
nontrauma_treat_params=nontrauma_treat_params,
prob_trauma=prob_trauma,
arrival_data=arrival_data,
log_level=log_level)
return(experiment)
}
5. Patient Trajectories
The DES package simmer
uses the concept of a trajectory
to model a process for a particular patient type. In the urgent care centre example trajectories allow us to model separate trauma and non-trauma processes. Note that different trajectories can share common resources.
The simmer
terminology for using resources and engaging in activities is easy to read:
seize
- queue and take a resource when it is available.timeout
- a process delay (e.g. treatment or diagnostics)release
- release a resource.
simmer
also provides a way to set an attribute of the trajectory
using set_attribute
. This is useful for storing timing information to display in a log: for example when a patient begins waiting for a resource (access via now(env)
).
Important notes:
- The function
log_
is used in combination withfunction()
paste
to provide a dynamic simulation trace to the R console.- Sampling code should look as follows:
timeout(task = function() rexp(1, 3.0)) %>%
The keyword
function()
must be included for dynamic sampling for each patient. Omittingfunction()
means that it is evaluated once at the time thetrajectory
is created.
5.1. Trauma Patients
We wrap the trajectory in a function called
create_trauma_pathway
. This allows us to pass an argumentexp
that can parameterise the trajectory for use in a discrete experiment.
<- function(exp){
create_trauma_pathway
<- trajectory(name="trauma_pathway") %>%
trauma_pathway set_attribute("patient_type", 1) %>%
# log patient arrival
log_(function() {paste("**Trauma arrival")}, level=1) %>%
# triage
set_attribute("start_triage_wait", function() {now(exp$env)}) %>%
visit("triage_bay", function() rexp(1, 1/exp$triage_mean)) %>%
log_(function() {paste("(T) Triage wait time:",
now(exp$env) - get_attribute(exp$env, "start_triage_wait"))},
level=1) %>%
# request trauma room for stabilization
set_attribute("start_trauma_room_wait", function() {now(exp$env)}) %>%
visit("trauma_room", function() rexp(1, 1/exp$stabilisation_mean)) %>%
log_(function() {paste("(T) Trauma room wait time:",
now(exp$env) - get_attribute(exp$env, "start_trauma_room_wait"))},
level=1) %>%
# request treatment cubicle
set_attribute("start_trauma_treat_wait", function() {now(exp$env)}) %>%
visit("trauma_treat_cubicle", function() rlnorm(1, exp$trauma_treat_params$mu,
$trauma_treat_params$sigma)) %>%
explog_(function() {paste("********************(T) Trauma treatment cubicle wait time:",
now(exp$env) - get_attribute(exp$env, "start_trauma_treat_wait"))},
level=1) %>%
# store the total time in system
set_attribute("total_time",
function() {now(exp$env) - get_attribute(exp$env, "start_triage_wait")})
return(trauma_pathway)
}
5.2 Non-trauma patients
<- function(exp){
create_nt_cubicle_treatment
<- trajectory() %>%
nt_cubicle_treatment log_(function() {paste("NT patient requirement treatment")},
level=1) %>%
seize(resource="nontrauma_treat_cubicle", amount=1) %>%
timeout(task = function() rlnorm(1, exp$nontrauma_treat_params$mu, exp$nontrauma_treat_params$sigma)) %>%
release(resource = "nontrauma_treat_cubicle", amount = 1) %>%
log_(function() {paste("NT treatment complete")},
level=1) %>%
return(nt_cubicle_treatment)
}
<- function(exp){
create_non_trauma_pathway # log messages
= "**Non-Trauma arrival**"
ARRIVAL_MSG = "(NT) Triage wait time:"
TRIAGE_MSG = "Reg wait time:"
REG_MSG = "Exam wait time:"
EXAM_MSG = "NT Total time in system:"
EXIT_MSG
# optional trajectory for proportion of patients that requirement treatment
<- create_nt_cubicle_treatment(exp)
nt_cubicle_treatment
<- trajectory(name="non_trauma_pathway") %>%
non_trauma_pathway set_attribute("patient_type", 2) %>%
# log non_trauma arrival
log_(function() {paste(ARRIVAL_MSG)}, level=1) %>%
# store start of waiting time for log calculations
set_attribute("start_triage_wait", function() {now(exp$env)}) %>%
# queue and use triage bay
visit("triage_bay", function() rexp(1, 1/exp$triage_mean)) %>%
log_(function() {paste(TRIAGE_MSG, now(exp$env) - get_attribute(exp$env, "start_triage_wait"))},
level=1) %>%
# queue and use registration clerk
set_attribute("start_reg_wait", function() {now(exp$env)}) %>%
visit("registration_clerk", function() rlnorm(1, exp$reg_params$mu,
$reg_params$sigma)) %>%
explog_(function() {paste(REG_MSG, now(exp$env) - get_attribute(exp$env, "start_reg_wait"))},
level=1) %>%
# queue and use examination room
set_attribute("start_exam_wait", function() {now(exp$env)}) %>%
visit("examination_room", function() rnorm(1, exp$exam_params$mean,
sqrt(exp$exam_params$var))) %>%
log_(function() {paste(EXAM_MSG, now(exp$env) - get_attribute(exp$env, "start_exam_wait"))},
level=1) %>%
# a Proportion of patients require treatment in a cubicle
branch (
function() sample_nt_trauma_treatment(exp$prob_non_trauma_treat), continue=T,
nt_cubicle_treatment%>%
) log_(function() {paste(EXIT_MSG, now(exp$env) - get_attribute(exp$env, "start_triage_wait"))},
level=1) %>%
# store the total time in system
set_attribute("total_time",
function() {now(exp$env) - get_attribute(exp$env, "start_triage_wait")})
return(non_trauma_pathway)
}
6. Modelling patient arrivals
Patients arrive a the urgent treatment centre following a time dependent process. When patients arrive they are classified as trauma or non-trauma.
To modify the classification of patients we will use a trajectory that uses the `branch` function from simmer
.
The function `sample_arrival_type` returns a 1 (trauma) or 2 (non-trauma). This is used to select the appropriate patient trajectory.
<- function(exp){
create_arrival_generator
<- "A patient has departed the UTC"
DEPART_MSG
# create and parameterise the trauma pathway trajectory
<- create_trauma_pathway(exp)
trauma_pathway
# create and parameterise the non-trauma pathway trajectory
<- create_non_trauma_pathway(exp)
non_trauma_pathway
<- trajectory() %>%
patient_arrival branch(
function() sample_arrival_type(exp$prob_trauma), continue=T,
trauma_pathway,
non_trauma_pathway%>%
) log_(function() {paste(DEPART_MSG)},level=1) %>%
set_attribute("departed", 1)
return(patient_arrival)
}
7. Single run of the model
Work in progress
<- function(env, exp,
single_run rep_number=1,
run_length=DEFAULT_RESULTS_COLLECTION_PERIOD,
debug_arrivals=FALSE){
# add the simmer environment to the experiment list.
<- c(exp, env=env)
exp
# Create the arrivals generator
<- create_arrival_generator(exp)
arrival_gen
# create model and run.
%>%
env add_resource("triage_bay", exp$n_triage_bays) %>%
add_resource("registration_clerk", exp$n_reg_clerks) %>%
add_resource("examination_room", exp$n_exam_rooms) %>%
add_resource("trauma_room", exp$n_trauma_rooms) %>%
add_resource("trauma_treat_cubicle", exp$n_trauma_cubicles) %>%
add_resource("nontrauma_treat_cubicle", exp$n_non_trauma_cubicles) %>%
add_generator("Patient", arrival_gen,
function() nspp_thinning(now(env), exp$arrival_data,
debug=debug_arrivals),
mon=2) %>%
run(until=run_length)
# return environment and all of its results.
return(env)
}
Script to conduct single run of the model
Note that the environment is created outside of the
single_run
function. This is to separate the creation of the environment from therun
function call. The reason is so that thenow(env)
function will work correctly in thenspp_thinning
sampling function (if we do not separate then the same time is always passed to the function).
set.seed(SEED)
<- create_experiment(log_level=0)
exp <- simmer("TreatSim", log_level=exp$log_level)
treat_sim <- single_run(treat_sim, exp)
treat_sim print("Simulation Complete.")
[1] "Simulation Complete."
8. Multiple replications
<- function(exp, n_reps=5, random_seed=0){
multiple_replications
# set seed in once place. No CRN
set.seed(random_seed)
# note unlike in simmer documentation we use a traditional for loop
# instead of lapply. This allows us to separate env creation
# from run and preserve the environment interaction between NSPP
# and current sim time.
# TO DO: look again -> can treat_sim be created inside single_run()
print("running replications...")
= vector()
reps for(rep in 1:n_reps){
<- simmer("TreatSimmer", log_level=exp$log_level)
treat_sim <- single_run(treat_sim, exp)
treat_sim # store the latest simulation environment and its results.
<- c(reps, treat_sim)
reps
}print("Complete.")
return(reps)
}
# create experiment
<- create_experiment(log_level=0)
exp
# run 50 replications of the model
<- multiple_replications(exp, n_reps=50, random_seed=0) reps
[1] "running replications..."
[1] "Complete."
9. Results analysis
Analysis of simmer
results is achieved using a mix of statistics collected automatically and custom attributes set by the modeller during the run.
In general, we follow a typical strategy in a simulation study. We calculate the mean Key Performance Indicator (KPI) seen during an individual replication of the model (e.g. waiting time for triage and utilisation of the the triage rooms). This is repeated for all replications and the distribution of results can be visualised or we use a summary measure such as the mean.
Below we construct a summary table of results providing the mean of 16 KPIs.
We assemble the table of KPIs across replications using the following key functions
resource_waiting_times_by_replication
- creates a data frame where columns report the mean waiting time within a replication for a activity in the model (e.g. triage waiting time)resource_utilisation_by_replication
- creates a data frame where columns report the utilisation of a resource within a replication (e.g. the proportion of the scheduled time that triage bays are in use).arrivals_by_replication
- creates a data frame of the total number of patient arrivals in a replication.system_kpi_by_replication
- creates a data frame containing system level KPIs by replication e.g. throughput and time in system (created from custom attributes).replication_results_table
- creates the overall data frame using the other functions.
Code quality is a work in progress. 😃
9.1. Waiting time KPIs
# assumes df is monitored arrivals
<- function(df){
waiting_time $waiting_time <-df$end_time - df$start_time - df$activity_time
dfreturn(df)
}
<- function(reps) {
resource_waiting_times_by_replication # - WAITING TIMES FOR RESOURCES - #
<- c("resource", "replication")
cols <- get_mon_arrivals(reps, per_resource=TRUE) %>%
waiting_times_wide # waiting time = end time - start time - activity time
waiting_time() %>%
# mean waiting time in each replication
group_by(across(all_of(cols))) %>%
# mean for each replication
summarise(rep_waiting_time=mean(waiting_time)) %>%
# recode kpi names
mutate(resource=recode(resource,
'triage_bay'='01a_triage_wait',
'registration_clerk'='02a_registration_wait',
'examination_room'='03a_examination_wait',
'nontrauma_treat_cubicle'='04a_treatment_wait(non_trauma)',
'trauma_room'='06a_stabilisation_wait',
'trauma_treat_cubicle'='07a_treatment_wait(trauma)')) %>%
# organise
arrange(resource) %>%
# long to wide format ...
spread(resource, rep_waiting_time)
return(waiting_times_wide)
}
9.2. Resource utilisation KPIs
<- function(exp) {
get_resource_counts = c("triage_bay",
resource "registration_clerk",
"examination_room",
"trauma_room",
"trauma_treat_cubicle",
"nontrauma_treat_cubicle")
= c(exp$n_triage_bays,
resource_counts $n_reg_clerks,
exp$n_exam_rooms,
exp$n_trauma_rooms,
exp$n_trauma_cubicles,
exp$n_non_trauma_cubicles)
exp
<- data.frame(resource)
df_resource $count <- resource_counts
df_resourcereturn(df_resource)
}
# simple calculation of total busy time / total scheduled resource time.
<- function(df, scheduled_time){
resource_utilisation $util = df$in_use / (scheduled_time * df$count)
dfreturn(df)
}
# calculate resource utilisation and return table (rows = reps and cols = resources)
<- function(reps, exp, results_collection_period){
resource_utilisation_by_replication
# get results dataframe broken down by resource and replication.
<- c("resource", "replication")
cols
# utilisation calculation:
# simple calculation of total busy time / total scheduled resource time.
# where total scheduled time = n_resource * results collection period.
<- get_mon_arrivals(reps, per_resource=TRUE) %>%
util_wide # total activity time in each replication per resource (long format)
group_by(across(all_of(cols))) %>%
summarise(in_use=sum(activity_time)) %>%
# merge with the number of resources available
merge(get_resource_counts(exp), by="resource", all=TRUE) %>%
# calculate the utilisation using scheduled resource availability
resource_utilisation(results_collection_period) %>%
# drop total activity time and count of resources
subset(select = c(replication, resource, util)) %>%
# recode names
mutate(resource=recode(resource,
'triage_bay'='01b_triage_util',
'registration_clerk'='02b_registration_util',
'examination_room'='03b_examination_util',
'nontrauma_treat_cubicle'='04b_treatment_util(non_trauma)',
'trauma_room'='06b_stabilisation_util',
'trauma_treat_cubicle'='07b_treatment_util(trauma)')) %>%
arrange(resource) %>%
# long to wide format...
spread(resource, util)
return(util_wide)
}
9.3. Patient arrival numbers output
# number of arrivals in each replication
<- function(envs){
arrivals_by_replication <- vector()
results for(env in envs){
<- c(results, get_n_generated(env, "Patient"))
results
}
<- data.frame(replication = c(1:length(results)),
results arrivals = results)
colnames(results) <- c("replication", "00_arrivals")
return(results)
}
9.4 System level KPIs
# mean time in the system and throughput
<- function(reps, rep_i){
system_kpi_for_rep_i
# get attributes
<- get_mon_attributes(reps)
att
# for speed - limit to replication number.
<- subset(att[att$replication == rep_i,], select = c(name, key, value)) %>%
data_wide spread(key, value)
# Patient type 1: trauma
# take the mean and ignore patients still in pathway
= mean(data_wide[data_wide$patient_type == 1,]$total_time, na.rm = TRUE)
mean_time_1
# Patient type 2: non_trauma
# take the mean and ignore patients still in pathway
= mean(data_wide[data_wide$patient_type == 2,]$total_time, na.rm = TRUE)
mean_time_2
# Throughput - discharges during opening hours.
<- sum(data_wide$departed, na.rm=TRUE)
throughput
# store and return data.frame of results
<- data.frame("replication" = rep_i,
rep_results "05_total_time(non-trauma)" = mean_time_2,
"08_total_time(trauma)" = mean_time_1,
"09_throughput"= throughput)
colnames(rep_results) = c("replication",
"05_total_time(non-trauma)",
"08_total_time(trauma)",
"09_throughput")
return(rep_results)
}
<- function(reps){
system_kpi_by_replication # calcs total time by patient type and total throughput
# empty dataframe for attribute calculations.
<- data.frame(matrix(ncol = 4, nrow = 0))
att_results colnames(att_results) <- c("replication",
"05_total_time(non-trauma)",
"08_total_time(trauma)",
"_09_throughput")
# add each rep separately as this works faster with pivot
for(rep_i in 1:length(reps)){
<- rbind(att_results, system_kpi_for_rep_i(reps, rep_i))
att_results
}
# return the KPIs by replications
return(att_results)
}
9.5. Function to create the replications table
<- function(reps, exp, results_collection_period){
replication_results_table # generate and merge all results tables on the replication column
<- arrivals_by_replication(reps) %>%
results_table merge(resource_waiting_times_by_replication(reps), by="replication", all=TRUE) %>%
merge(resource_utilisation_by_replication(reps, exp,
results_collection_period), by="replication", all=TRUE) %>%
merge(system_kpi_by_replication(reps), by="replication", all=TRUE) %>%
# sort by column names to get "replication" followed by ordered 00_, 01a, 01b and so on...
select(replication, sort(tidyselect::peek_vars()))
results_table
}
<- replication_results_table(reps, exp, DEFAULT_RESULTS_COLLECTION_PERIOD)
rep_table
head(rep_table)
replication | 00_arrivals | 01a_triage_wait | 01b_triage_util | 02a_registration_wait | 02b_registration_util | 03a_examination_wait | 03b_examination_util | 04a_treatment_wait(non_trauma) | 04b_treatment_util(non_trauma) | 05_total_time(non-trauma) | 06a_stabilisation_wait | 06b_stabilisation_util | 07a_treatment_wait(trauma) | 07b_treatment_util(trauma) | 08_total_time(trauma) | 09_throughput |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | 227 | 38.510472 | 0.6500712 | 55.63581 | 0.8313868 | 33.71627 | 0.8440569 | 161.3438 | 0.8528225 | 224.4021 | 322.3138 | 0.6826987 | 4.930242 | 0.2696664 | 448.4067 | 149 |
2 | 217 | 39.962735 | 0.5764697 | 92.59627 | 0.8241213 | 24.76566 | 0.8355781 | 120.8921 | 0.8715918 | 232.8177 | 341.0050 | 0.7482643 | 20.730095 | 0.3229111 | 501.7710 | 167 |
3 | 220 | 8.028919 | 0.5145182 | 125.19868 | 0.8363566 | 22.93733 | 0.8317090 | 151.4920 | 0.8235007 | 226.3541 | 175.9264 | 0.8472946 | 4.735720 | 0.4148506 | 273.4123 | 149 |
4 | 207 | 32.157743 | 0.5447273 | 100.08161 | 0.7600335 | 22.39284 | 0.7850996 | 104.7379 | 0.8154391 | 227.6200 | 217.9818 | 0.8518988 | 11.741513 | 0.3679632 | 358.0275 | 153 |
5 | 220 | 17.281597 | 0.5382466 | 95.79616 | 0.8497077 | 29.89311 | 0.8479559 | 124.9008 | 0.8803980 | 213.6180 | 163.9983 | 0.6404862 | 5.159307 | 0.1560905 | 347.3643 | 145 |
6 | 244 | 19.722275 | 0.6032575 | 139.37281 | 0.8861767 | 13.59604 | 0.8731107 | 150.0938 | 0.8974080 | 247.6859 | 220.1141 | 0.8915800 | 5.252764 | 0.3437842 | 310.7147 | 167 |
9.6 Histogram of replications
<- function(rep_table, column_name, unit_label, n_bins=10){
histogram_of_replications
# Divide the x range for selected column into n_bins
<- diff(range(select(rep_table, all_of(column_name))))/n_bins
binwidth
<- ggplot(rep_table, aes(.data[[column_name]])) +
g geom_histogram(binwidth = binwidth, fill="steelblue", colour = "black") +
xlab(paste(column_name, " (", unit_label, ")")) +
ylab("Replications")
return(g)
}
# as numeric names make sure to pass column name in ticks ``
histogram_of_replications(rep_table, "09_throughput", "patients/day", n_bins=10)
9.7 Results summary table
# modified summary table function
<- function(rep_table, dp=2){
create_summary_table # mean of all columns, but ignore rep number
<- data.frame(colMeans(rep_table[c(2:length(rep_table))]))
mean_values colnames(mean_values) <- c("mean")
return(round(mean_values, dp))
}create_summary_table(rep_table)
mean | |
---|---|
00_arrivals | 227.28 |
01a_triage_wait | 34.24 |
01b_triage_util | 0.60 |
02a_registration_wait | 104.06 |
02b_registration_util | 0.83 |
03a_examination_wait | 23.60 |
03b_examination_util | 0.83 |
04a_treatment_wait(non_trauma) | 126.82 |
04b_treatment_util(non_trauma) | 0.86 |
05_total_time(non-trauma) | 229.06 |
06a_stabilisation_wait | 217.85 |
06b_stabilisation_util | 0.74 |
07a_treatment_wait(trauma) | 7.22 |
07b_treatment_util(trauma) | 0.27 |
08_total_time(trauma) | 354.38 |
09_throughput | 154.86 |