Reproducing Table 6

This notebook aims to reproduce Table 6 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.

Table 6

Table 6

Set up

# To run model
import PHC
import numpy as np

# To import and process results
from reproduction_helpers import process_results
import pandas as pd
import xlrd
import os

# To speed up run time
from multiprocessing import Pool

# Additional package to record runtime of this notebook
import time
start = time.time()
table6_path = '../../original_study/tab6.csv'
final_results = '../outputs/tab6.csv'

Run model

Set parameters for each configuration based on Table 3.

# Have just specified the parameters that changed between configurations

t6_c1_param = {
    'doc_cap': 2,
    'OPD_iat': 4,
    'IPD_iat': 2880,
    'delivery_iat': 1440,
    'ANC_iat': 1440,
    'rep_file': 't6_c1.xls'
}

t6_c2_param = {
    'doc_cap': 1,
    'OPD_iat': 9,
    'IPD_iat': 2880,
    'delivery_iat': 2880,
    'ANC_iat': 2880,
    'rep_file': 't6_c2.xls'
}

t6_c3_param = {
    'doc_cap': 1,
    'OPD_iat': 9,
    'IPD_iat': 2880,
    'any_delivery': False,
    'any_ANC': False,
    'rep_file': 't6_c3.xls'
}

t6_c4_param = {
    'doc_cap': 2,
    'OPD_iat': 3,
    'mean': 5,
    'sd': 1,
    'consult_boundary_1': 2,
    'consult_boundary_2': 2,
    'IPD_iat': 2880,
    'delivery_iat': 1440,
    'ANC_iat': 1440,
    'rep_file': 't6_c4.xls'
}
# Create list of parameter dictionaries
config = [t6_c1_param, t6_c2_param, t6_c3_param, t6_c4_param]

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

Run model for each configuration using parallel processing.

# 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(4) as pool:
    # Run PHC.main() using each of inputs from config
    pool.map(wrapper, config)
 No of replications done 0
 No of replications done 0
 No of replications done 1
 No of replications done 1
 No of replications done 0
 No of replications done 2
 No of replications done 0
 No of replications done 2
 No of replications done 3
 No of replications done 3
 No of replications done 1
 No of replications done 4
 No of replications done 4
 No of replications done 5
 No of replications done 1
 No of replications done 5
 No of replications done 6
 No of replications done 2
 No of replications done 6
 No of replications done 7
 No of replications done 7
 No of replications done 8
 No of replications done 2
 No of replications done 3
 No of replications done 9
 No of replications done 8
 No of replications done 9
 No of replications done 4
 No of replications done 3
 No of replications done 5
 No of replications done 4
 No of replications done 6
 No of replications done 5
 No of replications done 7
 No of replications done 8
 No of replications done 6
 No of replications done 9
 No of replications done 7
 No of replications done 8
 No of replications done 9

Import and process replication results

# Make dictionary with labels from table 6, and corresponding names from model output
t6_labels = {
  'doc occ': 'Doctor utilisation',
  'NCD occ': 'NCD Nurse utilisation',
  'staff nurse occ': 'Staff nurse utilisation',
  'pharm occ': 'Pharmacist utilisation',
  'lab occ': 'Lab utilisation',
  'ipd bed occ': 'Inpatient bed utilisation',
  'del occ': 'Labour bed utilisation',  # "Del" stands for delivery
  'opd q len': 'Mean length of OPD queue (number of patients)',
  'OPD Q wt': 'OPD queue waiting time (minutes)',
  'pharmacy q len': 'Mean length of pharmacy queue (number of patients)',
  'Pharmacy Q wt': 'Pharmacy queue waiting time (minutes)',
  'lab q len': 'Mean length of Lab queue (number of patients)',
  'Lab Q wt': 'Lab queue waiting time (minutes)',
  'prop_del_referred': 'Fraction of childbirth cases referred'
}
# Get results from those files
files = ['t6_c1', 't6_c2', 't6_c3', 't6_c4']
summary = process_results(files, sd=True).reset_index().rename(
    columns= {'index': 'model_outcome'})

# Add labels to model results
summary['t6_outcome'] = summary['model_outcome'].map(t6_labels)

summary
model_outcome model_t6_c1_mean model_t6_c1_sd model_t6_c2_mean model_t6_c2_sd model_t6_c3_mean model_t6_c3_sd model_t6_c4_mean model_t6_c4_sd t6_outcome
0 OPD patients 33101.900000 187.146081 14859.700000 143.959910 14850.100000 109.377481 43965.100000 194.210338 NaN
1 IPD patients 184.800000 15.795921 184.400000 11.918240 185.900000 7.460265 178.600000 9.359487 NaN
2 ANC patients 359.300000 13.888844 210.000000 11.015141 0.000000 0.000000 360.900000 14.487159 NaN
3 Del patients 353.800000 15.368077 189.100000 11.948501 NaN NaN 373.300000 21.689987 NaN
4 OPD Q wt 0.008353 0.003899 0.178456 0.032152 0.035088 0.001552 6.936889 0.317485 OPD queue waiting time (minutes)
5 Pharmacy Q wt 1.028137 0.030612 0.243665 0.004954 0.230186 0.005995 1.282328 0.019748 Pharmacy queue waiting time (minutes)
6 Lab Q wt 2.093213 0.044016 0.597352 0.015955 0.560986 0.023920 3.123766 0.082075 Lab queue waiting time (minutes)
7 doc occ 0.268659 0.001616 0.371607 0.004346 0.353566 0.001425 1.141365 0.005321 Doctor utilisation
8 Lab patient list 188885.800000 104210.318073 85433.500000 47183.868802 85595.300000 46948.304603 250794.800000 138149.363001 NaN
9 OPD q len 0.006918 0.003500 0.179808 0.042491 0.034079 0.001502 6.851667 0.332248 NaN
10 ipd occ 0.096000 0.005164 0.061000 0.003162 0.012000 0.004216 0.098000 0.004216 NaN
11 opd q len 0.000736 0.000342 0.007070 0.001264 0.001387 0.000066 0.812750 0.040379 Mean length of OPD queue (number of patients)
12 pharmacy q len 0.090371 0.002949 0.009623 0.000204 0.009097 0.000242 0.149890 0.002749 Mean length of pharmacy queue (number of patie...
13 lab q len 0.094789 0.002610 0.012369 0.000372 0.011094 0.000492 0.185814 0.005814 Mean length of Lab queue (number of patients)
14 NCD occ 0.868296 0.009839 0.466542 0.004252 0.467458 0.003913 1.230139 0.014734 NCD Nurse utilisation
15 lab occ 0.554516 0.006856 0.253693 0.003409 0.238568 0.004396 0.728652 0.014631 Lab utilisation
16 pharm occ 0.642648 0.003439 0.288656 0.002787 0.288370 0.002555 0.853658 0.004257 Pharmacist utilisation
17 staff nurse occ 0.318275 0.006835 0.246430 0.005604 0.160976 0.000811 0.325778 0.009066 Staff nurse utilisation
18 del occ 0.280000 0.014142 0.157000 0.006749 NaN NaN 0.285000 0.012693 Labour bed utilisation
19 del referred 52.300000 5.869885 17.500000 2.223611 NaN NaN 62.200000 9.259230 NaN
20 ipd bed occ 0.091780 0.003905 0.057443 0.003663 0.011905 0.000581 0.093865 0.004500 Inpatient bed utilisation
21 prop_del_referred 0.147775 0.014515 0.092491 0.009510 NaN NaN 0.166276 0.019396 Fraction of childbirth cases referred

Import table 6 results and compare against run results

# Import table 6
t6 = pd.read_csv(table6_path).rename(columns={'outcome': 't6_outcome'})
t6
t6_outcome config1_mean config1_sd config2_mean config2_sd config3_mean config3_sd benchmark_mean benchmark_sd
0 Doctor utilisation 0.268 0.003 0.372 0.004 0.354 0.002 1.142 0.006
1 NCD Nurse utilisation 0.865 0.011 0.469 0.005 0.468 0.005 1.232 0.019
2 Staff nurse utilisation 0.323 0.008 0.243 0.006 0.160 0.001 0.322 0.008
3 Pharmacist utilisation 0.643 0.004 0.288 0.003 0.289 0.003 0.855 0.005
4 Lab utilisation 0.559 0.008 0.254 0.004 0.239 0.004 0.736 0.011
5 Inpatient bed utilisation 0.093 0.004 0.055 0.003 0.011 0.001 0.093 0.004
6 Labour bed utilisation 0.283 0.010 0.153 0.009 NaN NaN 0.281 0.012
7 Mean length of OPD queue (number of patients) 0.000 0.000 0.007 0.001 0.001 0.000 0.817 0.027
8 OPD queue waiting time (minutes) 0.009 0.004 0.171 0.032 0.034 0.001 6.789 0.268
9 Mean length of pharmacy queue (number of patie... 0.090 0.002 0.010 0.001 0.009 0.000 0.150 0.002
10 Pharmacy queue waiting time (minutes) 1.025 0.021 0.244 0.008 0.232 0.006 1.282 0.018
11 Mean length of Lab queue (number of patients) 0.094 0.003 0.012 0.001 0.011 0.000 0.188 0.001
12 Lab queue waiting time (minutes) 2.084 0.054 0.606 0.023 0.571 0.020 3.135 0.005
13 Fraction of childbirth cases referred 0.156 0.019 0.088 0.022 NaN NaN 0.157 0.180
# Round to 3dp (as in table) and merge
compare = t6.merge(round(summary,3))
compare.head()
t6_outcome config1_mean config1_sd config2_mean config2_sd config3_mean config3_sd benchmark_mean benchmark_sd model_outcome model_t6_c1_mean model_t6_c1_sd model_t6_c2_mean model_t6_c2_sd model_t6_c3_mean model_t6_c3_sd model_t6_c4_mean model_t6_c4_sd
0 Doctor utilisation 0.268 0.003 0.372 0.004 0.354 0.002 1.142 0.006 doc occ 0.269 0.002 0.372 0.004 0.354 0.001 1.141 0.005
1 NCD Nurse utilisation 0.865 0.011 0.469 0.005 0.468 0.005 1.232 0.019 NCD occ 0.868 0.010 0.467 0.004 0.467 0.004 1.230 0.015
2 Staff nurse utilisation 0.323 0.008 0.243 0.006 0.160 0.001 0.322 0.008 staff nurse occ 0.318 0.007 0.246 0.006 0.161 0.001 0.326 0.009
3 Pharmacist utilisation 0.643 0.004 0.288 0.003 0.289 0.003 0.855 0.005 pharm occ 0.643 0.003 0.289 0.003 0.288 0.003 0.854 0.004
4 Lab utilisation 0.559 0.008 0.254 0.004 0.239 0.004 0.736 0.011 lab occ 0.555 0.007 0.254 0.003 0.239 0.004 0.729 0.015
compare_col = [
    ('config1_mean', 'model_t6_c1_mean'),
    ('config1_sd', 'model_t6_c1_sd'),
    ('config2_mean', 'model_t6_c2_mean'),
    ('config2_sd', 'model_t6_c2_sd'),
    ('config3_mean', 'model_t6_c3_mean'),
    ('config3_sd', 'model_t6_c3_sd'),
    ('benchmark_mean', 'model_t6_c4_mean'),
    ('benchmark_sd', 'model_t6_c4_sd'),]

for col in compare_col:
    # Find difference between two columns
    compare[f'change_{col[1]}'] = abs(compare[col[1]] - compare[col[0]])
    # Find percent change between two columns
    subset = compare[list(col)]
    pct_change = subset.pct_change(axis=1).iloc[:, 1]*100
    compare[f'pct_change_{col[1]}'] = pct_change

# Display each of the results
for col in compare_col:
    # Set outcome as index, and get the two results plus percent change
    subset = compare.set_index('t6_outcome')[
        list(col) + [f'change_{col[1]}', f'pct_change_{col[1]}']]
    display(subset)
config1_mean model_t6_c1_mean change_model_t6_c1_mean pct_change_model_t6_c1_mean
t6_outcome
Doctor utilisation 0.268 0.269 0.001 0.373134
NCD Nurse utilisation 0.865 0.868 0.003 0.346821
Staff nurse utilisation 0.323 0.318 0.005 -1.547988
Pharmacist utilisation 0.643 0.643 0.000 0.000000
Lab utilisation 0.559 0.555 0.004 -0.715564
Inpatient bed utilisation 0.093 0.092 0.001 -1.075269
Labour bed utilisation 0.283 0.280 0.003 -1.060071
Mean length of OPD queue (number of patients) 0.000 0.001 0.001 inf
OPD queue waiting time (minutes) 0.009 0.008 0.001 -11.111111
Mean length of pharmacy queue (number of patients) 0.090 0.090 0.000 0.000000
Pharmacy queue waiting time (minutes) 1.025 1.028 0.003 0.292683
Mean length of Lab queue (number of patients) 0.094 0.095 0.001 1.063830
Lab queue waiting time (minutes) 2.084 2.093 0.009 0.431862
Fraction of childbirth cases referred 0.156 0.148 0.008 -5.128205
config1_sd model_t6_c1_sd change_model_t6_c1_sd pct_change_model_t6_c1_sd
t6_outcome
Doctor utilisation 0.003 0.002 0.001 -33.333333
NCD Nurse utilisation 0.011 0.010 0.001 -9.090909
Staff nurse utilisation 0.008 0.007 0.001 -12.500000
Pharmacist utilisation 0.004 0.003 0.001 -25.000000
Lab utilisation 0.008 0.007 0.001 -12.500000
Inpatient bed utilisation 0.004 0.004 0.000 0.000000
Labour bed utilisation 0.010 0.014 0.004 40.000000
Mean length of OPD queue (number of patients) 0.000 0.000 0.000 NaN
OPD queue waiting time (minutes) 0.004 0.004 0.000 0.000000
Mean length of pharmacy queue (number of patients) 0.002 0.003 0.001 50.000000
Pharmacy queue waiting time (minutes) 0.021 0.031 0.010 47.619048
Mean length of Lab queue (number of patients) 0.003 0.003 0.000 0.000000
Lab queue waiting time (minutes) 0.054 0.044 0.010 -18.518519
Fraction of childbirth cases referred 0.019 0.015 0.004 -21.052632
config2_mean model_t6_c2_mean change_model_t6_c2_mean pct_change_model_t6_c2_mean
t6_outcome
Doctor utilisation 0.372 0.372 0.000 0.000000
NCD Nurse utilisation 0.469 0.467 0.002 -0.426439
Staff nurse utilisation 0.243 0.246 0.003 1.234568
Pharmacist utilisation 0.288 0.289 0.001 0.347222
Lab utilisation 0.254 0.254 0.000 0.000000
Inpatient bed utilisation 0.055 0.057 0.002 3.636364
Labour bed utilisation 0.153 0.157 0.004 2.614379
Mean length of OPD queue (number of patients) 0.007 0.007 0.000 0.000000
OPD queue waiting time (minutes) 0.171 0.178 0.007 4.093567
Mean length of pharmacy queue (number of patients) 0.010 0.010 0.000 0.000000
Pharmacy queue waiting time (minutes) 0.244 0.244 0.000 0.000000
Mean length of Lab queue (number of patients) 0.012 0.012 0.000 0.000000
Lab queue waiting time (minutes) 0.606 0.597 0.009 -1.485149
Fraction of childbirth cases referred 0.088 0.092 0.004 4.545455
config2_sd model_t6_c2_sd change_model_t6_c2_sd pct_change_model_t6_c2_sd
t6_outcome
Doctor utilisation 0.004 0.004 0.000 0.000000
NCD Nurse utilisation 0.005 0.004 0.001 -20.000000
Staff nurse utilisation 0.006 0.006 0.000 0.000000
Pharmacist utilisation 0.003 0.003 0.000 0.000000
Lab utilisation 0.004 0.003 0.001 -25.000000
Inpatient bed utilisation 0.003 0.004 0.001 33.333333
Labour bed utilisation 0.009 0.007 0.002 -22.222222
Mean length of OPD queue (number of patients) 0.001 0.001 0.000 0.000000
OPD queue waiting time (minutes) 0.032 0.032 0.000 0.000000
Mean length of pharmacy queue (number of patients) 0.001 0.000 0.001 -100.000000
Pharmacy queue waiting time (minutes) 0.008 0.005 0.003 -37.500000
Mean length of Lab queue (number of patients) 0.001 0.000 0.001 -100.000000
Lab queue waiting time (minutes) 0.023 0.016 0.007 -30.434783
Fraction of childbirth cases referred 0.022 0.010 0.012 -54.545455
config3_mean model_t6_c3_mean change_model_t6_c3_mean pct_change_model_t6_c3_mean
t6_outcome
Doctor utilisation 0.354 0.354 0.000 0.000000
NCD Nurse utilisation 0.468 0.467 0.001 -0.213675
Staff nurse utilisation 0.160 0.161 0.001 0.625000
Pharmacist utilisation 0.289 0.288 0.001 -0.346021
Lab utilisation 0.239 0.239 0.000 0.000000
Inpatient bed utilisation 0.011 0.012 0.001 9.090909
Labour bed utilisation NaN NaN NaN NaN
Mean length of OPD queue (number of patients) 0.001 0.001 0.000 0.000000
OPD queue waiting time (minutes) 0.034 0.035 0.001 2.941176
Mean length of pharmacy queue (number of patients) 0.009 0.009 0.000 0.000000
Pharmacy queue waiting time (minutes) 0.232 0.230 0.002 -0.862069
Mean length of Lab queue (number of patients) 0.011 0.011 0.000 0.000000
Lab queue waiting time (minutes) 0.571 0.561 0.010 -1.751313
Fraction of childbirth cases referred NaN NaN NaN NaN
config3_sd model_t6_c3_sd change_model_t6_c3_sd pct_change_model_t6_c3_sd
t6_outcome
Doctor utilisation 0.002 0.001 0.001 -50.0
NCD Nurse utilisation 0.005 0.004 0.001 -20.0
Staff nurse utilisation 0.001 0.001 0.000 0.0
Pharmacist utilisation 0.003 0.003 0.000 0.0
Lab utilisation 0.004 0.004 0.000 0.0
Inpatient bed utilisation 0.001 0.001 0.000 0.0
Labour bed utilisation NaN NaN NaN NaN
Mean length of OPD queue (number of patients) 0.000 0.000 0.000 NaN
OPD queue waiting time (minutes) 0.001 0.002 0.001 100.0
Mean length of pharmacy queue (number of patients) 0.000 0.000 0.000 NaN
Pharmacy queue waiting time (minutes) 0.006 0.006 0.000 0.0
Mean length of Lab queue (number of patients) 0.000 0.000 0.000 NaN
Lab queue waiting time (minutes) 0.020 0.024 0.004 20.0
Fraction of childbirth cases referred NaN NaN NaN NaN
benchmark_mean model_t6_c4_mean change_model_t6_c4_mean pct_change_model_t6_c4_mean
t6_outcome
Doctor utilisation 1.142 1.141 0.001 -0.087566
NCD Nurse utilisation 1.232 1.230 0.002 -0.162338
Staff nurse utilisation 0.322 0.326 0.004 1.242236
Pharmacist utilisation 0.855 0.854 0.001 -0.116959
Lab utilisation 0.736 0.729 0.007 -0.951087
Inpatient bed utilisation 0.093 0.094 0.001 1.075269
Labour bed utilisation 0.281 0.285 0.004 1.423488
Mean length of OPD queue (number of patients) 0.817 0.813 0.004 -0.489596
OPD queue waiting time (minutes) 6.789 6.937 0.148 2.179997
Mean length of pharmacy queue (number of patients) 0.150 0.150 0.000 0.000000
Pharmacy queue waiting time (minutes) 1.282 1.282 0.000 0.000000
Mean length of Lab queue (number of patients) 0.188 0.186 0.002 -1.063830
Lab queue waiting time (minutes) 3.135 3.124 0.011 -0.350877
Fraction of childbirth cases referred 0.157 0.166 0.009 5.732484
benchmark_sd model_t6_c4_sd change_model_t6_c4_sd pct_change_model_t6_c4_sd
t6_outcome
Doctor utilisation 0.006 0.005 0.001 -16.666667
NCD Nurse utilisation 0.019 0.015 0.004 -21.052632
Staff nurse utilisation 0.008 0.009 0.001 12.500000
Pharmacist utilisation 0.005 0.004 0.001 -20.000000
Lab utilisation 0.011 0.015 0.004 36.363636
Inpatient bed utilisation 0.004 0.004 0.000 0.000000
Labour bed utilisation 0.012 0.013 0.001 8.333333
Mean length of OPD queue (number of patients) 0.027 0.040 0.013 48.148148
OPD queue waiting time (minutes) 0.268 0.317 0.049 18.283582
Mean length of pharmacy queue (number of patients) 0.002 0.003 0.001 50.000000
Pharmacy queue waiting time (minutes) 0.018 0.020 0.002 11.111111
Mean length of Lab queue (number of patients) 0.001 0.006 0.005 500.000000
Lab queue waiting time (minutes) 0.005 0.082 0.077 1540.000000
Fraction of childbirth cases referred 0.180 0.019 0.161 -89.444444

Format model results like table 6, and save

compare_col = [
    ['Configuration 1', 'model_t6_c1_mean', 'model_t6_c1_sd'],
    ['Configuration 2', 'model_t6_c2_mean', 'model_t6_c2_sd'],
    ['Configuration 3', 'model_t6_c3_mean', 'model_t6_c3_sd'],
    ['Benchmark Case', 'model_t6_c4_mean', 'model_t6_c4_sd']]

# Combine each pair of columns into string as in Table 6
for col in compare_col:
    compare[col[0]] = (
        round(compare[col[1]], 3).astype(str) + ' (' +
        round(compare[col[2]], 3).astype(str) + ')')

# Set outcome name as index, and select those columns
formatted_model_res = (compare
                       .set_index('t6_outcome')
                       .rename_axis('Simulation Outcome')[
                           [item[0] for item in compare_col]])
formatted_model_res
Configuration 1 Configuration 2 Configuration 3 Benchmark Case
Simulation Outcome
Doctor utilisation 0.269 (0.002) 0.372 (0.004) 0.354 (0.001) 1.141 (0.005)
NCD Nurse utilisation 0.868 (0.01) 0.467 (0.004) 0.467 (0.004) 1.23 (0.015)
Staff nurse utilisation 0.318 (0.007) 0.246 (0.006) 0.161 (0.001) 0.326 (0.009)
Pharmacist utilisation 0.643 (0.003) 0.289 (0.003) 0.288 (0.003) 0.854 (0.004)
Lab utilisation 0.555 (0.007) 0.254 (0.003) 0.239 (0.004) 0.729 (0.015)
Inpatient bed utilisation 0.092 (0.004) 0.057 (0.004) 0.012 (0.001) 0.094 (0.004)
Labour bed utilisation 0.28 (0.014) 0.157 (0.007) nan (nan) 0.285 (0.013)
Mean length of OPD queue (number of patients) 0.001 (0.0) 0.007 (0.001) 0.001 (0.0) 0.813 (0.04)
OPD queue waiting time (minutes) 0.008 (0.004) 0.178 (0.032) 0.035 (0.002) 6.937 (0.317)
Mean length of pharmacy queue (number of patients) 0.09 (0.003) 0.01 (0.0) 0.009 (0.0) 0.15 (0.003)
Pharmacy queue waiting time (minutes) 1.028 (0.031) 0.244 (0.005) 0.23 (0.006) 1.282 (0.02)
Mean length of Lab queue (number of patients) 0.095 (0.003) 0.012 (0.0) 0.011 (0.0) 0.186 (0.006)
Lab queue waiting time (minutes) 2.093 (0.044) 0.597 (0.016) 0.561 (0.024) 3.124 (0.082)
Fraction of childbirth cases referred 0.148 (0.015) 0.092 (0.01) nan (nan) 0.166 (0.019)
formatted_model_res.to_csv(final_results)

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 38s