numpy debug#

Challenge.

For this debug challenge you need to fix a monte-carlo simulation model.

The code currently does not run. Can you fix it?

Description of the code.#

The model is a simplification of a stroke treatment pathway at a hospital.
Patients must be treated within 180 minutes of the onset of their symptoms.

  • They must be brought to hospital

  • be seen in the ED

  • undego a CT scan

  • be clinically assessed by a stroke medic

  • not have any additional illnesses that prevent treatment

The model is implemented using a variety of distributions from numpy.random

The use of numpy reduces the need to sample using explicit for loops.

Code#


import numpy as np
import math

class MonteCarloParameters(object):
    '''
    Class to holds the parameters for the
    monte-carlo simulation model 
    '''
    def __init__(self):
        pass


def normal_moments_from_lognormal(m, v):
    """
    Returns mu and sigma of normal distribution
    underlying a lognormal with mean m and variance v
    
    source: 
    https://blogs.sas.com/content/iml/2014/06/04/simulate-lognormal-
    data-with-specified-mean-and-variance.html
    
    Parameters:
    ------
    m: float
        mean of lognormal distribution
    
    v: float
        variance of lognormal distribution
        
    Returns:
    -------
    float, float
    """
    phi = math.sqrt(v + m**2)
    mu = math.log(m**2/phi)
    sigma = math.sqrt(math.log(phi**2/m**2))
    return mu, sigma


def gamma_parameters(mean, var):
    """
    Returns tuple with scale and shape of gamma distribution
    based on a mean and variance.
    
    Parameters:
    mean: float
        mean of the gamma dist
    
    var: float
        variance of the gamma dist
    """
    scale = var / mean
    shape = var / (scale ** 2)
    return scale, shape
    


def results_summary(results):
    '''
    Display results
    
    Parameters:
    ---------
    results: numpy.ndarray 
        array of weekly treatment percentages
    '''
    print('Weekly Treatment Rate')
    print('mean:\t\t{0:.3f}'.format(results.mean()))
    print('std err:\t{0:.3f}'.format(results.std() / math.sqrt(results.shape[0])))
    lower = np.percentile(results, 5)
    upper = np.percentile(results, 95)
    print('middle 90%:\t{0:.3f} - {1:.3f}'.format(lower, upper))
    


def sample_patients(params):
    '''
    Sample weekly no. of stroke patients
    Returns integer no. of patients 
    
    Parameters:
    --------
    params: MonteCarloParameters
        monte carlo parameters container
        
    Returns:
    --------
    int
    '''
    return np.random.poisson(params.mean_strokes)

def sample_ota(params, patients):
    '''
    Sample onset to arrival time 
    Return np.ndarray of samples (size = patients)
    
    Parameters:
    --------
    params: MonteCarloParameters
        monte carlo parameters container
        
    patients: int
        no. of samples to make
        
    Returns:
    ------
    np.ndarray
    
    '''
    return np.random.gamma(shape = params.onset_shape, 
                           scale = params.onset_scale, size = patients)
    

def sample_ed_delay(params, patients):
    '''
    Sample delay a patient experiences in the ED 
    Return np.ndarray of samples (size = patients)
    
    Parameters:
    --------
    params: MonteCarloParameters
        monte carlo parameters container
        
    patients: int
        no. of samples to make
        
    Returns:
    ------
    np.ndarray
    
    '''
    return np.random.lognormal(mean = params.mu_ed, 
                               sigma = params.sigma_ed, size = patients)
    

def sample_ct_scan(params, patients):
    '''
    Sample time it takes to undergo a CT scan and report results
    Return np.ndarray of samples (size = patients)
    
    Parameters:
    --------
    params: MonteCarloParameters
        monte carlo parameters container
        
    patients: int
        no. of samples to make
        
    Returns:
    ------
    np.ndarray
    
    '''
    return np.random.triangular(left = params.min_scan_time, 
                                mode = params.mode_scan_time, 
                                right = params.max_scan_time, size = patients)


def sample_assess_duration(params, patients):
    '''
    Sample the duration of clinical assessment
    Return np.ndarray of samples (size = patients)
    
    Parameters:
    --------
    params: MonteCarloParameters
        monte carlo parameters container
        
    patients: int
        no. of samples to make
        
    Returns:
    ------
    np.ndarray
    
    '''
    return np.random.uniform(low = params.min_assess_time, 
                             high = params.max_assess_time)
  


def eligible(patients, params):
    samples = np.random.uniform(0, 1, size = patients)
    return (samples >= 1 - params.p_contra_indication).sum()
            
            

def run_model(params, no_replications = 10000):
    '''
    Run the monte-carlo simulation
    
    Parameters:
    --------
    params: MonteCarloParameters
        container for parameters for statistical 
        distributions eg. mean and std
        
    no_replications: int, optional (default = 10000)
        no. indepedent replications 
        
 
    Returns:
    ------
    np.ndarray
    
    '''
    
    results = np.zeros(1000)

    for rep in range(no_replications):
    
        #number of patients with strokes in the week
        patients = sample_patients(params)
        
        #time taken from onset of stroke to arrival at a hospital
        ota = sample_ota(params, patients)
        
        #time taken for identificaion in the ED and alert of stroke team
        ed_delay = sample_ed_delay(params, patients)
        
        #time take to scan patient and report results
        ct_time = sample_ct_scan(params, patients)
        
        #time for stroke team to clinically assess patient
        assessment_time = sample_assess_duration(params, patients)

        #onset to treatment time for each patient (sum of numpy arrays)
        ott = ota + ed_delay + ct_time + assessment_time
        
        #patients in time
        patients_in_time = (ott < params.treatment_deadline).sum()
        
        #patients eligible for treatment
        results[rep] =  eligible(patients_in_time, params)

        # calculate percentage treated
        results[rep] /= patients 

    return results


        

def main():
    params = MonteCarloParameters() 
    
    #patients can only be treated if they are ready within 180 minutes
    params.treatment_deadline = 180
    
    #every 2/5 strokes  cannot have treatment due to other medical problems
    params.p_contra_indication = 0.4 
   
    #mean no. of strokes seen per week - poisson distribution
    params.mean_strokes = 12.5
   
    #onset to arrival time- gamma distribution
    mean_ota = 120
    std_ota = 60
    params.onset_scale, params.onset_shape = gamma_parameters(mean_ota, std_ota ** 2)
   
    #time in the emergency department - lognormal distribution
    params.mu_ed, params.sigma_ed = normal_moments_from_lognormal(60, 20)
   
    #time spent in CT scanner - triangular distribution
    params.min_scan_time = 15 
    params.mode_scan_time = 20
    params.max_scan_time = 30
   
    #eligibility assessment time by stroke physcian - uniform distribution
    params.min_assess_time = 5
    params.max_assess_time = 30
   
    
    results = run_model(params)
    
    results_summary(results)    


if __name__ == '__main__':
    main()