def __init__(self, value=None, scorename='symptom', triggered_by_equal=True, min_triggered=1, **kwargs): ''' value: what value triggers this milestone (None means minimum possible given the scorename) scorename: which score the value refers to triggered_by_equal: if True, use <= for trigger, if False, use < for trigger min_triggered: the number of days in a row to fulfill the condition when sampling kwargs: passed to parent class ''' super().__init__(name='magnitude', **kwargs) #check and set scorename if scorename not in {'symptom', 'visual', 'symptom_noerror'}: raise ValueError(f"scorename of {scorename} not understood") self.scorename = scorename #check and set triggered_by_equal if not isinstance(triggered_by_equal, bool): raise TypeError( f"triggred_by_equal of {triggered_by_equal} should be a boolean" ) self.triggered_by_equal = triggered_by_equal #check and set value if value is None: value = get_MIN(self.scorename) if self.triggered_by_equal: if value < get_MIN(self.scorename): warn( f"value of {value} may be unobtainable since it is smaller than " f"{self.scorename}'s min of MIN of {get_MIN(self.scorename)}" ) else: if value <= get_MIN(self.scorename): warn( f"value of {value} may be unobtainable since it is smaller or equal to " f"{self.scorename}'s MIN of {get_MIN(self.scorename)}") self.value = value #check and set min_triggered if not isinstance(min_triggered, int) or min_triggered < 1: raise TypeError( f"min_triggered of {min_triggered} should be an int of at least 1" ) self.min_triggered = min_triggered
def _get_excluded_magnitude_late(self, scorename='symptom', recovered_score=None, lastday=NDAYS): if recovered_score is None: recovered_score = get_MIN(scorename) persons_recovered_late = np.min(self.scores[scorename][:, :lastday], axis=1) > recovered_score return persons_recovered_late
def _get_excluded_magnitude_early(self, scorename='symptom', recovered_score=None, firstday=FIRSTVISIT): if recovered_score is None: recovered_score = get_MIN(scorename) persons_recovered_early = np.any( self.scores[scorename][:, :firstday] <= recovered_score, axis=1) return persons_recovered_early
def sample(self, population, order, sampling_days): smilescores = population.scores[ self.scorename] #scores which the method value refers to smilescore_lowerbound = get_MIN(self.scorename) # Compute the days where the milestones are triggered comparison_array = ( smilescores <= self.value) if self.triggered_by_equal else ( smilescores < self.value) # Compute the days where the milestones are triggered consecutively if self.min_triggered == 1: pass #don't change comparison_array elif self.min_triggered > 1: triggered_in_a_row = np.ones_like( comparison_array[:, self.min_triggered - 1:]) #initial for start in range(self.min_triggered): end = start + 1 - self.min_triggered if end == 0: end = None triggered_in_a_row = triggered_in_a_row * comparison_array[:, start: end] # accumulate comparison_array[:, self.min_triggered - 1:] = triggered_in_a_row #we only checked when enough days have passed comparison_array[:, :self.min_triggered - 1] = False #the rest can't have had enough days in a row #only check on or after previous (valid) sample day by #setting the comparison values from days 0 to prev sample day (excluding end) to False if order > 0: for i in range(population.npersons): #for getting valid prev day for prev_order in range(order - 1, 0 - 1, -1): prev_sample_day = sampling_days[i, prev_order] if prev_sample_day < NDAYS: break #for setting days until then as don't consider comparison_array[i, :prev_sample_day] = False #if it is True on the same day as the previous sample day, the finish_sampling will consider it already_reached #the day at which the milestone is reached for each person sampling_days_temp = np.argmax(comparison_array, axis=1) #the day at which the milestone is reached for each person, inc. 0 for 'never reached' sampling_days[:, order] = sampling_days_temp #record of which persons reached the milestones persons_reached_milestone = np.take_along_axis( comparison_array, helper.to_vertical(sampling_days_temp), axis=1) #give invalid day to those who didn't reach sampling_days[~persons_reached_milestone.flatten(), order] = _UNREACHED_MAGNITUDE if not np.all(persons_reached_milestone): warn( f"There are {(~persons_reached_milestone.flatten()).sum()} who didn't reach their milestone" ) super().finish_sampling(population, order, sampling_days)
def _get_excluded_ratio_late(self, scorename='symptom', index_day=0, recovered_ratio=0.15, lastday=NDAYS): score_lowerbound = get_MIN(scorename) recovered_scores = (self.scores[scorename][:, index_day] - score_lowerbound) * ratio + score_lowerbound persons_recovered_late = np.min(self.scores[scorename][:, :lastday], axis=1) > recovered_scores return persons_recovered_late
def _get_excluded_ratio_early(self, scorename='symptom', index_day=0, recovered_ratio=0.15, firstday=FIRSTVISIT): score_lowerbound = get_MIN(scorename) recovered_scores = ( self.scores[scorename][:, index_day] - score_lowerbound) * recovered_ratio + score_lowerbound persons_recovered_early = self.scores[ scorename][:, firstday] <= recovered_scores return persons_recovered_early
def sample(self, population, order, sampling_days): smilescores = population.scores[ self.scorename] #scores which the method ratio refers to smilescore_lowerbound = get_MIN(self.scorename) #get and check index days if isinstance(self.index, int): index_days = np.full((population.npersons, ), self.index) elif isinstance(self.index, tuple): prev_sampling_days = sampling_days[:, :order] index_days = prev_sampling_days[:, self.day[1]] elif callable(self.index): prev_sampling_days = sampling_days[:, :order] #TODO check if int not outside NDAYS, FIRSTVISIT, LASTVISIT index_days = self.index((population.npersons, ), prev_sampling_days) # Compute the scores which will trigger milestones smilescores_at_index = np.take_along_axis( smilescores, helper.to_vertical(index_days), axis=1) #column array smile_vals = (smilescores_at_index - smilescore_lowerbound ) * self.ratio + smilescore_lowerbound #column array # Compute the days where the milestones are triggered comparison_array = ( smilescores <= smile_vals) if self.triggered_by_equal else ( smilescores < smile_vals) # Compute the days where the milestones are triggered consecutively if self.min_triggered == 1: pass #don't change comparison_array elif self.min_triggered > 1: triggered_in_a_row = np.ones_like( comparison_array[:, self.min_triggered - 1:]) #initial for start in range(self.min_triggered): end = start + 1 - self.min_triggered if end == 0: end = None triggered_in_a_row = triggered_in_a_row * comparison_array[:, start: end] # accumulate comparison_array[:, self.min_triggered - 1:] = triggered_in_a_row #we only checked when enough days have passed comparison_array[:, :self.min_triggered - 1] = False #the rest can't have had enough days in a row #only check on or after previous sample day by #setting the comparison values from days 0 to prev sample day (excluding end) to False for i in range(population.npersons): comparison_array[i, :sampling_days[i, order - 1]] = False #if it is True on the same day as the previous sample day, the finish_sampling will consider it already_reached #the day at which the milestone is reached for each person sampling_days_temp = np.argmax(comparison_array, axis=1) #the day at which the milestone is reached for each person, inc. 0 for 'never reached' sampling_days[:, order] = sampling_days_temp #record of which persons actually reached the milestones persons_reached_milestone = np.take_along_axis( comparison_array, helper.to_vertical(sampling_days_temp), axis=1) #give invalid day to those who didn't reach sampling_days[~persons_reached_milestone.flatten(), order] = _UNREACHED_SMILE if not np.all(persons_reached_milestone): warn( f"There are {(~persons_reached_milestone.flatten()).sum()} who didn't reach their milestone" ) super().finish_sampling(population, order, sampling_days)