Waheeb Algabri¶

Emergency room simulation

Introduction¶

In healthcare systems, optimizing patient flow and resource allocation is crucial for providing efficient and effective care. Simulation modeling allows us to analyze and evaluate different scenarios to improve the performance of medical facilities. In this simulation, we explore the dynamics of patient arrivals, triage, and treatment in an outpatient setting. By simulating the operations of a medical facility over a specified time period, we can assess key performance metrics, such as waiting time, treatment time, and resource utilization.

Problem and Significance¶

Problem: The emergency room (ER) is a critical part of a healthcare facility where patients with varying degrees of illness or injury seek immediate medical attention. However, managing the flow of patients in the ER can be challenging, particularly during peak hours or when resources are limited. It is essential to ensure that patients are efficiently triaged, treated, and allocated appropriate medical resources (doctors and nurses) based on the severity of their condition.

Significance: The project's simulation-based optimization of resource allocation and patient flow in an emergency room has the potential to enhance efficiency, improve patient outcomes, and enable data-driven decision making for healthcare administrators.

Flow Chart model¶

The flowchart of the process is shown below:

In [ ]:
from IPython.display import Image, display

# Specify the desired width and height
width = 500
height = 300

# Create the Image object with the specified dimensions and display it
display(Image(filename='flowchart.jpg', width=width, height=height))

Simulate the process for the appropriate number of iterations¶

In [ ]:
# Import required libraries
!pip install simpy
import simpy
import random
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
Collecting simpy
  Downloading simpy-4.0.1-py2.py3-none-any.whl (29 kB)
Installing collected packages: simpy
Successfully installed simpy-4.0.1
In [ ]:
# Constants
MEAN_INTERARRIVAL = 10
CHECKIN_MIN = 6
CHECKIN_MAX = 12
TRIAGE_MIN = 3
TRIAGE_MAX = 15
TRIAGE_MODE = 5
CRITICAL_PROB = 0.4

# Global variables
num_doctors = 2

# Data collection
results = []
utilization_doctors = []
utilization_nurses = []

# Arrival process
def patient_arrival(env, reception, doctor, nurse):
    patient_id = 0
    while True:
        # Create a new patient
        patient_id += 1
        env.process(patient_process(env, patient_id, reception, doctor, nurse))

        # Sample time until next arrival
        interarrival_time = random.expovariate(1.0 / MEAN_INTERARRIVAL)
        yield env.timeout(interarrival_time)

# Patient process
def patient_process(env, patient_id, reception, doctor, nurse):
    global results

    # Check-in
    checkin_time = random.uniform(CHECKIN_MIN, CHECKIN_MAX)
    yield env.timeout(checkin_time)
    print(f"Patient {patient_id} checked-in at {env.now}")
    arrival_time = env.now

    # Triage
    triage_time = random.triangular(TRIAGE_MIN, TRIAGE_MAX, TRIAGE_MODE)
    yield env.timeout(triage_time)
    print(f"Patient {patient_id} triaged at {env.now}")
    token_time = env.now

    # Check critical condition
    critical = random.random() < CRITICAL_PROB

    # Request doctor/nurse
    if critical:
        with doctor.request() as req:
            yield req
            print(f"Patient {patient_id} sees a doctor at {env.now}")
            yield env.timeout(15)  # Treatment time for critical patients
            print(f"Patient {patient_id} treated by doctor at {env.now}")
            treatment_time = 15
    else:
        with nurse.request() as req:
            yield req
            print(f"Patient {patient_id} sees a nurse at {env.now}")
            yield env.timeout(10)  # Treatment time for non-critical patients
            print(f"Patient {patient_id} treated by nurse at {env.now}")
            treatment_time = 10

    # Calculate waiting time and treatment time
    waiting_time = env.now - (checkin_time + triage_time)

    # Record the results
    result = {
        'Patient ID': patient_id,
        'Service': 'Doctor' if critical else 'Nurse',
        'Arrival Time': arrival_time,
        'Token Time': token_time,
        'Service Start Time': env.now - treatment_time,
        'Service Stop Time': env.now,
        'Token Time (Secs)': token_time * 60,
        'Service Time (Mins)': treatment_time,
        'Wait Time (Mins)': waiting_time
    }
    results.append(result)

# Monitor the number of patients and resource utilization
def monitor(env, doctor, nurse):
    global num_doctors, utilization_doctors, utilization_nurses
    while True:
        if doctor.count == num_doctors and doctor.queue:
            num_doctors += 1
            print(f"Additional doctor summoned at {env.now}. Total doctors: {num_doctors}")
        elif num_doctors > 2 and doctor.count == 0 and not doctor.queue:
            num_doctors -= 1
            print(f"Doctor dismissed at {env.now}. Total doctors: {num_doctors}")

        utilization_doctors.append(doctor.count / num_doctors)
        utilization_nurses.append(nurse.count / 2)

        yield env.timeout(1)

# Set up simulation
env = simpy.Environment()

# Resources
reception = simpy.Resource(env, capacity=3)
doctor = simpy.Resource(env, capacity=num_doctors)
nurse = simpy.Resource(env, capacity=2)

# Start processes
env.process(patient_arrival(env, reception, doctor, nurse))
env.process(monitor(env, doctor, nurse))

# Run simulation
env.run(until=300)  # Run for 5 hours

# Create a DataFrame from the collected results
df = pd.DataFrame(results, columns=['Patient ID', 'Service', 'Arrival Time', 'Token Time',
                                    'Service Start Time', 'Service Stop Time', 'Token Time (Secs)',
                                    'Service Time (Mins)', 'Wait Time (Mins)'])

# Performance metric calculation
avg_waiting_time = df['Wait Time (Mins)'].mean()
avg_treatment_time = df['Service Time (Mins)'].mean()
doctor_utilization = np.mean(utilization_doctors)
nurse_utilization = np.mean(utilization_nurses)


# Print performance metrics
print(f"Average waiting time: {avg_waiting_time:.2f} minutes")
print(f"Average treatment time: {avg_treatment_time:.2f} minutes")
print(f"Doctor utilization: {doctor_utilization:.2%}")
print(f"Nurse utilization: {nurse_utilization:.2%}")

# Data visualization
plt.figure(figsize=(10, 6))
plt.hist(df['Wait Time (Mins)'], bins=20, alpha=0.8, color='blue')
plt.xlabel('Waiting Time (minutes)')
plt.ylabel('Frequency')
plt.title('Distribution of Waiting Times')
plt.grid(True)
plt.show()
Patient 2 checked-in at 7.107477593918198
Patient 1 checked-in at 7.895147091461459
Patient 2 triaged at 12.328061501279926
Patient 2 sees a doctor at 12.328061501279926
Patient 1 triaged at 14.291124885485537
Patient 1 sees a nurse at 14.291124885485537
Patient 3 checked-in at 16.327093193689656
Patient 4 checked-in at 18.381798808270993
Patient 3 triaged at 23.297100795507266
Patient 3 sees a doctor at 23.297100795507266
Patient 5 checked-in at 24.221166986673683
Patient 1 treated by nurse at 24.291124885485537
Patient 4 triaged at 25.08085358281184
Patient 4 sees a nurse at 25.08085358281184
Patient 2 treated by doctor at 27.328061501279926
Patient 5 triaged at 31.660642507921192
Patient 5 sees a nurse at 31.660642507921192
Patient 6 checked-in at 33.93393996390836
Patient 4 treated by nurse at 35.08085358281184
Patient 3 treated by doctor at 38.29710079550726
Patient 7 checked-in at 39.47075696578161
Patient 6 triaged at 41.64965560063597
Patient 6 sees a doctor at 41.64965560063597
Patient 5 treated by nurse at 41.660642507921196
Patient 8 checked-in at 43.001911447847654
Patient 7 triaged at 47.86285884029084
Patient 7 sees a nurse at 47.86285884029084
Patient 8 triaged at 48.62275872529126
Patient 8 sees a nurse at 48.62275872529126
Patient 6 treated by doctor at 56.64965560063597
Patient 9 checked-in at 57.65314072889163
Patient 7 treated by nurse at 57.86285884029084
Patient 8 treated by nurse at 58.62275872529126
Patient 10 checked-in at 62.7980654990089
Patient 9 triaged at 68.97032435432139
Patient 9 sees a nurse at 68.97032435432139
Patient 10 triaged at 69.7675865699513
Patient 10 sees a nurse at 69.7675865699513
Patient 11 checked-in at 77.34633917684049
Patient 9 treated by nurse at 78.97032435432139
Patient 10 treated by nurse at 79.7675865699513
Patient 11 triaged at 83.79251869178242
Patient 11 sees a nurse at 83.79251869178242
Patient 11 treated by nurse at 93.79251869178242
Patient 12 checked-in at 98.12394874707319
Patient 14 checked-in at 99.54428664888336
Patient 13 checked-in at 101.13142987287719
Patient 12 triaged at 102.79492294781436
Patient 12 sees a nurse at 102.79492294781436
Patient 14 triaged at 104.57643957641866
Patient 14 sees a doctor at 104.57643957641866
Patient 13 triaged at 108.02779900389812
Patient 13 sees a nurse at 108.02779900389812
Patient 12 treated by nurse at 112.79492294781436
Patient 15 checked-in at 113.03208952665565
Patient 16 checked-in at 115.0473469831116
Patient 17 checked-in at 115.62509951526948
Patient 13 treated by nurse at 118.02779900389812
Patient 14 treated by doctor at 119.57643957641866
Patient 16 triaged at 121.1359560422893
Patient 16 sees a doctor at 121.1359560422893
Patient 15 triaged at 123.16559217406723
Patient 15 sees a doctor at 123.16559217406723
Patient 17 triaged at 123.38037600444471
Additional doctor summoned at 124. Total doctors: 3
Patient 18 checked-in at 127.84022199962337
Patient 19 checked-in at 129.4365994502287
Patient 19 triaged at 133.3889893635742
Patient 19 sees a nurse at 133.3889893635742
Patient 16 treated by doctor at 136.1359560422893
Patient 17 sees a doctor at 136.1359560422893
Patient 18 triaged at 136.4326605475934
Patient 18 sees a nurse at 136.4326605475934
Patient 20 checked-in at 137.62645870871103
Patient 15 treated by doctor at 138.16559217406723
Patient 19 treated by nurse at 143.3889893635742
Patient 21 checked-in at 143.5626927895542
Patient 20 triaged at 145.94678140944478
Patient 20 sees a nurse at 145.94678140944478
Patient 18 treated by nurse at 146.4326605475934
Patient 17 treated by doctor at 151.1359560422893
Doctor dismissed at 152. Total doctors: 2
Patient 21 triaged at 154.54399024872956
Patient 21 sees a nurse at 154.54399024872956
Patient 20 treated by nurse at 155.94678140944478
Patient 21 treated by nurse at 164.54399024872956
Patient 22 checked-in at 164.58497088388697
Patient 23 checked-in at 172.23028593194744
Patient 22 triaged at 174.33504288495845
Patient 22 sees a doctor at 174.33504288495845
Patient 23 triaged at 178.37657634337842
Patient 23 sees a doctor at 178.37657634337842
Patient 24 checked-in at 179.0559477318653
Patient 24 triaged at 187.10882306616125
Patient 24 sees a nurse at 187.10882306616125
Patient 22 treated by doctor at 189.33504288495845
Patient 25 checked-in at 192.45759488989896
Patient 23 treated by doctor at 193.37657634337842
Patient 24 treated by nurse at 197.10882306616125
Patient 25 triaged at 200.71857446446114
Patient 25 sees a nurse at 200.71857446446114
Patient 25 treated by nurse at 210.71857446446114
Patient 26 checked-in at 229.93573408781594
Patient 27 checked-in at 234.42955411820196
Patient 28 checked-in at 235.62307448290537
Patient 26 triaged at 237.14424567614142
Patient 26 sees a doctor at 237.14424567614142
Patient 28 triaged at 239.1801045062548
Patient 28 sees a doctor at 239.1801045062548
Patient 27 triaged at 242.09418924231676
Patient 29 checked-in at 242.98153249162516
Additional doctor summoned at 243. Total doctors: 3
Patient 30 checked-in at 247.77369677675972
Patient 29 triaged at 248.57567581807191
Patient 31 checked-in at 250.84040882688979
Patient 26 treated by doctor at 252.14424567614142
Patient 27 sees a doctor at 252.14424567614142
Patient 30 triaged at 253.99696054076733
Patient 30 sees a nurse at 253.99696054076733
Patient 28 treated by doctor at 254.1801045062548
Patient 29 sees a doctor at 254.1801045062548
Patient 33 checked-in at 259.0670234646141
Patient 31 triaged at 259.25342280942135
Patient 31 sees a nurse at 259.25342280942135
Patient 32 checked-in at 259.5418923025103
Patient 33 triaged at 263.0407806081464
Patient 32 triaged at 263.6999724860195
Patient 30 treated by nurse at 263.9969605407673
Patient 33 sees a nurse at 263.9969605407673
Patient 27 treated by doctor at 267.1442456761414
Patient 29 treated by doctor at 269.1801045062548
Patient 31 treated by nurse at 269.25342280942135
Patient 32 sees a nurse at 269.25342280942135
Doctor dismissed at 270. Total doctors: 2
Patient 33 treated by nurse at 273.9969605407673
Patient 35 checked-in at 278.13698553806915
Patient 34 checked-in at 278.22120137641843
Patient 32 treated by nurse at 279.25342280942135
Patient 34 triaged at 284.6289752480452
Patient 34 sees a nurse at 284.6289752480452
Patient 35 triaged at 288.11230795838964
Patient 35 sees a doctor at 288.11230795838964
Patient 36 checked-in at 288.15503057556475
Patient 37 checked-in at 290.36716698681164
Patient 34 treated by nurse at 294.6289752480452
Patient 36 triaged at 296.0313144045089
Patient 36 sees a doctor at 296.0313144045089
Patient 37 triaged at 298.2392308110931
Additional doctor summoned at 299. Total doctors: 3
Patient 38 checked-in at 299.77957141132697
Average waiting time: 136.67 minutes
Average treatment time: 11.91 minutes
Doctor utilization: 29.44%
Nurse utilization: 35.00%
In [ ]:
# Calculate average waiting time
avg_waiting_time = df['Wait Time (Mins)'].mean()

# Plot waiting time over time
plt.figure(figsize=(10, 6))
plt.plot(df['Wait Time (Mins)'])
plt.xlabel('Patient')
plt.ylabel('Waiting Time (minutes)')
plt.title('Waiting Time for Each Patient')
plt.grid(True)
plt.show()

# Print simulation report
simulation_report = f"Simulation Report:\n\n"
simulation_report += f"Average waiting time: {avg_waiting_time:.2f} minutes\n"
print(simulation_report)
Simulation Report:

Average waiting time: 136.67 minutes

Verification and Validation¶

The emergency room simulation model was verified during the development process by printing and checking intermediate outputs. By reviewing the generated report for each run of the simulation, it was ensured that the service stop time of one patient did not overlap with the service start time of the next customer. Within the given time frame of the simulation, it was observed that an expected number of patients , approximately 22-33, were being served in each run. Additionally, other variables and values were tested during each run to ensure that there were no unrealistic or absurd results.

Validation of the simulation model for the emergency room scenario presented more challenges since there was no available dataset for comparison. However, the flow of patiants and the reported wait times closely resembled personal experiences in emergency room , which provided a basis for validation. Although the simulation was based on a hypothetical situation, the simulated wait times and the overall behavior of the system were deemed reasonable and aligned with real-world observations. While validation against empirical data would have strengthened the reliability of the simulation, the model's outputs and behaviors were consistent with expectations and personal experiences. The simulation can be considered a useful tool for exploring and analyzing the emergency room scenario, providing insights and supporting decision making in the absence of real-world data.

In [ ]:
# Display the DataFrame with improved formatting
styled_df = df.style.set_properties(**{'text-align': 'center'})
styled_df = styled_df.set_table_styles([{'selector': 'th', 'props': [('text-align', 'center')]}])

# Show the styled DataFrame
display(styled_df)
  Patient ID Service Arrival Time Token Time Service Start Time Service Stop Time Token Time (Secs) Service Time (Mins) Wait Time (Mins)
0 1 Nurse 7.895147 14.291125 14.291125 24.291125 857.467493 10 10.000000
1 2 Doctor 7.107478 12.328062 12.328062 27.328062 739.683690 15 15.872620
2 4 Nurse 18.381799 25.080854 25.080854 35.080854 1504.851215 10 16.952347
3 3 Doctor 16.327093 23.297101 23.297101 38.297101 1397.826048 15 20.418614
4 5 Nurse 24.221167 31.660643 31.660643 41.660643 1899.638550 10 26.381226
5 6 Doctor 33.933940 41.649656 41.649656 56.649656 2498.979336 15 42.062173
6 7 Nurse 39.470757 47.862859 47.862859 57.862859 2871.771530 10 42.655131
7 8 Nurse 43.001911 48.622759 48.622759 58.622759 2917.365524 10 46.037264
8 9 Nurse 57.653141 68.970324 68.970324 78.970324 4138.219461 10 56.653208
9 10 Nurse 62.798065 69.767587 69.767587 79.767587 4186.055194 10 64.123082
10 11 Nurse 77.346339 83.792519 83.792519 93.792519 5027.551122 10 80.078263
11 12 Nurse 98.123949 102.794923 102.794923 112.794923 6167.695377 10 99.086593
12 13 Nurse 101.131430 108.027799 108.027799 118.027799 6481.667940 10 102.747919
13 14 Doctor 99.544287 104.576440 104.576440 119.576440 6274.586375 15 108.414100
14 16 Doctor 115.047347 121.135956 121.135956 136.135956 7268.157363 15 118.600642
15 15 Doctor 113.032090 123.165592 123.165592 138.165592 7389.935530 15 117.575891
16 19 Nurse 129.436599 133.388989 133.388989 143.388989 8003.339362 10 129.388659
17 18 Nurse 127.840222 136.432661 136.432661 146.432661 8185.959633 10 127.613154
18 17 Doctor 115.625100 123.380376 136.135956 151.135956 7402.822560 15 134.109272
19 20 Nurse 137.626459 145.946781 145.946781 155.946781 8756.806885 10 135.753020
20 21 Nurse 143.562693 154.543990 154.543990 164.543990 9272.639415 10 143.336282
21 22 Doctor 164.584971 174.335043 174.335043 189.335043 10460.102573 15 173.150985
22 23 Doctor 172.230286 178.376576 178.376576 193.376576 10702.594581 15 177.488951
23 24 Nurse 179.055948 187.108823 187.108823 197.108823 11226.529384 10 178.595377
24 25 Nurse 192.457595 200.718574 200.718574 210.718574 12043.114468 10 194.528510
25 26 Doctor 229.935734 237.144246 237.144246 252.144246 14228.654741 15 235.660090
26 28 Doctor 235.623074 239.180105 239.180105 254.180105 14350.806270 15 242.494385
27 30 Nurse 247.773697 253.996961 253.996961 263.996961 15239.817632 10 247.338318
28 27 Doctor 234.429554 242.094189 252.144246 267.144246 14525.651355 15 248.685813
29 29 Doctor 242.981532 248.575676 254.180105 269.180105 14914.540549 15 252.118567
30 31 Nurse 250.840409 259.253423 259.253423 269.253423 15555.205369 10 254.446699
31 33 Nurse 259.067023 263.040781 263.996961 273.996961 15782.446836 10 259.648308
32 32 Nurse 259.541892 263.699972 269.253423 279.253423 15821.998349 10 263.819010
33 34 Nurse 278.221201 284.628975 284.628975 294.628975 17077.738515 10 280.867469

Bar Chart for Service Type

In [ ]:
service_counts = df['Service'].value_counts()
plt.figure(figsize=(10, 6))
plt.bar(service_counts.index, service_counts.values)
plt.xlabel('Service Type')
plt.ylabel('Number of Patients')
plt.title('Distribution of Service Types')
plt.grid(True)
plt.show()
In [ ]:
# Calculate performance metrics
avg_waiting_time = df['Wait Time (Mins)'].mean()
avg_treatment_time = df['Service Time (Mins)'].mean()
doctor_utilization = np.mean(utilization_doctors)
nurse_utilization = np.mean(utilization_nurses)

# Print performance metrics
print(f"Average waiting time: {avg_waiting_time:.2f} minutes")
print(f"Average treatment time: {avg_treatment_time:.2f} minutes")
print(f"Doctor utilization: {doctor_utilization:.2%}")
print(f"Nurse utilization: {nurse_utilization:.2%}")
Average waiting time: 136.67 minutes
Average treatment time: 11.91 minutes
Doctor utilization: 29.44%
Nurse utilization: 35.00%

Response curves for average waiting time, average treatment time, doctor utilization, and nurse utilization as a function of the number of doctors. Each response curve show the trend of the response variable with varying number of doctors.

In [ ]:
# Line Chart of Resource Utilization
plt.figure(figsize=(10, 6))
plt.plot(range(len(utilization_doctors)), utilization_doctors, label='Doctor Utilization')
plt.plot(range(len(utilization_nurses)), utilization_nurses, label='Nurse Utilization')
plt.xlabel('Time')
plt.ylabel('Utilization')
plt.title('Resource Utilization Over Time')
plt.legend()
plt.grid(True)
plt.show()

# Stacked Bar Chart of Service Distribution
time_intervals = range(0, 100, 10)  # Customize the time intervals as needed
doctor_counts = df[df['Service'] == 'Doctor'].groupby(pd.cut(df['Service Start Time'], time_intervals)).size()
nurse_counts = df[df['Service'] == 'Nurse'].groupby(pd.cut(df['Service Start Time'], time_intervals)).size()

plt.figure(figsize=(10, 6))
plt.bar(time_intervals[:-1], doctor_counts, label='Doctor')
plt.bar(time_intervals[:-1], nurse_counts, bottom=doctor_counts, label='Nurse')
plt.xlabel('Time Intervals')
plt.ylabel('Number of Patients')
plt.title('Service Distribution Over Time')
plt.legend()
plt.grid(True)
plt.show()

# Box Plot of Waiting Times
plt.figure(figsize=(8, 6))
plt.boxplot(df['Wait Time (Mins)'])
plt.ylabel('Waiting Time (minutes)')
plt.title('Distribution of Waiting Times')
plt.grid(True)
plt.show()

# Histogram of Treatment Times by Service
plt.figure(figsize=(10, 6))
plt.hist(df[df['Service'] == 'Doctor']['Service Time (Mins)'], bins=20, alpha=0.8, color='blue', label='Doctor')
plt.hist(df[df['Service'] == 'Nurse']['Service Time (Mins)'], bins=20, alpha=0.8, color='green', label='Nurse')
plt.xlabel('Treatment Time (minutes)')
plt.ylabel('Frequency')
plt.title('Distribution of Treatment Times by Service')
plt.legend()
plt.grid(True)
plt.show()
# Animate the real-time visualization
ani = animation.FuncAnimation(fig, update, frames=range(0, 100), interval=100)
plt.show()

conclusion¶

The simulation based optimization of resource allocation and patient flow in an outpatient setting, particularly in the emergency room, offers a promising approach to improve healthcare system efficiency and patient outcomes. By utilizing simulation modeling techniques, we can analyze and evaluate different scenarios, assess critical performance metrics, and make data-driven decisions. The developed simulation model effectively captures patient dynamics, resource utilization, and key factors affecting patient flow. The results provide valuable insights into waiting times, treatment times, and resource utilization, enabling healthcare administrators to identify areas for improvement and optimize resource allocation. This simulation model holds great potential in enhancing healthcare system efficiency and supporting informed decision-making for improved patient care.