From 154926f799bda894489ef2cea0cdec37012593d7 Mon Sep 17 00:00:00 2001 From: Tom Smith Date: Fri, 26 Apr 2024 22:29:36 +0100 Subject: [PATCH 01/10] fix typo in function output --- R/wl_removal_stats.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/wl_removal_stats.R b/R/wl_removal_stats.R index b1a917d..e6420c1 100644 --- a/R/wl_removal_stats.R +++ b/R/wl_removal_stats.R @@ -69,7 +69,7 @@ wl_removal_stats <- function(waiting_list, removal_stats <- data.frame( "capacity.weekly" = capacity_weekly, - "capcity.daily" = capacity, + "capacity.daily" = capacity, "capacity.cov" = cv_removal, "removal.count" = num_removals ) From 66d734b7b647339d3587934e54471f621dca3e22 Mon Sep 17 00:00:00 2001 From: Tom Smith Date: Fri, 26 Apr 2024 23:50:26 +0100 Subject: [PATCH 02/10] end to end vignette example --- vignettes/example_waiting_list_functions.Rmd | 166 +++++++++++++++++++ 1 file changed, 166 insertions(+) diff --git a/vignettes/example_waiting_list_functions.Rmd b/vignettes/example_waiting_list_functions.Rmd index 5916a16..789cb16 100755 --- a/vignettes/example_waiting_list_functions.Rmd +++ b/vignettes/example_waiting_list_functions.Rmd @@ -6,3 +6,169 @@ vignette: > %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- + + +```{r, include = FALSE} +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>" +) +``` + +```{r setup} +library(NHSRwaitinglist) +library(ggplot2) + +# set a seed so that these plots are always the same +set.seed(1) +``` + +This vignette is a worked example using a sample dataset similar to that which you may be working with. + +In it's purest form, a waiting list consists of the dates that individuals arrived in a queue, and the dates that they left. They might leave the queue because they have been seen, or because they got bored and opted out. + +These are waiting list additions, and waiting list removals. + +## Simulating a waiting list + +So first we need a waiting list, and we can make a synthetic one using the `wl_simulator()` function. We decide how long our simulation should run for, and what our weekly demand and capacity is. In the example below the capacity is less than the demand, so over time we should expect a queue to form. + +```{r} +waiting_list <- wl_simulator( + start_date = "2020-01-01", + end_date = "2024-03-31", + demand = 10, # simulating 10 patient arrivals per week + capacity = 9 # simulating 9 patients being treated per week (removed from the waiting list) +) + +head(waiting_list, 10) + +``` + +Now that we have a waiting list, we should visualise it. We can use the `wl_queue_size()` function to tell us the size of the queue at the end of each day. We can use ggplot to make a plot of the queue size over time, and as expected it gets larger and larger because our demand is bigger than our capacity. + +```{r} +# calculate the queue size +queue_size <- wl_queue_size(waiting_list) + +head(queue_size) + +tail(queue_size) + +# visualise the queue with a plot +ggplot(queue_size, aes(dates, queue_size)) + + geom_line() + +``` + +### Referral statistics + +Next, we might be interested in some statistics about the referrals, or arrivals, to the queue. We can use the `wl_referral_stats()` function to calculate these. + +```{r} +referral_stats <- wl_referral_stats(waiting_list) + +head(referral_stats) +``` + +Now we can see that `r referral_stats$demand.count` patients joined our simulated waiting list, at an average rate of `r round(referral_stats$demand.weekly, 2)` per week, or `r round(referral_stats$demand.daily, 2)` per day. Very close to the 10 patients a week we requested when we made our simulated waiting list using `wl_simulator()`. The final statistic of interest is the coefficient of variation, which is `r round(referral_stats$demand.cov, 2)`. + +### Removal statistics + +Similarly, we might be interested in some statistics about the removals from the queue. We can use the `wl_removal_stats()` function to calculate these. + +```{r} +removal_stats <- wl_removal_stats(waiting_list) + +head(removal_stats) +``` + +Now we can see that `r removal_stats$removal.count` patients were treated and removed from our simulated waiting list, at an average rate of `r round(removal_stats$capacity.weekly, 2)` per week, or `r round(removal_stats$capacity.daily, 2)` per day. Very close to the 9 patients a week we set up using `wl_simulator()`. The final statistic of interest is the coefficient of variation (for removals), which is `r round(removal_stats$capacity.cov, 2)`. + +### Overall stats + +Finally, we can calculate a combined set of statistics to summarise the waiting list. To do this we need to provide the target waiting time. This might be 2 weeks for a cancer referral, or commonly 18 weeks for a standard non-cancer referral. + +```{r} +overall_stats <- wl_stats( + waiting_list = waiting_list, + target_wait = 18 # standard NHS 18wk target +) + +head(overall_stats) +``` + +This gives us a lot of useful information. Taking it step by step: + +The first 4 columns tell us whether the load is larger than 1. If it is, we can expect the queue to continue growing indefinitely. + +```{r, echo=FALSE} +knitr::kable( + overall_stats |> dplyr::select( + mean.demand, + mean.capacity, + load, + load.too.big + ), + align = "c" +) + +``` + +The next columns tell us about the resulting queue size at the end of our simulation, the target size we need to plan for in order to achieve the 18 week waiting target, and a judgement about whether the queue is too large. If the queue is too large, we need to implement some relief capacity to bring it within range before attempting to maintain the queue. + +```{r, echo=FALSE} +knitr::kable( + overall_stats|> dplyr::select( + queue_size, + target_queue_size, + queue.too.big, + mean_wait + ), + align = "c" +) + +``` + +There is a column to report the actual average patient waiting time, which is `r round(overall_stats$mean_wait, 2)` weeks, compared to our target of 18 weeks. + +```{r, echo=FALSE} +knitr::kable( + overall_stats |> dplyr::select(mean_wait), + align = "c" +) + +``` + +These two columns re-state the coefficients of variance for use in reporting. + +```{r, echo=FALSE} +knitr::kable( + overall_stats |> dplyr::select( + cv_arrival, + cv_removal + ), + align = "c" +) +``` + +The next two columns tell us about the required capacity. Only one will contain data. + +1. If the queue is not too large, "target.capacity" will report the capacity required to maintain the queue at it's target waiting time performance. +2. If the queue is too large, "relief.capacity" will report the capacity required to bring the queue to a maintainable size within 26 weeks (6 months). + +```{r, echo=FALSE} +knitr::kable( + overall_stats |> dplyr::select(target.capacity, relief.capacity), + align = "c" +) +``` + +The final column reports the waiting list "pressure". This will be useful later when comparing waiting lists of differing sizes, with differing targets. It allows waiting list pressures to be compared because the waiting list with the largest number of patients waiting is not always the list with the largest problem meeting it's target. + +```{r, echo=FALSE} +knitr::kable( + overall_stats |> dplyr::select(pressure), + align = "c" +) +``` \ No newline at end of file From 5b8219409161aefe435b70691b227880af2909b1 Mon Sep 17 00:00:00 2001 From: Tom Smith Date: Sat, 27 Apr 2024 00:15:54 +0100 Subject: [PATCH 03/10] add finely balanced example --- vignettes/example_waiting_list_functions.Rmd | 124 ++++++++++++++++++- 1 file changed, 120 insertions(+), 4 deletions(-) diff --git a/vignettes/example_waiting_list_functions.Rmd b/vignettes/example_waiting_list_functions.Rmd index 789cb16..b34431d 100755 --- a/vignettes/example_waiting_list_functions.Rmd +++ b/vignettes/example_waiting_list_functions.Rmd @@ -20,16 +20,20 @@ library(NHSRwaitinglist) library(ggplot2) # set a seed so that these plots are always the same -set.seed(1) +set.seed(2) ``` This vignette is a worked example using a sample dataset similar to that which you may be working with. -In it's purest form, a waiting list consists of the dates that individuals arrived in a queue, and the dates that they left. They might leave the queue because they have been seen, or because they got bored and opted out. +In it's purest form, a waiting list consists of the dates that individuals arrived in a queue, and the dates that they left having been seen by the service (doctor, nurse, or diagnostic test, etc). These dates are the are waiting list additions or arrivals, and waiting list removals. They correspond to demand (for arrivals), and capacity (for removals). -These are waiting list additions, and waiting list removals. +This vignette is going to simulate 3 different waiting lists: -## Simulating a waiting list +1. A list where demand is higher than capacity +2. A list where demand and capacity are similar +3. A list where there is sufficient capacity for the demand + +## 1. A growing waiting list So first we need a waiting list, and we can make a synthetic one using the `wl_simulator()` function. We decide how long our simulation should run for, and what our weekly demand and capacity is. In the example below the capacity is less than the demand, so over time we should expect a queue to form. @@ -166,6 +170,118 @@ knitr::kable( The final column reports the waiting list "pressure". This will be useful later when comparing waiting lists of differing sizes, with differing targets. It allows waiting list pressures to be compared because the waiting list with the largest number of patients waiting is not always the list with the largest problem meeting it's target. +```{r, echo=FALSE} +knitr::kable( + overall_stats |> dplyr::select(pressure), + align = "c" +) +``` + +## 2. A finely balanced waiting list + +This waiting list is finely balanced. Capacity is larger than demand, but not by a wide margin (there is approximately 5% "spare" capacity). + +```{r} +waiting_list <- wl_simulator( + start_date = "2020-01-01", + end_date = "2024-03-31", + demand = 10, # simulating 10 patient arrivals per week + capacity = 10.2 # simulating 10.2 patients being treated per week +) +``` + +Now we can visualise the new queue. +as expected it gets larger and larger because our demand is bigger than our capacity. + +```{r} +# calculate the queue size +queue_size <- wl_queue_size(waiting_list) + +# visualise the queue with a plot +ggplot(queue_size, aes(dates, queue_size)) + + geom_line() + +``` + +This time we will go straight to calculating the overall stats. + +```{r} +overall_stats <- wl_stats( + waiting_list = waiting_list, + target_wait = 18 # standard NHS 18wk target +) + +head(overall_stats) +``` + +This gives us a lot of useful information. Taking it step by step: + +The first 4 columns tell us whether the load is larger than 1. If it is, we can expect the queue to continue growing indefinitely. + +```{r, echo=FALSE} +knitr::kable( + overall_stats |> dplyr::select( + mean.demand, + mean.capacity, + load, + load.too.big + ), + align = "c" +) + +``` + +The next columns tell us about the resulting queue size at the end of our simulation, the target size we need to plan for in order to achieve the 18 week waiting target, and a judgement about whether the queue is too large. If the queue is too large, we need to implement some relief capacity to bring it within range before attempting to maintain the queue. + +```{r, echo=FALSE} +knitr::kable( + overall_stats|> dplyr::select( + queue_size, + target_queue_size, + queue.too.big, + mean_wait + ), + align = "c" +) + +``` + +There is a column to report the actual average patient waiting time, which is `r round(overall_stats$mean_wait, 2)` weeks, compared to our target of 18 weeks. + +```{r, echo=FALSE} +knitr::kable( + overall_stats |> dplyr::select(mean_wait), + align = "c" +) + +``` + +These two columns re-state the coefficients of variance for use in reporting. + +```{r, echo=FALSE} +knitr::kable( + overall_stats |> dplyr::select( + cv_arrival, + cv_removal + ), + align = "c" +) +``` + +The next two columns tell us about the required capacity. Only one will contain data. + +1. If the queue is not too large, "target.capacity" will report the capacity required to maintain the queue at it's target waiting time performance. +2. If the queue is too large, "relief.capacity" will report the capacity required to bring the queue to a maintainable size within 26 weeks (6 months). + +```{r, echo=FALSE} +knitr::kable( + overall_stats |> dplyr::select(target.capacity, relief.capacity), + align = "c" +) +``` + +The final column reports the waiting list "pressure". This will be useful later when comparing waiting lists of differing sizes, with differing targets. It allows waiting list pressures to be compared because the waiting list with the largest number of patients waiting is not always the list with the largest problem meeting it's target. + ```{r, echo=FALSE} knitr::kable( overall_stats |> dplyr::select(pressure), From 9c033cb61254121d78f742099658cbf8d6324769 Mon Sep 17 00:00:00 2001 From: Tom Smith Date: Wed, 1 May 2024 15:04:50 +0100 Subject: [PATCH 04/10] add a finely balanced example --- vignettes/example_waiting_list_functions.Rmd | 100 +++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/vignettes/example_waiting_list_functions.Rmd b/vignettes/example_waiting_list_functions.Rmd index b34431d..efdee7e 100755 --- a/vignettes/example_waiting_list_functions.Rmd +++ b/vignettes/example_waiting_list_functions.Rmd @@ -282,6 +282,106 @@ knitr::kable( The final column reports the waiting list "pressure". This will be useful later when comparing waiting lists of differing sizes, with differing targets. It allows waiting list pressures to be compared because the waiting list with the largest number of patients waiting is not always the list with the largest problem meeting it's target. +```{r, echo=FALSE} +knitr::kable( + overall_stats |> dplyr::select(pressure), + align = "c" +) +``` +``` + +Now we can visualise the new queue. +as expected it gets larger and larger because our demand is bigger than our capacity. + +```{r} +# calculate the queue size +queue_size <- wl_queue_size(waiting_list) + +# visualise the queue with a plot +ggplot(queue_size, aes(dates, queue_size)) + + geom_line() + +``` + +This time we will go straight to calculating the overall stats. + +```{r} +overall_stats <- wl_stats( + waiting_list = waiting_list, + target_wait = 18 # standard NHS 18wk target +) + +head(overall_stats) +``` + +This gives us a lot of useful information. Taking it step by step: + +The first 4 columns tell us whether the load is larger than 1. If it is, we can expect the queue to continue growing indefinitely. + +```{r, echo=FALSE} +knitr::kable( + overall_stats |> dplyr::select( + mean.demand, + mean.capacity, + load, + load.too.big + ), + align = "c" +) + +``` + +The next columns tell us about the resulting queue size at the end of our simulation, the target size we need to plan for in order to achieve the 18 week waiting target, and a judgement about whether the queue is too large. If the queue is too large, we need to implement some relief capacity to bring it within range before attempting to maintain the queue. + +```{r, echo=FALSE} +knitr::kable( + overall_stats|> dplyr::select( + queue_size, + target_queue_size, + queue.too.big, + mean_wait + ), + align = "c" +) + +``` + +There is a column to report the actual average patient waiting time, which is `r round(overall_stats$mean_wait, 2)` weeks, compared to our target of 18 weeks. + +```{r, echo=FALSE} +knitr::kable( + overall_stats |> dplyr::select(mean_wait), + align = "c" +) + +``` + +These two columns re-state the coefficients of variance for use in reporting. + +```{r, echo=FALSE} +knitr::kable( + overall_stats |> dplyr::select( + cv_arrival, + cv_removal + ), + align = "c" +) +``` + +The next two columns tell us about the required capacity. Only one will contain data. + +1. If the queue is not too large, "target.capacity" will report the capacity required to maintain the queue at it's target waiting time performance. +2. If the queue is too large, "relief.capacity" will report the capacity required to bring the queue to a maintainable size within 26 weeks (6 months). + +```{r, echo=FALSE} +knitr::kable( + overall_stats |> dplyr::select(target.capacity, relief.capacity), + align = "c" +) +``` + +The final column reports the waiting list "pressure". This will be useful later when comparing waiting lists of differing sizes, with differing targets. It allows waiting list pressures to be compared because the waiting list with the largest number of patients waiting is not always the list with the largest problem meeting it's target. + ```{r, echo=FALSE} knitr::kable( overall_stats |> dplyr::select(pressure), From d2dfe1967db25d330d67f1351d8e73f515983966 Mon Sep 17 00:00:00 2001 From: Tom Smith Date: Wed, 1 May 2024 15:11:50 +0100 Subject: [PATCH 05/10] add ggplot2 to dependencies --- DESCRIPTION | 1 + 1 file changed, 1 insertion(+) diff --git a/DESCRIPTION b/DESCRIPTION index 91b0cd6..ed0f011 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -23,6 +23,7 @@ Config/testthat/edition: 3 Imports: cli, dplyr, + ggplot2, rlang Suggests: knitr, From 6f42a79687e4504348a5ccaf03725c7618abfac4 Mon Sep 17 00:00:00 2001 From: Tom Smith Date: Wed, 17 Jul 2024 21:18:18 +0100 Subject: [PATCH 06/10] finished final example --- vignettes/example_waiting_list_functions.Rmd | 192 ++++++------------- 1 file changed, 54 insertions(+), 138 deletions(-) diff --git a/vignettes/example_waiting_list_functions.Rmd b/vignettes/example_waiting_list_functions.Rmd index efdee7e..b7ada8d 100755 --- a/vignettes/example_waiting_list_functions.Rmd +++ b/vignettes/example_waiting_list_functions.Rmd @@ -25,15 +25,16 @@ set.seed(2) This vignette is a worked example using a sample dataset similar to that which you may be working with. -In it's purest form, a waiting list consists of the dates that individuals arrived in a queue, and the dates that they left having been seen by the service (doctor, nurse, or diagnostic test, etc). These dates are the are waiting list additions or arrivals, and waiting list removals. They correspond to demand (for arrivals), and capacity (for removals). +In it's purest form, a waiting list consists of the dates that individuals arrived in a queue, and the dates that they left having been seen by the service (doctor, nurse, or diagnostic test, etc). These dates are the are waiting list additions (or arrivals, referrals), and waiting list removals (or treatments, discharges). They correspond to demand (for arrivals), and capacity (for removals). This vignette is going to simulate 3 different waiting lists: -1. A list where demand is higher than capacity -2. A list where demand and capacity are similar -3. A list where there is sufficient capacity for the demand +1. [A list where demand is higher than capacity](#one) +2. [A list where demand and capacity are similar](#two) +3. [A list where there is sufficient capacity for the demand](#three) -## 1. A growing waiting list +## 1. A growing waiting list {#one} +[Back to top...](#) So first we need a waiting list, and we can make a synthetic one using the `wl_simulator()` function. We decide how long our simulation should run for, and what our weekly demand and capacity is. In the example below the capacity is less than the demand, so over time we should expect a queue to form. @@ -177,9 +178,10 @@ knitr::kable( ) ``` -## 2. A finely balanced waiting list +## 2. A finely balanced waiting list {#two} +[Back to top...](#) -This waiting list is finely balanced. Capacity is larger than demand, but not by a wide margin (there is approximately 5% "spare" capacity). +The waiting list in this section is very finely balanced. The demand remains the same as the last example, but now capacity has been increased to be slightly larger than demand. It is not significantly larger (there is approximately 2% "spare"). ```{r} waiting_list <- wl_simulator( @@ -188,15 +190,23 @@ waiting_list <- wl_simulator( demand = 10, # simulating 10 patient arrivals per week capacity = 10.2 # simulating 10.2 patients being treated per week ) -``` -Now we can visualise the new queue. -as expected it gets larger and larger because our demand is bigger than our capacity. +referral_stats <- wl_referral_stats(waiting_list) +head(referral_stats) + +removal_stats <- wl_removal_stats(waiting_list) +head(removal_stats) -```{r} # calculate the queue size queue_size <- wl_queue_size(waiting_list) +``` +This time we processed `r removal_stats$removal.count` patients. +The increase in capacity not only allowed processing more patients, it also changed the shape of the queue. +Visualising the queue we can see that this time it did not grow uncontrollably, reaching a maximum size of `r max(queue_size$queue_size)` patients waiting over the same time period as the first simulation. +It also returned to zero length several times during the simulated period. + +```{r} # visualise the queue with a plot ggplot(queue_size, aes(dates, queue_size)) + geom_line() @@ -214,96 +224,50 @@ overall_stats <- wl_stats( head(overall_stats) ``` -This gives us a lot of useful information. Taking it step by step: +In this finely balanced example, the mean demand and mean capacity give a load very close to 1, at `r round(overall_stats$load, 4)`. While this is less than one, it is perhaps a little too close for comfort. -The first 4 columns tell us whether the load is larger than 1. If it is, we can expect the queue to continue growing indefinitely. +We can see that the finishing queue size is `r overall_stats$queue_size`, but as discussed above the waiting list fluctuated in size, and even returned to zero a couple of times during the simulated period. It has not grown uncontrollably as in the first example. -```{r, echo=FALSE} -knitr::kable( - overall_stats |> dplyr::select( - mean.demand, - mean.capacity, - load, - load.too.big - ), - align = "c" -) +The mean wait is `r round(overall_stats$mean_wait, 2)`, which is less than the target of 18 weeks, but is more than a quarter of the target. The exponential shape of waiting list distributions means that in this system we would expect more than a reasonable number of patients to be experiencing waiting times of over 18wks. -``` +This time, we do not need relief capacity because the queue is not too big. Instead, the package recommends a "target capacity", which we need to provide if we want to meet the 18wk standard for the right proportion of patients. In this case it is `r round(overall_stats$target.capacity, 3)`, which is only very marginally larger than the mean capacity we have available (`r round(overall_stats$mean.capacity, 3)`). -The next columns tell us about the resulting queue size at the end of our simulation, the target size we need to plan for in order to achieve the 18 week waiting target, and a judgement about whether the queue is too large. If the queue is too large, we need to implement some relief capacity to bring it within range before attempting to maintain the queue. - -```{r, echo=FALSE} -knitr::kable( - overall_stats|> dplyr::select( - queue_size, - target_queue_size, - queue.too.big, - mean_wait - ), - align = "c" -) -``` +## 3. A waiting list with sufficient capacity {#three} +[Back to top...](#) -There is a column to report the actual average patient waiting time, which is `r round(overall_stats$mean_wait, 2)` weeks, compared to our target of 18 weeks. +The final example is for a waiting list with sufficient capacity to meet demand. We'll use the recommended figure from the example above, assuming we have made some improvements and increased available capacity from 10.2 to 10.3 patients per week. -```{r, echo=FALSE} -knitr::kable( - overall_stats |> dplyr::select(mean_wait), - align = "c" +```{r} +waiting_list <- wl_simulator( + start_date = "2020-01-01", + end_date = "2024-03-31", + demand = 10, # simulating 10 patient arrivals per week + capacity = 10.3 # simulating 10.3 patients being treated per week ) -``` +referral_stats <- wl_referral_stats(waiting_list) +head(referral_stats) -These two columns re-state the coefficients of variance for use in reporting. +removal_stats <- wl_removal_stats(waiting_list) +head(removal_stats) -```{r, echo=FALSE} -knitr::kable( - overall_stats |> dplyr::select( - cv_arrival, - cv_removal - ), - align = "c" -) +# calculate the queue size +queue_size <- wl_queue_size(waiting_list) ``` -The next two columns tell us about the required capacity. Only one will contain data. - -1. If the queue is not too large, "target.capacity" will report the capacity required to maintain the queue at it's target waiting time performance. -2. If the queue is too large, "relief.capacity" will report the capacity required to bring the queue to a maintainable size within 26 weeks (6 months). - -```{r, echo=FALSE} -knitr::kable( - overall_stats |> dplyr::select(target.capacity, relief.capacity), - align = "c" -) -``` +This time we processed `r removal_stats$removal.count` patients. +Visualising the queue, again it looks different to the previous examples. While the maximum number of patients in the queue is similar to the last example, this time the queue size has frequently dropped to zero. This is a stable queue, which is able to empty more regularly. -The final column reports the waiting list "pressure". This will be useful later when comparing waiting lists of differing sizes, with differing targets. It allows waiting list pressures to be compared because the waiting list with the largest number of patients waiting is not always the list with the largest problem meeting it's target. - -```{r, echo=FALSE} -knitr::kable( - overall_stats |> dplyr::select(pressure), - align = "c" -) -``` -``` - -Now we can visualise the new queue. -as expected it gets larger and larger because our demand is bigger than our capacity. +**NOTE** When the queue is empty, the process serving it will also be idle. Conventional wisdom has it that at this point the process must have excess capacity, which can safely be removed. This is **not** the case. Returning to "Fact 2" of [Professor Neil Walton's white paper](https://www.medrxiv.org/content/10.1101/2022.08.23.22279117v1.full), "If you want to have low waiting times, then there must be a non-negligible fraction of time where services are not being used". ```{r} -# calculate the queue size -queue_size <- wl_queue_size(waiting_list) - # visualise the queue with a plot ggplot(queue_size, aes(dates, queue_size)) + geom_line() - ``` -This time we will go straight to calculating the overall stats. +Again calculating the overall stats. ```{r} overall_stats <- wl_stats( @@ -314,77 +278,29 @@ overall_stats <- wl_stats( head(overall_stats) ``` -This gives us a lot of useful information. Taking it step by step: +This time the simulation has created a mean demand and capacity which is slightly lower than we asked for, but the gap between them is similar to what we wanted. -The first 4 columns tell us whether the load is larger than 1. If it is, we can expect the queue to continue growing indefinitely. +The load comes out at `r round(overall_stats$load, 3)`, which is more comfortably below one. A still lower load would give more headroom, and may even become necessary if the variability of demand or capacity were to increase. -```{r, echo=FALSE} -knitr::kable( - overall_stats |> dplyr::select( - mean.demand, - mean.capacity, - load, - load.too.big - ), - align = "c" -) +The mean wait is `r round(overall_stats$mean_wait, 2)`, less than a week, which is very comfortably less than the target of 18 weeks. In this system we expect the 18wk target to be met for the vast majority of patients. -``` +Again, the package is recommending a "target capacity", this time of `r round(overall_stats$target.capacity, 3)`, which is a similar margin above the mean demand for this simulation (`r round(overall_stats$mean.demand, 3)`). -The next columns tell us about the resulting queue size at the end of our simulation, the target size we need to plan for in order to achieve the 18 week waiting target, and a judgement about whether the queue is too large. If the queue is too large, we need to implement some relief capacity to bring it within range before attempting to maintain the queue. -```{r, echo=FALSE} -knitr::kable( - overall_stats|> dplyr::select( - queue_size, - target_queue_size, - queue.too.big, - mean_wait - ), - align = "c" -) +## Conclusion +[Back to top...](#) -``` +This vignette has detailed some of the `wl_*` functions you can use to explore your waiting list performance. We also saw how altering service capacity without changing demand can have a dramatic effect on the behaviour of a waiting list. -There is a column to report the actual average patient waiting time, which is `r round(overall_stats$mean_wait, 2)` weeks, compared to our target of 18 weeks. +--- + +END -```{r, echo=FALSE} -knitr::kable( - overall_stats |> dplyr::select(mean_wait), - align = "c" -) -``` -These two columns re-state the coefficients of variance for use in reporting. -```{r, echo=FALSE} -knitr::kable( - overall_stats |> dplyr::select( - cv_arrival, - cv_removal - ), - align = "c" -) -``` -The next two columns tell us about the required capacity. Only one will contain data. -1. If the queue is not too large, "target.capacity" will report the capacity required to maintain the queue at it's target waiting time performance. -2. If the queue is too large, "relief.capacity" will report the capacity required to bring the queue to a maintainable size within 26 weeks (6 months). -```{r, echo=FALSE} -knitr::kable( - overall_stats |> dplyr::select(target.capacity, relief.capacity), - align = "c" -) -``` -The final column reports the waiting list "pressure". This will be useful later when comparing waiting lists of differing sizes, with differing targets. It allows waiting list pressures to be compared because the waiting list with the largest number of patients waiting is not always the list with the largest problem meeting it's target. -```{r, echo=FALSE} -knitr::kable( - overall_stats |> dplyr::select(pressure), - align = "c" -) -``` \ No newline at end of file From e5617ec7dbee2fb1dea94aa72d610229be29d329 Mon Sep 17 00:00:00 2001 From: Tom Smith Date: Wed, 17 Jul 2024 21:25:15 +0100 Subject: [PATCH 07/10] happy lintr, happy life --- vignettes/example_waiting_list_functions.Rmd | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/vignettes/example_waiting_list_functions.Rmd b/vignettes/example_waiting_list_functions.Rmd index b7ada8d..11ac17e 100755 --- a/vignettes/example_waiting_list_functions.Rmd +++ b/vignettes/example_waiting_list_functions.Rmd @@ -43,7 +43,7 @@ waiting_list <- wl_simulator( start_date = "2020-01-01", end_date = "2024-03-31", demand = 10, # simulating 10 patient arrivals per week - capacity = 9 # simulating 9 patients being treated per week (removed from the waiting list) + capacity = 9 # simulating 9 patients being treated per week ) head(waiting_list, 10) @@ -114,7 +114,7 @@ knitr::kable( mean.capacity, load, load.too.big - ), + ), align = "c" ) @@ -124,12 +124,12 @@ The next columns tell us about the resulting queue size at the end of our simula ```{r, echo=FALSE} knitr::kable( - overall_stats|> dplyr::select( + overall_stats |> dplyr::select( queue_size, target_queue_size, queue.too.big, mean_wait - ), + ), align = "c" ) @@ -139,7 +139,7 @@ There is a column to report the actual average patient waiting time, which is `r ```{r, echo=FALSE} knitr::kable( - overall_stats |> dplyr::select(mean_wait), + overall_stats |> dplyr::select(mean_wait), align = "c" ) @@ -152,7 +152,7 @@ knitr::kable( overall_stats |> dplyr::select( cv_arrival, cv_removal - ), + ), align = "c" ) ``` @@ -164,7 +164,7 @@ The next two columns tell us about the required capacity. Only one will contain ```{r, echo=FALSE} knitr::kable( - overall_stats |> dplyr::select(target.capacity, relief.capacity), + overall_stats |> dplyr::select(target.capacity, relief.capacity), align = "c" ) ``` @@ -173,7 +173,7 @@ The final column reports the waiting list "pressure". This will be useful later ```{r, echo=FALSE} knitr::kable( - overall_stats |> dplyr::select(pressure), + overall_stats |> dplyr::select(pressure), align = "c" ) ``` From e1a0ca8c29e0af89b2b4b817d8f76d430ca3f7e0 Mon Sep 17 00:00:00 2001 From: Tom Smith Date: Wed, 17 Jul 2024 21:42:44 +0100 Subject: [PATCH 08/10] rename vignette and resize plots --- ...ns.Rmd => three_example_waiting_lists.Rmd} | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) rename vignettes/{example_waiting_list_functions.Rmd => three_example_waiting_lists.Rmd} (94%) mode change 100755 => 100644 diff --git a/vignettes/example_waiting_list_functions.Rmd b/vignettes/three_example_waiting_lists.Rmd old mode 100755 new mode 100644 similarity index 94% rename from vignettes/example_waiting_list_functions.Rmd rename to vignettes/three_example_waiting_lists.Rmd index 11ac17e..a9c9dbb --- a/vignettes/example_waiting_list_functions.Rmd +++ b/vignettes/three_example_waiting_lists.Rmd @@ -1,8 +1,8 @@ --- -title: "Walkthrough of waiting list functions" +title: "Three example waiting lists" output: rmarkdown::html_vignette vignette: > - %\VignetteIndexEntry{Walkthrough a real waiting list example} + %\VignetteIndexEntry{Three example waiting lists} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- @@ -23,7 +23,9 @@ library(ggplot2) set.seed(2) ``` -This vignette is a worked example using a sample dataset similar to that which you may be working with. +This vignette is a set of worked examples using a sample dataset similar to that which you may be working with. It also demonstrates how to use the `wl_*` family of functions, such as `wl_simulator`, `wl_queue_size`, `wl_referral_stats`, `wl_removal_stats`, and `wl_stats`. + +## Anatomy of a waiting list In it's purest form, a waiting list consists of the dates that individuals arrived in a queue, and the dates that they left having been seen by the service (doctor, nurse, or diagnostic test, etc). These dates are the are waiting list additions (or arrivals, referrals), and waiting list removals (or treatments, discharges). They correspond to demand (for arrivals), and capacity (for removals). @@ -52,7 +54,7 @@ head(waiting_list, 10) Now that we have a waiting list, we should visualise it. We can use the `wl_queue_size()` function to tell us the size of the queue at the end of each day. We can use ggplot to make a plot of the queue size over time, and as expected it gets larger and larger because our demand is bigger than our capacity. -```{r} +```{r, fig.height=3, fig.width=6} # calculate the queue size queue_size <- wl_queue_size(waiting_list) @@ -62,7 +64,10 @@ tail(queue_size) # visualise the queue with a plot ggplot(queue_size, aes(dates, queue_size)) + - geom_line() + geom_line() + + labs( + title = "A growing waiting list" + ) ``` @@ -206,11 +211,13 @@ The increase in capacity not only allowed processing more patients, it also chan Visualising the queue we can see that this time it did not grow uncontrollably, reaching a maximum size of `r max(queue_size$queue_size)` patients waiting over the same time period as the first simulation. It also returned to zero length several times during the simulated period. -```{r} +```{r, fig.height=3, fig.width=6} # visualise the queue with a plot ggplot(queue_size, aes(dates, queue_size)) + - geom_line() - + geom_line() + + labs( + title = "A finely-balanced waiting list" + ) ``` This time we will go straight to calculating the overall stats. @@ -261,10 +268,13 @@ Visualising the queue, again it looks different to the previous examples. While **NOTE** When the queue is empty, the process serving it will also be idle. Conventional wisdom has it that at this point the process must have excess capacity, which can safely be removed. This is **not** the case. Returning to "Fact 2" of [Professor Neil Walton's white paper](https://www.medrxiv.org/content/10.1101/2022.08.23.22279117v1.full), "If you want to have low waiting times, then there must be a non-negligible fraction of time where services are not being used". -```{r} +```{r, fig.height=3, fig.width=6} # visualise the queue with a plot ggplot(queue_size, aes(dates, queue_size)) + - geom_line() + geom_line() + + labs( + title = "A stable waiting list" + ) ``` Again calculating the overall stats. From d6a75c42b66ab75574876e4102942fd9b041e1ec Mon Sep 17 00:00:00 2001 From: Tom Smith Date: Wed, 17 Jul 2024 21:44:05 +0100 Subject: [PATCH 09/10] run devtools::document() --- man/queue_load.Rd | 2 +- man/relief_capacity.Rd | 22 ++++++++++++++-------- man/target_capacity.Rd | 23 ++++++++++++++--------- man/target_queue_size.Rd | 21 ++++++++++++++------- man/wl_insert.Rd | 14 ++++++++------ man/wl_queue_size.Rd | 6 +++--- man/wl_referral_stats.Rd | 11 +++++++++-- man/wl_removal_stats.Rd | 11 +++++++++-- man/wl_schedule.Rd | 15 ++++++++++----- man/wl_simulator.Rd | 14 +++++++++----- man/wl_stats.Rd | 5 +++-- 11 files changed, 94 insertions(+), 50 deletions(-) diff --git a/man/queue_load.Rd b/man/queue_load.Rd index 128727b..0792025 100755 --- a/man/queue_load.Rd +++ b/man/queue_load.Rd @@ -26,5 +26,5 @@ by the capacity: queue_load = demand / capacity. \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(30, 27) } diff --git a/man/relief_capacity.Rd b/man/relief_capacity.Rd index 6715106..6e03263 100755 --- a/man/relief_capacity.Rd +++ b/man/relief_capacity.Rd @@ -7,23 +7,29 @@ relief_capacity(demand, queue_size, target_queue_size, time_to_target = 26) } \arguments{ -\item{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.} +\item{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.} -\item{queue_size}{Numeric value of current number of patients in queue.} +\item{queue_size}{Numeric value of current number of patients in queue.} -\item{target_queue_size}{Numeric value of desired number of patients in queue.} +\item{target_queue_size}{Numeric value of desired number of patients +in queue.} -\item{time_to_target}{Numeric value of desired number of time-steps to reach the target queue size by.} +\item{time_to_target}{Numeric value of desired number of time-steps to reach +the target queue size by.} } \value{ -A numeric value of the required rate of capacity to achieve a target queue size in a given period of time. +A numeric value of the required rate of capacity to achieve a target +queue size in a given period of time. } \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. - +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 +Relief Capacity = +Current Demand + (Queue Size - Target Queue Size)/Time Steps WARNING!: make sure units match. I.e. if demand is measured per week then time_to_target should be weeks diff --git a/man/target_capacity.Rd b/man/target_capacity.Rd index 8768d30..1ccc171 100644 --- a/man/target_capacity.Rd +++ b/man/target_capacity.Rd @@ -13,18 +13,22 @@ target_capacity( ) } \arguments{ -\item{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.} +\item{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.} -\item{target_wait}{Numeric value of number of weeks that has been set as the target within which the patient should be seen.} +\item{target_wait}{Numeric value of number of weeks that has been set as the +target within which the patient should be seen.} -\item{factor}{the amount we divide the target by in the waiting list e.g. if target is 52 weeks the mean wait should be 13 for a factor of 4} +\item{factor}{the amount we divide the target by in the waiting list +e.g. if target is 52 weeks the mean wait should be 13 for a factor of 4} \item{cv_demand}{coefficient of variation of time between arrivals} -\item{cv_capacity}{coefficient of variation between removals due to operations completed} +\item{cv_capacity}{coefficient of variation between removals due to +operations completed} } \value{ -A numeric value of target capacity required to achieve a target waiting time. +numeric. The capacity required to achieve a target waiting time. } \description{ Applies Kingman/Marchal's Formula : @@ -39,10 +43,11 @@ waiting_time = target_wait / factor } \examples{ -demand = 4 # weeks -target_wait = 52 # weeks +demand <- 4 # weeks +target_wait <- 52 # weeks -target_capacity(demand, target_wait) # number of operations per week to have mean wait of 52/4 +# number of operations per week to have mean wait of 52/4 +target_capacity(demand, target_wait) -#TODO: Include a couple of standard deviations for errors in the mean demand +# TODO: Include a couple of standard deviations for errors in the mean demand } diff --git a/man/target_queue_size.Rd b/man/target_queue_size.Rd index 4252822..4579942 100644 --- a/man/target_queue_size.Rd +++ b/man/target_queue_size.Rd @@ -7,17 +7,25 @@ target_queue_size(demand, target_wait, factor = 4) } \arguments{ -\item{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.} +\item{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.} -\item{target_wait}{Numeric value of number of weeks that has been set as the target within which the patient should be seen.} +\item{target_wait}{Numeric value of number of weeks that has been set as the +target within which the patient should be seen.} -\item{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.} +\item{factor}{Numeric factor used in average wait calculation +\itemize{ +\item to get a quarter of the target use factor=4 +\item to get one sixth of the target use factor = 6 etc. Defaults to 4. +}} } \value{ Numeric target queue length. } \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. +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. @@ -28,10 +36,9 @@ The factor defaults to 4. Only applicable when Capacity > Demand. } \examples{ -# If demand is 30 patients per week and the target wait is 52 weeks, then the +# 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(30, 52, 4) } diff --git a/man/wl_insert.Rd b/man/wl_insert.Rd index 83ffb3f..8d6c3e9 100755 --- a/man/wl_insert.Rd +++ b/man/wl_insert.Rd @@ -9,9 +9,11 @@ wl_insert(waiting_list, additions, referral_index = 1) \arguments{ \item{waiting_list}{dataframe. A df of referral dates and removals} -\item{additions}{character vector. A list of referral dates to add to the waiting list} +\item{additions}{character vector. A list of referral dates to add to the +waiting list} -\item{referral_index}{integer. The column number in the waiting_list which contains the referral dates} +\item{referral_index}{integer. The column number in the waiting_list which +contains the referral dates} } \value{ dataframe. A df of the updated waiting list @@ -20,10 +22,10 @@ dataframe. A df of the updated waiting list adds new referrals (removal date is set as NA) } \examples{ -referrals <- c.Date("2024-01-01","2024-01-04","2024-01-10","2024-01-16") -removals <- c.Date("2024-01-08",NA,NA,NA) -waiting_list <- data.frame("referral" = referrals ,"removal" = removals ) -additions <- c.Date("2024-01-03","2024-01-05","2024-01-18") +referrals <- c.Date("2024-01-01", "2024-01-04", "2024-01-10", "2024-01-16") +removals <- c.Date("2024-01-08", NA, NA, NA) +waiting_list <- data.frame("referral" = referrals, "removal" = removals) +additions <- c.Date("2024-01-03", "2024-01-05", "2024-01-18") longer_waiting_list <- wl_insert(waiting_list, additions) # TODO: What if more columns diff --git a/man/wl_queue_size.Rd b/man/wl_queue_size.Rd index fd7160e..6677ea2 100755 --- a/man/wl_queue_size.Rd +++ b/man/wl_queue_size.Rd @@ -20,8 +20,8 @@ a list of dates and queue sizes Calculates queue sizes from a waiting list } \examples{ -referrals <- c.Date("2024-01-01","2024-01-04","2024-01-10","2024-01-16") -removals <- c.Date("2024-01-08",NA,NA,NA) -waiting_list <- data.frame("referral" = referrals ,"removal" = removals ) +referrals <- c.Date("2024-01-01", "2024-01-04", "2024-01-10", "2024-01-16") +removals <- c.Date("2024-01-08", NA, NA, NA) +waiting_list <- data.frame("referral" = referrals, "removal" = removals) wl_queue_size(waiting_list) } diff --git a/man/wl_referral_stats.Rd b/man/wl_referral_stats.Rd index f61a525..1f0d3de 100644 --- a/man/wl_referral_stats.Rd +++ b/man/wl_referral_stats.Rd @@ -14,7 +14,8 @@ wl_referral_stats(waiting_list, start_date = NULL, end_date = NULL) \item{end_date}{date. The end date to calculate to} } \value{ -dataframe. A df containing number of referrals, mean demand, and the coefficient of variation of referrals +dataframe. A df containing number of referrals, mean demand, +and the coefficient of variation of referrals } \description{ Calculate some stats about referrals @@ -22,6 +23,12 @@ Calculate some stats about referrals \examples{ referrals <- c.Date("2024-01-01", "2024-01-04", "2024-01-10", "2024-01-16") removals <- c.Date("2024-01-08", NA, NA, NA) -waiting_list <- data.frame("referral" = referrals , "removal" = removals) +waiting_list <- data.frame("referral" = referrals, "removal" = removals) referral_stats <- wl_referral_stats(waiting_list) + +# TODO : referral <- arrival +# debug and test +# simplify notation +# add detail to params above +# arrival mean and variance } diff --git a/man/wl_removal_stats.Rd b/man/wl_removal_stats.Rd index 51c1e70..d4299be 100644 --- a/man/wl_removal_stats.Rd +++ b/man/wl_removal_stats.Rd @@ -14,7 +14,8 @@ wl_removal_stats(waiting_list, start_date = NULL, end_date = NULL) \item{end_date}{date. The end date to calculate to} } \value{ -dataframe. A df containing number of removals, mean capacity, and the coefficient of variation of removals +dataframe. A df containing number of removals, mean capacity, +and the coefficient of variation of removals } \description{ Calculate some stats about removals @@ -22,6 +23,12 @@ Calculate some stats about removals \examples{ referrals <- c.Date("2024-01-01", "2024-01-04", "2024-01-10", "2024-01-16") removals <- c.Date("2024-01-08", NA, NA, NA) -waiting_list <- data.frame("referral" = referrals , "removal" = removals) +waiting_list <- data.frame("referral" = referrals, "removal" = removals) removal_stats <- wl_removal_stats(waiting_list) + +# TODO : referral <- arrival +# debug and test +# simplify notation +# add detail to params above +# arrival mean and variance } diff --git a/man/wl_schedule.Rd b/man/wl_schedule.Rd index 2e25e88..0311c0e 100755 --- a/man/wl_schedule.Rd +++ b/man/wl_schedule.Rd @@ -9,19 +9,24 @@ wl_schedule(waiting_list, schedule, referral_index = 1, removal_index = 2) \arguments{ \item{waiting_list}{dataframe. A df of referral dates and removals} -\item{schedule}{vector of dates. The dates to schedule open referrals into (ie. dates of unbooked future capacity)} +\item{schedule}{vector of dates. The dates to schedule open referrals into +(ie. dates of unbooked future capacity)} -\item{referral_index}{integer. The column number in the waiting_list which contains the referral dates} +\item{referral_index}{integer. The column number in the waiting_list which +contains the referral dates} -\item{removal_index}{integer. The column number in the waiting_list which contains the removal dates} +\item{removal_index}{integer. The column number in the waiting_list which +contains the removal dates} } \value{ -dataframe. A df of the updated waiting list with removal dates added according to the schedule +dataframe. A df of the updated waiting list with removal dates added +according to the schedule } \description{ Takes a list of dates and schedules them to a waiting list, by adding a removal date to the dataframe. -This is done in referral date order. I.e. earlier referrals are scheduled first (FIFO). +This is done in referral date order, +I.e. earlier referrals are scheduled first (FIFO). } \examples{ referrals <- c.Date("2024-01-01", "2024-01-04", "2024-01-10", "2024-01-16") diff --git a/man/wl_simulator.Rd b/man/wl_simulator.Rd index d91f161..22d7a74 100644 --- a/man/wl_simulator.Rd +++ b/man/wl_simulator.Rd @@ -24,19 +24,23 @@ wl_simulator( \item{waiting_list}{integer. The number of patients on the waiting list} -\item{referral_index}{integer. The column number in the waiting_list which contains the referral dates} +\item{referral_index}{integer. The column number in the waiting_list which +contains the referral dates} } \value{ dataframe. A df of simulated referrals and removals } \description{ -Creates a simulated waiting list comprising referral dates, and removal dates +Creates a simulated waiting list comprising referral dates, +and removal dates } \examples{ -over_capacity_simulation <- wl_simulator("2024-01-01", "2024-03-31", 100, 110) -under_capacity_simulation <- wl_simulator("2024-01-01", "2024-03-31", 100, 90) +over_capacity_simulation <- + wl_simulator("2024-01-01", "2024-03-31", 100, 110) +under_capacity_simulation <- + wl_simulator("2024-01-01", "2024-03-31", 100, 90) -#TODO +# TODO # error messages (e.g. start_date > end_date) } diff --git a/man/wl_stats.Rd b/man/wl_stats.Rd index 96f1a72..29418bf 100755 --- a/man/wl_stats.Rd +++ b/man/wl_stats.Rd @@ -19,13 +19,13 @@ wl_stats(waiting_list, target_wait = 4, start_date = NULL, end_date = NULL) dataframe. A df of important waiting list statistics } \description{ -A summary of all the key statistics associated with a waiting list +A summary of all the key stats associated with a waiting list } \examples{ referrals <- c.Date("2024-01-01", "2024-01-04", "2024-01-10", "2024-01-16") removals <- c.Date("2024-01-08", NA, NA, NA) -waiting_list <- data.frame("referral" = referrals , "removal" = removals) +waiting_list <- data.frame("referral" = referrals, "removal" = removals) waiting_list_stats <- wl_stats(waiting_list) # TO DO!! @@ -33,6 +33,7 @@ waiting_list_stats <- wl_stats(waiting_list) # Error if dates are in the wrong order # Calculate the number of missed operations # Start date and end date calculations not working well +# # MAKE CONSISTENT NOTATION # default start and end date if empty # make units of output weekly operations not daily From f7d5c02029dfc941f68ef3c449dc0bbad9be1f6a Mon Sep 17 00:00:00 2001 From: Lextuga007 Date: Sun, 21 Jul 2024 18:43:50 +0100 Subject: [PATCH 10/10] Added dplyr as used in code, editing in line with NHS-R Way (remove Latin, full words and use of {} and ``) --- vignettes/three_example_waiting_lists.Rmd | 53 +++++++++-------------- 1 file changed, 21 insertions(+), 32 deletions(-) diff --git a/vignettes/three_example_waiting_lists.Rmd b/vignettes/three_example_waiting_lists.Rmd index a9c9dbb..7923e3a 100644 --- a/vignettes/three_example_waiting_lists.Rmd +++ b/vignettes/three_example_waiting_lists.Rmd @@ -18,6 +18,7 @@ knitr::opts_chunk$set( ```{r setup} library(NHSRwaitinglist) library(ggplot2) +library(dplyr, warn.conflicts = FALSE) # set a seed so that these plots are always the same set.seed(2) @@ -27,7 +28,7 @@ This vignette is a set of worked examples using a sample dataset similar to that ## Anatomy of a waiting list -In it's purest form, a waiting list consists of the dates that individuals arrived in a queue, and the dates that they left having been seen by the service (doctor, nurse, or diagnostic test, etc). These dates are the are waiting list additions (or arrivals, referrals), and waiting list removals (or treatments, discharges). They correspond to demand (for arrivals), and capacity (for removals). +In its purest form, a waiting list consists of the dates that individuals arrived in a queue, and the dates that they left having been seen by the service (doctor, nurse, or diagnostic test, and so on). These dates are the waiting list additions (or arrivals, referrals), and waiting list removals (or treatments, discharges). They correspond to demand (for arrivals), and capacity (for removals). This vignette is going to simulate 3 different waiting lists: @@ -49,10 +50,9 @@ waiting_list <- wl_simulator( ) head(waiting_list, 10) - ``` -Now that we have a waiting list, we should visualise it. We can use the `wl_queue_size()` function to tell us the size of the queue at the end of each day. We can use ggplot to make a plot of the queue size over time, and as expected it gets larger and larger because our demand is bigger than our capacity. +Now that we have a waiting list, we should visualise it. We can use the `wl_queue_size()` function to tell us the size of the queue at the end of each day. We can use {ggplot} to make a plot of the queue size over time, and as expected, it gets larger and larger because our demand is bigger than our capacity. ```{r, fig.height=3, fig.width=6} # calculate the queue size @@ -68,7 +68,6 @@ ggplot(queue_size, aes(dates, queue_size)) + labs( title = "A growing waiting list" ) - ``` ### Referral statistics @@ -102,7 +101,7 @@ Finally, we can calculate a combined set of statistics to summarise the waiting ```{r} overall_stats <- wl_stats( waiting_list = waiting_list, - target_wait = 18 # standard NHS 18wk target + target_wait = 18 # standard NHS 18 weeks target ) head(overall_stats) @@ -122,7 +121,6 @@ knitr::kable( ), align = "c" ) - ``` The next columns tell us about the resulting queue size at the end of our simulation, the target size we need to plan for in order to achieve the 18 week waiting target, and a judgement about whether the queue is too large. If the queue is too large, we need to implement some relief capacity to bring it within range before attempting to maintain the queue. @@ -137,7 +135,6 @@ knitr::kable( ), align = "c" ) - ``` There is a column to report the actual average patient waiting time, which is `r round(overall_stats$mean_wait, 2)` weeks, compared to our target of 18 weeks. @@ -147,7 +144,6 @@ knitr::kable( overall_stats |> dplyr::select(mean_wait), align = "c" ) - ``` These two columns re-state the coefficients of variance for use in reporting. @@ -164,8 +160,8 @@ knitr::kable( The next two columns tell us about the required capacity. Only one will contain data. -1. If the queue is not too large, "target.capacity" will report the capacity required to maintain the queue at it's target waiting time performance. -2. If the queue is too large, "relief.capacity" will report the capacity required to bring the queue to a maintainable size within 26 weeks (6 months). +1. If the queue is not too large, `"target.capacity"` will report the capacity required to maintain the queue at it's target waiting time performance. +2. If the queue is too large, `"relief.capacity"` will report the capacity required to bring the queue to a maintainable size within 26 weeks (6 months). ```{r, echo=FALSE} knitr::kable( @@ -174,7 +170,7 @@ knitr::kable( ) ``` -The final column reports the waiting list "pressure". This will be useful later when comparing waiting lists of differing sizes, with differing targets. It allows waiting list pressures to be compared because the waiting list with the largest number of patients waiting is not always the list with the largest problem meeting it's target. +The final column reports the waiting list `"pressure"`. This will be useful later when comparing waiting lists of differing sizes, with differing targets. It allows waiting list pressures to be compared because the waiting list with the largest number of patients waiting is not always the list with the largest problem meeting its target. ```{r, echo=FALSE} knitr::kable( @@ -186,7 +182,7 @@ knitr::kable( ## 2. A finely balanced waiting list {#two} [Back to top...](#) -The waiting list in this section is very finely balanced. The demand remains the same as the last example, but now capacity has been increased to be slightly larger than demand. It is not significantly larger (there is approximately 2% "spare"). +The waiting list in this section is very finely balanced. The demand remains the same as the last example, but now capacity has been increased to be slightly larger than demand. It is not significantly larger (there is approximately 2% `"spare"`). ```{r} waiting_list <- wl_simulator( @@ -207,6 +203,7 @@ queue_size <- wl_queue_size(waiting_list) ``` This time we processed `r removal_stats$removal.count` patients. + The increase in capacity not only allowed processing more patients, it also changed the shape of the queue. Visualising the queue we can see that this time it did not grow uncontrollably, reaching a maximum size of `r max(queue_size$queue_size)` patients waiting over the same time period as the first simulation. It also returned to zero length several times during the simulated period. @@ -220,7 +217,7 @@ ggplot(queue_size, aes(dates, queue_size)) + ) ``` -This time we will go straight to calculating the overall stats. +This time we will go straight to calculating the overall statistics. ```{r} overall_stats <- wl_stats( @@ -233,11 +230,11 @@ head(overall_stats) In this finely balanced example, the mean demand and mean capacity give a load very close to 1, at `r round(overall_stats$load, 4)`. While this is less than one, it is perhaps a little too close for comfort. -We can see that the finishing queue size is `r overall_stats$queue_size`, but as discussed above the waiting list fluctuated in size, and even returned to zero a couple of times during the simulated period. It has not grown uncontrollably as in the first example. +We can see that the finishing queue size is `r overall_stats$queue_size`, but as discussed above, the waiting list fluctuated in size, and even returned to zero a couple of times during the simulated period. It has not grown uncontrollably as in the first example. -The mean wait is `r round(overall_stats$mean_wait, 2)`, which is less than the target of 18 weeks, but is more than a quarter of the target. The exponential shape of waiting list distributions means that in this system we would expect more than a reasonable number of patients to be experiencing waiting times of over 18wks. +The mean wait is `r round(overall_stats$mean_wait, 2)`, which is less than the target of 18 weeks, but is more than a quarter of the target. The exponential shape of waiting list distributions means that in this system we would expect more than a reasonable number of patients to be experiencing waiting times of over 18 weeks. -This time, we do not need relief capacity because the queue is not too big. Instead, the package recommends a "target capacity", which we need to provide if we want to meet the 18wk standard for the right proportion of patients. In this case it is `r round(overall_stats$target.capacity, 3)`, which is only very marginally larger than the mean capacity we have available (`r round(overall_stats$mean.capacity, 3)`). +This time, we do not need relief capacity because the queue is not too big. Instead, the package recommends a `"target capacity"`, which we need to provide if we want to meet the 18 week standard for the right proportion of patients. In this case it is `r round(overall_stats$target.capacity, 3)`, which is only very marginally larger than the mean capacity we have available (`r round(overall_stats$mean.capacity, 3)`). ## 3. A waiting list with sufficient capacity {#three} @@ -266,7 +263,9 @@ queue_size <- wl_queue_size(waiting_list) This time we processed `r removal_stats$removal.count` patients. Visualising the queue, again it looks different to the previous examples. While the maximum number of patients in the queue is similar to the last example, this time the queue size has frequently dropped to zero. This is a stable queue, which is able to empty more regularly. -**NOTE** When the queue is empty, the process serving it will also be idle. Conventional wisdom has it that at this point the process must have excess capacity, which can safely be removed. This is **not** the case. Returning to "Fact 2" of [Professor Neil Walton's white paper](https://www.medrxiv.org/content/10.1101/2022.08.23.22279117v1.full), "If you want to have low waiting times, then there must be a non-negligible fraction of time where services are not being used". +**NOTE** When the queue is empty, the process serving it will also be idle. Conventional wisdom has it that at this point the process must have excess capacity, which can safely be removed. This is **not** the case. Returning to "Fact 2" of [Professor Neil Walton's white paper](https://www.medrxiv.org/content/10.1101/2022.08.23.22279117v1.full), + +> If you want to have low waiting times, then there must be a non-negligible fraction of time where services are not being used. ```{r, fig.height=3, fig.width=6} # visualise the queue with a plot @@ -277,12 +276,12 @@ ggplot(queue_size, aes(dates, queue_size)) + ) ``` -Again calculating the overall stats. +Again calculating the overall statistics. ```{r} overall_stats <- wl_stats( waiting_list = waiting_list, - target_wait = 18 # standard NHS 18wk target + target_wait = 18 # standard NHS 18 weeks target ) head(overall_stats) @@ -290,12 +289,11 @@ head(overall_stats) This time the simulation has created a mean demand and capacity which is slightly lower than we asked for, but the gap between them is similar to what we wanted. -The load comes out at `r round(overall_stats$load, 3)`, which is more comfortably below one. A still lower load would give more headroom, and may even become necessary if the variability of demand or capacity were to increase. - -The mean wait is `r round(overall_stats$mean_wait, 2)`, less than a week, which is very comfortably less than the target of 18 weeks. In this system we expect the 18wk target to be met for the vast majority of patients. +The load comes out at `r round(overall_stats$load, 3)`, which is more comfortably below one. A still lower load would give more headroom, and may even become necessary if the variability of demand or capacity were to increase. -Again, the package is recommending a "target capacity", this time of `r round(overall_stats$target.capacity, 3)`, which is a similar margin above the mean demand for this simulation (`r round(overall_stats$mean.demand, 3)`). +The mean wait is `r round(overall_stats$mean_wait, 2)`, less than a week, which is very comfortably less than the target of 18 weeks. In this system we expect the 18 weeks target to be met for the vast majority of patients. +Again, the package is recommending a `"target capacity"`, this time of `r round(overall_stats$target.capacity, 3)`, which is a similar margin above the mean demand for this simulation (`r round(overall_stats$mean.demand, 3)`). ## Conclusion [Back to top...](#) @@ -305,12 +303,3 @@ This vignette has detailed some of the `wl_*` functions you can use to explore y --- END - - - - - - - - -