Skip to content

Commit d2cbf12

Browse files
authored
Merge pull request #70 from pythonhealthdatascience/dev2
Dev2
2 parents a70289e + 501b0dc commit d2cbf12

28 files changed

+765
-586
lines changed

.Rbuildignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,5 @@
1212
^rmarkdown$
1313
^CITATION\.cff$
1414
^run_rmarkdown\.sh$
15+
^inputs$
16+
^mock_paper\.md$

DESCRIPTION

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,16 @@ Imports:
2929
ggplot2,
3030
tibble,
3131
gridExtra,
32-
R6,
33-
fitdistrplus,
34-
lubridate,
35-
plotly
32+
R6
3633
Suggests:
3734
testthat (>= 3.0.0),
3835
patrick,
3936
lintr,
4037
devtools,
4138
xtable,
4239
data.table,
43-
mockery
40+
mockery,
41+
fitdistrplus,
42+
lubridate,
43+
plotly
4444
Config/testthat/edition: 3

NAMESPACE

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ export(ReplicationTabuliser)
44
export(ReplicationsAlgorithm)
55
export(WelfordStats)
66
export(calc_arrivals)
7+
export(calc_mean_patients_in_service)
8+
export(calc_mean_queue)
79
export(calc_mean_serve_length)
810
export(calc_mean_wait)
911
export(calc_unseen_mean)
@@ -23,6 +25,7 @@ importFrom(dplyr,across)
2325
importFrom(dplyr,arrange)
2426
importFrom(dplyr,bind_cols)
2527
importFrom(dplyr,bind_rows)
28+
importFrom(dplyr,desc)
2629
importFrom(dplyr,filter)
2730
importFrom(dplyr,full_join)
2831
importFrom(dplyr,group_by)
@@ -35,6 +38,7 @@ importFrom(dplyr,rename)
3538
importFrom(dplyr,select)
3639
importFrom(dplyr,slice)
3740
importFrom(dplyr,summarise)
41+
importFrom(dplyr,transmute)
3842
importFrom(dplyr,ungroup)
3943
importFrom(future,multisession)
4044
importFrom(future,plan)
@@ -58,6 +62,7 @@ importFrom(simmer,get_attribute)
5862
importFrom(simmer,get_mon_arrivals)
5963
importFrom(simmer,get_mon_attributes)
6064
importFrom(simmer,get_mon_resources)
65+
importFrom(simmer,get_queue_count)
6166
importFrom(simmer,now)
6267
importFrom(simmer,release)
6368
importFrom(simmer,run)

R/get_run_results.R

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ get_run_results <- function(results, run_number) {
3131
# Calculate metrics of interest
3232
metrics <- list(
3333
calc_arrivals(results[["arrivals"]]),
34+
calc_mean_patients_in_service(results[["patients_in_service"]]),
35+
calc_mean_queue(results[["arrivals"]]),
3436
calc_mean_wait(results[["arrivals"]], results[["resources"]]),
3537
calc_mean_serve_length(results[["arrivals"]], results[["resources"]]),
3638
calc_utilisation(results[["resources"]]),
@@ -63,6 +65,76 @@ calc_arrivals <- function(arrivals, groups = NULL) {
6365
}
6466

6567

68+
#' Calculate the time-weighted mean number of patients in the service.
69+
#'
70+
#' @param patient_count Dataframe with patient counts over time.
71+
#' @param groups Optional list of columns to group by for the calculation.
72+
#'
73+
#' @return Tibble with column containing mean number of patients in the service.
74+
#' @export
75+
76+
calc_mean_patients_in_service <- function(patient_count, groups = NULL) {
77+
# If provided, group the dataset
78+
if (!is.null(groups)) {
79+
patient_count <- group_by(patient_count, across(all_of(groups)))
80+
}
81+
# Calculate the time-weighted number of patients in the service
82+
patient_count %>%
83+
# Sort by time
84+
arrange(.data[["time"]]) %>%
85+
# Calculate time between this row and the next
86+
mutate(interval_duration = (lead(.data[["time"]]) - .data[["time"]])) %>%
87+
# Multiply each patient count by its own unique duration. The total of
88+
# those is then divided by the total duration of all intervals.
89+
# Hence, we are calculated a time-weighted average patient count.
90+
summarise(
91+
mean_patients_in_service = (
92+
sum(.data[["count"]] * .data[["interval_duration"]], na.rm = TRUE) /
93+
sum(.data[["interval_duration"]], na.rm = TRUE)
94+
)
95+
) %>%
96+
ungroup()
97+
}
98+
99+
100+
#' Calculate the time-weighted mean queue length.
101+
#'
102+
#' @param arrivals Dataframe with times for each patient with each resource.
103+
#' @param groups Optional list of columns to group by for the calculation.
104+
#'
105+
#' @return Tibble with column containing mean queue length.
106+
#' @export
107+
108+
calc_mean_queue <- function(arrivals, groups = NULL) {
109+
# Create list of grouping variables (always "resource", but can add others)
110+
group_vars <- c("resource", groups)
111+
112+
# Calculate mean queue length for each resource
113+
arrivals %>%
114+
group_by(across(all_of(group_vars))) %>%
115+
# Sort by arrival time
116+
arrange(.data[["start_time"]]) %>%
117+
# Calculate time between this row and the next
118+
mutate(
119+
interval_duration = (lead(.data[["start_time"]]) - .data[["start_time"]])
120+
) %>%
121+
# Multiply each queue length by its own unique duration. The total of
122+
# those is then divided by the total duration of all intervals.
123+
# Hence, we are calculated a time-weighted average queue length.
124+
summarise(mean_queue_length = (
125+
sum(.data[["queue_on_arrival"]] *
126+
.data[["interval_duration"]], na.rm = TRUE) /
127+
sum(.data[["interval_duration"]], na.rm = TRUE)
128+
)
129+
) %>%
130+
# Reshape dataframe
131+
pivot_wider(names_from = "resource",
132+
values_from = "mean_queue_length",
133+
names_glue = "mean_queue_length_{resource}") %>%
134+
ungroup()
135+
}
136+
137+
66138
#' Calculate the mean wait time for each resource
67139
#'
68140
#' @param arrivals Dataframe with times for each patient with each resource.

R/model.R

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@
88
#'
99
#' @importFrom simmer trajectory seize timeout release simmer add_resource
1010
#' @importFrom simmer add_generator run wrap get_mon_arrivals set_attribute
11-
#' @importFrom simmer get_attribute get_mon_attributes
11+
#' @importFrom simmer get_attribute get_mon_attributes get_queue_count
1212
#' @importFrom magrittr %>%
1313
#' @importFrom stats rexp
1414
#' @importFrom utils capture.output
15-
#' @importFrom dplyr select left_join
15+
#' @importFrom dplyr select left_join transmute desc
1616
#' @importFrom tidyselect all_of
1717
#'
1818
#' @return Named list with three tables: monitored arrivals,
@@ -37,6 +37,9 @@ model <- function(run_number, param, set_seed = TRUE) {
3737

3838
# Define the patient trajectory
3939
patient <- trajectory("appointment") %>%
40+
# Record queue length on arrival
41+
set_attribute("nurse_queue_on_arrival",
42+
function() get_queue_count(env, "nurse")) %>%
4043
seize("nurse", 1L) %>%
4144
# Manually record the time when the patient is served (i.e. resource
4245
# becomes available) and the sampled length of the activity.
@@ -103,12 +106,33 @@ model <- function(run_number, param, set_seed = TRUE) {
103106
result <- filter_warmup(result, param[["warm_up_period"]])
104107
}
105108

109+
# Gather all start and end times, with a row for each, marked with +1 or -1
110+
# Drop NA for end time, as those are patients who haven't left system
111+
# at the end of the simulation
112+
arrivals_start <- transmute(
113+
result[["arrivals"]], time = .data[["start_time"]], change = 1L
114+
)
115+
arrivals_end <- result[["arrivals"]] %>%
116+
drop_na(all_of("end_time")) %>%
117+
transmute(time = .data[["end_time"]], change = -1L)
118+
events <- bind_rows(arrivals_start, arrivals_end)
119+
120+
# Determine the count of patients in the service with each entry/exit
121+
result[["patients_in_service"]] <- events %>%
122+
# Sort events by time
123+
arrange(.data[["time"]], desc(.data[["change"]])) %>%
124+
# Use cumulative sum to find number of patients in system at each time
125+
mutate(count = cumsum(.data[["change"]])) %>%
126+
dplyr::select(c("time", "count"))
127+
106128
# Replace replication with appropriate run number (as these functions
107129
# assume, if not supplied with list of envs, that there was one replication)
108130
result[["arrivals"]] <- mutate(result[["arrivals"]],
109131
replication = run_number)
110132
result[["resources"]] <- mutate(result[["resources"]],
111133
replication = run_number)
134+
result[["patients_in_service"]] <- mutate(result[["patients_in_service"]],
135+
replication = run_number)
112136

113137
# Calculate the wait time of patients who were seen, and also for those
114138
# who remained unseen at the end of the simulation
@@ -120,6 +144,9 @@ model <- function(run_number, param, set_seed = TRUE) {
120144
now(env) - .data[["start_time"]], NA
121145
)
122146
)
147+
148+
} else {
149+
result[["patients_in_service"]] <- NULL
123150
}
124151

125152
# Calculate the average results for that run and add to result list

R/runner.R

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,15 @@ runner <- function(param, use_future_seeding = TRUE) {
7171
)
7272
# Bind rows will fill NA - e.g. if some runs have no results columns
7373
# as had no arrivals, will set those to NA for that row
74+
all_patients_in_service <- dplyr::bind_rows(
75+
lapply(results, function(x) x[["patients_in_service"]])
76+
)
7477
all_run_results <- dplyr::bind_rows(
7578
lapply(results, function(x) x[["run_results"]])
7679
)
7780
results <- list(arrivals = all_arrivals,
7881
resources = all_resources,
82+
patients_in_service = all_patients_in_service,
7983
run_results = all_run_results)
8084
}
8185

man/ReplicationsAlgorithm.Rd

Lines changed: 4 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/calc_mean_patients_in_service.Rd

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/calc_mean_queue.Rd

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/confidence_interval_method.Rd

Lines changed: 8 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)