# To run model
import PHC
# To import results and produce figures
from reproduction_helpers import process_results
import pandas as pd
import os
import matplotlib.pyplot as plt
import numpy as np
# To speed up run time
from multiprocessing import Pool
# Additional package to record runtime of this notebook
import time
= time.time() start
Reproducing in-text results 6 and 7
This notebook aims to reproduce in-text results 6 and 7 from Shoaib M, Ramamohan V. Simulation modeling and analysis of primary health center operations. SIMULATION 98(3):183-208. (2022). https://doi.org/10.1177/00375497211030931.
In-text result 6:
“When the administrative work alone is assigned to the staff nurse the average utilisation of the NCD nurse decreases to 100%”
In-text result 7:
“Further, in addition to the administrative work when the staff nurse assisted for NCD checks (for 10% cases) the utilisation of NCD nurse dropped to 71%.”
Parameters
These results are variants on the scenario from Figure 2B, when the IAT is 3 minutes (170 arrivals/day) and the NCD nurse utilisation exceeds 100% (is at 123%). The variants are to:
- Assign administrative work to the staff nurse
- Have the staff nurse complete 10% of NCD checks
Set up
# Paths to save results to
= '../outputs'
output_folder = os.path.join(output_folder, 'intext6.csv')
txt6_path = os.path.join(output_folder, 'intext7.csv')
txt7_path = os.path.join(output_folder, 'intext7_100rep.csv')
txt7_100rep_path = os.path.join(output_folder, 'intext7_20p.csv') txt7_20p_path
Function
This is a function for getting the NCD utilisation.
def get_util(df):
'''
Gets NCD utilisation and transforms the dataframe to aid readability
Parameters:
-----------
df : pandas DataFrame
Dataframe with mean results for different outputs
Returns:
--------
util : pandas DataFrame
Dataframe with the NCD utilisation in each scenario
'''
# Get NCD utilisation
= round(pd.DataFrame(df.loc['NCD occ']).T, 3)
util
# Rename index and row
= util.rename_axis('Output')
util = util.rename({'NCD occ': 'NCD nurse utilisation'})
util
# Rename columns
= util.rename({
util 'in67_base': 'Normal',
'in6_base_admin': 'Admin from NCD to staff nurse',
'in7_base_appointment': '10% OPD appointments from NCD to staff nurse',
'in7_base_admin_appointment': (
'Admin and 10% OPD appointments from NCD to staff nurse'),
'in7_base_admin_appointment_100rep': (
'Admin and 10% OPD appointments from NCD to staff nurse'),
'in7_base_admin_appointment_20p': (
'Admin and 20% OPD appointments from NCD to staff nurse')}, axis=1)
return util
Run model
As per usual, I have run these with ten replications. These runs give us the results needed for in-text result 6 and 7.
= [
dict_list
{'OPD_iat': 3,
'rep_file': 'in67_base.xls',
},
{'OPD_iat': 3,
'admin_ncd_to_staff': True,
'rep_file': 'in6_base_admin.xls',
},
{'OPD_iat': 3,
'opd_ncd_to_staff': 0.1,
'rep_file': 'in7_base_appointment.xls',
},
{'OPD_iat': 3,
'admin_ncd_to_staff': True,
'opd_ncd_to_staff': 0.1,
'rep_file': 'in7_base_admin_appointment.xls',
} ]
# Append 's_' to all items
for i, d in enumerate(dict_list):
= {f's_{k}': v for k, v in d.items()} dict_list[i]
# Wrapper function to allow input of dictionary with pool
def wrapper(d):
return PHC.main(**d)
# Create a process pool that uses all CPUs
with Pool() as pool:
# Run PHC.main() using each of inputs from config
map(wrapper, dict_list) pool.
No of replications done 0
No of replications done 0
No of replications done 0
No of replications done 0
No of replications done 1
No of replications done 1
No of replications done 1
No of replications done 1
No of replications done 2
No of replications done 2
No of replications done 2
No of replications done 2
No of replications done 3
No of replications done 3
No of replications done 3
No of replications done 3
No of replications done 4
No of replications done 4
No of replications done 4
No of replications done 4
No of replications done 5
No of replications done 5
No of replications done 5
No of replications done 5
No of replications done 6
No of replications done 6
No of replications done 6
No of replications done 6
No of replications done 7
No of replications done 7
No of replications done 7
No of replications done 7
No of replications done 8
No of replications done 8
No of replications done 8
No of replications done 8
No of replications done 9
No of replications done 9
No of replications done 9
No of replications done 9
Process results
# Process results
= process_results([i['s_rep_file'] for i in dict_list], xls=True) data
In-text result 6
= get_util(data[['in67_base', 'in6_base_admin']])
txt6
# Save and display results
txt6.to_csv(txt6_path) txt6
Normal | Admin from NCD to staff nurse | |
---|---|---|
Output | ||
NCD nurse utilisation | 1.232 | 0.995 |
In-text result 7
= get_util(data)
txt7
# Save and display results
txt7.to_csv(txt7_path) txt7
Normal | Admin from NCD to staff nurse | 10% OPD appointments from NCD to staff nurse | Admin and 10% OPD appointments from NCD to staff nurse | |
---|---|---|---|---|
Output | ||||
NCD nurse utilisation | 1.232 | 0.995 | 1.069 | 0.834 |
In-text 7 variant: Run model with 100 replications
We typically run with 10 replications to save time, since results are often very similar between 10 and 100 replications. Regardless, to confirm that the difference observed for in-text result 7 is not due to the replication number, I ran it with 100 replications.
'''PHC.main(s_OPD_iat=3,
s_admin_ncd_to_staff=True,
s_opd_ncd_to_staff=0.1,
s_replication=100,
s_rep_file='in7_base_admin_appointment_100rep.xls')'''
"PHC.main(s_OPD_iat=3,\n s_admin_ncd_to_staff=True,\n s_opd_ncd_to_staff=0.1,\n s_replication=100,\n s_rep_file='in7_base_admin_appointment_100rep.xls')"
= process_results(['in7_base_admin_appointment_100rep.xls'], xls=True)
res_100 = get_util(res_100)
txt7_100rep
txt7_100rep.to_csv(txt7_100rep_path) txt7_100rep
Admin and 10% OPD appointments from NCD to staff nurse | |
---|---|
Output | |
NCD nurse utilisation | 0.827 |
In-text 7 variant: Run model with 20%
Although the paper states that 10% of cases were transferred, I explored whether transferring 20% of cases resulted in a utilisation that was closer to the reported (71%).
'''PHC.main(s_OPD_iat=3,
s_admin_ncd_to_staff=True,
s_opd_ncd_to_staff=0.2,
s_rep_file='in7_base_admin_appointment_20p.xls')'''
"PHC.main(s_OPD_iat=3,\n s_admin_ncd_to_staff=True,\n s_opd_ncd_to_staff=0.2,\n s_rep_file='in7_base_admin_appointment_20p.xls')"
= process_results(['in7_base_admin_appointment_20p.xls'], xls=True)
res_20p = get_util(res_20p)
txt7_20p
txt7_20p.to_csv(txt7_20p_path) txt7_20p
Admin and 20% OPD appointments from NCD to staff nurse | |
---|---|
Output | |
NCD nurse utilisation | 0.69 |
Run time
# Find run time in seconds
= time.time()
end = round(end-start)
runtime
# Display converted to minutes and seconds
print(f'Notebook run time: {runtime//60}m {runtime%60}s')
Notebook run time: 2m 49s