def get_doubling_time(sim, series=None, interval=None, start_day=None, end_day=None, moving_window=None, exp_approx=False, max_doubling_time=100, eps=1e-3, verbose=None): ''' Method to calculate doubling time Can be used in various ways: 1. get_doubling_time(sim, interval=[3,30]) returns the doubling time over the given interval (single float) 2. get_doubling_time(sim, interval=[3,30], moving_window=3) returns doubling times calculated over moving windows (array) Instead of an interval, can pass in the start and end days (as integers - TODO, change this to accept dates) Can pass in a series or the name of a result ''' # Set verbose level if verbose is None: verbose = sim['verbose'] # Validate inputs: series if series is None or isinstance(series, str): if not sim.results_ready: raise Exception( f"Results not ready, cannot calculate doubling time") else: if series is None or series not in sim.reskeys: sc.printv( f"Series not supplied or not found in results; defaulting to use cumulative exposures", 1, verbose) series = 'cum_infections' series = sim.results[series].values else: series = sc.promotetoarray(series) # Validate inputs: interval if interval is not None: if len(interval) != 2: sc.printv( f"Interval should be a list/array/tuple of length 2, not {len(interval)}. Resetting to length of series.", 1, verbose) interval = [0, len(series)] start_day, end_day = interval[0], interval[1] if len(series) < end_day: sc.printv( f"End day {end_day} is after the series ends ({len(series)}). Resetting to length of series.", 1, verbose) end_day = len(series) int_length = end_day - start_day # Deal with moving window if moving_window is not None: if not sc.isnumber(moving_window): sc.printv( f"Moving window should be an integer; ignoring and calculating single result", 1, verbose) doubling_time = get_doubling_time(sim, series=series, start_day=start_day, end_day=end_day, moving_window=None, exp_approx=exp_approx) else: if not isinstance(moving_window, int): sc.printv( f"Moving window should be an integer; recasting {moving_window} the nearest integer... ", 1, verbose) moving_window = int(moving_window) if moving_window < 2: sc.printv( f"Moving window should be greater than 1; recasting {moving_window} to 2", 1, verbose) moving_window = 2 doubling_time = [] for w in range(int_length - moving_window + 1): this_start = start_day + w this_end = this_start + moving_window this_doubling_time = get_doubling_time(sim, series=series, start_day=this_start, end_day=this_end, exp_approx=exp_approx) doubling_time.append(this_doubling_time) # Do calculations else: if not exp_approx: try: import statsmodels.api as sm except ModuleNotFoundError as E: errormsg = f'Could not import statsmodels ({E}), falling back to exponential approximation' print(errormsg) exp_approx = True if exp_approx: if series[start_day] > 0: r = series[end_day] / series[start_day] if r > 1: doubling_time = int_length * np.log(2) / np.log(r) doubling_time = min( doubling_time, max_doubling_time) # Otherwise, it's unbounded else: raise ValueError( f"Can't calculate doubling time with exponential approximation when initial value is zero." ) else: if np.any(series[start_day:end_day] ): # Deal with zero values if possible nonzero = np.nonzero(series[start_day:end_day])[0] if len(nonzero) >= 2: exog = sm.add_constant(np.arange(len(nonzero))) endog = np.log2((series[start_day:end_day])[nonzero]) model = sm.OLS(endog, exog) doubling_rate = model.fit().params[1] if doubling_rate > eps: doubling_time = 1.0 / doubling_rate else: doubling_time = max_doubling_time else: raise ValueError( f"Can't calculate doubling time for series {series[start_day:end_day]}. Check whether series is growing." ) else: raise ValueError( f"Can't calculate doubling time for series {series[start_day:end_day]}. Check whether series is growing." ) return doubling_time
def set_prognoses(sim, popdict): ''' Determine the prognosis of an infected person: probability of being aymptomatic, or if symptoms develop, probability of developing severe symptoms and dying, based on their age ''' # Initialize input and output by_age = sim['prog_by_age'] ages = sc.promotetoarray(popdict['age']) # Ensure it's an array n = len(ages) prognoses = sc.objdict() prog_pars = cvpars.get_default_prognoses(by_age=by_age) # If not by age, same value for everyone if not by_age: prognoses.symp_prob = sim[ 'rel_symp_prob'] * prog_pars.symp_prob * np.ones(n) prognoses.severe_prob = sim[ 'rel_severe_prob'] * prog_pars.severe_prob * np.ones(n) prognoses.crit_prob = sim[ 'rel_crit_prob'] * prog_pars.crit_prob * np.ones(n) prognoses.death_prob = sim[ 'rel_death_prob'] * prog_pars.death_prob * np.ones(n) # Otherwise, calculate probabilities of symptoms, severe symptoms, and death by age else: # Conditional probabilities of severe symptoms (given symptomatic) and death (given severe symptoms) severe_if_sym = np.array( [ sev / sym if sym > 0 and sev / sym > 0 else 0 for (sev, sym) in zip(prog_pars.severe_probs, prog_pars.symp_probs) ] ) # Conditional probabilty of developing severe symptoms, given symptomatic crit_if_severe = np.array( [ crit / sev if sev > 0 and crit / sev > 0 else 0 for (crit, sev) in zip(prog_pars.crit_probs, prog_pars.severe_probs) ] ) # Conditional probabilty of developing critical symptoms, given severe death_if_crit = np.array([ d / c if c > 0 and d / c > 0 else 0 for (d, c) in zip(prog_pars.death_probs, prog_pars.crit_probs) ]) # Conditional probabilty of dying, given critical symp_probs = sim[ 'rel_symp_prob'] * prog_pars.symp_probs # Overall probability of developing symptoms severe_if_sym = sim[ 'rel_severe_prob'] * severe_if_sym # Overall probability of developing severe symptoms (https://www.medrxiv.org/content/10.1101/2020.03.09.20033357v1.full.pdf) crit_if_severe = sim[ 'rel_crit_prob'] * crit_if_severe # Overall probability of developing critical symptoms (derived from https://www.cdc.gov/mmwr/volumes/69/wr/mm6912e2.htm) death_if_crit = sim[ 'rel_death_prob'] * death_if_crit # Overall probability of dying (https://www.imperial.ac.uk/media/imperial-college/medicine/sph/ide/gida-fellowships/Imperial-College-COVID19-NPI-modelling-16-03-2020.pdf) # Calculate prognosis for each person symp_prob, severe_prob, crit_prob, death_prob = [], [], [], [] age_cutoffs = prog_pars.age_cutoffs for age in ages: # Figure out which probability applies to a person of the specified age ind = next((ind for ind, val in enumerate( [True if age < cutoff else False for cutoff in age_cutoffs]) if val), -1) this_symp_prob = symp_probs[ ind] # Probability of developing symptoms this_severe_prob = severe_if_sym[ ind] # Probability of developing severe symptoms this_crit_prob = crit_if_severe[ ind] # Probability of developing critical symptoms this_death_prob = death_if_crit[ ind] # Probability of dying after developing critical symptoms symp_prob.append(this_symp_prob) severe_prob.append(this_severe_prob) crit_prob.append(this_crit_prob) death_prob.append(this_death_prob) # Return output prognoses.symp_prob = symp_prob prognoses.severe_prob = severe_prob prognoses.crit_prob = crit_prob prognoses.death_prob = death_prob popdict.update(prognoses) # Add keys to popdict return