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

# 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
start = time.time()
# Paths to save image files to
output_folder = '../outputs'
txt3 = os.path.join(output_folder, 'intext3.csv')
txt4 = os.path.join(output_folder, 'intext4.csv')

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:
    dict_list.append({**base_model, **var})

# Append 's_' to all items
for i, d in enumerate(dict_list):
    dict_list[i] = {f's_{k}': v for k, v in d.items()}

# View example
dict_list[0]
{'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
    pool.map(wrapper, dict_list)
 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

data = process_results([i['s_rep_file'] for i in dict_list], xls=True)
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
    util = round(pd.DataFrame(df.loc[index_name]).T, 3)

    # Rename index
    util = util.rename_axis('Output')

    # Rename row
    util = util.rename({index_name: new_name})

    # Rename columns for clarity for readers
    util = util.rename({
        '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
subset3 = data[['in34_normal', 'in3_delivery', 'in3_admin', 'in3_admin_delivery']]
util3 = pd.concat([
    get_utilisation(subset3, 'doc occ', 'Doctor utilisation'),
    get_utilisation(subset3, 'staff nurse occ', 'Staff nurse utilisation')])

# Save results and display results
util3.to_csv(txt3, index=True)
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
subset4 = data[['in34_normal', 'in4_doctor', 'in4_admin_delivery_doctor']]
util4 = get_utilisation(subset4, 'doc occ', 'Doctor utilisation')

# Save results and display results
util4.to_csv(txt4, index=True)
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
end = time.time()
runtime = round(end-start)

# Display converted to minutes and seconds
print(f'Notebook run time: {runtime//60}m {runtime%60}s')
Notebook run time: 3m 7s