library(tidyverse)
library(dplyr)
library(ggplot2)
library(gridExtra)
library(grid)
library(reshape2)
library(gtsummary)
library(kableExtra)
set.seed(2023)
Biostat 213
Midterm
Load necessary libraries
Problem Statement
Conduct a simulation on your Urgent Care Center business. Here are some simplifying assumptions. Feel free to change if you wish:
- Assume that your business opens at 8am and closes at 5pm. Customers arrive at a rate of 4/hr.
- Between the hours of noon (12) and 1 that rate increases to 6/hr.
- Suppose you have 2 tellers. There are, in general, 3 types of queuing systems:
- 1 line with tandem servers.
- 1 line with tandem servers.
- oneline service (In-N-out example)
- where each teller has their own line (grocery store).
Questions:
- What is the expected waiting time for each type of system. Simulate 1 week (do 7 times and take the average).
- Which system do you think is the best?
- Does it matter where you place the “slow” teller?
- Does you “optimal” depend on your choices?
Solution
Generate customer arrival times based on varying rates
First, we will simulate the customer arrivals times for the 1 line with two tellers.
# Parameters
<- 4 / 60 # Arrival rate (4 per hour) before 12 pm and after 1 pm
lambda1 <- 6 / 60 # Arrival rate (6 per hour) between 12 pm and 1 pm
lambda2 <- 8 * 60 # Total simulation time per day in minutes (8 hours)
T <- 4 * 60 # Start time of increased rate (12 pm in minutes from 8 am)
midday_start <- 5 * 60 # End time of increased rate (1 pm in minutes from 8 am)
midday_end
# Function to generate customer arrivals with two different rates
<- function(lambda1, lambda2, T, midday_start, midday_end) {
generate_arrivals <- 0
current_time <- c()
arrival_times
while (current_time < T) {
# Choose rate based on time of day
if (current_time >= midday_start & current_time < midday_end) {
<- rexp(1, rate = lambda2)
interarrival_time else {
} <- rexp(1, rate = lambda1)
interarrival_time
}
# Update current time
<- current_time + interarrival_time
current_time if (current_time < T) {
<- c(arrival_times, current_time)
arrival_times
}
}return(arrival_times)
}
Plot customer arrival times on a single line.
# Generate 7 sets of arrival times and store them in a data frame
<- do.call(rbind, lapply(1:7, function(day) {
arrival_data <- generate_arrivals(lambda1, lambda2, T, midday_start, midday_end)
arrival_times data.frame(
Day = paste("Day", day),
time = arrival_times,
customer = 1:length(arrival_times),
label = ifelse(arrival_times >= midday_start & arrival_times < midday_end, "12-1 pm", "other times")
)
}))
# Split arrival_data by day to get arrival times for each day
<- split(arrival_data$time, arrival_data$Day)
arrival_times_by_day
# Plot using ggplot2 with facet_wrap for shared axes
ggplot(arrival_data, aes(x = time, y = customer, color = label)) +
geom_point() +
facet_wrap(~ Day, ncol = 3) +
theme_bw() +
labs(title = "Customer Arrival Times Over 7 Days",
x = "Time (minutes)",
y = "Customer",
color = "Time Period") +
theme(legend.position = "top",
plot.title = element_text(size = 10),
axis.text.x = element_text(angle = 45, hjust = 1))
Case 1: All servers have same rate and do type 1, 2, 3.
Assumptions:
Customers arrive following a Poisson process with two arrival rates (4/hr generally, increasing to 6/hr between 12-1 pm).
Each teller (representing a stage) has an exponential service time with rate \(\mu\) hours.
Customers first see Teller 1, then move to Teller 2 once Teller 1’s service is complete.
Type 1: One line with two servers (Tandem)
In this system, customers first go to Teller 1, then move to Teller 2 once Teller 1’s service is complete.
Code
# Set up the data for the two tellers
<- data.frame(
teller_data x = c(1, 2),
y = c(0, 0),
label = c("Teller 1", "Teller 2")
)
# Create the plot
ggplot() +
# Add the line connecting the tellers
geom_segment(aes(x = 0.5, y = 0, xend = 2.5, yend = 0), size = 1, color = "black") +
# Add points for each teller location
geom_point(data = teller_data, aes(x = x, y = y), shape = 1, size = 8, color = "black") +
# Add labels for each teller
geom_text(data = teller_data, aes(x = x, y = y + 0.1, label = label), vjust = -1, hjust = 0.5, size = 5) +
# Add an arrow to the end of the line
geom_segment(aes(x = 2.5, y = 0, xend = 3, yend = 0),
arrow = arrow(length = unit(0.3, "cm")), size = 1, color = "black") +
# Set plot limits and remove axis lines
coord_cartesian(xlim = c(0, 3), ylim = c(-0.5, 0.5)) +
theme_void() %>% suppressMessages()
Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
ℹ Please use `linewidth` instead.
Here, I will the service rate is \(\mu = 5 \text{ customers/hr}\).
<- 5 / 60 # Service rate for each teller in minutes mu
# Function to simulate 1 line with tandem servers for one day
<- function(arrival_times, mu) {
simulate_tandem_servers_day
<- c()
wait_times_stage1 <- c()
wait_times_stage2 <- c()
service_times_stage1 <- c()
service_times_stage2
# Initialize tellers' availability times
<- 0
teller1_free_at <- 0
teller2_free_at
# Process each arrival in the line with tandem servers
for (arrival_time in arrival_times) {
# For each customer, first go to Teller 1, then Teller 2
# Calculate the wait time and service time for each teller
# Stage 1: Teller 1
<- max(0, teller1_free_at - arrival_time)
wait_time1 <- rexp(1, rate = mu)
service_time1 <- arrival_time + wait_time1 + service_time1
teller1_free_at <- c(wait_times_stage1, wait_time1)
wait_times_stage1 <- c(service_times_stage1, service_time1)
service_times_stage1
# Stage 2: Teller 2
<- teller1_free_at
arrival_time_stage2 <- max(0, teller2_free_at - arrival_time_stage2)
wait_time2 <- rexp(1, rate = mu)
service_time2 <- arrival_time_stage2 + wait_time2 + service_time2
teller2_free_at <- c(wait_times_stage2, wait_time2)
wait_times_stage2 <- c(service_times_stage2, service_time2)
service_times_stage2
}
# Calculate total wait times
<- wait_times_stage1 + wait_times_stage2
total_wait_times <- mean(total_wait_times)
avg_wait_time
return(list(avg_wait_time = avg_wait_time,
arrival_times = arrival_times,
wait_times_stage1 = wait_times_stage1,
wait_times_stage2 = wait_times_stage2,
total_wait_times = total_wait_times))
}
Now, we will run the simulation for 7 days and calculate the average wait time over the week.
# Apply the simulate_tandem_servers_day function to each day's arrival times
<- lapply(arrival_times_by_day, function(arrival_times) {
tandem_weekly_results simulate_tandem_servers_day(arrival_times, mu)
})
# Summary of results
<- mean(tandem_weekly_results %>%
tandem_avg_weekly_wait_time sapply(`[[`, "avg_wait_time"))
cat("Average wait time over 7 days for tandem servers:",
/ 60, "\n") tandem_avg_weekly_wait_time
Average wait time over 7 days for tandem servers: 1.405915
Finally, we will plot the wait times for each teller and the total wait time for each customer over the week.
Code
# Combine data for all days into a single data frame
<- do.call(rbind, lapply(1:7, function(i) {
all_days_data data.frame(
Day = paste("Day", i),
ArrivalTime = tandem_weekly_results[[i]]$arrival_times,
WaitTimeStage1 = tandem_weekly_results[[i]]$wait_times_stage1,
WaitTimeStage2 = tandem_weekly_results[[i]]$wait_times_stage2,
TotalWaitTime = tandem_weekly_results[[i]]$total_wait_times
)
}))
# Melt data into a long format for plotting
<- melt(all_days_data, id.vars = c("Day", "ArrivalTime"),
all_days_data_melted variable.name = "Teller", value.name = "WaitTime")
# Custom color palette in the style of Nature or Science journals
<- c("WaitTimeStage1" = "#1f78b4", # Blue
custom_colors "WaitTimeStage2" = "#33a02c", # Green
"TotalWaitTime" = "#e31a1c") # Red
# Plot using ggplot2 with facet_wrap for shared axes
ggplot(all_days_data_melted, aes(x = ArrivalTime, y = WaitTime, color = Teller)) +
geom_step(size = 0.8) +
facet_wrap(~ Day, ncol = 3) +
labs(x = "Arrival Time (minutes)", y = "Wait Time (minutes)",
color = "Wait Time Type") +
scale_color_manual(values = custom_colors,
labels = c("Teller 1", "Teller 2", "Total")) +
theme_bw() +
theme(legend.position = "top",
plot.title = element_text(size = 10),
axis.text.x = element_text(angle = 45, hjust = 1),
panel.grid.minor = element_blank(),
panel.grid.major = element_blank()) +
ggtitle("Customer Wait Times Over 7 Days for tandem Servers") %>% suppressMessages()
Since the simulation results can vary due to randomness, we can run the simulation multiple times (1000 times) to get a more accurate estimate of the average wait time over 7 days.
# Perform 1000 simulations over 7 days, for each day's arrival times
<- replicate(1000, {
tandem_sim_results # Apply simulate_tandem_servers_day for each day's arrival times
lapply(arrival_times_by_day, function(arrival_times) {
simulate_tandem_servers_day(arrival_times, mu)
})simplify = FALSE)
},
# Extract and calculate average wait times for each simulation over 7 days
<- sapply(tandem_sim_results, function(simulation) {
tandem_avg_total_wait_times mean(sapply(simulation, function(day_result) mean(day_result$total_wait_times)))
})<- sapply(tandem_sim_results, function(simulation) {
tandem_avg_wait_times_stage1 mean(sapply(simulation, function(day_result) mean(day_result$wait_times_stage1)))
})<- sapply(tandem_sim_results, function(simulation) {
tandem_avg_wait_times_stage2 mean(sapply(simulation, function(day_result) mean(day_result$wait_times_stage2)))
})
# Create a data frame for the summary
<- data.frame(
tandem_results_df Avg_Total_Wait_Time = tandem_avg_total_wait_times / 60, # Convert to hours
Avg_Wait_Time_Server1 = tandem_avg_wait_times_stage1 / 60, # Convert to hours
Avg_Wait_Time_Server2 = tandem_avg_wait_times_stage2 / 60 # Convert to hours
)
# Create a summary table with gtsummary
<- tandem_results_df %>%
tandem_summary_table tbl_summary(
statistic = all_continuous() ~ "{mean} ± {sd}",
label = list(
~ "Average Total Wait Time (hours)",
Avg_Total_Wait_Time ~ "Average Wait Time for Server 1 (hours)",
Avg_Wait_Time_Server1 ~ "Average Wait Time for Server 2 (hours)"
Avg_Wait_Time_Server2
),digits = all_continuous() ~ 2 # Round to 2 decimal places
%>%
) modify_header(label = "**Statistic**") %>%
modify_caption("**Tandem Service Wait Times Over 7 Days**")
# Print the summary table
tandem_summary_table
Statistic | N = 1,0001 |
---|---|
Average Total Wait Time (hours) | 1.18 ± 0.17 |
Average Wait Time for Server 1 (hours) | 0.71 ± 0.15 |
Average Wait Time for Server 2 (hours) | 0.47 ± 0.13 |
1 Mean ± SD |
Type 2: One Line, Multiple Servers (Customers Choose)
In this case, there is also only one queue. Customer will go to either server 1 or server 2. And customers will get their food while ordering. For example, in an In-N-Out restaurant, and customers go to the first server by default to place their order if both servers are available. If one server is busy, the customer will go to the other server. If both servers are busy, the customer will wait in the queue and go to the first server when available.
Code
# Data for tellers and customers
<- data.frame(
teller_data x = c(1, 3),
y = c(4, 4),
label = c("Teller 1", "Teller 2")
)
<- data.frame(
customer_data x = rep(2, 4),
y = c(2.5, 2, 1.5, 1),
label = rep("Customer", 4)
)
# Create the plot
ggplot() +
# Add circles for tellers
geom_point(data = teller_data, aes(x = x, y = y), shape = 1, size = 8, color = "black") +
# Add labels for tellers
geom_text(data = teller_data, aes(x = x, y = y + 0.2, label = label), vjust = -1, hjust = 0.5, size = 5) +
# Add squares for customers
geom_point(data = customer_data, aes(x = x, y = y), shape = 15, size = 5, color = "black") +
# Add arrows from tellers to the first customer position
geom_segment(aes(x = 2, y = 2.5, xend = 1.2, yend = 3.8), linetype=2,
arrow = arrow(length = unit(0.3, "cm")), color = "black") +
geom_segment(aes(x = 2, y = 2.5, xend = 2.8, yend = 3.8), linetype=2,
arrow = arrow(length = unit(0.3, "cm")), color = "black") +
# Add "or" text between the arrows
annotate("text", x = 2, y = 3, label = "or", size = 5, fontface = "italic") +
# Set plot limits and remove axis lines
coord_cartesian(xlim = c(0.5, 3.5), ylim = c(0.5, 5)) +
theme_void()
Since, in this case, the customer will get their food while ordering, the service speed will be slower than the previous case.
<- 2.5 / 60 # Service rate for each teller in minutes mu_slower
# Function to simulate oneline service with 1 line and 2 tellers for one day
<- function(arrival_times, mu) {
simulate_oneline_service_day
<- c()
wait_times <- c()
service_times
# Initialize tellers' availability times
<- 0
teller1_free_at <- 0
teller2_free_at
# Process each customer arrival
for (arrival_time in arrival_times) {
# Determine which teller will serve the customer
# Both tellers are available, go to Teller 1 by default
if (arrival_time >= teller1_free_at && arrival_time >= teller2_free_at) {
<- 0
wait_time <- rexp(1, rate = mu)
service_time <- arrival_time + service_time
teller1_free_at
# Teller 1 is available
else if (arrival_time >= teller1_free_at) {
} <- 0
wait_time <- rexp(1, rate = mu)
service_time <- arrival_time + service_time
teller1_free_at
# Teller 2 is available
else if (arrival_time >= teller2_free_at) {
} <- 0
wait_time <- rexp(1, rate = mu)
service_time <- arrival_time + service_time
teller2_free_at
# Both tellers are busy, customer waits in queue and for the next available teller
else {
} <- min(teller1_free_at, teller2_free_at)
next_free_time <- next_free_time - arrival_time
wait_time
# Assign to the teller who becomes available first
if (teller1_free_at <= teller2_free_at) {
<- rexp(1, rate = mu)
service_time <- next_free_time + service_time
teller1_free_at else {
} <- rexp(1, rate = mu)
service_time <- next_free_time + service_time
teller2_free_at
}
}
# Record wait and service times
<- c(wait_times, wait_time)
wait_times <- c(service_times, service_time)
service_times
}
# Calculate total wait times
<- wait_times
total_wait_times <- mean(total_wait_times)
avg_wait_time
return(list(avg_wait_time = avg_wait_time,
arrival_times = arrival_times,
wait_times = wait_times,
service_times = service_times,
total_wait_times = total_wait_times))
}
Now, we will run the simulation for 7 days and calculate the average wait time over the week. Here, in order to keep the consistency, we will use the same arrival times for each day.
# Apply simulate_oneline_service_day function to each day's arrival times
<- lapply(arrival_times_by_day, function(arrival_times) {
oneline_weekly_results simulate_oneline_service_day(arrival_times, mu_slower)
})
# Calculate the average wait time over 7 days
<- mean(sapply(oneline_weekly_results, `[[`, "avg_wait_time"))
oneline_avg_weekly_wait_time
# Print the average weekly wait time in hours
cat("Average wait time over 7 days for oneline service (hours):", oneline_avg_weekly_wait_time / 60, "\n")
Average wait time over 7 days for oneline service (hours): 0.8161425
I think the the serving speed \(\mu\) is important for the wait time.
Code
# Combine data for all days into a single data frame
<- do.call(rbind, lapply(1:7, function(i) {
all_days_data data.frame(
Day = paste("Day", i),
ArrivalTime = oneline_weekly_results[[i]]$arrival_times,
WaitTime = oneline_weekly_results[[i]]$total_wait_times
)
}))
# Plot for all_days_data using ggplot2 with facet_wrap
ggplot(all_days_data, aes(x = ArrivalTime, y = WaitTime)) +
geom_step(color = "#e31a1c", size = 0.8) +
facet_wrap(~ Day, ncol = 3) +
labs(x = "Arrival Time (minutes)", y = "Wait Time (minutes)",
title = "Wait Times for oneline Service Over 7 Days") +
theme_bw(base_size = 12) +
theme(
legend.position = "none", # No legend needed for single color
panel.grid.major = element_blank(), # Remove major grid lines
panel.grid.minor = element_blank(), # Remove minor grid lines
strip.text = element_text(size = 10), # Bold facet labels
plot.title = element_text(size = 14, hjust = 0.5), # Centered and bold title
axis.text = element_text(size = 10), # Adjust axis text size
axis.title = element_text(size = 12), # Bold axis titles
axis.text.x = element_text(angle = 45, hjust = 1) # Rotate x-axis labels
%>%
) suppressMessages()
Since the simulation results can vary due to randomness, we can run the simulation multiple times (1000 times) to get a more accurate estimate of the average wait time over 7 days.
# Perform 1000 simulations over 7 days, using each day's arrival times
<- replicate(200, {
oneline_sim_results # Apply simulate_oneline_service_day for each day's arrival times
lapply(arrival_times_by_day, function(arrival_times) {
simulate_oneline_service_day(arrival_times, mu_slower)
})simplify = FALSE)
},
# Extract and calculate average total wait times for each simulation over 7 days
<- sapply(oneline_sim_results, function(simulation) {
oneline_avg_total_wait_times mean(sapply(simulation, function(day_result) mean(day_result$total_wait_times)))
})
# Convert average total wait times from minutes to hours
<- oneline_avg_total_wait_times / 60
oneline_avg_total_wait_times_hours
# Create a data frame for the summary
<- data.frame(
oneline_results_df Avg_Total_Wait_Time = oneline_avg_total_wait_times_hours # Average wait time in hours
)
# Create a summary table with gtsummary
<- oneline_results_df %>%
oneline_summary_table tbl_summary(
statistic = all_continuous() ~ "{mean} ± {sd}",
label = list(
~ "Average Total Wait Time (hours)"
Avg_Total_Wait_Time
),digits = all_continuous() ~ 2 # Round to 2 decimal places
%>%
) modify_header(label = "**Statistic**") %>%
modify_caption("**Oneline Service Wait Times Over 7 Days**")
# Print the summary table
oneline_summary_table
Statistic | N = 2001 |
---|---|
Average Total Wait Time (hours) | 0.61 ± 0.15 |
1 Mean ± SD |
Type 3: Each teller has their own line (grocery store)
In this scenario, each teller has their own line, and customers choose the shortest line to join. This is similar to the setup in a grocery store, where each checkout lane has its own queue.
Code
# Data for tellers
<- data.frame(
teller_data x = c(1, 3),
y = c(5, 5),
label = c("Teller 1", "Teller 2")
)
# Data for customers in Teller 1's and Teller 2's queues
<- data.frame(
customer_data x = rep(c(1, 3), each = 3),
y = c(4, 3, 2, 4, 3, 2),
label = "Customer"
)
# Create the plot
ggplot() +
# Add circles for tellers
geom_point(data = teller_data, aes(x = x, y = y), shape = 1, size = 8, color = "black") +
# Add labels for tellers
geom_text(data = teller_data, aes(x = x, y = y + 0.2, label = label), vjust = -1, hjust = 0.5, size = 5) +
# Add squares for customers in each teller's queue
geom_point(data = customer_data, aes(x = x, y = y), shape = 15, size = 5, color = "black") +
# Set plot limits and remove axis lines
coord_cartesian(xlim = c(0.5, 3.5), ylim = c(1, 6)) +
theme_void()
<- 2.5 / 60 # Service rate for each teller in minutes mu_slower
# Function to simulate service with each teller having their own line for one day
<- function(arrival_times, mu) {
simulate_separate_lines_day
# Initialize variables for each teller
<- c()
wait_times_teller1 <- c()
wait_times_teller2 <- c()
service_times_teller1 <- c()
service_times_teller2
# Initialize tellers' availability times and queue lengths
<- 0
teller1_free_at <- 0
teller2_free_at <- 0
queue_length_teller1 <- 0
queue_length_teller2
# Initialize an empty data frame to store results
<- data.frame(
results ArrivalTime = numeric(0),
WaitTime = numeric(0),
ServiceTime = numeric(0),
Teller = character(0),
QueueLength_Teller1 = numeric(0),
QueueLength_Teller2 = numeric(0)
)
# Process each customer arrival
for (arrival_time in arrival_times) {
# Record the current queue lengths
<- queue_length_teller1
current_queue_length1 <- queue_length_teller2
current_queue_length2
# Determine which teller the customer will go to based on the shorter queue
if (queue_length_teller1 < queue_length_teller2 ||
== queue_length_teller2 && runif(1) < 0.5)) {
(queue_length_teller1
# Assign to Teller 1
if (arrival_time >= teller1_free_at) {
# Teller 1 is free, no wait time
<- 0
wait_time <- rexp(1, rate = mu)
service_time <- arrival_time + service_time
teller1_free_at else {
} # Teller 1 is busy, customer waits
<- teller1_free_at - arrival_time
wait_time <- rexp(1, rate = mu)
service_time <- teller1_free_at + service_time
teller1_free_at <- queue_length_teller1 + 1
queue_length_teller1
}
# Record the result for Teller 1
<- rbind(results, data.frame(
results ArrivalTime = arrival_time,
WaitTime = wait_time,
ServiceTime = service_time,
Teller = "Teller 1",
QueueLength_Teller1 = current_queue_length1,
QueueLength_Teller2 = current_queue_length2
))
else {
}
# Assign to Teller 2
if (arrival_time >= teller2_free_at) {
# Teller 2 is free, no wait time
<- 0
wait_time <- rexp(1, rate = mu)
service_time <- arrival_time + service_time
teller2_free_at else {
} # Teller 2 is busy, customer waits
<- teller2_free_at - arrival_time
wait_time <- rexp(1, rate = mu)
service_time <- teller2_free_at + service_time
teller2_free_at <- queue_length_teller2 + 1
queue_length_teller2
}
# Record the result for Teller 2
<- rbind(results, data.frame(
results ArrivalTime = arrival_time,
WaitTime = wait_time,
ServiceTime = service_time,
Teller = "Teller 2",
QueueLength_Teller1 = current_queue_length1,
QueueLength_Teller2 = current_queue_length2
))
}
# Update queue lengths after the patient is served (if applicable)
if (arrival_time >= teller1_free_at) {
<- max(0, queue_length_teller1 - 1)
queue_length_teller1
}if (arrival_time >= teller2_free_at) {
<- max(0, queue_length_teller2 - 1)
queue_length_teller2
}
}
# Calculate average wait times for each teller
<- mean(results$WaitTime[results$Teller == "Teller 1"])
avg_wait_time_teller1 <- mean(results$WaitTime[results$Teller == "Teller 2"])
avg_wait_time_teller2
return(list(
results = results,
avg_wait_time_teller1 = avg_wait_time_teller1,
avg_wait_time_teller2 = avg_wait_time_teller2
)) }
Now, we will run the simulation for 7 days and calculate the average wait time over the week. Here, in order to keep the consistency, we will use the same arrival times for each day.
# Apply simulate_separate_lines_day function to each day's arrival times
<- lapply(arrival_times_by_day, function(arrival_times) {
separate_weekly_results simulate_separate_lines_day(arrival_times, mu_slower)
})
# Calculate the average weekly wait time for each teller
<- mean(sapply(separate_weekly_results, `[[`, "avg_wait_time_teller1"))
avg_weekly_wait_time_teller1 <- mean(sapply(separate_weekly_results, `[[`, "avg_wait_time_teller2"))
avg_weekly_wait_time_teller2
# Convert from minutes to hours for reporting
<- avg_weekly_wait_time_teller1 / 60
avg_weekly_wait_time_teller1_hours <- avg_weekly_wait_time_teller2 / 60
avg_weekly_wait_time_teller2_hours
# Print the average weekly wait times for each teller
cat("Average weekly wait time for Teller 1 (hours):", round(avg_weekly_wait_time_teller1_hours, 2), "\n")
Average weekly wait time for Teller 1 (hours): 0.5
cat("Average weekly wait time for Teller 2 (hours):", round(avg_weekly_wait_time_teller2_hours, 2), "\n")
Average weekly wait time for Teller 2 (hours): 0.55
Plotting the distribution of wait times for each teller over the week.
Code
# Combine data for all days into a single data frame for both tellers
<- do.call(rbind, lapply(1:7, function(i) {
all_days_data <- separate_weekly_results[[i]]$results
day_data $Day <- paste("Day", i)
day_datareturn(day_data)
}))
# Convert 'Day' column to a factor for ordering in facets
$Day <- factor(all_days_data$Day, levels = paste("Day", 1:7))
all_days_data
# Plot using ggplot2 with facet_wrap
ggplot(all_days_data, aes(x = ArrivalTime, y = WaitTime, color = Teller)) +
geom_step(size = 0.8) +
facet_wrap(~ Day, ncol = 3) +
labs(x = "Arrival Time (minutes)", y = "Wait Time (minutes)",
title = "Wait Times for Separate Lines Over 7 Days") +
scale_color_manual(values = c("Teller 1" = "#1f78b4", "Teller 2" = "#33a02c")) + # Colors for tellers
theme_bw(base_size = 12) +
theme(
legend.position = "top",
panel.grid.major = element_blank(), # Remove major grid lines
panel.grid.minor = element_blank(), # Remove minor grid lines
strip.text = element_text(size = 10), # Bold facet labels
plot.title = element_text(size = 14, hjust = 0.5), # Centered and bold title
axis.text = element_text(size = 10), # Adjust axis text size
axis.title = element_text(size = 12), # Bold axis titles
axis.text.x = element_text(angle = 45, hjust = 1) # Rotate x-axis labels
%>%
) suppressMessages()
Finally, since the simulation is stochastic, we can run it multiple times to get a sense of the variability in the results.
# Perform 100 simulations over 7 days, using each day's arrival times
<- replicate(1000, {
separate_sim_results # Apply simulate_separate_lines_day for each day's arrival times
lapply(arrival_times_by_day, function(arrival_times) {
simulate_separate_lines_day(arrival_times, mu_slower)
})simplify = FALSE)
},
# Extract and calculate average wait times and queue lengths for each simulation over 7 days
<- sapply(separate_sim_results, function(simulation) {
separate_avg_wait_time_teller1 mean(sapply(simulation, function(day_result) mean(day_result$results$WaitTime[day_result$results$Teller == "Teller 1"])))
})
<- sapply(separate_sim_results, function(simulation) {
separate_avg_wait_time_teller2 mean(sapply(simulation, function(day_result) mean(day_result$results$WaitTime[day_result$results$Teller == "Teller 2"])))
})
<- sapply(separate_sim_results, function(simulation) {
separate_avg_queue_length_teller1 mean(sapply(simulation, function(day_result) mean(day_result$results$QueueLength_Teller1[day_result$results$Teller == "Teller 1"])))
})
<- sapply(separate_sim_results, function(simulation) {
separate_avg_queue_length_teller2 mean(sapply(simulation, function(day_result) mean(day_result$results$QueueLength_Teller2[day_result$results$Teller == "Teller 2"])))
})
# Convert average wait times from minutes to hours
<- separate_avg_wait_time_teller1 / 60
separate_avg_wait_time_teller1_hours <- separate_avg_wait_time_teller2 / 60
separate_avg_wait_time_teller2_hours
# Create a data frame for the summary
<- data.frame(
separate_results_df Avg_Wait_Time_Teller1 = separate_avg_wait_time_teller1_hours, # Average wait time for Teller 1 in hours
Avg_Wait_Time_Teller2 = separate_avg_wait_time_teller1_hours, # Average wait time for Teller 2 in hours
Avg_Queue_Length_Teller1 = separate_avg_queue_length_teller1, # Average queue length for Teller 1
Avg_Queue_Length_Teller2 = separate_avg_queue_length_teller2 # Average queue length for Teller 2
)
# Create a summary table with gtsummary
<- separate_results_df %>%
separate_summary_table tbl_summary(
statistic = all_continuous() ~ "{mean} ± {sd}",
label = list(
~ "Average Wait Time for Teller 1 (hours)",
Avg_Wait_Time_Teller1 ~ "Average Wait Time for Teller 2 (hours)",
Avg_Wait_Time_Teller2 ~ "Average Queue Length for Teller 1",
Avg_Queue_Length_Teller1 ~ "Average Queue Length for Teller 2"
Avg_Queue_Length_Teller2
),digits = all_continuous() ~ 2 # Round to 2 decimal places
%>%
) modify_header(label = "**Statistic**") %>%
modify_caption("**Separate Line Wait Times and Queue Lengths Over 7 Days**")
# Print the summary table
separate_summary_table
Statistic | N = 1,0001 |
---|---|
Average Wait Time for Teller 1 (hours) | 0.75 ± 0.20 |
Average Wait Time for Teller 2 (hours) | 0.75 ± 0.20 |
Average Queue Length for Teller 1 | 4.38 ± 0.48 |
Average Queue Length for Teller 2 | 4.38 ± 0.48 |
1 Mean ± SD |
Conclusion
Assumptions:
The simulation assumes that customers arrive according to a Poisson process, and service times are exponentially distributed. The simulation also assumes that customers are served on a first-come, first-served basis.
In this simulation, I assume that the service rate for the oneline System
system, each teller has a service rate of \(\mu = 5 \text{ customers per hour}\), while for the Separate Lines
and One Line
systems, each teller has a service rate of \(\mu = 2.5 \text{ customers per hour}\). Because, in the oneline System
system, both tellers serve customers simultaneously and they divide the total service rate of \(\mu = 5 \text{ customers per hour}\), while in the Separate Lines
and One Line
systems, each teller need to finish the service before the next teller can start serving the next customer.
System Type | Service Rate per Teller (μ) | Description |
---|---|---|
Tandem System | 5 customers per hour | Each teller has a service rate of 5 customers per hour, as they serve customers simultaneously. |
Separate Lines | 2.5 customers per hour | Each teller has a service rate of 2.5 customers per hour, as they must finish serving one customer before starting the next. |
One Line | 2.5 customers per hour | Similar to Separate Lines, each teller has a service rate of 2.5 customers per hour, with one teller needing to finish before the other starts. |
Based on the simulation results, the One Line System
has the lowest average total wait time, followed by the Separate Lines
system and the Tandem
system. The One Line System
also has the lowest average queue length for both tellers, indicating efficient utilization of resources.
Code
# Combine the data frames into a single data frame for a cohesive summary
<- data.frame(
summary_data oneline_Systems = oneline_results_df$Avg_Total_Wait_Time,
Separate_Systems = separate_results_df$Avg_Wait_Time_Teller1,
tandem_Systems = tandem_results_df$Avg_Total_Wait_Time
)
# Create a summary table using gtsummary with Nature or Science style formatting
<- summary_data %>%
summary_table tbl_summary(
statistic = all_continuous() ~ "{mean} ± {sd}", # Display mean and standard deviation
label = list(
~ "Separate Lines",
Separate_Systems ~ "One Line",
oneline_Systems ~ "Tandem"
tandem_Systems
),digits = all_continuous() ~ 2 # Round to 2 decimal places for clarity
%>%
) modify_header(label = "**System Type**", stat_0 = "**Average Wait Time (hours)**") %>%
modify_caption("**Summary of Average Wait Times Across Systems**") %>%
as_gt()
# Display the summary table
summary_table
System Type | Average Wait Time (hours)1 |
---|---|
One Line | 0.61 ± 0.15 |
Separate Lines | 0.75 ± 0.20 |
Tandem | 1.18 ± 0.17 |
1 Mean ± SD |
The service rate \(\mu\) is an important parameter in the simulation, as it determines the speed at which tellers can serve customers; thereby affecting the wait times and queue lengths in each system.
Case 2: Servers have different rates
Type 1: One line with two servers (Tandem)
In this system, customers first go to Teller 1, then move to Teller 2 once Teller 1’s service is complete. In this system, Teller 1 has a service rate of \(\mu_1 = 5 \text{ customers per hour}\), and Teller 2 has a service rate of \(\mu_2 = 3 \text{ customers per hour}\). The simulation will calculate the average total wait time and average queue length for each teller over a 7-day period.
<- 5 / 60 # Service rate for Teller 1 (customers per minute)
mu1 <- 3 / 60 # Service rate for Teller 2 (customers per minute) mu2
# Function to simulate 1 line with tandem servers for one day
<- function(arrival_times, mu1, mu2) {
simulate_tandem_servers_day
<- c()
wait_times_stage1 <- c()
wait_times_stage2 <- c()
service_times_stage1 <- c()
service_times_stage2
# Initialize tellers' availability times
<- 0
teller1_free_at <- 0
teller2_free_at
# Process each arrival in the line with tandem servers
for (arrival_time in arrival_times) {
# For each customer, first go to Teller 1, then Teller 2
# Calculate the wait time and service time for each teller
# Stage 1: Teller 1
<- max(0, teller1_free_at - arrival_time)
wait_time1 <- rexp(1, rate = mu1)
service_time1 <- arrival_time + wait_time1 + service_time1
teller1_free_at <- c(wait_times_stage1, wait_time1)
wait_times_stage1 <- c(service_times_stage1, service_time1)
service_times_stage1
# Stage 2: Teller 2
<- teller1_free_at
arrival_time_stage2 <- max(0, teller2_free_at - arrival_time_stage2)
wait_time2 <- rexp(1, rate = mu2)
service_time2 <- arrival_time_stage2 + wait_time2 + service_time2
teller2_free_at <- c(wait_times_stage2, wait_time2)
wait_times_stage2 <- c(service_times_stage2, service_time2)
service_times_stage2
}
# Calculate total wait times
<- wait_times_stage1 + wait_times_stage2
total_wait_times <- mean(total_wait_times)
avg_wait_time
return(list(avg_wait_time = avg_wait_time,
arrival_times = arrival_times,
wait_times_stage1 = wait_times_stage1,
wait_times_stage2 = wait_times_stage2,
total_wait_times = total_wait_times))
}
Now, we will run the simulation for 7 days and calculate the average wait time over the week.
# Apply the simulate_tandem_servers_day function to each day's arrival times
<- lapply(arrival_times_by_day, function(arrival_times) {
tandem_weekly_results simulate_tandem_servers_day(arrival_times, mu1, mu2)
})
# Summary of results
<- mean(tandem_weekly_results %>%
tandem_avg_weekly_wait_time sapply(`[[`, "avg_wait_time"))
cat("Average wait time over 7 days for tandem servers:",
/ 60, "\n") tandem_avg_weekly_wait_time
Average wait time over 7 days for tandem servers: 2.799473
Finally, we will plot the wait times for each teller and the total wait time for each customer over the week.
Code
# Combine data for all days into a single data frame
<- do.call(rbind, lapply(1:7, function(i) {
all_days_data data.frame(
Day = paste("Day", i),
ArrivalTime = tandem_weekly_results[[i]]$arrival_times,
WaitTimeStage1 = tandem_weekly_results[[i]]$wait_times_stage1,
WaitTimeStage2 = tandem_weekly_results[[i]]$wait_times_stage2,
TotalWaitTime = tandem_weekly_results[[i]]$total_wait_times
)
}))
# Melt data into a long format for plotting
<- melt(all_days_data, id.vars = c("Day", "ArrivalTime"),
all_days_data_melted variable.name = "Teller", value.name = "WaitTime")
# Custom color palette in the style of Nature or Science journals
<- c("WaitTimeStage1" = "#1f78b4", # Blue
custom_colors "WaitTimeStage2" = "#33a02c", # Green
"TotalWaitTime" = "#e31a1c") # Red
# Plot using ggplot2 with facet_wrap for shared axes
ggplot(all_days_data_melted, aes(x = ArrivalTime, y = WaitTime, color = Teller)) +
geom_step(size = 0.8) +
facet_wrap(~ Day, ncol = 3) +
labs(x = "Arrival Time (minutes)", y = "Wait Time (minutes)",
color = "Wait Time Type") +
scale_color_manual(values = custom_colors,
labels = c("Teller 1", "Teller 2", "Total")) +
theme_bw() +
theme(legend.position = "top",
plot.title = element_text(size = 10),
axis.text.x = element_text(angle = 45, hjust = 1),
panel.grid.minor = element_blank(),
panel.grid.major = element_blank()) +
ggtitle("Customer Wait Times Over 7 Days for Tandem Servers") %>%
suppressMessages()
Since the simulation results can vary due to randomness, we can run the simulation multiple times (1000 times) to get a more accurate estimate of the average wait time over 7 days.
# Perform 1000 simulations over 7 days, for each day's arrival times
<- replicate(1000, {
tandem_sim_results # Apply simulate_tandem_servers_day for each day's arrival times
lapply(arrival_times_by_day, function(arrival_times) {
simulate_tandem_servers_day(arrival_times, mu1, mu2)
})simplify = FALSE)
},
# Extract and calculate average wait times for each simulation over 7 days
<- sapply(tandem_sim_results, function(simulation) {
tandem_avg_total_wait_times mean(sapply(simulation, function(day_result) mean(day_result$total_wait_times)))
})<- sapply(tandem_sim_results, function(simulation) {
tandem_avg_wait_times_stage1 mean(sapply(simulation, function(day_result) mean(day_result$wait_times_stage1)))
})<- sapply(tandem_sim_results, function(simulation) {
tandem_avg_wait_times_stage2 mean(sapply(simulation, function(day_result) mean(day_result$wait_times_stage2)))
})
# Create a data frame for the summary
<- data.frame(
tandem_results_df Avg_Total_Wait_Time = tandem_avg_total_wait_times / 60, # Convert to hours
Avg_Wait_Time_Server1 = tandem_avg_wait_times_stage1 / 60, # Convert to hours
Avg_Wait_Time_Server2 = tandem_avg_wait_times_stage2 / 60 # Convert to hours
)
# Create a summary table with gtsummary
<- tandem_results_df %>%
tandem_summary_table tbl_summary(
statistic = all_continuous() ~ "{mean} ± {sd}",
label = list(
~ "Average Total Wait Time (hours)",
Avg_Total_Wait_Time ~ "Average Wait Time for Server 1 (hours)",
Avg_Wait_Time_Server1 ~ "Average Wait Time for Server 2 (hours)"
Avg_Wait_Time_Server2
),digits = all_continuous() ~ 2 # Round to 2 decimal places
%>%
) modify_header(label = "**Statistic**") %>%
modify_caption("**Tandem Service Wait Times Over 7 Days**")
# Print the summary table
tandem_summary_table
Statistic | N = 1,0001 |
---|---|
Average Total Wait Time (hours) | 2.69 ± 0.37 |
Average Wait Time for Server 1 (hours) | 0.70 ± 0.16 |
Average Wait Time for Server 2 (hours) | 1.98 ± 0.38 |
1 Mean ± SD |
Type 2: One Line, Multiple Servers (Customers Choose)
In this case, there is also only one queue. Customer will go to either server 1 or server 2. And customers will get their food while ordering. For example, in an In-N-Out restaurant, and customers go to the first server by default to place their order if both servers are available. If one server is busy, the customer will go to the other server. If both servers are busy, the customer will wait in the queue and go to the first server when available.
Since, in this case, the customer will get their food while ordering, the service speed will be slower than the previous case. So, the Teller 1 will have \(\mu_{1_{slower}} = 2.5 \text{ customers per hour}\) and the Teller 2 will have \(\mu_{2_{slower}} = 1.5 \text{ customers per hour}\).
<- 2.5 / 60 # Service rate for Teller 1 (customers per minute)
mu1_slower <- 1.5 / 60 # Service rate for Teller 2 (customers per minute) mu2_slower
# Function to simulate oneline service with 1 line and 2 tellers for one day
<- function(arrival_times, mu1, mu2) {
simulate_oneline_service_day
<- c()
wait_times <- c()
service_times
# Initialize tellers' availability times
<- 0
teller1_free_at <- 0
teller2_free_at
# Process each customer arrival
for (arrival_time in arrival_times) {
# Determine which teller will serve the customer
# Both tellers are available, go to Teller 1 by default
if (arrival_time >= teller1_free_at && arrival_time >= teller2_free_at) {
<- 0
wait_time <- rexp(1, rate = mu)
service_time <- arrival_time + service_time
teller1_free_at
# Teller 1 is available
else if (arrival_time >= teller1_free_at) {
} <- 0
wait_time <- rexp(1, rate = mu1)
service_time <- arrival_time + service_time
teller1_free_at
# Teller 2 is available
else if (arrival_time >= teller2_free_at) {
} <- 0
wait_time <- rexp(1, rate = mu2)
service_time <- arrival_time + service_time
teller2_free_at
# Both tellers are busy, customer waits in queue and for the next available teller
else {
} <- min(teller1_free_at, teller2_free_at)
next_free_time <- next_free_time - arrival_time
wait_time
# Assign to the teller who becomes available first
if (teller1_free_at <= teller2_free_at) {
<- rexp(1, rate = mu1)
service_time <- next_free_time + service_time
teller1_free_at else {
} <- rexp(1, rate = mu2)
service_time <- next_free_time + service_time
teller2_free_at
}
}
# Record wait and service times
<- c(wait_times, wait_time)
wait_times <- c(service_times, service_time)
service_times
}
# Calculate total wait times
<- wait_times
total_wait_times <- mean(total_wait_times)
avg_wait_time
return(list(avg_wait_time = avg_wait_time,
arrival_times = arrival_times,
wait_times = wait_times,
service_times = service_times,
total_wait_times = total_wait_times))
}
Now, we will run the simulation for 7 days and calculate the average wait time over the week. Here, in order to keep the consistency, we will use the same arrival times for each day.
# Apply simulate_oneline_service_day function to each day's arrival times
<- lapply(arrival_times_by_day, function(arrival_times) {
oneline_weekly_results simulate_oneline_service_day(arrival_times, mu1_slower, mu2_slower)
})
# Calculate the average wait time over 7 days
<- mean(sapply(oneline_weekly_results, `[[`, "avg_wait_time"))
oneline_avg_weekly_wait_time
# Print the average weekly wait time in hours
cat("Average wait time over 7 days for one line service (hours):", oneline_avg_weekly_wait_time / 60, "\n")
Average wait time over 7 days for one line service (hours): 0.757806
Code
# Combine data for all days into a single data frame
<- do.call(rbind, lapply(1:7, function(i) {
all_days_data data.frame(
Day = paste("Day", i),
ArrivalTime = oneline_weekly_results[[i]]$arrival_times,
WaitTime = oneline_weekly_results[[i]]$total_wait_times
)
}))
# Plot for all_days_data using ggplot2 with facet_wrap
ggplot(all_days_data, aes(x = ArrivalTime, y = WaitTime)) +
geom_step(color = "#e31a1c", size = 0.8) +
facet_wrap(~ Day, ncol = 3) +
labs(x = "Arrival Time (minutes)", y = "Wait Time (minutes)",
title = "Wait Times for One Line Service Over 7 Days") +
theme_bw(base_size = 12) +
theme(
legend.position = "none", # No legend needed for single color
panel.grid.major = element_blank(), # Remove major grid lines
panel.grid.minor = element_blank(), # Remove minor grid lines
strip.text = element_text(size = 10), # Bold facet labels
plot.title = element_text(size = 14, hjust = 0.5), # Centered and bold title
axis.text = element_text(size = 10), # Adjust axis text size
axis.title = element_text(size = 12), # Bold axis titles
axis.text.x = element_text(angle = 45, hjust = 1) # Rotate x-axis labels
%>%
) suppressMessages()
Since the simulation results can vary due to randomness, we can run the simulation multiple times (1000 times) to get a more accurate estimate of the average wait time over 7 days.
# Perform 1000 simulations over 7 days, using each day's arrival times
<- replicate(1000, {
oneline_sim_results # Apply simulate_oneline_service_day for each day's arrival times
lapply(arrival_times_by_day, function(arrival_times) {
simulate_oneline_service_day(arrival_times, mu1_slower, mu2_slower)
})simplify = FALSE)
},
# Extract and calculate average total wait times for each simulation over 7 days
<- sapply(oneline_sim_results, function(simulation) {
oneline_avg_total_wait_times mean(sapply(simulation, function(day_result) mean(day_result$total_wait_times)))
})
# Convert average total wait times from minutes to hours
<- oneline_avg_total_wait_times / 60
oneline_avg_total_wait_times_hours
# Create a data frame for the summary
<- data.frame(
oneline_results_df Avg_Total_Wait_Time = oneline_avg_total_wait_times_hours # Average wait time in hours
)
# Create a summary table with gtsummary
<- oneline_results_df %>%
oneline_summary_table tbl_summary(
statistic = all_continuous() ~ "{mean} ± {sd}",
label = list(
~ "Average Total Wait Time (hours)"
Avg_Total_Wait_Time
),digits = all_continuous() ~ 2 # Round to 2 decimal places
%>%
) modify_header(label = "**Statistic**") %>%
modify_caption("**One Line Service Wait Times Over 7 Days**")
# Print the summary table
oneline_summary_table
Statistic | N = 1,0001 |
---|---|
Average Total Wait Time (hours) | 1.00 ± 0.21 |
1 Mean ± SD |
Type 3: Each teller has their own line (grocery store)
In this scenario, each teller has their own line, and customers choose the shortest line to join. This is similar to the setup in a grocery store, where each checkout lane has its own queue.
The Teller 1 will have \(\mu_{1{\text{slower}}} = 2.5 \text{ customers per hour}\) and Teller 2 will have \(\mu_{2{\text{slower}}} = 1.5 \text{ customers per hour}\).
<- 2.5 / 60 # Service rate for Teller 1 (slower)
mu1_slower <- 1.5 / 60 # Service rate for Teller 2 (slower) mu2_slower
# Function to simulate service with each teller having their own line for one day
<- function(arrival_times, mu1, mu2) {
simulate_separate_lines_day
# Initialize variables for each teller
<- c()
wait_times_teller1 <- c()
wait_times_teller2 <- c()
service_times_teller1 <- c()
service_times_teller2
# Initialize tellers' availability times and queue lengths
<- 0
teller1_free_at <- 0
teller2_free_at <- 0
queue_length_teller1 <- 0
queue_length_teller2
# Initialize an empty data frame to store results
<- data.frame(
results ArrivalTime = numeric(0),
WaitTime = numeric(0),
ServiceTime = numeric(0),
Teller = character(0),
QueueLength_Teller1 = numeric(0),
QueueLength_Teller2 = numeric(0)
)
# Process each customer arrival
for (arrival_time in arrival_times) {
# Record the current queue lengths
<- queue_length_teller1
current_queue_length1 <- queue_length_teller2
current_queue_length2
# Determine which teller the customer will go to based on the shorter queue
if (queue_length_teller1 < queue_length_teller2 ||
== queue_length_teller2 && runif(1) < 0.5)) {
(queue_length_teller1
# Assign to Teller 1
if (arrival_time >= teller1_free_at) {
# Teller 1 is free, no wait time
<- 0
wait_time <- rexp(1, rate = mu1)
service_time <- arrival_time + service_time
teller1_free_at else {
} # Teller 1 is busy, customer waits
<- teller1_free_at - arrival_time
wait_time <- rexp(1, rate = mu2)
service_time <- teller1_free_at + service_time
teller1_free_at <- queue_length_teller1 + 1
queue_length_teller1
}
# Record the result for Teller 1
<- rbind(results, data.frame(
results ArrivalTime = arrival_time,
WaitTime = wait_time,
ServiceTime = service_time,
Teller = "Teller 1",
QueueLength_Teller1 = current_queue_length1,
QueueLength_Teller2 = current_queue_length2
))
else {
}
# Assign to Teller 2
if (arrival_time >= teller2_free_at) {
# Teller 2 is free, no wait time
<- 0
wait_time <- rexp(1, rate = mu1)
service_time <- arrival_time + service_time
teller2_free_at else {
} # Teller 2 is busy, customer waits
<- teller2_free_at - arrival_time
wait_time <- rexp(1, rate = mu2)
service_time <- teller2_free_at + service_time
teller2_free_at <- queue_length_teller2 + 1
queue_length_teller2
}
# Record the result for Teller 2
<- rbind(results, data.frame(
results ArrivalTime = arrival_time,
WaitTime = wait_time,
ServiceTime = service_time,
Teller = "Teller 2",
QueueLength_Teller1 = current_queue_length1,
QueueLength_Teller2 = current_queue_length2
))
}
# Update queue lengths after the patient is served (if applicable)
if (arrival_time >= teller1_free_at) {
<- max(0, queue_length_teller1 - 1)
queue_length_teller1
}if (arrival_time >= teller2_free_at) {
<- max(0, queue_length_teller2 - 1)
queue_length_teller2
}
}
# Calculate average wait times for each teller
<- mean(results$WaitTime[results$Teller == "Teller 1"])
avg_wait_time_teller1 <- mean(results$WaitTime[results$Teller == "Teller 2"])
avg_wait_time_teller2
return(list(
results = results,
avg_wait_time_teller1 = avg_wait_time_teller1,
avg_wait_time_teller2 = avg_wait_time_teller2
)) }
Now, we will run the simulation for 7 days and calculate the average wait time over the week. Here, in order to keep the consistency, we will use the same arrival times for each day.
# Apply simulate_separate_lines_day function to each day's arrival times
<- lapply(arrival_times_by_day, function(arrival_times) {
separate_weekly_results simulate_separate_lines_day(arrival_times, mu1_slower, mu2_slower)
})
# Calculate the average weekly wait time for each teller
<- mean(sapply(separate_weekly_results, `[[`, "avg_wait_time_teller1"))
avg_weekly_wait_time_teller1 <- mean(sapply(separate_weekly_results, `[[`, "avg_wait_time_teller2"))
avg_weekly_wait_time_teller2
# Convert from minutes to hours for reporting
<- avg_weekly_wait_time_teller1 / 60
avg_weekly_wait_time_teller1_hours <- avg_weekly_wait_time_teller2 / 60
avg_weekly_wait_time_teller2_hours
# Print the average weekly wait times for each teller
cat("Average weekly wait time for Teller 1 (hours):", round(avg_weekly_wait_time_teller1_hours, 2), "\n")
Average weekly wait time for Teller 1 (hours): 1.43
cat("Average weekly wait time for Teller 2 (hours):", round(avg_weekly_wait_time_teller2_hours, 2), "\n")
Average weekly wait time for Teller 2 (hours): 2.08
Plotting the distribution of wait times for each teller over the week.
Code
# Combine data for all days into a single data frame for both tellers
<- do.call(rbind, lapply(1:7, function(i) {
all_days_data <- separate_weekly_results[[i]]$results
day_data $Day <- paste("Day", i)
day_datareturn(day_data)
}))
# Convert 'Day' column to a factor for ordering in facets
$Day <- factor(all_days_data$Day, levels = paste("Day", 1:7))
all_days_data
# Plot using ggplot2 with facet_wrap
ggplot(all_days_data, aes(x = ArrivalTime, y = WaitTime, color = Teller)) +
geom_step(size = 0.8) +
facet_wrap(~ Day, ncol = 3) +
labs(x = "Arrival Time (minutes)", y = "Wait Time (minutes)",
title = "Wait Times for Separate Lines Over 7 Days") +
scale_color_manual(values = c("Teller 1" = "#1f78b4", "Teller 2" = "#33a02c")) + # Colors for tellers
theme_bw(base_size = 12) +
theme(
legend.position = "top",
panel.grid.major = element_blank(), # Remove major grid lines
panel.grid.minor = element_blank(), # Remove minor grid lines
strip.text = element_text(size = 10), # Bold facet labels
plot.title = element_text(size = 14, hjust = 0.5), # Centered and bold title
axis.text = element_text(size = 10), # Adjust axis text size
axis.title = element_text(size = 12), # Bold axis titles
axis.text.x = element_text(angle = 45, hjust = 1) # Rotate x-axis labels
%>%
) suppressMessages()
Finally, since the simulation is stochastic, we can run it multiple times to get a sense of the variability in the results.
# Perform 100 simulations over 7 days, using each day's arrival times
<- replicate(200, {
separate_sim_results # Apply simulate_separate_lines_day for each day's arrival times
lapply(arrival_times_by_day, function(arrival_times) {
simulate_separate_lines_day(arrival_times, mu1_slower, mu2_slower)
})simplify = FALSE)
},
# Extract and calculate average wait times and queue lengths for each simulation over 7 days
<- sapply(separate_sim_results, function(simulation) {
separate_avg_wait_time_teller1 mean(sapply(simulation, function(day_result) mean(day_result$results$WaitTime[day_result$results$Teller == "Teller 1"])))
})
<- sapply(separate_sim_results, function(simulation) {
separate_avg_wait_time_teller2 mean(sapply(simulation, function(day_result) mean(day_result$results$WaitTime[day_result$results$Teller == "Teller 2"])))
})
<- sapply(separate_sim_results, function(simulation) {
separate_avg_queue_length_teller1 mean(sapply(simulation, function(day_result) mean(day_result$results$QueueLength_Teller1[day_result$results$Teller == "Teller 1"])))
})
<- sapply(separate_sim_results, function(simulation) {
separate_avg_queue_length_teller2 mean(sapply(simulation, function(day_result) mean(day_result$results$QueueLength_Teller2[day_result$results$Teller == "Teller 2"])))
})
# Convert average wait times from minutes to hours
<- separate_avg_wait_time_teller1 / 60
separate_avg_wait_time_teller1_hours <- separate_avg_wait_time_teller2 / 60
separate_avg_wait_time_teller2_hours
# Create a data frame for the summary
<- data.frame(
separate_results_df Avg_Wait_Time_Teller1 = separate_avg_wait_time_teller1_hours, # Average wait time for Teller 1 in hours
Avg_Wait_Time_Teller2 = separate_avg_wait_time_teller1_hours, # Average wait time for Teller 2 in hours
Avg_Queue_Length_Teller1 = separate_avg_queue_length_teller1, # Average queue length for Teller 1
Avg_Queue_Length_Teller2 = separate_avg_queue_length_teller2 # Average queue length for Teller 2
)
# Create a summary table with gtsummary
<- separate_results_df %>%
separate_summary_table tbl_summary(
statistic = all_continuous() ~ "{mean} ± {sd}",
label = list(
~ "Average Wait Time for Teller 1 (hours)",
Avg_Wait_Time_Teller1 ~ "Average Wait Time for Teller 2 (hours)",
Avg_Wait_Time_Teller2 ~ "Average Queue Length for Teller 1",
Avg_Queue_Length_Teller1 ~ "Average Queue Length for Teller 2"
Avg_Queue_Length_Teller2
),digits = all_continuous() ~ 2 # Round to 2 decimal places
%>%
) modify_header(label = "**Statistic**") %>%
modify_caption("**Separate Line Wait Times and Queue Lengths Over 7 Days**")
# Print the summary table
separate_summary_table
Statistic | N = 2001 |
---|---|
Average Wait Time for Teller 1 (hours) | 2.13 ± 0.45 |
Average Wait Time for Teller 2 (hours) | 2.13 ± 0.45 |
Average Queue Length for Teller 1 | 5.42 ± 0.46 |
Average Queue Length for Teller 2 | 5.41 ± 0.45 |
1 Mean ± SD |
Conclusion
Assumptions:
The simulation assumes that customers arrive according to a Poisson process, and service times are exponentially distributed. The simulation also assumes that customers are served on a first-come, first-served basis.
In this simulation, I assume that the service rate for the Tandem
system, teller 1 has a service rate of \(\mu = 5 \text{ customers per hour}\), teller 2 has a service rate of \(\mu = 3 \text{ customers per hour}\), and they serve customers simultaneously. While for the Separate Lines
and One Line System
systems, teller 1 has a service rate of \(\mu = 2.5 \text{ customers per hour}\), and teller 2 has a service rate of \(\mu = 2.5 \text{ customers per hour}\), and they must finish serving one customer before starting the next.
System Type | Service Rate (μ) per Teller | Description |
---|---|---|
Tandem Line | Teller 1: 5 customers per hour Teller 2: 3 customers per hour |
Teller 1 and Teller 2 serve customers simultaneously, each with their respective service rates. |
Separate Lines | Teller 1: 2.5 customers per hour Teller 2: 2.5 customers per hour |
Each teller has a service rate of 2.5 customers per hour and must finish serving one customer before starting the next. |
One Line System | Teller 1: 2.5 customers per hour Teller 2: 2.5 customers per hour |
Similar to Separate Lines, with each teller having a service rate of 2.5 customers per hour, but one teller must finish before the next teller can start. |
Based on the simulation results, the One Line System
has the lowest average total wait time, followed by the Separate Lines
system and the Tandem
system. The One Line System
also has the lowest average queue length for both tellers, indicating efficient utilization of resources.
# Combine the data frames into a single data frame for a cohesive summary
<- data.frame(
summary_data oneline_Systems = oneline_results_df$Avg_Total_Wait_Time,
Separate_Systems = separate_results_df$Avg_Wait_Time_Teller1,
tandem_Systems = tandem_results_df$Avg_Total_Wait_Time
)
# Create a summary table using gtsummary with Nature or Science style formatting
<- summary_data %>%
summary_table tbl_summary(
statistic = all_continuous() ~ "{mean} ± {sd}", # Display mean and standard deviation
label = list(
~ "Separate Lines",
Separate_Systems ~ "One Line",
oneline_Systems ~ "Tandem"
tandem_Systems
),digits = all_continuous() ~ 2 # Round to 2 decimal places for clarity
%>%
) modify_header(label = "**System Type**", stat_0 = "**Average Wait Time (hours)**") %>%
modify_caption("**Summary of Average Wait Times Across Systems**") %>%
as_gt()
# Display the summary table
summary_table
System Type | Average Wait Time (hours)1 |
---|---|
One Line | 1.00 ± 0.21 |
Separate Lines | 2.13 ± 0.45 |
Tandem | 2.69 ± 0.37 |
1 Mean ± SD |
Based on the simulation results, the One Line System
has the lowest average total wait time, followed by the Separate Lines
system and the tandem Line
system. The One Line System
also has the lowest average queue length for both tellers, indicating efficient utilization of resources.
Code
# Combine the data frames into a single data frame for a cohesive summary
<- data.frame(
summary_data oneline_Systems = oneline_results_df$Avg_Total_Wait_Time,
Separate_Systems = separate_results_df$Avg_Wait_Time_Teller1,
tandem_Systems = tandem_results_df$Avg_Total_Wait_Time
)
# Create a summary table using gtsummary with Nature or Science style formatting
<- summary_data %>%
summary_table tbl_summary(
statistic = all_continuous() ~ "{mean} ± {sd}", # Display mean and standard deviation
label = list(
~ "Separate Lines",
Separate_Systems ~ "One Line",
oneline_Systems ~ "Tandem"
tandem_Systems
),digits = all_continuous() ~ 2 # Round to 2 decimal places for clarity
%>%
) modify_header(label = "**System Type**", stat_0 = "**Average Wait Time (hours)**") %>%
modify_caption("**Summary of Average Wait Times Across Systems**") %>%
as_gt()
# Display the summary table
summary_table
System Type | Average Wait Time (hours)1 |
---|---|
One Line | 1.00 ± 0.21 |
Separate Lines | 2.13 ± 0.45 |
Tandem | 2.69 ± 0.37 |
1 Mean ± SD |
The service rate \(\mu\) is an important parameter in the simulation, as it determines the speed at which tellers can serve customers; thereby affecting the wait times and queue lengths in each system.
Case 3: Three Servers with Different Service Rates
Permutations of Service Rates
There are \(3! = 6\) possible permutations of service rates for the three servers: Fast, Medium, and Slow. The service rates are as follows:
# Define service rates for Fast, Medium, and Slow servers
<- 7 / 60 # Fast server: 7 customers per hour
fast_rate <- 5 / 60 # Medium server: 5 customers per hour
medium_rate <- 3 / 60 # Slow server: 3 customers per hour
slow_rate <- fast_rate / 2 # Half the rate of the fast server
fast_rate_half <- medium_rate / 2 # Half the rate of the medium server
medium_rate_half <- slow_rate / 2 # Half the rate of the slow server
slow_rate_half
# Generate all permutations of service rates
<- list(
service_rate_permutations c(fast_rate, medium_rate, slow_rate),
c(fast_rate, slow_rate, medium_rate),
c(medium_rate, fast_rate, slow_rate),
c(medium_rate, slow_rate, fast_rate),
c(slow_rate, fast_rate, medium_rate),
c(slow_rate, medium_rate, fast_rate)
)
<- list(
service_rate_permutations_half c(fast_rate_half, medium_rate_half, slow_rate_half),
c(fast_rate_half, slow_rate_half, medium_rate_half),
c(medium_rate_half, fast_rate_half, slow_rate_half),
c(medium_rate_half, slow_rate_half, fast_rate_half),
c(slow_rate_half, fast_rate_half, medium_rate_half),
c(slow_rate_half, medium_rate_half, fast_rate_half)
)
# Define descriptive names for each permutation based on rate order
<- c("fast-medium-slow", "fast-slow-medium", "medium-fast-slow",
rate_labels "medium-slow-fast", "slow-fast-medium", "slow-medium-fast")
Type 1: One line with two servers (Tandem)
In this system, customers first go to Teller 1, then move to Teller 2 once Teller 1’s service is complete. In this system, Teller 1 has a service rate of \(\mu_1 = 7 \text{ customers per hour}\), Teller 2 has a service rate of \(\mu_2 = 5 \text{ customers per hour}\), and Teller 3 has a service rate of \(\mu_3 = 3 \text{ customers per hour}\). The simulation will calculate the average total wait time and average queue length for each teller over a 7-day period.
# Function to simulate 1 line with tandem servers for one day with three tellers
<- function(arrival_times, mu1, mu2, mu3) {
simulate_tandem_servers_day
<- c()
wait_times_stage1 <- c()
wait_times_stage2 <- c()
wait_times_stage3 <- c()
service_times_stage1 <- c()
service_times_stage2 <- c()
service_times_stage3
# Initialize tellers' availability times
<- 0
teller1_free_at <- 0
teller2_free_at <- 0
teller3_free_at
# Process each arrival in the line with tandem servers
for (arrival_time in arrival_times) {
# Stage 1: Teller 1
<- max(0, teller1_free_at - arrival_time)
wait_time1 <- rexp(1, rate = mu1)
service_time1 <- arrival_time + wait_time1 + service_time1
teller1_free_at <- c(wait_times_stage1, wait_time1)
wait_times_stage1 <- c(service_times_stage1, service_time1)
service_times_stage1
# Stage 2: Teller 2
<- teller1_free_at
arrival_time_stage2 <- max(0, teller2_free_at - arrival_time_stage2)
wait_time2 <- rexp(1, rate = mu2)
service_time2 <- arrival_time_stage2 + wait_time2 + service_time2
teller2_free_at <- c(wait_times_stage2, wait_time2)
wait_times_stage2 <- c(service_times_stage2, service_time2)
service_times_stage2
# Stage 3: Teller 3
<- teller2_free_at
arrival_time_stage3 <- max(0, teller3_free_at - arrival_time_stage3)
wait_time3 <- rexp(1, rate = mu3)
service_time3 <- arrival_time_stage3 + wait_time3 + service_time3
teller3_free_at <- c(wait_times_stage3, wait_time3)
wait_times_stage3 <- c(service_times_stage3, service_time3)
service_times_stage3
}
# Calculate total wait times across all stages
<- wait_times_stage1 + wait_times_stage2 + wait_times_stage3
total_wait_times <- mean(total_wait_times)
avg_wait_time
return(list(
avg_wait_time = avg_wait_time,
arrival_times = arrival_times,
wait_times_stage1 = wait_times_stage1,
wait_times_stage2 = wait_times_stage2,
wait_times_stage3 = wait_times_stage3,
service_times_stage1 = service_times_stage1,
service_times_stage2 = service_times_stage2,
service_times_stage3 = service_times_stage3,
total_wait_times = total_wait_times
)) }
Function to perform the simulation, create step plots, and generate results data frames
<- function(arrival_times_by_day, permutations,
run_permutation_simulation num_simulations = 1000) {
rate_labels,
<- list()
results_list
for (i in seq_along(permutations)) {
<- permutations[[i]]
rates <- rates[1]
mu1 <- rates[2]
mu2 <- rates[3]
mu3
# Run 1000 simulations over 7 days for the current permutation
<- replicate(num_simulations, {
tandem_sim_results lapply(arrival_times_by_day, function(arrival_times) {
simulate_tandem_servers_day(arrival_times, mu1, mu2, mu3)
})simplify = FALSE)
},
# Extract average wait times for each simulation
<- sapply(tandem_sim_results, function(simulation) {
avg_total_wait_times mean(sapply(simulation, function(day_result) mean(day_result$total_wait_times)))
})<- sapply(tandem_sim_results, function(simulation) {
avg_wait_times_stage1 mean(sapply(simulation, function(day_result) mean(day_result$wait_times_stage1)))
})<- sapply(tandem_sim_results, function(simulation) {
avg_wait_times_stage2 mean(sapply(simulation, function(day_result) mean(day_result$wait_times_stage2)))
})<- sapply(tandem_sim_results, function(simulation) {
avg_wait_times_stage3 mean(sapply(simulation, function(day_result) mean(day_result$wait_times_stage3)))
})
# Create a data frame for the summary
<- data.frame(
results_df Avg_Total_Wait_Time = avg_total_wait_times / 60, # Convert to hours
Avg_Wait_Time_Server1 = avg_wait_times_stage1 / 60, # Convert to hours
Avg_Wait_Time_Server2 = avg_wait_times_stage2 / 60, # Convert to hours
Avg_Wait_Time_Server3 = avg_wait_times_stage3 / 60 # Convert to hours
)
# Collect data for step plot for the first simulation run of the 7 days
<- do.call(rbind, lapply(1:7, function(day) {
first_run_data data.frame(
Day = paste("Day", day),
ArrivalTime = tandem_sim_results[[1]][[day]]$arrival_times,
WaitTimeStage1 = tandem_sim_results[[1]][[day]]$wait_times_stage1,
WaitTimeStage2 = tandem_sim_results[[1]][[day]]$wait_times_stage2,
WaitTimeStage3 = tandem_sim_results[[1]][[day]]$wait_times_stage3,
TotalWaitTime = tandem_sim_results[[1]][[day]]$total_wait_times
)
}))
# Melt data into long format for plotting
<- melt(first_run_data, id.vars = c("Day", "ArrivalTime"),
first_run_data_melted variable.name = "Teller", value.name = "WaitTime")
# Custom color palette
<- c("WaitTimeStage1" = "#1f78b4",
custom_colors "WaitTimeStage2" = "#33a02c",
"WaitTimeStage3" = "#ff7f00",
"TotalWaitTime" = "#e31a1c")
# Generate step plot
<- ggplot(first_run_data_melted, aes(x = ArrivalTime, y = WaitTime, color = Teller)) +
step_plot geom_step(size = 0.8) +
facet_wrap(~ Day, ncol = 3) +
labs(x = "Arrival Time (minutes)", y = "Wait Time (minutes)",
color = "Wait Time Type") +
scale_color_manual(values = custom_colors,
labels = c("Teller 1", "Teller 2", "Teller 3", "Total Wait Time")) +
theme_bw(base_size = 12) +
theme(
legend.position = "top",
legend.title = element_text(size = 10),
legend.text = element_text(size = 9),
strip.text = element_text(size = 10), # Bold facet labels
plot.title = element_text(size = 14, hjust = 0.5), # Centered and bold title
axis.text = element_text(size = 10), # Adjust axis text size
axis.title = element_text(size = 12), # Bold axis titles
axis.text.x = element_text(angle = 45, hjust = 1), # Rotate x-axis labels
panel.grid.major = element_blank(), # Remove major grid lines
panel.grid.minor = element_blank()
+
) ggtitle(paste("Customer Wait Times for", rate_labels[i], "Over 7 Days"))
# Store the results
<-
results_list[[rate_labels[i]]] list(
results_df = results_df,
step_plot = step_plot
)
}
return(results_list)
}
# Run the function for all permutations
<- run_permutation_simulation(arrival_times_by_day,
tandem_permutation_results
service_rate_permutations,num_simulations = 1000)
rate_labels,
# Loop to access and display results
for (perm_name in names(tandem_permutation_results)) {
cat("\n", perm_name, "Summary Table:\n")
# Apply gtsummary to the results_df for a detailed summary table
<- tandem_permutation_results[[perm_name]]$results_df %>%
summary_table tbl_summary(
statistic = all_continuous() ~ "{mean} ± {sd}",
label = list(
~ "Average Total Wait Time (hours)",
Avg_Total_Wait_Time ~ "Average Wait Time for Server 1 (hours)",
Avg_Wait_Time_Server1 ~ "Average Wait Time for Server 2 (hours)",
Avg_Wait_Time_Server2 ~ "Average Wait Time for Server 3 (hours)"
Avg_Wait_Time_Server3
),digits = all_continuous() ~ 2
%>%
) modify_header(label = "**Statistic**") %>%
modify_caption(paste("**Tandem Service Wait Times for", perm_name, "**"))
# Extract the gtsummary table as a data frame
<- as_tibble(summary_table)
summary_df
# Print the data frame in a clean format
print(summary_df)
print(tandem_permutation_results[[perm_name]]$step_plot)
}
fast-medium-slow Summary Table:
# A tibble: 4 × 2
`**Statistic**` `**N = 1,000**`
<chr> <chr>
1 Average Total Wait Time (hours) 2.79 ± 0.34
2 Average Wait Time for Server 1 (hours) 0.26 ± 0.07
3 Average Wait Time for Server 2 (hours) 0.63 ± 0.15
4 Average Wait Time for Server 3 (hours) 1.90 ± 0.35
fast-slow-medium Summary Table:
# A tibble: 4 × 2
`**Statistic**` `**N = 1,000**`
<chr> <chr>
1 Average Total Wait Time (hours) 2.78 ± 0.35
2 Average Wait Time for Server 1 (hours) 0.26 ± 0.07
3 Average Wait Time for Server 2 (hours) 2.30 ± 0.37
4 Average Wait Time for Server 3 (hours) 0.22 ± 0.07
medium-fast-slow Summary Table:
# A tibble: 4 × 2
`**Statistic**` `**N = 1,000**`
<chr> <chr>
1 Average Total Wait Time (hours) 2.76 ± 0.35
2 Average Wait Time for Server 1 (hours) 0.70 ± 0.16
3 Average Wait Time for Server 2 (hours) 0.17 ± 0.05
4 Average Wait Time for Server 3 (hours) 1.88 ± 0.36
medium-slow-fast Summary Table:
# A tibble: 4 × 2
`**Statistic**` `**N = 1,000**`
<chr> <chr>
1 Average Total Wait Time (hours) 2.79 ± 0.35
2 Average Wait Time for Server 1 (hours) 0.71 ± 0.16
3 Average Wait Time for Server 2 (hours) 2.00 ± 0.35
4 Average Wait Time for Server 3 (hours) 0.09 ± 0.03
slow-fast-medium Summary Table:
# A tibble: 4 × 2
`**Statistic**` `**N = 1,000**`
<chr> <chr>
1 Average Total Wait Time (hours) 2.78 ± 0.34
2 Average Wait Time for Server 1 (hours) 2.47 ± 0.37
3 Average Wait Time for Server 2 (hours) 0.09 ± 0.03
4 Average Wait Time for Server 3 (hours) 0.22 ± 0.07
slow-medium-fast Summary Table:
# A tibble: 4 × 2
`**Statistic**` `**N = 1,000**`
<chr> <chr>
1 Average Total Wait Time (hours) 2.77 ± 0.34
2 Average Wait Time for Server 1 (hours) 2.46 ± 0.36
3 Average Wait Time for Server 2 (hours) 0.23 ± 0.07
4 Average Wait Time for Server 3 (hours) 0.09 ± 0.03
Results
# Extract the average wait times for each permutation and combine them into a single data frame
<- tibble(
tandem_summary_df `fast-medium-slow` = tandem_permutation_results$`fast-medium-slow`$results_df$Avg_Total_Wait_Time,
`fast-slow-medium` = tandem_permutation_results$`fast-slow-medium`$results_df$Avg_Total_Wait_Time,
`medium-fast-slow` = tandem_permutation_results$`medium-fast-slow`$results_df$Avg_Total_Wait_Time,
`medium-slow-fast` = tandem_permutation_results$`medium-slow-fast`$results_df$Avg_Total_Wait_Time,
`slow-fast-medium` = tandem_permutation_results$`slow-fast-medium`$results_df$Avg_Total_Wait_Time,
`slow-medium-fast` = tandem_permutation_results$`slow-medium-fast`$results_df$Avg_Total_Wait_Time
)
# Create a summary table using gtsummary
%>%
tandem_summary_df tbl_summary(
statistic = all_continuous() ~ "{mean} ± {sd}", # Display mean and standard deviation
digits = all_continuous() ~ 2, # Round to 2 decimal places for clarity
%>%
) modify_header(label = "**Permutation**", stat_0 = "**Average Wait Time (hours)**") %>%
modify_caption("**Summary of Average Wait Times Across Permutations (Tandem)**")
Permutation | Average Wait Time (hours)1 |
---|---|
fast-medium-slow | 2.79 ± 0.34 |
fast-slow-medium | 2.78 ± 0.35 |
medium-fast-slow | 2.76 ± 0.35 |
medium-slow-fast | 2.79 ± 0.35 |
slow-fast-medium | 2.78 ± 0.34 |
slow-medium-fast | 2.77 ± 0.34 |
1 Mean ± SD |
Type 2: One Line, Multiple Servers (Customers Choose)
In this case, there is also only one queue. Customer will go to either server 1, server 2, or server 3. And customers will get their food while ordering. For example, in an In-N-Out restaurant, and customers go to the first server by default to place their order if both servers are available. If one server is busy, the customer will go to the other server. If both servers are busy, the customer will wait in the queue and go to the first server when available.
Since, in this case, the customer will get their food while ordering, the service speed will be slower than the previous case. So, the Teller 1 will have \(\mu_{1_{slower}} = 3.5 \text{ customers per hour}\) and the Teller 2 will have \(\mu_{2_{slower}} = 2.5 \text{ customers per hour}\), and the Teller 3 will have \(\mu_{3_{slower}} = 1 \text{ customers per hour}\).
# Function to simulate oneline service with three servers for one day
<- function(arrival_times, mu1, mu2, mu3) {
simulate_oneline_service_day
<- c()
wait_times <- c()
service_times
# Initialize tellers' availability times
<- 0
teller1_free_at <- 0
teller2_free_at <- 0
teller3_free_at
# Process each customer arrival
for (arrival_time in arrival_times) {
# Both tellers are available, go to Teller 1 by default
if (arrival_time >= teller1_free_at && arrival_time >= teller2_free_at && arrival_time >= teller3_free_at) {
<- 0
wait_time <- rexp(1, rate = mu1)
service_time <- arrival_time + service_time
teller1_free_at
else if (arrival_time >= teller1_free_at) {
} # Teller 1 is available
<- 0
wait_time <- rexp(1, rate = mu1)
service_time <- arrival_time + service_time
teller1_free_at
else if (arrival_time >= teller2_free_at) {
} # Teller 2 is available
<- 0
wait_time <- rexp(1, rate = mu2)
service_time <- arrival_time + service_time
teller2_free_at
else if (arrival_time >= teller3_free_at) {
} # Teller 3 is available
<- 0
wait_time <- rexp(1, rate = mu3)
service_time <- arrival_time + service_time
teller3_free_at
else {
} # All tellers are busy, customer waits for the next available teller
<- min(teller1_free_at, teller2_free_at, teller3_free_at)
next_free_time <- next_free_time - arrival_time
wait_time
# Assign to the teller who becomes available first
if (teller1_free_at <= teller2_free_at && teller1_free_at <= teller3_free_at) {
<- rexp(1, rate = mu1)
service_time <- next_free_time + service_time
teller1_free_at else if (teller2_free_at <= teller3_free_at) {
} <- rexp(1, rate = mu2)
service_time <- next_free_time + service_time
teller2_free_at else {
} <- rexp(1, rate = mu3)
service_time <- next_free_time + service_time
teller3_free_at
}
}
# Record wait and service times
<- c(wait_times, wait_time)
wait_times <- c(service_times, service_time)
service_times
}
# Calculate total wait times
<- wait_times
total_wait_times <- mean(total_wait_times)
avg_wait_time
return(list(avg_wait_time = avg_wait_time,
arrival_times = arrival_times,
wait_times = wait_times,
service_times = service_times,
total_wait_times = total_wait_times))
}
# Now, run the simulation for 7 days with half rates for each server
<- lapply(arrival_times_by_day, function(arrival_times) {
oneline_weekly_results simulate_oneline_service_day(arrival_times, fast_rate_half, medium_rate_half, slow_rate_half)
})
# Calculate the average wait time over 7 days
<- mean(sapply(oneline_weekly_results, `[[`, "avg_wait_time"))
oneline_avg_weekly_wait_time
# Print the average weekly wait time in hours
cat("Average wait time over 7 days for one line service (hours):", oneline_avg_weekly_wait_time / 60, "\n")
Average wait time over 7 days for one line service (hours): 0.07955179
# Function to run permutation simulation for oneline service
<- function(arrival_times_by_day, permutations,
run_oneline_permutation_simulation num_simulations = 1000) {
rate_labels, <- list()
results_list
for (i in seq_along(permutations)) {
<- permutations[[i]]
rates <- rates[1]
mu1 <- rates[2]
mu2 <- rates[3]
mu3
# Run simulations for the current permutation
<- replicate(num_simulations, {
oneline_sim_results lapply(arrival_times_by_day, function(arrival_times) {
simulate_oneline_service_day(arrival_times, mu1, mu2, mu3)
})simplify = FALSE)
},
# Extract average wait times for each simulation
<- sapply(oneline_sim_results, function(simulation) {
avg_total_wait_times mean(sapply(simulation, function(day_result) mean(day_result$total_wait_times)))
})
# Create a data frame for the summary
<- data.frame(
results_df Avg_Total_Wait_Time = avg_total_wait_times / 60 # Convert to hours
)
# Collect data for step plot for the first simulation run of the 7 days
<- do.call(rbind, lapply(1:7, function(day) {
first_run_data data.frame(
Day = paste("Day", day),
ArrivalTime = oneline_sim_results[[1]][[day]]$arrival_times,
WaitTime = oneline_sim_results[[1]][[day]]$total_wait_times
)
}))
# Generate step plot for the first simulation run
<- ggplot(first_run_data, aes(x = ArrivalTime, y = WaitTime)) +
step_plot geom_step(color = "#e31a1c", size = 0.8) +
facet_wrap(~ Day, ncol = 3) +
labs(x = "Arrival Time (minutes)", y = "Wait Time (minutes)",
title = paste("Wait Times for", rate_labels[i], "Over 7 Days")) +
theme_bw(base_size = 12) +
theme(
legend.position = "none",
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
strip.text = element_text(size = 10),
plot.title = element_text(size = 14, hjust = 0.5),
axis.text = element_text(size = 10),
axis.title = element_text(size = 12),
axis.text.x = element_text(angle = 45, hjust = 1)
)
# Store the results
<- list(
results_list[[rate_labels[i]]] results_df = results_df,
step_plot = step_plot
)
}
return(results_list)
}
# Run the function for all permutations
<- run_oneline_permutation_simulation(arrival_times_by_day,
oneline_permutation_results
service_rate_permutations_half,num_simulations = 1000) rate_labels,
# Loop to access and display results
for (perm_name in names(oneline_permutation_results)) {
cat("\n", perm_name, "Summary Table:\n")
# Apply gtsummary to the results_df for a detailed summary table
<- oneline_permutation_results[[perm_name]]$results_df %>%
summary_table tbl_summary(
statistic = all_continuous() ~ "{mean} ± {sd}",
label = list(
~ "Average Total Wait Time (hours)"
Avg_Total_Wait_Time
),digits = all_continuous() ~ 2
%>%
) modify_header(label = "**Statistic**") %>%
modify_caption(paste("**One Line Service Wait Times for", perm_name, "**"))
# Extract the gtsummary table as a data frame
<- as_tibble(summary_table)
summary_df
# Print the data frame in a clean format
print(summary_df)
print(oneline_permutation_results[[perm_name]]$step_plot)
}
fast-medium-slow Summary Table:
# A tibble: 1 × 2
`**Statistic**` `**N = 1,000**`
<chr> <chr>
1 Average Total Wait Time (hours) 0.12 ± 0.05
fast-slow-medium Summary Table:
# A tibble: 1 × 2
`**Statistic**` `**N = 1,000**`
<chr> <chr>
1 Average Total Wait Time (hours) 0.13 ± 0.05
medium-fast-slow Summary Table:
# A tibble: 1 × 2
`**Statistic**` `**N = 1,000**`
<chr> <chr>
1 Average Total Wait Time (hours) 0.12 ± 0.05
medium-slow-fast Summary Table:
# A tibble: 1 × 2
`**Statistic**` `**N = 1,000**`
<chr> <chr>
1 Average Total Wait Time (hours) 0.13 ± 0.05
slow-fast-medium Summary Table:
# A tibble: 1 × 2
`**Statistic**` `**N = 1,000**`
<chr> <chr>
1 Average Total Wait Time (hours) 0.13 ± 0.05
slow-medium-fast Summary Table:
# A tibble: 1 × 2
`**Statistic**` `**N = 1,000**`
<chr> <chr>
1 Average Total Wait Time (hours) 0.14 ± 0.05
Results
# Extract the average wait times for each permutation and combine them into a single data frame
<- tibble(
oneline_summary_df `fast-medium-slow` = oneline_permutation_results[["fast-medium-slow"]]$results_df$Avg_Total_Wait_Time,
`fast-slow-medium` = oneline_permutation_results[["fast-slow-medium"]]$results_df$Avg_Total_Wait_Time,
`medium-fast-slow` = oneline_permutation_results[["medium-fast-slow"]]$results_df$Avg_Total_Wait_Time,
`medium-slow-fast` = oneline_permutation_results[["medium-slow-fast"]]$results_df$Avg_Total_Wait_Time,
`slow-fast-medium` = oneline_permutation_results[["slow-fast-medium"]]$results_df$Avg_Total_Wait_Time,
`slow-medium-fast` = oneline_permutation_results[["slow-medium-fast"]]$results_df$Avg_Total_Wait_Time
)
# Create a summary table using gtsummary
%>%
oneline_summary_df tbl_summary(
statistic = all_continuous() ~ "{mean} ± {sd}", # Display mean and standard deviation
digits = all_continuous() ~ 2, # Round to 2 decimal places for clarity
%>%
) modify_header(label = "**Permutation**") %>%
modify_caption("**Summary of Average Wait Times Across Permutations (One Line)**")
Permutation | N = 1,0001 |
---|---|
fast-medium-slow | 0.12 ± 0.05 |
fast-slow-medium | 0.13 ± 0.05 |
medium-fast-slow | 0.12 ± 0.05 |
medium-slow-fast | 0.13 ± 0.05 |
slow-fast-medium | 0.13 ± 0.05 |
slow-medium-fast | 0.14 ± 0.05 |
1 Mean ± SD |
Type 3: Each teller has their own line (grocery store)
In this scenario, each teller has their own line, and customers choose the shortest line to join. This is similar to the setup in a grocery store, where each checkout lane has its own queue.
The Teller 1 will have \(\mu_{1{\text{slower}}} = 3.5 \text{ customers per hour}\) and Teller 2 will have \(\mu_{2{\text{slower}}} = 2.5 \text{ customers per hour}\) and Teller 3 will have \(\mu_{3{\text{slower}}} = 1.5 \text{ customers per hour}\).
# Function to simulate service with each teller having their own line for one day (3 tellers)
<- function(arrival_times, mu1, mu2, mu3) {
simulate_separate_lines_day
# Initialize variables for each teller
<- c()
wait_times_teller1 <- c()
wait_times_teller2 <- c()
wait_times_teller3 <- c()
service_times_teller1 <- c()
service_times_teller2 <- c()
service_times_teller3
# Initialize tellers' availability times and queue lengths
<- 0
teller1_free_at <- 0
teller2_free_at <- 0
teller3_free_at <- 0
queue_length_teller1 <- 0
queue_length_teller2 <- 0
queue_length_teller3
# Initialize an empty data frame to store results
<- data.frame(
results ArrivalTime = numeric(0),
WaitTime = numeric(0),
ServiceTime = numeric(0),
Teller = character(0),
QueueLength_Teller1 = numeric(0),
QueueLength_Teller2 = numeric(0),
QueueLength_Teller3 = numeric(0)
)
# Process each customer arrival
for (arrival_time in arrival_times) {
# Record the current queue lengths
<- queue_length_teller1
current_queue_length1 <- queue_length_teller2
current_queue_length2 <- queue_length_teller3
current_queue_length3
# Determine which teller the customer will go to based on the shortest queue
if ((queue_length_teller1 <= queue_length_teller2) && (queue_length_teller1 <= queue_length_teller3)) {
# Assign to Teller 1
if (arrival_time >= teller1_free_at) {
# Teller 1 is free, no wait time
<- 0
wait_time <- rexp(1, rate = mu1)
service_time <- arrival_time + service_time
teller1_free_at else {
} # Teller 1 is busy, customer waits
<- teller1_free_at - arrival_time
wait_time <- rexp(1, rate = mu1)
service_time <- teller1_free_at + service_time
teller1_free_at <- queue_length_teller1 + 1
queue_length_teller1
}
# Record the result for Teller 1
<- rbind(results, data.frame(
results ArrivalTime = arrival_time,
WaitTime = wait_time,
ServiceTime = service_time,
Teller = "Teller 1",
QueueLength_Teller1 = current_queue_length1,
QueueLength_Teller2 = current_queue_length2,
QueueLength_Teller3 = current_queue_length3
))
else if ((queue_length_teller2 < queue_length_teller1) && (queue_length_teller2 <= queue_length_teller3)) {
}
# Assign to Teller 2
if (arrival_time >= teller2_free_at) {
# Teller 2 is free, no wait time
<- 0
wait_time <- rexp(1, rate = mu2)
service_time <- arrival_time + service_time
teller2_free_at else {
} # Teller 2 is busy, customer waits
<- teller2_free_at - arrival_time
wait_time <- rexp(1, rate = mu2)
service_time <- teller2_free_at + service_time
teller2_free_at <- queue_length_teller2 + 1
queue_length_teller2
}
# Record the result for Teller 2
<- rbind(results, data.frame(
results ArrivalTime = arrival_time,
WaitTime = wait_time,
ServiceTime = service_time,
Teller = "Teller 2",
QueueLength_Teller1 = current_queue_length1,
QueueLength_Teller2 = current_queue_length2,
QueueLength_Teller3 = current_queue_length3
))
else {
}
# Assign to Teller 3
if (arrival_time >= teller3_free_at) {
# Teller 3 is free, no wait time
<- 0
wait_time <- rexp(1, rate = mu3)
service_time <- arrival_time + service_time
teller3_free_at else {
} # Teller 3 is busy, customer waits
<- teller3_free_at - arrival_time
wait_time <- rexp(1, rate = mu3)
service_time <- teller3_free_at + service_time
teller3_free_at <- queue_length_teller3 + 1
queue_length_teller3
}
# Record the result for Teller 3
<- rbind(results, data.frame(
results ArrivalTime = arrival_time,
WaitTime = wait_time,
ServiceTime = service_time,
Teller = "Teller 3",
QueueLength_Teller1 = current_queue_length1,
QueueLength_Teller2 = current_queue_length2,
QueueLength_Teller3 = current_queue_length3
))
}
# Update queue lengths after the customer is served (if applicable)
if (arrival_time >= teller1_free_at) queue_length_teller1 <- max(0, queue_length_teller1 - 1)
if (arrival_time >= teller2_free_at) queue_length_teller2 <- max(0, queue_length_teller2 - 1)
if (arrival_time >= teller3_free_at) queue_length_teller3 <- max(0, queue_length_teller3 - 1)
}
# Calculate average wait times for each teller
<- mean(results$WaitTime[results$Teller == "Teller 1"])
avg_wait_time_teller1 <- mean(results$WaitTime[results$Teller == "Teller 2"])
avg_wait_time_teller2 <- mean(results$WaitTime[results$Teller == "Teller 3"])
avg_wait_time_teller3
return(list(
results = results,
avg_wait_time_teller1 = avg_wait_time_teller1,
avg_wait_time_teller2 = avg_wait_time_teller2,
avg_wait_time_teller3 = avg_wait_time_teller3,
queue_length_teller1 = queue_length_teller1,
queue_length_teller2 = queue_length_teller2,
queue_length_teller3 = queue_length_teller3
)) }
<- function(arrival_times_by_day, permutations,
run_permutation_simulation_separate_lines num_simulations = 100) {
rate_labels,
<- list()
results_list
for (i in seq_along(permutations)) {
<- permutations[[i]]
rates <- rates[1]
mu1 <- rates[2]
mu2 <- rates[3]
mu3
# Run simulations for each day with the current permutation
<- replicate(num_simulations, {
separate_sim_results lapply(arrival_times_by_day, function(arrival_times) {
simulate_separate_lines_day(arrival_times, mu1, mu2, mu3)
})simplify = FALSE)
},
# Extract average wait times and queue lengths for each teller across simulations
<- sapply(separate_sim_results, function(simulation) {
avg_wait_times_teller1 mean(sapply(simulation, function(day_result) mean(day_result$results$WaitTime[day_result$results$Teller == "Teller 1"], na.rm = TRUE)))
})<- sapply(separate_sim_results, function(simulation) {
avg_wait_times_teller2 mean(sapply(simulation, function(day_result) mean(day_result$results$WaitTime[day_result$results$Teller == "Teller 2"], na.rm = TRUE)))
})<- sapply(separate_sim_results, function(simulation) {
avg_wait_times_teller3 mean(sapply(simulation, function(day_result) mean(day_result$results$WaitTime[day_result$results$Teller == "Teller 3"], na.rm = TRUE)))
})
<- avg_wait_times_teller1 + avg_wait_times_teller2 + avg_wait_times_teller3
avg_total_wait_times
<- sapply(separate_sim_results, function(simulation) {
avg_queue_lengths_teller1 mean(sapply(simulation, function(day_result) mean(day_result$results$QueueLength_Teller1, na.rm = TRUE)))
})<- sapply(separate_sim_results, function(simulation) {
avg_queue_lengths_teller2 mean(sapply(simulation, function(day_result) mean(day_result$results$QueueLength_Teller2, na.rm = TRUE)))
})<- sapply(separate_sim_results, function(simulation) {
avg_queue_lengths_teller3 mean(sapply(simulation, function(day_result) mean(day_result$results$QueueLength_Teller3, na.rm = TRUE)))
})
# Create a data frame for the summary
<- data.frame(
results_df Avg_Total_Wait_Time = avg_total_wait_times / 60, # Convert to hours
Avg_Wait_Time_Teller1 = avg_wait_times_teller1 / 60, # Convert to hours
Avg_Wait_Time_Teller2 = avg_wait_times_teller2 / 60, # Convert to hours
Avg_Wait_Time_Teller3 = avg_wait_times_teller3 / 60, # Convert to hours
Avg_Queue_Length_Teller1 = avg_queue_lengths_teller1,
Avg_Queue_Length_Teller2 = avg_queue_lengths_teller2,
Avg_Queue_Length_Teller3 = avg_queue_lengths_teller3
)
<- na.omit(results_df)
results_df
# Collect data for step plot for the first simulation run of the 7 days
<- do.call(rbind, lapply(1:7, function(day) {
first_run_data <- separate_sim_results[[1]][[day]]$results
day_data $Day <- paste("Day", day)
day_data
day_data
}))
# Melt data into long format for plotting
<- melt(first_run_data, id.vars = c("Day", "ArrivalTime", "Teller"),
first_run_data_melted measure.vars = "WaitTime", variable.name = "WaitTime_Type", value.name = "WaitTime")
# Custom color palette
<- c("Teller 1" = "#1f78b4",
custom_colors "Teller 2" = "#33a02c",
"Teller 3" = "#ff7f00")
# Generate step plot
<- ggplot(first_run_data_melted, aes(x = ArrivalTime, y = WaitTime, color = Teller)) +
step_plot geom_step(size = 0.8) +
facet_wrap(~ Day, ncol = 3) +
labs(x = "Arrival Time (minutes)", y = "Wait Time (minutes)",
color = "Teller") +
scale_color_manual(values = custom_colors) +
theme_bw(base_size = 12) +
theme(
legend.position = "top",
legend.title = element_text(size = 10),
legend.text = element_text(size = 9),
strip.text = element_text(size = 10), # Bold facet labels
plot.title = element_text(size = 14, hjust = 0.5), # Centered and bold title
axis.text = element_text(size = 10), # Adjust axis text size
axis.title = element_text(size = 12), # Bold axis titles
axis.text.x = element_text(angle = 45, hjust = 1), # Rotate x-axis labels
panel.grid.major = element_blank(), # Remove major grid lines
panel.grid.minor = element_blank()
+
) ggtitle(paste("Customer Wait Times for", rate_labels[i], "Over 7 Days"))
# Store the results
<-
results_list[[rate_labels[i]]] list(
results_df = results_df,
step_plot = step_plot
)
}
return(results_list)
}
# Run the function for all permutations
<- run_permutation_simulation_separate_lines(arrival_times_by_day,
separate_line_permutation_results
service_rate_permutations_half,num_simulations = 10)
rate_labels,
# Loop to access and display results
for (perm_name in names(separate_line_permutation_results)) {
cat("\n", perm_name, "Summary Table:\n")
# Apply gtsummary to the results_df for a detailed summary table
<- separate_line_permutation_results[[perm_name]]$results_df %>%
summary_table tbl_summary(
statistic = all_continuous() ~ "{mean} ± {sd}",
label = list(
~ "Average Total Wait Time (hours)",
Avg_Total_Wait_Time ~ "Average Wait Time for Teller 1 (hours)",
Avg_Wait_Time_Teller1 ~ "Average Wait Time for Teller 2 (hours)",
Avg_Wait_Time_Teller2 ~ "Average Wait Time for Teller 3 (hours)",
Avg_Wait_Time_Teller3 ~ "Average Queue Length for Teller 1",
Avg_Queue_Length_Teller1 ~ "Average Queue Length for Teller 2",
Avg_Queue_Length_Teller2 ~ "Average Queue Length for Teller 3"
Avg_Queue_Length_Teller3
),digits = all_continuous() ~ 2
%>%
) modify_header(label = "**Statistic**") %>%
modify_caption(paste("**Separate Lines Service Wait Times for", perm_name, "**"))
# Extract the gtsummary table as a data frame
<- as_tibble(summary_table)
summary_df
# Print the data frame in a clean format
print(summary_df)
print(separate_line_permutation_results[[perm_name]]$step_plot)
}
fast-medium-slow Summary Table:
# A tibble: 7 × 2
`**Statistic**` `**N = 10**`
<chr> <chr>
1 Average Total Wait Time (hours) 0.94 ± 0.16
2 Average Wait Time for Teller 1 (hours) 0.14 ± 0.03
3 Average Wait Time for Teller 2 (hours) 0.26 ± 0.09
4 Average Wait Time for Teller 3 (hours) 0.54 ± 0.15
5 Average Queue Length for Teller 1 1.49 ± 0.27
6 Average Queue Length for Teller 2 1.26 ± 0.26
7 Average Queue Length for Teller 3 1.10 ± 0.28
fast-slow-medium Summary Table:
# A tibble: 7 × 2
`**Statistic**` `**N = 10**`
<chr> <chr>
1 Average Total Wait Time (hours) 0.93 ± 0.35
2 Average Wait Time for Teller 1 (hours) 0.14 ± 0.05
3 Average Wait Time for Teller 2 (hours) 0.55 ± 0.25
4 Average Wait Time for Teller 3 (hours) 0.24 ± 0.09
5 Average Queue Length for Teller 1 1.40 ± 0.26
6 Average Queue Length for Teller 2 1.34 ± 0.30
7 Average Queue Length for Teller 3 0.94 ± 0.27
medium-fast-slow Summary Table:
# A tibble: 7 × 2
`**Statistic**` `**N = 10**`
<chr> <chr>
1 Average Total Wait Time (hours) 0.88 ± 0.20
2 Average Wait Time for Teller 1 (hours) 0.26 ± 0.06
3 Average Wait Time for Teller 2 (hours) 0.14 ± 0.05
4 Average Wait Time for Teller 3 (hours) 0.48 ± 0.16
5 Average Queue Length for Teller 1 1.64 ± 0.32
6 Average Queue Length for Teller 2 1.24 ± 0.32
7 Average Queue Length for Teller 3 1.13 ± 0.31
medium-slow-fast Summary Table:
# A tibble: 7 × 2
`**Statistic**` `**N = 10**`
<chr> <chr>
1 Average Total Wait Time (hours) 1.15 ± 0.22
2 Average Wait Time for Teller 1 (hours) 0.30 ± 0.09
3 Average Wait Time for Teller 2 (hours) 0.71 ± 0.17
4 Average Wait Time for Teller 3 (hours) 0.15 ± 0.06
5 Average Queue Length for Teller 1 1.75 ± 0.23
6 Average Queue Length for Teller 2 1.58 ± 0.21
7 Average Queue Length for Teller 3 1.12 ± 0.22
slow-fast-medium Summary Table:
# A tibble: 7 × 2
`**Statistic**` `**N = 10**`
<chr> <chr>
1 Average Total Wait Time (hours) 0.89 ± 0.18
2 Average Wait Time for Teller 1 (hours) 0.54 ± 0.15
3 Average Wait Time for Teller 2 (hours) 0.13 ± 0.04
4 Average Wait Time for Teller 3 (hours) 0.22 ± 0.06
5 Average Queue Length for Teller 1 1.74 ± 0.28
6 Average Queue Length for Teller 2 1.28 ± 0.28
7 Average Queue Length for Teller 3 1.02 ± 0.28
slow-medium-fast Summary Table:
# A tibble: 7 × 2
`**Statistic**` `**N = 10**`
<chr> <chr>
1 Average Total Wait Time (hours) 1.09 ± 0.26
2 Average Wait Time for Teller 1 (hours) 0.64 ± 0.19
3 Average Wait Time for Teller 2 (hours) 0.29 ± 0.09
4 Average Wait Time for Teller 3 (hours) 0.16 ± 0.08
5 Average Queue Length for Teller 1 1.97 ± 0.27
6 Average Queue Length for Teller 2 1.58 ± 0.28
7 Average Queue Length for Teller 3 1.19 ± 0.29
Results
# Extract the average wait times for each permutation and combine them into a single data frame
<- tibble(
separate_summary_df `fast-medium-slow` = separate_line_permutation_results[["fast-medium-slow"]]$results_df$Avg_Total_Wait_Time,
`fast-slow-medium` = separate_line_permutation_results[["fast-slow-medium"]]$results_df$Avg_Total_Wait_Time,
`medium-fast-slow` = separate_line_permutation_results[["medium-fast-slow"]]$results_df$Avg_Total_Wait_Time,
`medium-slow-fast` = separate_line_permutation_results[["medium-slow-fast"]]$results_df$Avg_Total_Wait_Time,
`slow-fast-medium` = separate_line_permutation_results[["slow-fast-medium"]]$results_df$Avg_Total_Wait_Time,
`slow-medium-fast` = separate_line_permutation_results[["slow-medium-fast"]]$results_df$Avg_Total_Wait_Time
)
# Create a summary table using gtsummary
%>%
separate_summary_df tbl_summary(
statistic = all_continuous() ~ "{mean} ± {sd}", # Display mean and standard deviation
digits = all_continuous() ~ 2, # Round to 2 decimal places for clarity
%>%
) modify_header(label = "**Permutation**") %>%
modify_caption("**Summary of Average Wait Times Across Permutations (Separate Lines)**")
Permutation | N = 101 |
---|---|
fast-medium-slow | 0.94 ± 0.16 |
fast-slow-medium | 0.93 ± 0.35 |
medium-fast-slow | 0.88 ± 0.20 |
medium-slow-fast | 1.15 ± 0.22 |
slow-fast-medium | 0.89 ± 0.18 |
slow-medium-fast | 1.09 ± 0.26 |
1 Mean ± SD |
Conclusion
Code
# Extract the average wait times for each permutation and combine them into a single data frame for tandem
<- tibble(
tandem_summary_df `fast-medium-slow` = tandem_permutation_results$`fast-medium-slow`$results_df$Avg_Total_Wait_Time,
`fast-slow-medium` = tandem_permutation_results$`fast-slow-medium`$results_df$Avg_Total_Wait_Time,
`medium-fast-slow` = tandem_permutation_results$`medium-fast-slow`$results_df$Avg_Total_Wait_Time,
`medium-slow-fast` = tandem_permutation_results$`medium-slow-fast`$results_df$Avg_Total_Wait_Time,
`slow-fast-medium` = tandem_permutation_results$`slow-fast-medium`$results_df$Avg_Total_Wait_Time,
`slow-medium-fast` = tandem_permutation_results$`slow-medium-fast`$results_df$Avg_Total_Wait_Time
)
# Create summary table for tandem system
<- tandem_summary_df %>%
tandem_summary tbl_summary(
statistic = all_continuous() ~ "{mean} ± {sd}",
digits = all_continuous() ~ 2
%>%
) modify_caption("**Summary of Average Wait Times Across Permutations**")
# Repeat for oneline
<- tibble(
oneline_summary_df `fast-medium-slow` = oneline_permutation_results[["fast-medium-slow"]]$results_df$Avg_Total_Wait_Time,
`fast-slow-medium` = oneline_permutation_results[["fast-slow-medium"]]$results_df$Avg_Total_Wait_Time,
`medium-fast-slow` = oneline_permutation_results[["medium-fast-slow"]]$results_df$Avg_Total_Wait_Time,
`medium-slow-fast` = oneline_permutation_results[["medium-slow-fast"]]$results_df$Avg_Total_Wait_Time,
`slow-fast-medium` = oneline_permutation_results[["slow-fast-medium"]]$results_df$Avg_Total_Wait_Time,
`slow-medium-fast` = oneline_permutation_results[["slow-medium-fast"]]$results_df$Avg_Total_Wait_Time
)
# Create summary table for oneline system
<- oneline_summary_df %>%
oneline_summary tbl_summary(
statistic = all_continuous() ~ "{mean} ± {sd}",
digits = all_continuous() ~ 2
%>%
) modify_caption("**Summary of Average Wait Times Across Permutations**")
# Repeat for separate lines
<- tibble(
separate_summary_df `fast-medium-slow` = separate_line_permutation_results[["fast-medium-slow"]]$results_df$Avg_Total_Wait_Time,
`fast-slow-medium` = separate_line_permutation_results[["fast-slow-medium"]]$results_df$Avg_Total_Wait_Time,
`medium-fast-slow` = separate_line_permutation_results[["medium-fast-slow"]]$results_df$Avg_Total_Wait_Time,
`medium-slow-fast` = separate_line_permutation_results[["medium-slow-fast"]]$results_df$Avg_Total_Wait_Time,
`slow-fast-medium` = separate_line_permutation_results[["slow-fast-medium"]]$results_df$Avg_Total_Wait_Time,
`slow-medium-fast` = separate_line_permutation_results[["slow-medium-fast"]]$results_df$Avg_Total_Wait_Time
)
# Create summary table for separate lines system
<- separate_summary_df %>%
separate_summary tbl_summary(
statistic = all_continuous() ~ "{mean} ± {sd}",
digits = all_continuous() ~ 2
%>%
) modify_caption("**Summary of Average Wait Times Across Permutations**")
# Combine all three tables side-by-side
<- tbl_merge(
combined_summary tbls = list(tandem_summary, oneline_summary, separate_summary),
tab_spanner = c("**Tandem**", "**One Line**", "**Separate Lines**")
)
# Print the combined summary table
combined_summary
Characteristic | Tandem | One Line | Separate Lines |
---|---|---|---|
N = 1,0001 | N = 1,0001 | N = 101 | |
fast-medium-slow | 2.79 ± 0.34 | 0.12 ± 0.05 | 0.94 ± 0.16 |
fast-slow-medium | 2.78 ± 0.35 | 0.13 ± 0.05 | 0.93 ± 0.35 |
medium-fast-slow | 2.76 ± 0.35 | 0.12 ± 0.05 | 0.88 ± 0.20 |
medium-slow-fast | 2.79 ± 0.35 | 0.13 ± 0.05 | 1.15 ± 0.22 |
slow-fast-medium | 2.78 ± 0.34 | 0.13 ± 0.05 | 0.89 ± 0.18 |
slow-medium-fast | 2.77 ± 0.34 | 0.14 ± 0.05 | 1.09 ± 0.26 |
1 Mean ± SD |
- The simulation results show that the average total wait time for customers varies depending on the order of service rates at the tellers. The
One Line
system generally outperforms theSeparate Lines
andTandem
system in terms of average total wait time. - The results suggest that the order of service rates at the tellers can have a significant impact on customer wait times. In the
fast-medium-slow
permutation is the most efficient, while theslow-fast-medium
permutation is the least efficient. - In the
One Line
andTandem
systems, the average total wait time is more consistent across different permutations, while in theSeparate Lines
system, the order of service rates can lead to differences in average total wait time.