Skip to content

Commit 437fac4

Browse files
authored
Merge pull request #79 from pythonhealthdatascience/dev
Dev
2 parents 94c79f1 + 45bbbc2 commit 437fac4

40 files changed

+864
-583
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples
2+
# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help
3+
on:
4+
push:
5+
branches: [main]
6+
workflow_dispatch:
7+
8+
name: test-coverage.yaml
9+
10+
permissions: read-all
11+
12+
jobs:
13+
test-coverage:
14+
runs-on: ubuntu-latest
15+
env:
16+
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
17+
18+
steps:
19+
- uses: actions/checkout@v4
20+
21+
- uses: r-lib/actions/setup-r@v2
22+
with:
23+
use-public-rspm: true
24+
25+
- uses: r-lib/actions/setup-r-dependencies@v2
26+
with:
27+
extra-packages: any::covr, any::xml2
28+
needs: coverage
29+
30+
- name: Test coverage
31+
run: |
32+
cov <- covr::package_coverage(
33+
quiet = FALSE,
34+
clean = FALSE,
35+
install_path = file.path(normalizePath(Sys.getenv("RUNNER_TEMP"), winslash = "/"), "package")
36+
)
37+
print(cov)
38+
covr::to_cobertura(cov)
39+
shell: Rscript {0}
40+
41+
- uses: codecov/codecov-action@v5
42+
with:
43+
# Fail if error if not on PR, or if on PR and token is given
44+
fail_ci_if_error: ${{ github.event_name != 'pull_request' || secrets.CODECOV_TOKEN }}
45+
files: ./cobertura.xml
46+
plugins: noop
47+
disable_search: true
48+
token: ${{ secrets.CODECOV_TOKEN }}
49+
50+
- name: Show testthat output
51+
if: always()
52+
run: |
53+
## --------------------------------------------------------------------
54+
find '${{ runner.temp }}/package' -name 'testthat.Rout*' -exec cat '{}' \; || true
55+
shell: bash
56+
57+
- name: Upload test results
58+
if: failure()
59+
uses: actions/upload-artifact@v4
60+
with:
61+
name: coverage-test-failures
62+
path: ${{ runner.temp }}/package

CITATION.cff

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
cff-version: 1.2.0
55
title: >-
6-
R DES RAP Template
6+
Simple M/M/s queuing model: R DES RAP
77
message: >-
88
If you use this software, please cite it using the
99
metadata from this file.
@@ -17,7 +17,7 @@ authors:
1717
repository-code: >-
1818
https://github.com/pythonhealthdatascience/rdesrap_mms
1919
abstract: >-
20-
A template for creating discrete-event simulation (DES) models in R
21-
within a reproducible analytical pipeline (RAP).
20+
Reproducible analytical pipeline (RAP) for R discrete-event simulation (DES)
21+
implementing a simple M/M/s queueing model.
2222
version: 0.4.0
2323
date-released: '2025-06-04'

DESCRIPTION

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ Authors@R: c(
1010
)
1111
)
1212
URL: https://github.com/pythonhealthdatascience/rdesrap_mms
13-
Description: Template reproducible analytical pipeline (RAP) for simple R
14-
discrete-event simulation (DES) model.
13+
Description: Reproducible analytical pipeline (RAP) for R discrete-event
14+
simulation (DES) implementing a simple M/M/s queueing model.
1515
License: MIT + file LICENSE
1616
Encoding: UTF-8
1717
LazyData: true
@@ -40,5 +40,7 @@ Suggests:
4040
mockery,
4141
fitdistrplus,
4242
lubridate,
43-
plotly
43+
plotly,
44+
covr,
45+
DT
4446
Config/testthat/edition: 3

NEWS.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# Simple M/M/s queuing model: R DES RAP (unreleased)
2+
3+
TBC.
4+
15
# R DES RAP Template 0.4.0
26

37
Major changes include: addition of two new metrics (queue length and patients in service); input modelling; and synthetic data. Also, enabled solutions below the defined initial replications for the replications algorithm.

R/choose_replications.R

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -574,9 +574,9 @@ confidence_interval_method <- function(replications, desired_precision,
574574
)
575575
if (nrow(compare) > 0L) {
576576
# Get minimum number
577-
n_reps <- compare %>%
578-
dplyr::slice_head() %>%
579-
dplyr::select(replications) %>%
577+
n_reps <- compare |>
578+
dplyr::slice_head() |>
579+
dplyr::select(replications) |>
580580
dplyr::pull()
581581
message("Reached desired precision (", desired_precision, ") in ",
582582
n_reps, " replications.")

R/choose_warmup.R

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,23 +27,23 @@ time_series_inspection <- function(result, file_path, warm_up = NULL) {
2727
metrics <- list()
2828

2929
# Wait time of each patient at each time point
30-
metrics[[1L]] <- result[["arrivals"]] %>%
31-
rename(time = .data[["serve_start"]]) %>%
30+
metrics[[1L]] <- result[["arrivals"]] |>
31+
rename(time = .data[["serve_start"]]) |>
3232
select(.data[["replication"]],
3333
.data[["time"]],
3434
.data[["wait_time"]])
3535

3636
# Service length of each patient at each time point
37-
metrics[[2L]] <- result[["arrivals"]] %>%
38-
rename(time = .data[["serve_start"]]) %>%
37+
metrics[[2L]] <- result[["arrivals"]] |>
38+
rename(time = .data[["serve_start"]]) |>
3939
select(.data[["replication"]],
4040
.data[["time"]],
4141
.data[["serve_length"]])
4242

4343
# Utilisation at each time point
4444
metrics[[3L]] <- calc_utilisation(result[["resources"]],
4545
groups = c("resource", "replication"),
46-
summarise = FALSE) %>%
46+
summarise = FALSE) |>
4747
select(.data[["replication"]],
4848
.data[["time"]],
4949
.data[["utilisation"]])
@@ -55,18 +55,18 @@ time_series_inspection <- function(result, file_path, warm_up = NULL) {
5555
metric <- setdiff(names(metrics[[i]]), c("time", "replication"))
5656

5757
# Calculate cumulative mean for the current metric
58-
cumulative <- metrics[[i]] %>%
59-
arrange(.data[["replication"]], .data[["time"]]) %>%
60-
group_by(.data[["replication"]]) %>%
58+
cumulative <- metrics[[i]] |>
59+
arrange(.data[["replication"]], .data[["time"]]) |>
60+
group_by(.data[["replication"]]) |>
6161
mutate(cumulative_mean = cumsum(.data[[metric]]) /
62-
seq_along(.data[[metric]])) %>%
62+
seq_along(.data[[metric]])) |>
6363
ungroup()
6464

6565
# Repeat calculation, but including all replications in one
66-
overall_cumulative <- metrics[[i]] %>%
67-
arrange(.data[["time"]]) %>%
66+
overall_cumulative <- metrics[[i]] |>
67+
arrange(.data[["time"]]) |>
6868
mutate(cumulative_mean = cumsum(.data[[metric]]) /
69-
seq_along(.data[[metric]])) %>%
69+
seq_along(.data[[metric]])) |>
7070
ungroup()
7171

7272
# Create plot

R/get_run_results.R

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ calc_arrivals <- function(arrivals, groups = NULL) {
5959
arrivals <- group_by(arrivals, across(all_of(groups)))
6060
}
6161
# Calculate number of arrivals
62-
arrivals %>%
63-
summarise(arrivals = n_distinct(.data[["name"]])) %>%
62+
arrivals |>
63+
summarise(arrivals = n_distinct(.data[["name"]])) |>
6464
ungroup()
6565
}
6666

@@ -79,11 +79,11 @@ calc_mean_patients_in_service <- function(patient_count, groups = NULL) {
7979
patient_count <- group_by(patient_count, across(all_of(groups)))
8080
}
8181
# Calculate the time-weighted number of patients in the service
82-
patient_count %>%
82+
patient_count |>
8383
# Sort by time
84-
arrange(.data[["time"]]) %>%
84+
arrange(.data[["time"]]) |>
8585
# Calculate time between this row and the next
86-
mutate(interval_duration = (lead(.data[["time"]]) - .data[["time"]])) %>%
86+
mutate(interval_duration = (lead(.data[["time"]]) - .data[["time"]])) |>
8787
# Multiply each patient count by its own unique duration. The total of
8888
# those is then divided by the total duration of all intervals.
8989
# Hence, we are calculated a time-weighted average patient count.
@@ -92,7 +92,7 @@ calc_mean_patients_in_service <- function(patient_count, groups = NULL) {
9292
sum(.data[["count"]] * .data[["interval_duration"]], na.rm = TRUE) /
9393
sum(.data[["interval_duration"]], na.rm = TRUE)
9494
)
95-
) %>%
95+
) |>
9696
ungroup()
9797
}
9898

@@ -110,14 +110,14 @@ calc_mean_queue <- function(arrivals, groups = NULL) {
110110
group_vars <- c("resource", groups)
111111

112112
# Calculate mean queue length for each resource
113-
arrivals %>%
114-
group_by(across(all_of(group_vars))) %>%
113+
arrivals |>
114+
group_by(across(all_of(group_vars))) |>
115115
# Sort by arrival time
116-
arrange(.data[["start_time"]]) %>%
116+
arrange(.data[["start_time"]]) |>
117117
# Calculate time between this row and the next
118118
mutate(
119119
interval_duration = (lead(.data[["start_time"]]) - .data[["start_time"]])
120-
) %>%
120+
) |>
121121
# Multiply each queue length by its own unique duration. The total of
122122
# those is then divided by the total duration of all intervals.
123123
# Hence, we are calculated a time-weighted average queue length.
@@ -126,11 +126,11 @@ calc_mean_queue <- function(arrivals, groups = NULL) {
126126
.data[["interval_duration"]], na.rm = TRUE) /
127127
sum(.data[["interval_duration"]], na.rm = TRUE)
128128
)
129-
) %>%
129+
) |>
130130
# Reshape dataframe
131131
pivot_wider(names_from = "resource",
132132
values_from = "mean_queue_length",
133-
names_glue = "mean_queue_length_{resource}") %>%
133+
names_glue = "mean_queue_length_{resource}") |>
134134
ungroup()
135135
}
136136

@@ -156,12 +156,12 @@ calc_mean_wait <- function(arrivals, resources, groups = NULL) {
156156
group_vars <- c("resource", groups)
157157

158158
# Calculate mean wait time for each resource
159-
complete_arrivals %>%
160-
group_by(across(all_of(group_vars))) %>%
161-
summarise(mean_waiting_time = mean(.data[["wait_time"]])) %>%
159+
complete_arrivals |>
160+
group_by(across(all_of(group_vars))) |>
161+
summarise(mean_waiting_time = mean(.data[["wait_time"]])) |>
162162
pivot_wider(names_from = "resource",
163163
values_from = "mean_waiting_time",
164-
names_glue = "mean_waiting_time_{resource}") %>%
164+
names_glue = "mean_waiting_time_{resource}") |>
165165
ungroup()
166166
} else {
167167
# But if no patients are seen, create same tibble with values set to NA
@@ -195,12 +195,12 @@ calc_mean_serve_length <- function(arrivals, resources, groups = NULL) {
195195
group_vars <- c("resource", groups)
196196

197197
# Calculate mean serve time for each resource
198-
complete_arrivals %>%
199-
group_by(across(all_of(group_vars))) %>%
200-
summarise(mean_serve_time = mean(.data[["serve_length"]])) %>%
198+
complete_arrivals |>
199+
group_by(across(all_of(group_vars))) |>
200+
summarise(mean_serve_time = mean(.data[["serve_length"]])) |>
201201
pivot_wider(names_from = "resource",
202202
values_from = "mean_serve_time",
203-
names_glue = "mean_serve_time_{resource}") %>%
203+
names_glue = "mean_serve_time_{resource}") |>
204204
ungroup()
205205
} else {
206206
# But if no patients are seen, create same tibble with values set to NA
@@ -239,8 +239,8 @@ calc_utilisation <- function(resources, groups = NULL, summarise = TRUE) {
239239
group_vars <- c("resource", groups)
240240

241241
# Calculate utilisation
242-
util_df <- resources %>%
243-
group_by(across(all_of(group_vars))) %>%
242+
util_df <- resources |>
243+
group_by(across(all_of(group_vars))) |>
244244
mutate(
245245
# Time between this row and the next
246246
interval_duration = lead(.data[["time"]]) - .data[["time"]],
@@ -256,18 +256,18 @@ calc_utilisation <- function(resources, groups = NULL, summarise = TRUE) {
256256

257257
# If summarise = TRUE, find total utilisation
258258
if (summarise) {
259-
util_df %>%
259+
util_df |>
260260
summarise(
261261
# Multiply each utilisation by its own unique duration. The total of
262262
# those is then divided by the total duration of all intervals.
263263
# Hence, we are calculated a time-weighted average utilisation.
264264
utilisation = (sum(.data[["utilisation"]] *
265265
.data[["interval_duration"]], na.rm = TRUE) /
266266
sum(.data[["interval_duration"]], na.rm = TRUE))
267-
) %>%
267+
) |>
268268
pivot_wider(names_from = "resource",
269269
values_from = "utilisation",
270-
names_glue = "utilisation_{resource}") %>%
270+
names_glue = "utilisation_{resource}") |>
271271
ungroup()
272272
} else {
273273
# If summarise = FALSE, just return the util_df with no further processing
@@ -288,12 +288,12 @@ calc_unseen_n <- function(arrivals, groups = NULL) {
288288
# Create list of grouping variables (always "resource", but can add others)
289289
group_vars <- c("resource", groups)
290290
# Calculate number of patients waiting
291-
arrivals %>%
292-
group_by(across(all_of(group_vars))) %>%
293-
summarise(value = sum(!is.na(.data[["wait_time_unseen"]]))) %>%
291+
arrivals |>
292+
group_by(across(all_of(group_vars))) |>
293+
summarise(value = sum(!is.na(.data[["wait_time_unseen"]]))) |>
294294
pivot_wider(names_from = "resource",
295295
values_from = "value",
296-
names_glue = "count_unseen_{resource}") %>%
296+
names_glue = "count_unseen_{resource}") |>
297297
ungroup()
298298
}
299299

@@ -311,11 +311,11 @@ calc_unseen_mean <- function(arrivals, groups = NULL) {
311311
# Create list of grouping variables (always "resource", but can add others)
312312
group_vars <- c("resource", groups)
313313
# Calculate wait time of unseen patients
314-
arrivals %>%
315-
group_by(across(all_of(group_vars))) %>%
316-
summarise(value = mean(.data[["wait_time_unseen"]], na.rm = TRUE)) %>%
314+
arrivals |>
315+
group_by(across(all_of(group_vars))) |>
316+
summarise(value = mean(.data[["wait_time_unseen"]], na.rm = TRUE)) |>
317317
pivot_wider(names_from = "resource",
318318
values_from = "value",
319-
names_glue = "mean_waiting_time_unseen_{resource}") %>%
319+
names_glue = "mean_waiting_time_unseen_{resource}") |>
320320
ungroup()
321321
}

0 commit comments

Comments
 (0)