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

# 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
start = time.time()
# Paths to save results to
output_folder = '../outputs'
txt6_path = os.path.join(output_folder, 'intext6.csv')
txt7_path = os.path.join(output_folder, 'intext7.csv')
txt7_100rep_path = os.path.join(output_folder, 'intext7_100rep.csv')
txt7_20p_path = os.path.join(output_folder, 'intext7_20p.csv')

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
    util = round(pd.DataFrame(df.loc['NCD occ']).T, 3)

    # Rename index and row
    util = util.rename_axis('Output')
    util = util.rename({'NCD occ': 'NCD nurse utilisation'})

    # Rename columns
    util = util.rename({
        '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):
    dict_list[i] = {f's_{k}': v for k, v in d.items()}
# 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 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
data = process_results([i['s_rep_file'] for i in dict_list], xls=True)

In-text result 6

txt6 = get_util(data[['in67_base', 'in6_base_admin']])

# 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

txt7 = get_util(data)

# 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')"
res_100 = process_results(['in7_base_admin_appointment_100rep.xls'], xls=True)
txt7_100rep = get_util(res_100)
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')"
res_20p = process_results(['in7_base_admin_appointment_20p.xls'], xls=True)
txt7_20p = get_util(res_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
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: 2m 49s