This is the orthopaedic surgery model developed as part of the hospital efficiency project.
original model author = Harper, Alison and Monks, Thomas
license = MIT
title = Hospital Efficiency Project Orthopaedic Planning Model Discrete-Event Simulation
url = https://github.com/AliHarp/HEP
It has been used as a test case here to allow the development and testing of several key features of the event log animations:
adding of logging to a model from scratch
ensuring the requirement to use simpy stores instead of simpy resources doesn’t prevent the uses of certain common modelling patterns (in this case, conditional logic where patients will leave the system if a bed is not available within a specified period of time)
displaying different icons for different classes of patients
displaying custom resource icons
displaying additional static information as part of the icon (in this case, whether the client’s discharge is delayed)
displaying information that updates with each animation step as part of the icon (in this case, the LoS of the patient at each time point)
import timeimport pandas as pdimport plotly.express as pximport plotly.graph_objects as gofrom examples.example_13_additional_synchronised_traces_method_1.simulation_execution_functions import multiple_replicationsfrom examples.example_13_additional_synchronised_traces_method_1.model_classes import Scenario, Schedulefrom vidigi.prep import reshape_for_animations, generate_animation_dffrom vidigi.animation import generate_animationfrom plotly.subplots import make_subplotsimport plotly.io as piopio.renderers.default ="notebook"TRACE =Truedebug_mode=Trueschedule = Schedule()
/opt/conda/envs/vidigi_package_dev/lib/python3.11/site-packages/sim_tools/output_analysis.py:18: UserWarning:
A NumPy version >=1.26.4 and <2.7.0 is required for this version of SciPy (detected version 1.26.2)
4 theatres
5 day/week
Each theatre has three sessions per day:
Morning: 1 revision OR 2 primary
Afternoon: 1 revision OR 2 primary
Evening: 1 primary
40 ring-fenced beds for recovery from these operations
n_beds =35primary_hip_los =4.4primary_knee_los =4.7revision_hip_los =6.9revision_knee_los =7.2unicompart_knee_los =2.9los_delay =16.5los_delay_sd =15.2prop_delay =0.076replications =30runtime =60warmup=7args = Scenario(schedule=schedule, primary_hip_mean_los=primary_hip_los, primary_knee_mean_los=primary_knee_los, revision_hip_mean_los=revision_hip_los, revision_knee_mean_los=revision_knee_los, unicompart_knee_mean_los=unicompart_knee_los, prob_ward_delay=prop_delay, n_beds=n_beds, delay_post_los_mean=los_delay, delay_post_los_sd=los_delay_sd )results = multiple_replications( return_detailed_logs=True, scenario=args, n_reps=replications, results_collection=runtime )# Join the event log with a list of patients to add a column that will determine# the icon set used for a patient (in this case, we want to distinguish between the# knee/hip patients)event_log = results[4]event_log = event_log[event_log['rep'] ==1].copy()event_log['patient'] = event_log['patient'].astype('str') + event_log['pathway']primary_patients = results[2]primary_patients = primary_patients[primary_patients['rep'] ==1]primary_patients['patient class'] = primary_patients['patient class'].str.title()primary_patients['ID'] = primary_patients['ID'].astype('str') + primary_patients['patient class']revision_patients = results[3]revision_patients = revision_patients[revision_patients['rep'] ==1]revision_patients['patient class'] = revision_patients['patient class'].str.title()revision_patients['ID'] = revision_patients['ID'].astype('str') + revision_patients['patient class']full_log_with_patient_details = event_log.merge(pd.concat([primary_patients, revision_patients]), how="left", left_on=["patient", "pathway"], right_on=["ID", "patient class"]).reset_index(drop=True).drop(columns="ID")pid_table = full_log_with_patient_details[['patient']].drop_duplicates().reset_index(drop=True).reset_index(drop=False).rename(columns={'index': 'pid'})full_log_with_patient_details = full_log_with_patient_details.merge(pid_table, how='left', on='patient').drop(columns='patient').rename(columns={'pid':'patient'})
/tmp/ipykernel_881/3232257187.py:51: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
/tmp/ipykernel_881/3232257187.py:52: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
/tmp/ipykernel_881/3232257187.py:56: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
/tmp/ipykernel_881/3232257187.py:57: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
full_patient_df = reshape_for_animations(full_log_with_patient_details, entity_col_name="patient", every_x_time_units=1, limit_duration=runtime, step_snapshot_max=50, debug_mode=debug_mode )if debug_mode:print(f'Reshaped animation dataframe finished construction at {time.strftime("%H:%M:%S", time.localtime())}')
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
/workspace/vidigi/prep.py:281: FutureWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
Iteration through time-unit-by-time-unit logs complete 10:29:01
Snapshot df concatenation complete at 10:29:01
Reshaped animation dataframe finished construction at 10:29:01
Placement dataframe started construction at 10:29:01
Placement dataframe finished construction at 10:29:01
/tmp/ipykernel_881/4097784259.py:1: UserWarning:
`step_snapshot_max` is not a multiple of `wrap_queues_at`.The animation will display better if this is resolved.
def set_icon(row):if row["surgery type"] =="p_knee":return"🦵<br>1️⃣<br> "elif row["surgery type"] =="r_knee":return"🦵<br>♻️<br> "elif row["surgery type"] =="p_hip":return"🕺<br>1️⃣<br> "elif row["surgery type"] =="r_hip":return"🕺<br>♻️<br> "elif row["surgery type"] =="uni_knee":return"🦵<br>✳️<br> "else:returnf"CHECK<br>{row['icon']}"full_patient_df_plus_pos = full_patient_df_plus_pos.assign(icon=full_patient_df_plus_pos.apply(set_icon, axis=1))# TODO: Check why this doesn't seem to be working quite right for the 'discharged after stay'# step. e.g. 194Primary is discharged on 28th July showing a LOS of 1 but prior to this shows a LOS of 9.def add_los_to_icon(row):if row["event"] =="post_surgery_stay_begins":returnf'{row["icon"]}<br><br>{row["snapshot_time"]-row["time"]:.0f}'elif row["event"] =="discharged_after_stay":returnf'{row["icon"]}<br>{row["los"]:.0f}'else:return row["icon"]full_patient_df_plus_pos = full_patient_df_plus_pos.assign(icon=full_patient_df_plus_pos.apply(add_los_to_icon, axis=1))def indicate_delay_via_icon(row):if row["delayed discharge"] isTrue:returnf'{row["icon"]}<br>*'else:returnf'{row["icon"]}<br> 'full_patient_df_plus_pos = full_patient_df_plus_pos.assign(icon=full_patient_df_plus_pos.apply(indicate_delay_via_icon, axis=1))cancelled_due_to_no_bed_available =len(full_log_with_patient_details[full_log_with_patient_details['event'] =="no_bed_available"]["patient"].unique())total_patients =len(full_log_with_patient_details["patient"].unique())cancelled_perc = cancelled_due_to_no_bed_available/total_patients# st.markdown(f"Surgeries cancelled due to no bed being available in time: {cancelled_perc:.2%} ({cancelled_due_to_no_bed_available} of {total_patients})")# st.markdown(# """# **Key**:# 🦵1️⃣: Primary Knee# 🦵♻️: Revision Knee# 🕺1️⃣: Primary Hip# 🕺♻️: Revision Hip# 🦵✳️: Primary Unicompartment Knee# An asterisk (*) indicates that the patient has a delayed discharge from the ward.# The numbers below patients indicate their length of stay.# Note that the "No Bed Available: Surgery Cancelled" and "Discharged from Hospital after Recovery" stages in the animation are lagged by one day.# For example, on the 2nd of July, this will show the patients who had their surgery cancelled on 1st July or were discharged on 1st July.# These steps are included to make it easier to understand the destinations of different clients, but due to the size of the simulation step shown (1 day) it is difficult to demonstrate this differently.# """# )
fig = generate_animation( full_entity_df_plus_pos=full_patient_df_plus_pos, entity_col_name="patient", event_position_df=event_position_df, scenario=args, plotly_height=750, plotly_width=1000, override_x_max=1000, override_y_max=700, entity_icon_size=11, resource_icon_size=13, text_size=14, wrap_resources_at=40, gap_between_resources=20, include_play_button=True, add_background_image=None,# we want the stage labels, but due to a bug# when we add in additional animated traces later,# they will disappear - so better to leave them out here# and then re-add them manually display_stage_labels=True, custom_resource_icon="🛏️", time_display_units="d", simulation_time_unit="days", start_date="2022-06-27", setup_mode=False, frame_duration=1500, #milliseconds frame_transition_duration=1000, #milliseconds debug_mode=False )fig
/workspace/vidigi/animation.py:314: FutureWarning:
The 'unit' keyword in TimedeltaIndex construction is deprecated and will be removed in a future version. Use pd.to_timedelta instead.
# Set up the desired subplot layoutROWS =4sp = make_subplots( rows=ROWS, cols=1, row_heights=[0.75, 0.05, 0.05, 0.15], vertical_spacing=0.05, subplot_titles=("", # Original Animation"", # Completed Operations"", # Lost Slot Cumulative Counts""# Daily Lost Slots Plot ) )# Overwrite the domain of our original x and y axis with domain from the new axisfig.layout['xaxis']['domain'] = sp.layout['xaxis']['domain']fig.layout['yaxis']['domain'] = sp.layout['yaxis']['domain']for i inrange(2, ROWS+1):# Add in the attributes for the secondary axis from our subplot fig.layout[f'xaxis{i}'] = sp.layout[f'xaxis{i}'] fig.layout[f'yaxis{i}'] = sp.layout[f'yaxis{i}']# Final key step - copy over the _grid_ref attribute# This isn't meant to be something we modify but it's an essential# part of the subplot code because otherwise plotly doesn't truly know# how the different subplots are arranged and referencedfig._grid_ref = sp._grid_reffig.update_layout( xaxis2=dict( showgrid=False, zeroline=False, showline=False, showticklabels=False, ), yaxis2=dict( showgrid=False, zeroline=False, showline=False, showticklabels=False, ), xaxis3=dict( showgrid=False, zeroline=False, showline=False, showticklabels=False, ), yaxis3=dict( showgrid=False, zeroline=False, showline=False, showticklabels=False, ), xaxis4=dict( showticklabels=False ))
print(len(fig.data))
3
###################################################### Adding additional animation traces########################################################################################################### Initialize static and animated traces####################################################### First, add each trace so it will show up initially# Plotly requires that all traces that will appear in animation frames are first# defined in `fig.data`. Otherwise, they appear to "fly in" from undefined positions,# or exhibit flickering due to missing interpolation references.# We add each trace in order, with placeholder data and correct styling,# so the animation engine has full knowledge of the traces from the outset.# Due to issues detailed in the following SO threads, it's essential to initialize the traces# outside of the frames argument else they will not show up at all (or show up intermittently)# https://stackoverflow.com/questions/69867334/multiple-traces-per-animation-frame-in-plotly# https://stackoverflow.com/questions/69367344/plotly-animating-a-variable-number-of-traces-in-each-frame-in-r# TODO: More explanation and investigation needed of why sometimes traces do and don't show up after being added in# via this method. Behaviour seems very inconsistent and not always logical (e.g. order you put traces in to the later# loop sometimes seems to make a difference but sometimes doesn't; making initial trace transparent sometimes seems to# stop it showing up when added in the frames but not always; sometimes the initial trace doesn't disappear).# First, extract the trace containing the resource iconsposition_label_trace = fig.data[1]icon_trace = fig.data[2]# Now keep our figure data as just the initial trace.fig.data = (fig.data[0],)# 1. BED ICONS TRACE# Readd the bed icons trace in a consistent manner# Confusingly, when we start messing with the naimation frames, we lose the bed/resource icon trace# even though it appeared fine until this point - so we have to handle it herefig.add_trace(icon_trace)print(f"Length after adding bed trace: {len(fig.data)}")# 2. EVENT LABELS (static position text, but added dynamically per frame to avoid disappearing)# This is a similar thing to the fig labels - except we never added them in the first place!# Add trace for the event labels (as these get lost from the animation once we start trying to add other things in,# so need manually re-adding)# fig.add_trace(go.Scatter(# x=[pos+10 for pos in event_position_df['x'].to_list()],# y=event_position_df['y'].to_list(),# mode="text",# name="",# text=event_position_df['label'].to_list(),# textposition="middle right",# hoverinfo='none'# ))fig.add_trace(position_label_trace)# Finally, match the font size for the position labelsfig.data[-1].textfont.sizeprint(f"Length after adding 'position labels:' trace: {len(fig.data)}")# 3. OPERATIONS COMPLETED TEXT (animated text annotation)# Add animated text trace that gives running total of operations completedfig.add_trace(go.Scatter( x=[5], y=[10],# text="", text=f"Operations Completed: {int(counts_ops_completed['running_total'][0])}", mode='text', textposition="middle left", textfont=dict(size=20),# opacity=0, showlegend=False, xaxis="x2", yaxis="y2" ), row=2, col=1)print(f"Length after adding 'operations completed:' trace: {len(fig.data)}")# 4. SLOTS LOST TEXT (animated text annotation)# Add animated trace giving running total of slots lost and percentage of total slots this representsfig.add_trace(go.Scatter( x=[5], y=[10],# text="", text=f"Total slots lost: {int(counts_not_avail['running_total'][0])} ({counts_not_avail['perc_slots_lost'][0]:.1%})", mode='text', textfont=dict(size=20),# opacity=0, showlegend=False, textposition="middle left", xaxis="x3", yaxis="y3"), row=3, col=1)print(f"Length after adding 'slots lost:' trace: {len(fig.data)}")# # 5. LINE PLOT ON SECONDARY AXIS (animated line in subplot)# Initialize with a single point and assign it to subplot axes (x2/y2)fig.add_trace(go.Scatter( x=[counts_not_avail['snapshot_time'].iloc[0]], y=[counts_not_avail['patient_x'].iloc[0]], mode="lines", line=dict(color="rgba(255,0,0,1)"), # semi-transparent initial showlegend=False, name="slots_lost_line", xaxis="x4", yaxis="y4"# We place it in our new subplot using the following line), row=4, col=1)# Add an initial trace to our secondary line chartfig.add_trace(go.Scatter( x=counts_not_avail['snapshot_time'], y=counts_not_avail['patient_x'], mode='lines', showlegend=False,# name='line', opacity=0.2, xaxis="x4", yaxis="y4"# We place it in our new subplot using the following line), row=4, col=1)print(f"Length after adding additional line plot trace: {len(fig.data)}")
Length after adding bed trace: 2
Length after adding 'position labels:' trace: 3
Length after adding 'operations completed:' trace: 4
Length after adding 'slots lost:' trace: 5
Length after adding additional line plot trace: 7
########################################################### Define animation frames: one per simulation time step##################################################################################################################### Now we need to add our traces to each individual frame########################################################### IMPORTANT: To work correctly, these need to be provided in the same order as the traces above# This includes:# 0: bed icons# 1: event labels# 2: operations completed text# 3: slots lost text# 4: time series line in subplot# # Now ensure we tell it which traces we are animating# # (as per https://chart-studio.plotly.com/~empet/15243/animating-traces-in-subplotsbr/#/)for i, frame inenumerate(fig.frames):# Your original frame.data# This will be a tuple# We'll ensure we only take the first entry# original_data = (frame.data[0], ) original_data = frame.data# if i == 5:# print(original_data)# The new data you want to add for this specific frame new_data = (# 0: bed icons icon_trace,# 1: Position labels go.Scatter( x=[pos+10for pos in event_position_df['x'].to_list()], y=event_position_df['y'].to_list(), mode="text", text=event_position_df['label'].to_list(), textposition="middle right", hoverinfo='none', showlegend=False, ),# 2: Slots used/operations occurred go.Scatter( x=[5], y=[10], text=f"Operations Completed: {int(counts_ops_completed.sort_values('snapshot_time')['running_total'][i])}", mode='text', textposition="middle left", textfont=dict(size=20), showlegend=False, xaxis='x2', yaxis='y2' ),# 3: Slots lost go.Scatter( x=[5], y=[10], text=f"Total slots lost: {int(counts_not_avail.sort_values('snapshot_time')['running_total'][i])} ({counts_not_avail.sort_values('snapshot_time')['perc_slots_lost'][i]:.1%})", mode='text', textfont=dict(size=20), textposition="middle left", showlegend=False, xaxis='x3', yaxis='y3' ),# 4: Line subplot go.Scatter( x=counts_not_avail.sort_values('snapshot_time')['snapshot_time'][0: i+1].values, y=counts_not_avail.sort_values('snapshot_time')['patient_x'][0: i+1].values, mode="lines", showlegend=False, name="line_subplot", line=dict(color="rgba(255,0,0,1)"), # semi-transparent initial xaxis='x4', yaxis='y4' ), )# print(f"Type of new data: {type(new_data)}")# Combine the original frame data with your new data frame.data = original_data + new_data# if i == 5:# print(frame.data)# print(f"Type of final frame data: {type(frame.data)}")# Finally, match the font size for the position labelsfig.data[2].textfont.sizefig
# After modifying the data in all frames, now correctly set the 'traces' property.# Get the total number of animated traces from the first (now updated) frame.num_total_traces =len(fig.frames[0].data)# Create the list of indices that all traces will be mapped to.# This should be [0, 1, 2, ..., n-1] where n is the total number of animated traces.trace_indices =list(range(num_total_traces))# Apply this correct list of indices to every frame.for frame in fig.frames: frame.traces = trace_indices