# To run model
import PHC
# To import results and produce figures
from reproduction_helpers import process_results
import pandas as pd
import os
# 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 3 and 4
This notebook aims to reproduce in-text results 3 and 4 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 3:
“…then considered a situation wherein the staff nurses require minimal intervention in childbirth cases. We assumed that in 50% of childbirth cases, staff nurses require no intervention by the doctor; require only one- third of the typical amount of intervention in 30% of cases, and require full intervention in the remaining 20% of cases. This led to a decrease of the doctor’s utilisation to 101% (a further decrease of approximately 1%), and an increase in the nurse’s utilisation to 40%.”
In-text result 4:
“..investigated the effect of stationing an additional doctor in the PHC. This yielded an average utilisation of well below 100% for each doctor.”
Parameters
This result starts with the doctor utilisation from Figure 2A which is over 100% when patient load is 170 (IAT 3) and appointment times are 5 minutes (mean 5, SD 1, boundary 2), and with admin assigned to the staff nurse rather than the doctor
The variants introduced are to reduce the doctor intervention during delivery so that:
- 50% delivery cases: no doctor
- 30% delivery cases: doctor but only one-third of typical intervention
- 20% delivery cases: doctor and full intervention
And adding an additional doctor.
Set up
# Paths to save image files to
= '../outputs'
output_folder = os.path.join(output_folder, 'intext3.csv')
txt3 = os.path.join(output_folder, 'intext4.csv') txt4
Run model
# Parameters used in both models
= {
base_model 'OPD_iat': 3,
'rep_file': 'arr170',
'mean': 5,
'sd': 1,
'consult_boundary_1': 2,
'consult_boundary_2': 2
}
# Model variants
= [
variants # Base case for in-text result 3 and 4
{'rep_file': 'in34_normal.xls'
},# Scenarios for in-text result 3
{'admin_doc_to_staff': True,
'rep_file': 'in3_admin.xls'
},
{'doctor_delivery_scenario': True,
'rep_file': 'in3_delivery.xls'
},
{'doctor_delivery_scenario': True,
'admin_doc_to_staff': True,
'rep_file': 'in3_admin_delivery.xls'
},# Scenarios for in-text result 4
{'doc_cap': 3,
'rep_file': 'in4_doctor.xls'
},
{'doc_cap': 3,
'doctor_delivery_scenario': True,
'admin_doc_to_staff': True,
'rep_file': 'in4_admin_delivery_doctor.xls'
}, ]
# Combine dictionaries
= []
dict_list for var in variants:
**base_model, **var})
dict_list.append({
# 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]
# View example
0] dict_list[
{'s_OPD_iat': 3,
's_rep_file': 'in34_normal.xls',
's_mean': 5,
's_sd': 1,
's_consult_boundary_1': 2,
's_consult_boundary_2': 2}
# 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 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 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 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 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 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 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 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 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 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
No of replications done No of replications done 99
Process results
= process_results([i['s_rep_file'] for i in dict_list], xls=True)
data data
in34_normal | in3_admin | in3_delivery | in3_admin_delivery | in4_doctor | in4_admin_delivery_doctor | |
---|---|---|---|---|---|---|
OPD patients | 43965.100000 | 43965.100000 | 44063.100000 | 44063.100000 | 44026.000000 | 44088.300000 |
IPD patients | 178.600000 | 178.600000 | 187.200000 | 187.200000 | 186.500000 | 187.300000 |
ANC patients | 360.900000 | 360.900000 | 357.900000 | 357.900000 | 366.700000 | 361.900000 |
Del patients | 373.300000 | 373.300000 | 365.500000 | 365.500000 | 367.500000 | 368.800000 |
OPD Q wt | 6.936889 | 6.936889 | 6.643605 | 6.643605 | 0.542593 | 0.520176 |
Pharmacy Q wt | 1.282328 | 1.282328 | 1.282265 | 1.282265 | 2.419622 | 2.442825 |
Lab Q wt | 3.123766 | 3.123766 | 3.113023 | 3.113023 | 4.454260 | 4.524105 |
doc occ | 1.141365 | 1.023434 | 1.131213 | 1.013403 | 0.761388 | 0.675327 |
Lab patient list | 250794.800000 | 250794.800000 | 252130.800000 | 252130.800000 | 251894.800000 | 253860.600000 |
OPD q len | 6.851667 | 6.851667 | 6.619829 | 6.619829 | 0.537120 | 0.514162 |
ipd occ | 0.098000 | 0.098000 | 0.098000 | 0.098000 | 0.098000 | 0.097000 |
opd q len | 0.812750 | 0.812750 | 0.779243 | 0.779243 | 0.063533 | 0.061060 |
pharmacy q len | 0.149890 | 0.149890 | 0.150061 | 0.150061 | 0.282726 | 0.286158 |
lab q len | 0.185814 | 0.185814 | 0.186597 | 0.186597 | 0.266636 | 0.272577 |
NCD occ | 1.230139 | 1.230139 | 1.234149 | 1.234149 | 1.228365 | 1.235298 |
lab occ | 0.728652 | 0.728652 | 0.735114 | 0.735114 | 0.730388 | 0.736550 |
pharm occ | 0.853658 | 0.853658 | 0.855077 | 0.855077 | 0.853199 | 0.855696 |
staff nurse occ | 0.325778 | 0.404399 | 0.323088 | 0.401629 | 0.323845 | 0.403779 |
del occ | 0.285000 | 0.285000 | 0.286000 | 0.286000 | 0.286000 | 0.281000 |
del referred | 62.200000 | 62.200000 | 55.800000 | 55.800000 | 56.100000 | 56.800000 |
ipd bed occ | 0.093865 | 0.093865 | 0.095156 | 0.095156 | 0.095393 | 0.095193 |
prop_del_referred | 0.166276 | 0.166276 | 0.152398 | 0.152398 | 0.152683 | 0.153875 |
def get_utilisation(df, index_name, new_name):
'''
Creates dataframe with columns for utilisation (for a given row) before and
after scenario change.
Parameters:
-----------
df : pandas DataFrame
Results from across the replications
index_name : string
Name of row with utilisation data
new_name : string
To rename the utilisation row (e.g. doc occ -> doctor utilisation)
Returns:
--------
util : pandas DataFrame
Dataframe with three columns for utilisation before and after admin,
and change in utilisation.
'''
# Get utilisation
= round(pd.DataFrame(df.loc[index_name]).T, 3)
util
# Rename index
= util.rename_axis('Output')
util
# Rename row
= util.rename({index_name: new_name})
util
# Rename columns for clarity for readers
= util.rename({
util 'in34_normal': 'Normal',
'in3_delivery': 'Less delivery',
'in3_admin': 'Less admin',
'in3_admin_delivery': 'Less admin + less delivery',
'in4_doctor': 'Extra doctor',
'in4_admin_delivery_doctor': (
'Extra doctor + less admin + less delivery')}, axis=1)
return util
Get in-text result 3
# Get results for doctor utilisation and staff nurse utilisation
= data[['in34_normal', 'in3_delivery', 'in3_admin', 'in3_admin_delivery']]
subset3 = pd.concat([
util3 'doc occ', 'Doctor utilisation'),
get_utilisation(subset3, 'staff nurse occ', 'Staff nurse utilisation')])
get_utilisation(subset3,
# Save results and display results
=True)
util3.to_csv(txt3, index util3
Normal | Less delivery | Less admin | Less admin + less delivery | |
---|---|---|---|---|
Output | ||||
Doctor utilisation | 1.141 | 1.131 | 1.023 | 1.013 |
Staff nurse utilisation | 0.326 | 0.323 | 0.404 | 0.402 |
Get in-text result 4
# Get results for doctor utilisation and staff nurse utilisation
= data[['in34_normal', 'in4_doctor', 'in4_admin_delivery_doctor']]
subset4 = get_utilisation(subset4, 'doc occ', 'Doctor utilisation')
util4
# Save results and display results
=True)
util4.to_csv(txt4, index util4
Normal | Extra doctor | Extra doctor + less admin + less delivery | |
---|---|---|---|
Output | |||
Doctor utilisation | 1.141 | 0.761 | 0.675 |
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: 3m 7s