From 6510c7b9eebb6d536117bd93bd660797f9fc7016 Mon Sep 17 00:00:00 2001 From: Matt Dray <18232097+matt-dray@users.noreply.github.com> Date: Fri, 23 Feb 2024 14:53:50 +0000 Subject: [PATCH 1/3] Add GitHub Action workflow for linting --- .github/workflows/lint.yaml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .github/workflows/lint.yaml diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml new file mode 100644 index 0000000..f60d047 --- /dev/null +++ b/.github/workflows/lint.yaml @@ -0,0 +1,32 @@ +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples +# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help +on: + push: + branches: [main, master] + pull_request: + branches: [main, master] + +name: lint + +jobs: + lint: + runs-on: ubuntu-latest + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v4 + + - uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::lintr, local::. + needs: lint + + - name: Lint + run: lintr::lint_package() + shell: Rscript {0} + env: + LINTR_ERROR_ON_LINT: true From ee6fcc5df599ce79a93fb456e2958797d39abb54 Mon Sep 17 00:00:00 2001 From: Matt Dray <18232097+matt-dray@users.noreply.github.com> Date: Fri, 23 Feb 2024 15:49:01 +0000 Subject: [PATCH 2/3] Add .lintr file to set/override defaults --- .lintr | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .lintr diff --git a/.lintr b/.lintr new file mode 100644 index 0000000..bd8bf65 --- /dev/null +++ b/.lintr @@ -0,0 +1,5 @@ +linters: linters_with_defaults( + object_usage_linter(NULL)) +exclude: "# nolint" +exclude_start: "# Begin Exclude Linting" +exclude_end: "# End Exclude Linting" From 68e37a30c287b9c63ff38515f3c78b2c6bfbd1d1 Mon Sep 17 00:00:00 2001 From: Matt Dray <18232097+matt-dray@users.noreply.github.com> Date: Fri, 23 Feb 2024 15:49:48 +0000 Subject: [PATCH 3/3] Appease linting as best as possible --- R/average_wait.R | 52 ++- R/queue_load.R | 50 +-- R/relief_capacity.R | 64 +-- R/target_capacity.R | 56 +-- R/target_queue_size.R | 65 +-- R/waiting_list_pressure.R | 46 +- tests/testthat/test-average_wait.R | 106 +++-- tests/testthat/test-queue_load.R | 104 +++-- tests/testthat/test-relief_capacity.R | 132 +++--- tests/testthat/test-target_capacity.R | 104 +++-- tests/testthat/test-target_queue_size.R | 98 ++-- tests/testthat/test-waiting_list_pressure.R | 100 ++--- vignettes/example_walkthrough.Rmd | 466 ++++++++++---------- 13 files changed, 730 insertions(+), 713 deletions(-) diff --git a/R/average_wait.R b/R/average_wait.R index 394c98d..7f80962 100644 --- a/R/average_wait.R +++ b/R/average_wait.R @@ -1,23 +1,29 @@ -#' @title Average Waiting Time -#' -#' @description This calculates the target mean wait given the two inputs of target_wait and a numerical value for factor -#' The average wait is actually the target mean wait and is calculated as follows: target_wait / factor -#' If we want to have a chance between 1.8%-0.2% of making a waiting time target, then the average patient should -#' have a waiting time between a quarter and a sixth of the target. Therefore: -#' The mean wait should sit somewhere between target_wait/factor=6 < Average Waiting Time < target_wait/factor=4 -#' -#' @param target_wait Numeric value of the number of weeks that has been set as the target within which the patient should be seen. -#' @param factor Numeric factor used in average wait calculation - to get a quarter of the target use factor=4 and one sixth of the target use factor = 6 etc. Defaults to 4. -#' -#' @return Numeric value of target mean waiting time to achieve a given target wait. -#' @export -#' -#' @examples -#' # If the target wait is 52 weeks then the target mean wait with a factor of 4 would be 13 -#' # weeks and with a factor of 6 it would be 8.67 weeks. -#' average_wait(52, 4) -#' -average_wait <- function(target_wait, factor = 4) { - target_mean_wait <- target_wait / factor - return(target_mean_wait) -} +#' @title Average Waiting Time +#' +#' @description This calculates the target mean wait given the two inputs of +#' target_wait and a numerical value for factor. The average wait is actually +#' the target mean wait and is calculated as follows: target_wait / factor. If +#' we want to have a chance between 1.8%-0.2% of making a waiting time target, +#' then the average patient should have a waiting time between a quarter and a +#' sixth of the target. Therefore: The mean wait should sit somewhere between +#' target_wait/factor=6 < Average Waiting Time < target_wait/factor=4. +#' +#' @param target_wait Numeric value of the number of weeks that has been set as +#' the target within which the patient should be seen. +#' @param factor Numeric factor used in average wait calculation - to get a +#' quarter of the target use factor=4 and one sixth of the target use factor = +#' 6 etc. Defaults to 4. +#' +#' @return Numeric value of target mean waiting time to achieve a given target +#' wait. +#' +#' @export +#' +#' @examples +#' # If the target wait is 52 weeks then the target mean wait with a factor of 4 +#' # would be 13 weeks and with a factor of 6 it would be 8.67 weeks. +#' average_wait(52, 4) +average_wait <- function(target_wait, factor = 4) { + target_mean_wait <- target_wait / factor + return(target_mean_wait) +} diff --git a/R/queue_load.R b/R/queue_load.R index ba3eb01..ab435ca 100644 --- a/R/queue_load.R +++ b/R/queue_load.R @@ -1,25 +1,25 @@ -#' @title Queue Load -#' -#' @description -#' Calculates the queue load. The queue load is the number of arrivals that occur for every patient leaving the queue (given that the -#' waiting list did not empty). -#' It could also be described as the rate of service at the queue. -#' The queue load is calculated by dividing the demand by the capacity: -#' queue_load = demand / capacity -#' -#' @param demand Numeric value of rate of demand in same units as target wait - e.g. if target wait is weeks, then demand in units of patients/week. -#' @param capacity Numeric value of the number of patients that can be served (removals) from the waiting list each week. -#' -#' @return Numeric value of load which is the ratio between demand and capacity -#' @export -#' -#' @examples -#' # If 30 patients are added to the waiting list each week (demand) and 27 removed (capacity) -#' # this results in a queue load of 1.11 (30/27) -#' queue_load(30,27) -#' -#' -queue_load <- function(demand, capacity) { - load <- demand / capacity - return (load) -} \ No newline at end of file +#' @title Calculate Queue Load +#' +#' @description Calculates the queue load. The queue load is the number of +#' arrivals that occur for every patient leaving the queue (given that the +#' waiting list did not empty). It could also be described as the rate of +#' service at the queue. The queue load is calculated by dividing the demand +#' by the capacity: queue_load = demand / capacity. +#' +#' @param demand Numeric value of rate of demand in same units as target wait - +#' e.g. if target wait is weeks, then demand in units of patients/week. +#' @param capacity Numeric value of the number of patients that can be served +#' (removals) from the waiting list each week. +#' +#' @return Numeric value of load which is the ratio between demand and capacity. +#' +#' @export +#' +#' @examples +#' # If 30 patients are added to the waiting list each week (demand) and 27 +#' removed (capacity) this results in a queue load of 1.11 (30/27). +#' queue_load(30,27) +queue_load <- function(demand, capacity) { + load <- demand / capacity + return(load) +} diff --git a/R/relief_capacity.R b/R/relief_capacity.R index 0c0758e..8e03d41 100644 --- a/R/relief_capacity.R +++ b/R/relief_capacity.R @@ -1,29 +1,35 @@ -#' @title Relief Capacity -#' -#' @description -#' Calculates required relief capacity to achieve target queue size in a given period of time as a function of demand, queue size, target queue size and time period. -#' -#' Relief Capacity is required if Queue Size > 2 * Target Queue Size. -#' -#' Relief Capacity = Current Demand + (Queue Size - Target Queue Size)/Time Steps -#' -#' @param demand Numeric value of rate of demand in same units as target wait - e.g. if target wait is weeks, then demand in units of patients/week. -#' @param queue_size Numeric value of current number of patients in queue. -#' @param target_queue_size Numeric value of desired number of patients in queue. -#' @param weeks_to_target Numeric value of desired number of time-steps to reach the target queue size by. -#' -#' @return A numeric value of the required rate of capacity to achieve a target queue size in a given period of time. -#' @export -#' -#' @examples -#' # If demand is 30 patients per week, the current queue size is 1200 and the -#' # target is to achieve a queue size of 390 in 26 weeks, then -#' -#' # Relief Capacity = 30 + (1200 - 390)/26 = 61.15 patients per week. -#' -#' relief_capacity(30, 1200, 390, 26) -#' -relief_capacity <- function(demand, queue_size, target_queue_size, weeks_to_target) { - rel_cap <- demand + (queue_size - target_queue_size) / weeks_to_target - return(rel_cap) -} +#' @title Calculate Relief Capacity +#' +#' @description Calculates required relief capacity to achieve target queue size +#' in a given period of time as a function of demand, queue size, target queue +#' size and time period. +#' +#' Relief Capacity is required if Queue Size > 2 * Target Queue Size. +#' +#' Relief Capacity = Current Demand + (Queue Size - Target Queue Size) / Time +#' Steps. +#' +#' @param demand Numeric value of rate of demand in same units as target wait - +#' e.g. if target wait is weeks, then demand in units of patients/week. +#' @param queue_size Numeric value of current number of patients in queue. +#' @param target_queue_size Numeric value of desired number of patients in +#' queue. +#' @param weeks_to_target Numeric value of desired number of time-steps to reach +#' the target queue size by. +#' +#' @return A numeric value of the required rate of capacity to achieve a target +#' queue size in a given period of time. +#' +#' @export +#' +#' @examples +#' # If demand is 30 patients per week, the current queue size is 1200 and the +#' # target is to achieve a queue size of 390 in 26 weeks, then +#' # Relief Capacity = 30 + (1200 - 390) / 26 = 61.15 patients per week. +#' +#' relief_capacity(30, 1200, 390, 26) +relief_capacity <- function( + demand, queue_size, target_queue_size, weeks_to_target) { + rel_cap <- demand + (queue_size - target_queue_size) / weeks_to_target + return(rel_cap) +} diff --git a/R/target_capacity.R b/R/target_capacity.R index 26278df..db1736a 100644 --- a/R/target_capacity.R +++ b/R/target_capacity.R @@ -1,26 +1,30 @@ -#' @title Target Capacity -#' -#' @description -#' Calculates the target capacity to achieve a given target waiting time as a function of observed demand, target waiting time and a variability coefficient F. -#' -#' Target Capacity = Demand + 2 * ( 1 + 4 * F ) / Target Wait -#' F defaults to 1. -#' -#' @param demand Numeric value of rate of demand in same units as target wait - e.g. if target wait is weeks, then demand in units of patients/week. -#' @param target_wait Numeric value of number of weeks that has been set as the target within which the patient should be seen. -#' @param F Variability coefficient, F = V/C * (D/C)^2 where C is the current number of operations per week; V is the current variance in the number of operations per week; D is the observed demand. Defaults to 1. -#' -#' @return A numeric value of target capacity required to achieve a target waiting time. -#' @export -#' -#' @examples -#' -#' # If the target wait is 52 weeks, demand is 30 patients per week and F = 3 then -#' # Target capacity = 30 + 2*(1+4*3)/52 = 30.5 patients per week. -#' -#' target_capacity(30,52,3) -#' -target_capacity <- function(demand, target_wait, F = 1) { - target_cap <- demand + 2 * ( 1 + 4 * F ) / target_wait - return(target_cap) -} +#' @title Calculate Target Capacity +#' +#' @description Calculates the target capacity to achieve a given target waiting +#' time as a function of observed demand, target waiting time and a variability +#' coefficient F. +#' +#' Target Capacity = Demand + 2 * ( 1 + 4 * F ) / Target Wait F defaults to 1. +#' +#' @param demand Numeric value of rate of demand in same units as target wait - +#' e.g. if target wait is weeks, then demand in units of patients/week. +#' @param target_wait Numeric value of number of weeks that has been set as the +#' target within which the patient should be seen. +#' @param F Variability coefficient, F = V/C * (D/C)^2 where C is the current +#' number of operations per week; V is the current variance in the number of +#' operations per week; D is the observed demand. Defaults to 1. +#' +#' @return A numeric value of target capacity required to achieve a target +#' waiting time. +#' +#' @export +#' +#' @examples +#' +#' # If the target wait is 52 weeks, demand is 30 patients per week and F = 3 +#' # then Target capacity = 30 + 2 * (1 + 4 * 3)/52 = 30.5 patients per week. +#' target_capacity(30,52,3) +target_capacity <- function(demand, target_wait, F = 1) { + target_cap <- demand + 2 * (1 + 4 * F) / target_wait + return(target_cap) +} diff --git a/R/target_queue_size.R b/R/target_queue_size.R index 8fbc988..23d522b 100644 --- a/R/target_queue_size.R +++ b/R/target_queue_size.R @@ -1,31 +1,34 @@ -#' @title Target Queue Size -#' -#' @description -#' Uses Little's Law to calculate the target queue size to achieve a target waiting time as a function of observed demand, target wait and a variability factor used in the target mean waiting time calculation. -#' -#' Target Queue Size = Demand * Target Wait / 4. -#' -#' The average wait should sit somewhere between -#' target_wait/factor=6 < Average Waiting Time < target_wait/factor=4 -#' The factor defaults to 4. -#' -#' Only applicable when Capacity > Demand. -#' -#' @param demand Numeric value of rate of demand in same units as target wait - e.g. if target wait is weeks, then demand in units of patients/week. -#' @param target_wait Numeric value of number of weeks that has been set as the target within which the patient should be seen. -#' @param factor Numeric factor used in average wait calculation - to get a quarter of the target use factor=4 and one sixth of the target use factor = 6 etc. Defaults to 4. -#' -#' @return Numeric target queue length. -#' @export -#' -#' @examples -#' # If demand is 30 patients per week and the target wait is 52 weeks, then the -#' # Target queue size = 30 * 52/4 = 390 patients. -#' -#' target_queue_size(30,52,4) -#' -target_queue_size <- function(demand, target_wait, factor = 4) { - mean_wait <- average_wait(target_wait, factor) - target_queue_length <- demand * mean_wait - return(target_queue_length) -} +#' @title Calculate Target Queue Size +#' +#' @description Uses Little's Law to calculate the target queue size to achieve +#' a target waiting time as a function of observed demand, target wait and a +#' variability factor used in the target mean waiting time calculation. +#' +#' Target Queue Size = Demand * Target Wait / 4. +#' +#' The average wait should sit somewhere between target_wait/factor=6 < +#' Average Waiting Time < target_wait/factor=4 The factor defaults to 4. +#' +#' Only applicable when Capacity > Demand. +#' +#' @param demand Numeric value of rate of demand in same units as target wait - +#' e.g. if target wait is weeks, then demand in units of patients/week. +#' @param target_wait Numeric value of number of weeks that has been set as the +#' target within which the patient should be seen. +#' @param factor Numeric factor used in average wait calculation - to get a +#' quarter of the target use factor=4 and one sixth of the target use factor = +#' 6 etc. Defaults to 4. +#' +#' @return Numeric target queue length. +#' +#' @export +#' +#' @examples +#' # If demand is 30 patients per week and the target wait is 52 weeks, then the +#' # Target queue size = 30 * 52 / 4 = 390 patients. +#' target_queue_size(30, 52, 4) +target_queue_size <- function(demand, target_wait, factor = 4) { + mean_wait <- average_wait(target_wait, factor) + target_queue_length <- demand * mean_wait + return(target_queue_length) +} diff --git a/R/waiting_list_pressure.R b/R/waiting_list_pressure.R index 2655610..a02642c 100644 --- a/R/waiting_list_pressure.R +++ b/R/waiting_list_pressure.R @@ -1,22 +1,24 @@ -#' @title Calculate the waiting list pressure -#' -#' @description For a waiting list with target waiting time, the pressure on the waiting list is twice -#' the mean delay divided by the waiting list target. -#' The pressure of any given waiting list should be less than 1. -#' If the pressure is greater than 1 then the waiting list is most likely going to miss its target. -#' The waiting list pressure is calculated as follows: -#' pressure = 2 x mean_wait / target_wait -#' -#' @param mean_wait Numeric value of target mean waiting time to achieve a given target wait -#' @param target_wait Numeric value of the number of weeks that has been set as the target within which the patient should be seen -#' -#' @return Numeric value of wait_pressure which is the waiting list pressure -#' @export -#' -#' @examples -#' waiting_list_pressure(63,52) -#' -waiting_list_pressure <- function(mean_wait, target_wait) { - wait_pressure <- 2 * mean_wait / target_wait - return(wait_pressure) -} +#' @title Calculate Waiting List Pressure +#' +#' @description For a waiting list with target waiting time, the pressure on the +#' waiting list is twice the mean delay divided by the waiting list target. +#' The pressure of any given waiting list should be less than 1. If the +#' pressure is greater than 1 then the waiting list is most likely going to +#' miss its target. The waiting list pressure is calculated as follows: +#' pressure = 2 * mean_wait / target_wait. +#' +#' @param mean_wait Numeric value of target mean waiting time to achieve a given +#' target wait. +#' @param target_wait Numeric value of the number of weeks that has been set as +#' the target within which the patient should be seen. +#' +#' @return Numeric value of wait_pressure which is the waiting list pressure. +#' +#' @export +#' +#' @examples +#' waiting_list_pressure(63, 52) +waiting_list_pressure <- function(mean_wait, target_wait) { + wait_pressure <- 2 * mean_wait / target_wait + return(wait_pressure) +} diff --git a/tests/testthat/test-average_wait.R b/tests/testthat/test-average_wait.R index 60f2afa..6351c0e 100644 --- a/tests/testthat/test-average_wait.R +++ b/tests/testthat/test-average_wait.R @@ -1,54 +1,52 @@ -# # Anticipated test from the error handling that Matt Dray is drafting -# test_that("function catches null values and reports error", { -# em <- "target_wait(): no error message when function is run with no inputs." -# expect_error(target_wait(), em) -# }) -# -# # Anticipated test from the error handling that Matt Dray is drafting -# test_that("function catches mismatched input lengths", { -# em <- "target_wait(): no error message when functions inputs are of different length." -# expect_error(target_wait(c(22,25,26), c(4, 3)), em) -# }) -# -# # Anticipated test from the error handling that Matt Dray is drafting -# test_that("it returns an error if either input aren't numeric", { -# in1 <- Sys.Date() -# in2 <- 4 -# -# em <- "target_wait(): all inputs must be numeric." -# expect_error(target_wait(in1, in2), em) -# }) - -test_that("it returns an expected result with fixed single values, against arithmetic", { - em <- "average_wait(): arithmetic error with single value inputs." - expect_equal(average_wait(52, 4), 52/4) -}) - -test_that("it returns an expected result with fixed single values", { - em <- "average_wait(): arithmetic error with single value inputs." - expect_equal(average_wait(52, 4), 13) -}) - - - -test_that("it returns an expected result with vector of fixed values", { - em <- "average_wait(): aritmetic error with vector of values as inputs." - expect_equal( - average_wait( - c(35, 30, 52), - c(4,4,6) - ) - , c(8.75, 7.5, 8.6666667) - ) -}) - - -test_that("it returns the same length output as provided on input", { - n <- round(runif(1, 0,30)) - in1 <- rnorm(n = n, 50, 20) - in2 <- rnorm(n = n, 4, 2) - em <- "target_queue_size(): output vector length != input vector length." - expect_length(average_wait(in1, in2), length(in1)) -}) - - +# # Anticipated test from the error handling that Matt Dray is drafting +# test_that("function catches null values and reports error", { +# em <- "target_wait(): no error message when function is run with no inputs." +# expect_error(target_wait(), em) +# }) +# +# # Anticipated test from the error handling that Matt Dray is drafting +# test_that("function catches mismatched input lengths", { +# em <- "target_wait(): no error message when functions inputs are of different length." +# expect_error(target_wait(c(22,25,26), c(4, 3)), em) +# }) +# +# # Anticipated test from the error handling that Matt Dray is drafting +# test_that("it returns an error if either input aren't numeric", { +# in1 <- Sys.Date() +# in2 <- 4 +# +# em <- "target_wait(): all inputs must be numeric." +# expect_error(target_wait(in1, in2), em) +# }) + +test_that("it returns expected result with fixed single values vs arithmetic", { + em <- "average_wait(): arithmetic error with single value inputs." + expect_equal(average_wait(52, 4), 52 / 4) +}) + +test_that("it returns an expected result with fixed single values", { + em <- "average_wait(): arithmetic error with single value inputs." + expect_equal(average_wait(52, 4), 13) +}) + + + +test_that("it returns an expected result with vector of fixed values", { + em <- "average_wait(): aritmetic error with vector of values as inputs." + expect_equal( + average_wait( + c(35, 30, 52), + c(4, 4, 6) + ), + c(8.75, 7.5, 8.6666667) + ) +}) + + +test_that("it returns the same length output as provided on input", { + n <- round(runif(1, 0, 30)) + in1 <- rnorm(n = n, 50, 20) + in2 <- rnorm(n = n, 4, 2) + em <- "target_queue_size(): output vector length != input vector length." + expect_length(average_wait(in1, in2), length(in1)) +}) diff --git a/tests/testthat/test-queue_load.R b/tests/testthat/test-queue_load.R index 651080a..6ddfa35 100644 --- a/tests/testthat/test-queue_load.R +++ b/tests/testthat/test-queue_load.R @@ -1,53 +1,51 @@ -# # Anticipated test from the error handling that Matt Dray is drafting -# test_that("function catches null values and reports error", { -# em <- "queue_load(): no error message when function is run with no inputs." -# expect_error(queue_load(), em) -# }) -# -# # Anticipated test from the error handling that Matt Dray is drafting -# test_that("function catches mismatched input lengths", { -# em <- "queue_load(): no error message when functions inputs are of different length." -# expect_error(queue_load(c(22,25,26), c(15, 20)), em) -# }) -# -# # Anticipated test from the error handling that Matt Dray is drafting -# test_that("it returns an error if either input aren't numeric", { -# in1 <- Sys.Date() -# in2 <- 27 -# -# em <- "queue_load(): all inputs must be numeric." -# expect_error(queue_load(in1, in2), em) -# }) - -test_that("it returns an expected result with fixed single values, against arithmetic", { - em <- "queue_load(): arithmetic error with single value inputs." - expect_equal(queue_load(30, 27), 30/27) -}) - -test_that("it returns an expected result with fixed single values", { - em <- "queue_load(): arithmetic error with single value inputs." - expect_equal(queue_load(30, 27), 1.11111111) -}) - - -test_that("it returns an expected result with vector of fixed values", { - em <- "queue_load(): arithmetic error with vector of values as inputs." - expect_equal( - queue_load( - c(35, 30, 52), - c(27,25,42) - ) - , c( 1.2962963, 1.2, 1.23809524) - ) -}) - - -test_that("it returns the same length output as provided on input", { - n <- round(runif(1, 0,30)) - in1 <- rnorm(n = n, 50, 20) - in2 <- rnorm(n = n, 30, 5) - em <- "target_queue_size(): output vector length != input vector length." - expect_length(queue_load(in1, in2), length(in1)) -}) - - +# # Anticipated test from the error handling that Matt Dray is drafting +# test_that("function catches null values and reports error", { +# em <- "queue_load(): no error message when function is run with no inputs." +# expect_error(queue_load(), em) +# }) +# +# # Anticipated test from the error handling that Matt Dray is drafting +# test_that("function catches mismatched input lengths", { +# em <- "queue_load(): no error message when functions inputs are of different length." +# expect_error(queue_load(c(22,25,26), c(15, 20)), em) +# }) +# +# # Anticipated test from the error handling that Matt Dray is drafting +# test_that("it returns an error if either input aren't numeric", { +# in1 <- Sys.Date() +# in2 <- 27 +# +# em <- "queue_load(): all inputs must be numeric." +# expect_error(queue_load(in1, in2), em) +# }) + +test_that("it returns expected result with fixed single values vs arithmetic", { + em <- "queue_load(): arithmetic error with single value inputs." + expect_equal(queue_load(30, 27), 30 / 27) +}) + +test_that("it returns an expected result with fixed single values", { + em <- "queue_load(): arithmetic error with single value inputs." + expect_equal(queue_load(30, 27), 1.11111111) +}) + + +test_that("it returns an expected result with vector of fixed values", { + em <- "queue_load(): arithmetic error with vector of values as inputs." + expect_equal( + queue_load( + c(35, 30, 52), + c(27, 25, 42) + ), + c(1.2962963, 1.2, 1.23809524) + ) +}) + + +test_that("it returns the same length output as provided on input", { + n <- round(runif(1, 0, 30)) + in1 <- rnorm(n = n, 50, 20) + in2 <- rnorm(n = n, 30, 5) + em <- "target_queue_size(): output vector length != input vector length." + expect_length(queue_load(in1, in2), length(in1)) +}) diff --git a/tests/testthat/test-relief_capacity.R b/tests/testthat/test-relief_capacity.R index d1d84b6..f95bc9f 100644 --- a/tests/testthat/test-relief_capacity.R +++ b/tests/testthat/test-relief_capacity.R @@ -1,67 +1,65 @@ -# # Anticipated test from the error handling that Matt Dray is drafting -# test_that("function catches null values and reports error", { -# em <- "relief_capacity(): no error message when function is run with no inputs." -# expect_error(relief_capacity(), em) -# }) -# -# # Anticipated test from the error handling that Matt Dray is drafting -# test_that("function catches mismatched input lengths", { -# em <- "relief_capacity(): no error message when functions inputs are of different length." -# expect_error( -# relief_capacity( -# c(30, 33, 35 ) -# , c(1200, 800, 250) -# , c(390,200) -# , c(26, 30, 15) -# ) -# , em) -# }) -# -# # Anticipated test from the error handling that Matt Dray is drafting -# test_that("it returns an error if either input aren't numeric", { -# in1 <- Sys.Date() -# in2 <- 1200 -# in3 <- 390 -# in4 <- 26 -# -# em <- "relief_capacity(): all inputs must be numeric." -# expect_error(relief_capacity(in1, in2, in3, in4), em) -# }) -#' - -test_that("it returns an expected result with fixed single values, against arithmetic", { - em <- "relief_capacity(): arithmetic error with single value inputs." - expect_equal(relief_capacity(30, 1200, 390, 26), 30 + (1200 - 390)/26) -}) - -test_that("it returns an expected result with fixed single values", { - em <- "relief_capacity(): arithmetic error with single value inputs." - expect_equal(relief_capacity(30, 1200, 390, 26), 61.153846) -}) - -test_that("it returns an expected result with vector of fixed values", { - em <- "relief_capacity(): arithmetic error with vector of input values." - expect_equal( - relief_capacity( - c(30, 33, 35 ), - c(1200, 800, 250), - c(390,200,100), - c(26, 30, 15) - ) - , c(61.153846, 53, 45) - ) -}) - - -test_that("it returns the same length output as provided on input", { - n <- round(runif(1, 0,30)) - in1 <- rnorm(n = n, 50, 20) - in2 <- in1 * (15 * runif(1,0 ,1.5)) - in3 <- in1 * (5 * runif(1,1 ,1.5)) - in4 <- in1 * (runif(1,0.5,1.5)) - - em <- "relief_capacity(): output vector length != input vector length." - expect_length(relief_capacity(in1, in2, in3, in4), length(in1)) -}) - - +# # Anticipated test from the error handling that Matt Dray is drafting +# test_that("function catches null values and reports error", { +# em <- "relief_capacity(): no error message when function is run with no inputs." +# expect_error(relief_capacity(), em) +# }) +# +# # Anticipated test from the error handling that Matt Dray is drafting +# test_that("function catches mismatched input lengths", { +# em <- "relief_capacity(): no error message when functions inputs are of different length." +# expect_error( +# relief_capacity( +# c(30, 33, 35 ) +# , c(1200, 800, 250) +# , c(390,200) +# , c(26, 30, 15) +# ) +# , em) +# }) +# +# # Anticipated test from the error handling that Matt Dray is drafting +# test_that("it returns an error if either input aren't numeric", { +# in1 <- Sys.Date() +# in2 <- 1200 +# in3 <- 390 +# in4 <- 26 +# +# em <- "relief_capacity(): all inputs must be numeric." +# expect_error(relief_capacity(in1, in2, in3, in4), em) +# }) +#' + +test_that("it returns expected result with fixed single values vs arithmetic", { + em <- "relief_capacity(): arithmetic error with single value inputs." + expect_equal(relief_capacity(30, 1200, 390, 26), 30 + (1200 - 390) / 26) +}) + +test_that("it returns an expected result with fixed single values", { + em <- "relief_capacity(): arithmetic error with single value inputs." + expect_equal(relief_capacity(30, 1200, 390, 26), 61.153846) +}) + +test_that("it returns an expected result with vector of fixed values", { + em <- "relief_capacity(): arithmetic error with vector of input values." + expect_equal( + relief_capacity( + c(30, 33, 35), + c(1200, 800, 250), + c(390, 200, 100), + c(26, 30, 15) + ), + c(61.153846, 53, 45) + ) +}) + + +test_that("it returns the same length output as provided on input", { + n <- round(runif(1, 0, 30)) + in1 <- rnorm(n = n, 50, 20) + in2 <- in1 * (15 * runif(1, 0, 1.5)) + in3 <- in1 * (5 * runif(1, 1, 1.5)) + in4 <- in1 * (runif(1, 0.5, 1.5)) + + em <- "relief_capacity(): output vector length != input vector length." + expect_length(relief_capacity(in1, in2, in3, in4), length(in1)) +}) diff --git a/tests/testthat/test-target_capacity.R b/tests/testthat/test-target_capacity.R index 94b740b..1243d4d 100644 --- a/tests/testthat/test-target_capacity.R +++ b/tests/testthat/test-target_capacity.R @@ -1,53 +1,51 @@ -# # Anticipated test from the error handling that Matt Dray is drafting -# test_that("function catches null values and reports error", { -# em <- "target_capacity(): no error message when function is run with no inputs." -# expect_error(target_capacity(), em) -# }) -# -# # Anticipated test from the error handling that Matt Dray is drafting -# test_that("function catches mismatched input lengths", { -# em <- "target_capacity(): no error message when functions inputs are of different length." -# expect_error(target_capacity(c(22,25,26), c(10, 12)), em) -# }) -# -# # Anticipated test from the error handling that Matt Dray is drafting -# test_that("it returns an error if either input aren't numeric", { -# in1 <- Sys.Date() -# in2 <- 1 -# -# em <- "target_capacity(): all inputs must be numeric." -# expect_error(target_capacity(in1, in2), em) -# }) - -test_that("it returns an expected result with fixed single values, against arithmetic", { - em <- "target_capacity(): arithmetic error with single value inputs." - expect_equal(target_capacity(30,52,3), 30 + 2*(1+4*3)/52) -}) - -test_that("it returns an expected result with fixed single values", { - em <- "target_capacity(): arithmetic error with single value inputs." - expect_equal(target_capacity(30,52,3), 30.5) -}) - -test_that("it returns an expected result with vector of fixed values", { - em <- "target_capacity(): arithmetic error with vector of input values." - expect_equal( - target_capacity( - c(30, 42, 35 ), - c(52, 65, 50), - c(3,2,1) - ) - , c(30.5, 42.276923, 35.2) - ) -}) - - -test_that("it returns the same length output as provided on input", { - n <- round(runif(1, 0,30)) - in1 <- rnorm(n = n, 50, 20) - in2 <- in1 * runif(1,0.5,1.5) - em <- "target_capacity(): output vector length != input vector length." - expect_length(target_capacity(in1, in2), length(in1)) -}) - - +# # Anticipated test from the error handling that Matt Dray is drafting +# test_that("function catches null values and reports error", { +# em <- "target_capacity(): no error message when function is run with no inputs." +# expect_error(target_capacity(), em) +# }) +# +# # Anticipated test from the error handling that Matt Dray is drafting +# test_that("function catches mismatched input lengths", { +# em <- "target_capacity(): no error message when functions inputs are of different length." +# expect_error(target_capacity(c(22,25,26), c(10, 12)), em) +# }) +# +# # Anticipated test from the error handling that Matt Dray is drafting +# test_that("it returns an error if either input aren't numeric", { +# in1 <- Sys.Date() +# in2 <- 1 +# +# em <- "target_capacity(): all inputs must be numeric." +# expect_error(target_capacity(in1, in2), em) +# }) + +test_that("it returns expected result with fixed single values vs arithmetic", { + em <- "target_capacity(): arithmetic error with single value inputs." + expect_equal(target_capacity(30, 52, 3), 30 + 2 * (1 + 4 * 3) / 52) +}) + +test_that("it returns an expected result with fixed single values", { + em <- "target_capacity(): arithmetic error with single value inputs." + expect_equal(target_capacity(30, 52, 3), 30.5) +}) + +test_that("it returns an expected result with vector of fixed values", { + em <- "target_capacity(): arithmetic error with vector of input values." + expect_equal( + target_capacity( + c(30, 42, 35), + c(52, 65, 50), + c(3, 2, 1) + ), + c(30.5, 42.276923, 35.2) + ) +}) + + +test_that("it returns the same length output as provided on input", { + n <- round(runif(1, 0, 30)) + in1 <- rnorm(n = n, 50, 20) + in2 <- in1 * runif(1, 0.5 ,1.5) + em <- "target_capacity(): output vector length != input vector length." + expect_length(target_capacity(in1, in2), length(in1)) +}) diff --git a/tests/testthat/test-target_queue_size.R b/tests/testthat/test-target_queue_size.R index 5a18e90..7e2bfe4 100644 --- a/tests/testthat/test-target_queue_size.R +++ b/tests/testthat/test-target_queue_size.R @@ -1,50 +1,48 @@ -# # Anticipated test from the error handling that Matt Dray is drafting -# test_that("function catches null values and reports error", { -# em <- "target_queue_size(): no error message when function is run with no inputs." -# expect_error(target_queue_size(), em) -# }) -# -# # Anticipated test from the error handling that Matt Dray is drafting -# test_that("function catches mismatched input lengths", { -# em <- "target_queue_size(): no error message when functions inputs are of different length." -# expect_error(target_queue_size(c(22,25,26), c(10, 12)), em) -# }) -# # Anticipated test from the error handling that Matt Dray is drafting -# test_that("it returns an error if either input aren't numeric", { -# in1 <- Sys.Date() -# in2 <- 1 -# -# em <- "target_queue_size(): all inputs must be numeric." -# expect_error(target_queue_size(in1, in2), em) -# }) - - -test_that("it returns an expected result with fixed single values", { - em <- "target_queue_size(): arithmetic error with single value inputs." - expect_equal(target_queue_size(30, 52), 390) - expect_equal(target_queue_size(30, 50), 375) - expect_equal(target_queue_size(30, 50, 6), 250) -}) - -test_that("it returns an expected result with vector of fixed values", { - em <- "target_queue_size(): arithmetic error with vector of values as inputs." - expect_equal( - target_queue_size( - c(30, 30, 30 ), - c(52, 50, 50), - c(4,4,6) - ) - , c(390, 375, 250) - ) -}) - - -test_that("it returns the same length output as provided on input", { - n <- round(runif(1, 0,30)) - in1 <- rnorm(n = n, 50, 20) - in2 <- in1 * (1.2 + runif(1,0,1.5)) - em <- "target_queue_size(): output vector length != input vector length." - expect_length(target_queue_size(in1, in2), length(in1)) -}) - - +# # Anticipated test from the error handling that Matt Dray is drafting +# test_that("function catches null values and reports error", { +# em <- "target_queue_size(): no error message when function is run with no inputs." +# expect_error(target_queue_size(), em) +# }) +# +# # Anticipated test from the error handling that Matt Dray is drafting +# test_that("function catches mismatched input lengths", { +# em <- "target_queue_size(): no error message when functions inputs are of different length." +# expect_error(target_queue_size(c(22,25,26), c(10, 12)), em) +# }) +# # Anticipated test from the error handling that Matt Dray is drafting +# test_that("it returns an error if either input aren't numeric", { +# in1 <- Sys.Date() +# in2 <- 1 +# +# em <- "target_queue_size(): all inputs must be numeric." +# expect_error(target_queue_size(in1, in2), em) +# }) + + +test_that("it returns an expected result with fixed single values", { + em <- "target_queue_size(): arithmetic error with single value inputs." + expect_equal(target_queue_size(30, 52), 390) + expect_equal(target_queue_size(30, 50), 375) + expect_equal(target_queue_size(30, 50, 6), 250) +}) + +test_that("it returns an expected result with vector of fixed values", { + em <- "target_queue_size(): arithmetic error with vector of values as inputs." + expect_equal( + target_queue_size( + c(30, 30, 30), + c(52, 50, 50), + c(4, 4, 6) + ), + c(390, 375, 250) + ) +}) + + +test_that("it returns the same length output as provided on input", { + n <- round(runif(1, 0, 30)) + in1 <- rnorm(n = n, 50, 20) + in2 <- in1 * (1.2 + runif(1, 0, 1.5)) + em <- "target_queue_size(): output vector length != input vector length." + expect_length(target_queue_size(in1, in2), length(in1)) +}) diff --git a/tests/testthat/test-waiting_list_pressure.R b/tests/testthat/test-waiting_list_pressure.R index 74f49a3..2c2af82 100644 --- a/tests/testthat/test-waiting_list_pressure.R +++ b/tests/testthat/test-waiting_list_pressure.R @@ -1,51 +1,49 @@ -# # Anticipated test from the error handling that Matt Dray is drafting -# test_that("function catches null values and reports error", { -# em <- "waiting_list_pressure(): no error message when function is run with no inputs." -# expect_error(waiting_list_pressure(), em) -# }) -# -# # Anticipated test from the error handling that Matt Dray is drafting -# test_that("function catches mismatched input lengths", { -# em <- "waiting_list_pressure(): no error message when functions inputs are of different length." -# expect_error(waiting_list_pressure(c(22,25,26), c(10, 12)), em) -# }) -# # Anticipated test from the error handling that Matt Dray is drafting -# test_that("it returns an error if either input aren't numeric", { -# in1 <- Sys.Date() -# in2 <- 1 -# -# em <- "waiting_list_pressure(): all inputs must be numeric." -# expect_error(waiting_list_pressure(in1, in2), em) -# }) - -test_that("it returns an expected result with fixed single values, against arithmetic", { - em <- "waiting_list_pressure(): arithmetic error with single value inputs." - expect_equal(waiting_list_pressure(63, 52), 2 * 63 / 52) -}) - -test_that("it returns an expected result with fixed single values", { - em <- "waiting_list_pressure(): arithmetic error with single value inputs." - expect_equal(waiting_list_pressure(63, 52), 2.42307692) -}) - -test_that("it returns an expected result with vector of fixed values", { - em <- "waiting_list_pressure(): arithmetic error with vector of input values." - expect_equal( - waiting_list_pressure( - c(63, 42, 55 ), - c(52, 24, 50) - ) - , c(2.42307692, 3.5, 2.2) - ) -}) - - -test_that("it returns the same length output as provided on input", { - n <- round(runif(1, 0,30)) - in1 <- rnorm(n = n, 50, 20) - in2 <- in1 * (1.2 + runif(1,0,1.5)) - em <- "waiting_list_pressure(): output vector length != input vector length." - expect_length(waiting_list_pressure(in1, in2), length(in1)) -}) - - +# # Anticipated test from the error handling that Matt Dray is drafting +# test_that("function catches null values and reports error", { +# em <- "waiting_list_pressure(): no error message when function is run with no inputs." +# expect_error(waiting_list_pressure(), em) +# }) +# +# # Anticipated test from the error handling that Matt Dray is drafting +# test_that("function catches mismatched input lengths", { +# em <- "waiting_list_pressure(): no error message when functions inputs are of different length." +# expect_error(waiting_list_pressure(c(22,25,26), c(10, 12)), em) +# }) +# # Anticipated test from the error handling that Matt Dray is drafting +# test_that("it returns an error if either input aren't numeric", { +# in1 <- Sys.Date() +# in2 <- 1 +# +# em <- "waiting_list_pressure(): all inputs must be numeric." +# expect_error(waiting_list_pressure(in1, in2), em) +# }) + +test_that("it returns expected result with fixed single values vs arithmetic", { + em <- "waiting_list_pressure(): arithmetic error with single value inputs." + expect_equal(waiting_list_pressure(63, 52), 2 * 63 / 52) +}) + +test_that("it returns an expected result with fixed single values", { + em <- "waiting_list_pressure(): arithmetic error with single value inputs." + expect_equal(waiting_list_pressure(63, 52), 2.42307692) +}) + +test_that("it returns an expected result with vector of fixed values", { + em <- "waiting_list_pressure(): arithmetic error with vector of input values." + expect_equal( + waiting_list_pressure( + c(63, 42, 55), + c(52, 24, 50) + ), + c(2.42307692, 3.5, 2.2) + ) +}) + + +test_that("it returns the same length output as provided on input", { + n <- round(runif(1, 0, 30)) + in1 <- rnorm(n = n, 50, 20) + in2 <- in1 * (1.2 + runif(1, 0, 1.5)) + em <- "waiting_list_pressure(): output vector length != input vector length." + expect_length(waiting_list_pressure(in1, in2), length(in1)) +}) diff --git a/vignettes/example_walkthrough.Rmd b/vignettes/example_walkthrough.Rmd index 768a5e4..49d726b 100644 --- a/vignettes/example_walkthrough.Rmd +++ b/vignettes/example_walkthrough.Rmd @@ -1,229 +1,237 @@ ---- -title: "Walkthrough a real waiting list example" -output: rmarkdown::html_vignette -vignette: > - %\VignetteIndexEntry{Walkthrough a real waiting list example} - %\VignetteEngine{knitr::rmarkdown} - %\VignetteEncoding{UTF-8} ---- - -```{r, include = FALSE} -knitr::opts_chunk$set( - collapse = TRUE, - comment = "#>" -) -``` - -```{r setup} -library(NHSRwaitinglist) -``` - -This vignette is a practical demonstration of the {NHSRwaitinglist} functions, using the same running example that is used in the reference white paper [Fong el al.](https://www.medrxiv.org/content/10.1101/2022.08.23.22279117v1.full-text), and [video](https://www.youtube.com/watch?v=NWthhW5Fgls). - -The example is centred on a P4 (priority 4) Ear, Nose & Throat (ENT) waiting list at an acute hospital. - -The package functions we will be using are: -```{r child = 'functions_table.md'} -``` - -## Setup - -First, we'll add the initial data we need, taken from the white paper. - -```{r} -# Queue size (patients) -queue_size <- 1200 - -# Waiting time target (weeks) -waiting_time_target <- 52 - -# Average waiting time in the queue (weeks) -avg_waiting_time <- 63 - -# Proportion of waiting list who have missed the 52 week target (%) -perc_missing_target <- 0.51 - -# Demand (patients per week) -demand <- 30 - -# Capacity (procedures per week) -capacity <- 27 - -# Standard deviation of number of operations per week -std_dev_procedures <- 160 - -``` - -## Demand, capacity, and load - -> Fact 1: Capacity must be larger than demand, otherwise the waiting list size will grow indefinitely. - -```{r} -load <- queue_load(demand, capacity) -load -``` - -We see that the load is `r round(load, 2)`, which is greater than 1. -The queue will therefore grow in size indefinitely. - -> Fact 2: If the load is greater than 1, then the queue is unstable, and the waiting list will grow indefinitely. -If the load is less than 1, then the queue will be stable and the load is the proportion of the time that that waiting list is non-empty. - -## Waiting list targets - -> Fact 3: If the load on a queue is less than 1 then the chance of missing the target halves each time we increase the target by some fixed number of days. - -> Fact 4: If we want to have a chance between 1.8%-0.2% of not achieving a waiting time target, then the average patient should have a waiting time between a quarter and a sixth of the target. - -In the case of a P4 waiting list, the target wait is 52 weeks. -Thus, we should expect the average patient being operated on to have waited between 9 and 13 weeks. -In the case of P2 customers, the target is 4 weeks. -Thus, the mean wait of a typical patient should be under one week. - -```{r} -average_wait <- average_wait(waiting_time_target) -average_wait -``` - -We see that the average wait is `r average_wait` weeks. - -## Target queue length - -> Fact 5: Little's Law. Assuming capacity exceeds demand, the average queue size is demand multiplied by average waiting time. - -If, as given in Fact 4 above, we want the average waiting time to be a quarter of the target, then Little's Law leads to fact 6. - -## Target queue size - -> Fact 6: Target queue size is demand multiplied by target wait, divided by 4. - -```{r} -target_queue_size <- target_queue_size(demand, waiting_time_target) -target_queue_size - -queue_ratio <- queue_size / target_queue_size -queue_ratio -``` - -In this example, the target queue size is `r target_queue_size`, and the actual queue is `r queue_size`. -The queue ratio is `r round(queue_ratio, 1)`, meaning that the queue is `r round(queue_ratio, 1)` times its target size. - -If the waiting list size is over twice the target queue size, then we consider that special measures are needed to increase capacity, and reduce waiting list size. - -## Relief capacity - -> Fact 7: If the actual queue size is more than double the target queue size, then decide on a target date by which the queue will be brought down, and apply the necessary relief capacity. - -```{r} -weeks_until_target_acheived <- 26 - -relief_capacity <- relief_capacity( - demand = demand, - queue_size = queue_size, - target_queue_size = target_queue_size, - weeks_to_target = weeks_until_target_acheived -) -relief_capacity - -``` - -In this example, we decide that the target should be achieved by the start of the summer, 26 weeks away. -To do this, the capacity needed is `r round(relief_capacity, 1)` procedures per week, compared to `r capacity` procedures per week currently being performed. - -## Target capacity - -As discussed above if the queue size is more than double its target then capacity should be increased temporarily. -However, once the queue size is within an acceptable range, we can maintain the waiting time target with what is potentially a much smaller capacity allocation to the waiting list. - -We know the waiting time (`r average_wait` weeks) and queue size (`r target_queue_size` patients) of a waiting list operating at its target equilibrium. -Now we calculate a capacity allocation that will maintain this equilibrium in the long run. - -> Fact 8: Target capacity formula, based on the Pollaczek-Khinchine formula. -The target capacity depends on demand, plus an additional capacity which is based on serice variability, and the waiting time target. - -The parameter "F" depends on the variability of the service. -If we do not know F, we can assume F = 1. -Values less than 1 are good. -Higher values represent more variability, which in turn will increase the capacity required to maintain equilibrium. - -```{r} -# set the "F" variability parameter -f_1 <- 1 - -target_capacity_1 <- target_capacity( - demand = demand, - target_wait = waiting_time_target, - F = f_1) -target_capacity_1 -``` - -If F is `r f_1`, we can see that the capacity required is `r round(target_capacity_1, 2)`, or `r round(target_capacity_1 - demand, 2)` more than the demand. - -```{r} -f_2 <- 6.58 - -target_capacity_2 <- target_capacity( - demand = demand, - target_wait = waiting_time_target, - F = f_2) -target_capacity_2 -``` - -If F is `r f_2`, we can see that the capacity required is `r round(target_capacity_2, 2)`, or `r round(target_capacity_2 - demand, 2)` more than the demand. - -So, decreasing variability of service (for example by stabilising operating theatre schedules from day to day and week to week) has the effect of reducing the capacity required to achieve a given service waiting standard. - - - -## Waiting list pressure - -> Fact 9: Waiting list pressure. For a waiting list with target waiting time, the pressure on the waiting list is twice the mean waiting time divided by the target waiting time. -The pressure of any given waiting list should be less than 1. -If the pressure is greater than 1 then the waiting list is most likely going to miss its target. - - - -Measuring waiting list pressure can give a comparative measure with which to compare waiting lists, and help make resource allocation decisions. - -For the P4 ENT example we have been following: - -```{r} -waiting_list_pressure_p4 <- waiting_list_pressure(avg_waiting_time, waiting_time_target) -waiting_list_pressure_p4 -``` - -The queue size is large, with `r queue_size` patients waiting. -The waiting time target is `r waiting_time_target` weeks, and the average waiting time being experienced is `r avg_waiting_time` weeks. -This gives a waiting list pressure of `r round(waiting_list_pressure_p4, 2)`. -**NOTE** these numbers are slightly different to the [white paper](https://www.medrxiv.org/content/10.1101/2022.08.23.22279117v1.full-text), which changes the average waiting time from 63 weeks to 61 weeks during the example. - -If we look at the P2 ENT example: - -```{r} -queue_size_p2 <- 220 -avg_waiting_time_p2 <- 24 -waiting_time_target_p2 <- 4 - -waiting_list_pressure_p2 <- waiting_list_pressure(avg_waiting_time_p2, waiting_time_target_p2) -waiting_list_pressure_p2 -``` - -The queue size is smaller, with `r queue_size_p2` patients waiting. -The waiting time target is `r waiting_time_target_p2` weeks, and the average waiting time being experienced is `r avg_waiting_time_p2`. -This gives a waiting list pressure of `r round(waiting_list_pressure_p2, 2)`. - -In these two examples the pressure on the much shorter P2 waiting list is 5 times higher than that on the P4 list. -Closer attention should be paid to P2 procedures. - -## Summary - -This worked example aims to demonstrate the functions available in this package. -In chronological order of application they were: -```{r child = 'functions_table.md'} -``` - -## Further reading - -For examples of practical applications, and other considerations, see the helpful "Case Studies" section towards the end of the [white paper](https://www.medrxiv.org/content/10.1101/2022.08.23.22279117v1.full-text). - -END +--- +title: "Walkthrough a real waiting list example" +output: rmarkdown::html_vignette +vignette: > + %\VignetteIndexEntry{Walkthrough a real waiting list example} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +```{r, include = FALSE} +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>" +) +``` + +```{r setup} +library(NHSRwaitinglist) +``` + +This vignette is a practical demonstration of the {NHSRwaitinglist} functions, using the same running example that is used in the reference white paper [Fong el al.](https://www.medrxiv.org/content/10.1101/2022.08.23.22279117v1.full-text), and [video](https://www.youtube.com/watch?v=NWthhW5Fgls). + +The example is centred on a P4 (priority 4) Ear, Nose & Throat (ENT) waiting list at an acute hospital. + +The package functions we will be using are: +```{r child = 'functions_table.md'} +``` + +## Setup + +First, we'll add the initial data we need, taken from the white paper. + +```{r} +# Queue size (patients) +queue_size <- 1200 + +# Waiting time target (weeks) +waiting_time_target <- 52 + +# Average waiting time in the queue (weeks) +avg_waiting_time <- 63 + +# Proportion of waiting list who have missed the 52 week target (%) +perc_missing_target <- 0.51 + +# Demand (patients per week) +demand <- 30 + +# Capacity (procedures per week) +capacity <- 27 + +# Standard deviation of number of operations per week +std_dev_procedures <- 160 + +``` + +## Demand, capacity, and load + +> Fact 1: Capacity must be larger than demand, otherwise the waiting list size will grow indefinitely. + +```{r} +load <- queue_load(demand, capacity) +load +``` + +We see that the load is `r round(load, 2)`, which is greater than 1. +The queue will therefore grow in size indefinitely. + +> Fact 2: If the load is greater than 1, then the queue is unstable, and the waiting list will grow indefinitely. +If the load is less than 1, then the queue will be stable and the load is the proportion of the time that that waiting list is non-empty. + +## Waiting list targets + +> Fact 3: If the load on a queue is less than 1 then the chance of missing the target halves each time we increase the target by some fixed number of days. + +> Fact 4: If we want to have a chance between 1.8%-0.2% of not achieving a waiting time target, then the average patient should have a waiting time between a quarter and a sixth of the target. + +In the case of a P4 waiting list, the target wait is 52 weeks. +Thus, we should expect the average patient being operated on to have waited between 9 and 13 weeks. +In the case of P2 customers, the target is 4 weeks. +Thus, the mean wait of a typical patient should be under one week. + +```{r} +average_wait <- average_wait(waiting_time_target) +average_wait +``` + +We see that the average wait is `r average_wait` weeks. + +## Target queue length + +> Fact 5: Little's Law. Assuming capacity exceeds demand, the average queue size is demand multiplied by average waiting time. + +If, as given in Fact 4 above, we want the average waiting time to be a quarter of the target, then Little's Law leads to fact 6. + +## Target queue size + +> Fact 6: Target queue size is demand multiplied by target wait, divided by 4. + +```{r} +target_queue_size <- target_queue_size(demand, waiting_time_target) +target_queue_size + +queue_ratio <- queue_size / target_queue_size +queue_ratio +``` + +In this example, the target queue size is `r target_queue_size`, and the actual queue is `r queue_size`. +The queue ratio is `r round(queue_ratio, 1)`, meaning that the queue is `r round(queue_ratio, 1)` times its target size. + +If the waiting list size is over twice the target queue size, then we consider that special measures are needed to increase capacity, and reduce waiting list size. + +## Relief capacity + +> Fact 7: If the actual queue size is more than double the target queue size, then decide on a target date by which the queue will be brought down, and apply the necessary relief capacity. + +```{r} +weeks_until_target_acheived <- 26 + +relief_capacity <- relief_capacity( + demand = demand, + queue_size = queue_size, + target_queue_size = target_queue_size, + weeks_to_target = weeks_until_target_acheived +) +relief_capacity + +``` + +In this example, we decide that the target should be achieved by the start of the summer, 26 weeks away. +To do this, the capacity needed is `r round(relief_capacity, 1)` procedures per week, compared to `r capacity` procedures per week currently being performed. + +## Target capacity + +As discussed above if the queue size is more than double its target then capacity should be increased temporarily. +However, once the queue size is within an acceptable range, we can maintain the waiting time target with what is potentially a much smaller capacity allocation to the waiting list. + +We know the waiting time (`r average_wait` weeks) and queue size (`r target_queue_size` patients) of a waiting list operating at its target equilibrium. +Now we calculate a capacity allocation that will maintain this equilibrium in the long run. + +> Fact 8: Target capacity formula, based on the Pollaczek-Khinchine formula. +The target capacity depends on demand, plus an additional capacity which is based on serice variability, and the waiting time target. + +The parameter "F" depends on the variability of the service. +If we do not know F, we can assume F = 1. +Values less than 1 are good. +Higher values represent more variability, which in turn will increase the capacity required to maintain equilibrium. + +```{r} +# set the "F" variability parameter +f_1 <- 1 + +target_capacity_1 <- target_capacity( + demand = demand, + target_wait = waiting_time_target, + F = f_1 +) +target_capacity_1 +``` + +If F is `r f_1`, we can see that the capacity required is `r round(target_capacity_1, 2)`, or `r round(target_capacity_1 - demand, 2)` more than the demand. + +```{r} +f_2 <- 6.58 + +target_capacity_2 <- target_capacity( + demand = demand, + target_wait = waiting_time_target, + F = f_2 +) +target_capacity_2 +``` + +If F is `r f_2`, we can see that the capacity required is `r round(target_capacity_2, 2)`, or `r round(target_capacity_2 - demand, 2)` more than the demand. + +So, decreasing variability of service (for example by stabilising operating theatre schedules from day to day and week to week) has the effect of reducing the capacity required to achieve a given service waiting standard. + + + +## Waiting list pressure + +> Fact 9: Waiting list pressure. For a waiting list with target waiting time, the pressure on the waiting list is twice the mean waiting time divided by the target waiting time. +The pressure of any given waiting list should be less than 1. +If the pressure is greater than 1 then the waiting list is most likely going to miss its target. + + + +Measuring waiting list pressure can give a comparative measure with which to compare waiting lists, and help make resource allocation decisions. + +For the P4 ENT example we have been following: + +```{r} +waiting_list_pressure_p4 <- waiting_list_pressure( + avg_waiting_time, + waiting_time_target +) +waiting_list_pressure_p4 +``` + +The queue size is large, with `r queue_size` patients waiting. +The waiting time target is `r waiting_time_target` weeks, and the average waiting time being experienced is `r avg_waiting_time` weeks. +This gives a waiting list pressure of `r round(waiting_list_pressure_p4, 2)`. +**NOTE** these numbers are slightly different to the [white paper](https://www.medrxiv.org/content/10.1101/2022.08.23.22279117v1.full-text), which changes the average waiting time from 63 weeks to 61 weeks during the example. + +If we look at the P2 ENT example: + +```{r} +queue_size_p2 <- 220 +avg_waiting_time_p2 <- 24 +waiting_time_target_p2 <- 4 + +waiting_list_pressure_p2 <- waiting_list_pressure( + avg_waiting_time_p2, + waiting_time_target_p2 +) +waiting_list_pressure_p2 +``` + +The queue size is smaller, with `r queue_size_p2` patients waiting. +The waiting time target is `r waiting_time_target_p2` weeks, and the average waiting time being experienced is `r avg_waiting_time_p2`. +This gives a waiting list pressure of `r round(waiting_list_pressure_p2, 2)`. + +In these two examples the pressure on the much shorter P2 waiting list is 5 times higher than that on the P4 list. +Closer attention should be paid to P2 procedures. + +## Summary + +This worked example aims to demonstrate the functions available in this package. +In chronological order of application they were: +```{r child = 'functions_table.md'} +``` + +## Further reading + +For examples of practical applications, and other considerations, see the helpful "Case Studies" section towards the end of the [white paper](https://www.medrxiv.org/content/10.1101/2022.08.23.22279117v1.full-text). + +END