--- title: "Analysing Waiting Lists with Histogram Data" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Analysing with histogram data} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", fig.height = 5, fig.width = 8 ) options(rmarkdown.html_vignette.check_title = FALSE) ``` ```{r setup} library(NHSRwaitinglist) library(dplyr) library(ggplot2) # set up a theme for our charts theme_set( theme_minimal() + theme( plot.subtitle = element_text(face = "italic") ) ) ``` # Introduction The NHSRwaitinglist package provides two complementary approaches for analysing waiting lists: 1. **Patient-level data**: Individual records with referral and removal dates 2. **Histogram data**: Aggregated counts by waiting time intervals This vignette focuses on the **histogram approach**, which is particularly valuable for: - **Processing NHS national statistics**: Published data is aggregated, not patient-level - **Privacy-preserving analysis**: No individual patient records required - **Large dataset performance**: Efficient computation with time-bucketed summaries - **Historical data**: Working with pre-aggregated reported statistics ## When to Use Histogram Functions Use histogram functions when your data is: - Aggregated into time bins (weeks, months) with patient counts - Published in summary form (e.g., NHS England RTT statistics) - Too large for patient-level processing - Subject to privacy constraints ## Histogram Data Structure A histogram in this package is a data frame with these essential columns: - `arrival_since` (Date): Start of the arrival time interval - `arrival_before` (Date): End of the arrival time interval - `n` (Numeric): Count of patients in that interval - `report_date` (Date): Reference date of the waiting list snapshot Additional categorical columns (e.g., specialty, priority) are optional. # Creating Histogram Data ## Example: Converting Weekly Bins ```{r create-histogram} # Example: Waiting list snapshot from 31 Jan 2024 # Bins represent weeks waiting wl_hist_jan <- data.frame( arrival_since = as.Date(c("2024-01-24", "2024-01-17", "2024-01-10", "2024-01-03")), arrival_before = as.Date(c("2024-01-30", "2024-01-23", "2024-01-16", "2024-01-09")), n = c(45, 38, 52, 61), # Patients waiting 0-1, 1-2, 2-3, 3-4 weeks report_date = as.Date("2024-01-31") ) print(wl_hist_jan) ``` The first row shows 45 patients who arrived between 24-30 Jan (waiting < 1 week on 31 Jan). ## Multiple Time Points For removal statistics, we need multiple snapshots: ```{r multiple-snapshots} # Add February snapshot wl_hist_feb <- data.frame( arrival_since = as.Date(c("2024-02-21", "2024-02-14", "2024-02-07", "2024-01-31")), arrival_before = as.Date(c("2024-02-27", "2024-02-20", "2024-02-13", "2024-02-06")), n = c(42, 35, 48, 55), report_date = as.Date("2024-02-29") ) # Combine into single histogram wl_hist <- rbind(wl_hist_jan, wl_hist_feb) # View unique report dates unique(wl_hist$report_date) ``` ## Using format_histogram() The `format_histogram()` function ensures your data has the correct structure: ```{r format-histogram} # If you only have arrival_since, format_histogram adds arrival_before hist_simple <- data.frame( arrival_since = as.Date(c("2024-01-24", "2024-01-17", "2024-01-10")), n = c(45, 38, 52), report_date = as.Date("2024-01-31") ) hist_formatted <- format_histogram(hist_simple, end_date = "2024-01-31") print(hist_formatted) ``` # Core Analysis Functions ## Queue Size Calculate the total number of patients waiting: ```{r queue-size} # Queue size at end of January queue_jan <- wl_queue_size_hist(wl_hist_jan) cat("Queue size (31 Jan):", queue_jan, "patients\n") # Queue size at end of February queue_feb <- wl_queue_size_hist(wl_hist_feb) cat("Queue size (29 Feb):", queue_feb, "patients\n") ``` ## Referral Statistics (Demand) Calculate demand statistics from the most recent snapshot: ```{r referral-stats} referral_stats <- wl_referral_stats_hist(wl_hist_feb) print(referral_stats) ``` This shows: - `demand_weekly`: Average weekly referrals - `demand_daily`: Average daily referrals - `demand_cov`: Coefficient of variation (uncertainty in arrival pattern) - `demand_count`: Total referrals over the period ## Removal Statistics (Capacity) Calculate removal/capacity statistics by comparing multiple snapshots: ```{r removal-stats} # Uses both Jan and Feb snapshots to estimate removals removal_stats <- wl_removal_stats_hist(wl_hist) print(removal_stats) ``` This shows: - `capacity_weekly`: Average weekly removals (treatments) - `capacity_daily`: Average daily removals - `capacity_cov`: Coefficient of variation - `removal_count`: Total removals between snapshots The function compares consecutive snapshots to detect where counts decreased (indicating removals). ## Mean Waiting Time Calculate average waiting time from a snapshot: ```{r mean-wait} mean_wait <- wl_mean_wait_age_hist(wl_hist_feb) cat("Mean waiting time:", round(mean_wait, 1), "weeks\n") ``` ## Percentile Waiting Times Find specific percentiles of the waiting time distribution: ```{r percentiles} # Median (50th percentile) p50 <- wl_percentile_hist(wl_hist_feb, percentage = 50) cat("Median wait:", round(p50$weeks, 1), "weeks\n") # 95th percentile p95 <- wl_percentile_hist(wl_hist_feb, percentage = 95) cat("95th percentile wait:", round(p95$weeks, 1), "weeks\n") ``` # Comprehensive Statistics with wl_stats_hist() The `wl_stats_hist()` function provides a complete analysis in one call: ```{r comprehensive-stats} # Calculate all key statistics stats <- wl_stats_hist(wl_hist, target_wait = 18) print(stats) ``` This returns a data frame with: - **Demand/Capacity metrics**: Mean demand, mean capacity, load (demand/capacity ratio) - **Queue metrics**: Current size, target size, whether queue is too big - **Wait time metrics**: Mean wait, pressure indicator - **Planning metrics**: Target capacity, relief capacity (if needed) - **Variability**: Coefficients of variation for arrivals and removals ### Understanding the Results ```{r interpret-stats} cat("Current queue:", stats$queue_size, "patients\n") cat("Target queue:", round(stats$target_queue_size), "patients\n") cat("Queue too big?", stats$queue_too_big, "\n\n") cat("Mean demand:", round(stats$mean_demand, 1), "patients/week\n") cat("Mean capacity:", round(stats$mean_capacity, 1), "patients/week\n") cat("Load (demand/capacity):", round(stats$load, 2), "\n") cat("Load too big?", stats$load_too_big, "\n\n") cat("Mean waiting time:", round(stats$mean_wait_age, 1), "weeks\n") cat("Target wait:", 18, "weeks\n") cat("Pressure (2 × mean / target):", round(stats$pressure, 2), "\n") ``` **Key indicators:** - `load >= 1.0`: System is unstable (demand exceeds capacity) - `queue_too_big = TRUE`: Queue exceeds twice the target size - `pressure > 1.0`: Unlikely to meet waiting time targets # Working with Multiple Snapshots ## Time Series Analysis ```{r time-series-example} # Create a longer time series (3 months) wl_hist_mar <- data.frame( arrival_since = as.Date(c("2024-03-20", "2024-03-13", "2024-03-06", "2024-02-28")), arrival_before = as.Date(c("2024-03-26", "2024-03-19", "2024-03-12", "2024-03-05")), n = c(48, 41, 50, 58), report_date = as.Date("2024-03-31") ) wl_hist_full <- rbind(wl_hist_jan, wl_hist_feb, wl_hist_mar) # Calculate stats using all three time points stats_full <- wl_stats_hist(wl_hist_full, target_wait = 18) # Removal stats averaged across Jan→Feb and Feb→Mar removal_stats_full <- wl_removal_stats_hist(wl_hist_full) print(removal_stats_full) ``` ## Filtering Date Ranges Specify which time period to analyze: ```{r date-filters} # Only analyze Feb→Mar period stats_feb_mar <- wl_stats_hist( wl_hist_full, target_wait = 18, start_date = "2024-02-01", end_date = "2024-03-31" ) cat("Capacity (Feb-Mar only):", round(stats_feb_mar$mean_capacity, 1), "\n") # Compare to full period cat("Capacity (Jan-Mar):", round(stats_full$mean_capacity, 1), "\n") ``` # Working with Categorical Variables ## Multiple Specialties ```{r categorical-data} # Histogram with multiple specialties wl_hist_specialty <- data.frame( specialty = rep(c("Cardiology", "Orthopedics"), each = 3), arrival_since = rep(as.Date(c("2024-01-24", "2024-01-17", "2024-01-10")), 2), arrival_before = rep(as.Date(c("2024-01-30", "2024-01-23", "2024-01-16")), 2), n = c(45, 38, 52, 30, 42, 28), report_date = as.Date("2024-01-31") ) print(wl_hist_specialty) ``` ## Aggregating Across Categories Remove categorical splits to get overall statistics: ```{r aggregate-histogram} # Aggregate across specialties wl_hist_total <- aggregate_histogram(wl_hist_specialty) print(wl_hist_total) # Calculate overall queue size total_queue <- wl_queue_size_hist(wl_hist_total) cat("Total queue (all specialties):", total_queue, "\n") ``` ## Keeping Categories Analyze by specialty: ```{r by-category} # Keep specialty grouping wl_hist_by_specialty <- aggregate_histogram( wl_hist_specialty, group_columns = "specialty" ) print(wl_hist_by_specialty) ``` # Practical Example: NHS National Data Pattern This example shows a typical workflow for NHS published statistics: ```{r nhs-example} # Simulate NHS RTT data structure (weekly bins) create_nhs_snapshot <- function(report_date, base_count = 100) { # NHS typically reports in 1-week bins up to 52+ weeks weeks <- c(1, 2, 3, 4, 6, 8, 10, 12, 15, 18, 21, 26, 52) data.frame( arrival_since = as.Date(report_date) - (weeks * 7 + 6), arrival_before = as.Date(report_date) - (weeks * 7), n = round(base_count * exp(-weeks / 20)), # Exponential decay report_date = as.Date(report_date) ) } # Create quarterly snapshots nhs_q4_2023 <- create_nhs_snapshot("2023-12-31", base_count = 120) nhs_q1_2024 <- create_nhs_snapshot("2024-03-31", base_count = 110) nhs_q2_2024 <- create_nhs_snapshot("2024-06-30", base_count = 105) nhs_hist <- rbind(nhs_q4_2023, nhs_q1_2024, nhs_q2_2024) # Analyze NHS data nhs_stats <- wl_stats_hist(nhs_hist, target_wait = 18) cat("NHS Example Analysis\n") cat("====================\n") cat("Queue size (Jun 2024):", nhs_stats$queue_size, "\n") cat("Target queue size:", round(nhs_stats$target_queue_size), "\n") cat("Mean waiting time:", round(nhs_stats$mean_wait_age, 1), "weeks\n") cat("Meeting 18-week target?", nhs_stats$pressure <= 1.0, "\n") ``` ## Trade-offs **Patient-level approach:** - ✓ More precise calculations - ✓ Can track individual patient journeys - ✗ Requires detailed data - ✗ Privacy concerns - ✗ Slower with large datasets **Histogram approach:** - ✓ Works with aggregate published data - ✓ Privacy-preserving - ✓ Efficient for large datasets - ✗ Less precision (binned data) - ✗ Cannot track individuals - ✗ Requires multiple snapshots for removals # Summary ## Key Functions | Function | Purpose | Input | |----------|---------|-------| | `format_histogram()` | Prepare/validate data | Histogram with arrival_since | | `aggregate_histogram()` | Collapse categories | Histogram with categorical variables | | `wl_queue_size_hist()` | Current queue size | Single snapshot | | `wl_referral_stats_hist()` | Demand statistics | Single snapshot | | `wl_removal_stats_hist()` | Capacity statistics | Multiple snapshots | | `wl_mean_wait_age_hist()` | Mean waiting time | Single snapshot | | `wl_percentile_hist()` | Percentile waits | Single snapshot | | `wl_stats_hist()` | Complete analysis | Multiple snapshots | ## Best Practices 1. **Always include report_date**: Essential for multi-snapshot analysis 2. **Use consistent time bins**: Weekly or monthly bins work best 3. **Multiple snapshots for accuracy**: More snapshots = better capacity estimates 4. **Validate your data**: Use `format_histogram()` to ensure structure 5. **Check for missing dates**: Ensure consecutive snapshots for reliable trends ## Next Steps - Follow the [histogram walkthrough](histogram_walkthrough.html) for a worked end-to-end example - Explore the [simulation vignette](waiting_list_sim.html) for forward projections - See [example walkthrough](example_walkthrough.html) for patient-level analysis - Read about [queuing theory foundations](three_example_waiting_lists.html) # References This package implements methods described in: Fong et al. (2022). "Queueing Theory and Machine Learning Applied to National Scale Medical Waiting Lists." *medRxiv*. doi:10.1101/2022.08.23.22279117