Skip to content

susceptibility_distribution

SusceptibilityDistribution

Bases: Updateable

Source code in emod_api/demographics/susceptibility_distribution.py
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
class SusceptibilityDistribution(Updateable):
    def __init__(self,
                 ages_years: List[float],
                 susceptible_fraction: List[float]):
        """

        A by-age susceptibility to infection distribution in fraction units 0 to 1. This is used whenever an agent is
        created, such as during model initialization and when agents are born.

        For Generic (GENERIC_SIM) simulations only.

        The SusceptibilityDistribution provides a probability each agent will be initialized as susceptible to
        infection (or not). It models the effect of natural immunity in preventing infection entirely in (1-fraction)
        of the population. Those that are allowed to acquire an infection can also be affected by other interventions
        or immunity derived from getting the disease. Agents are identified at creation time as 'susceptible to
        infection' by a uniform random number draw that is compared to the susceptibility distribution value at the
        corresponding agent age. If an agents age lies between two provided ages, its chances of being susceptible to
        infection are linearly interpolated from the two closest corresponding ages. If the agents age lies beyond the
        provided ages, the closest age-corresponding susceptibility will be used.

        WARNING: This complex distribution is different than when using a SimpleDistribution for this feature. The
        complex distribution makes people either completely susceptible or completely immune. In contrast, simple
        distributions give each person a probability of acquiring an infection (i.e. value between 0 and 1 versus
        just 0 or 1).

        Args:
            ages_years: (List[float]) A list of ages (in years) that susceptibility fraction data will be provided for.
                Must be a list of monotonically increasing floats within range 0 <= age <= 200 years.
            susceptible_fraction: (List[float]) A list of susceptibility fractions corresponding to the provided
                ages_years list. These represent the chances an initialized agent at a given age will be susceptible to
                infection. Must be a list of floats within range 0 <= fraction <= 1 .

        Example:
            ages_years: [0, 10, 20, 50, 100]
            susceptible_fraction: [0.9, 0.7, 0.3, 0.5, 0.8]

            Agent age 10 years
                susceptible chance: 0.7
            Agent age 15 years:
                susceptible chance: 0.7 + (15 - 10) * ((0.3-0.7) / (20-10)) = 0.5
            Agent age 1000 years (beyond provided age range)
                susceptible chance: 0.8 (nearest corresponding fraction)
        """
        super().__init__()
        self.ages_years = ages_years
        self.susceptible_fraction = susceptible_fraction
        # This will convert the object to an susceptibility distribution dictionary and then validate it reporting
        # object-relevant messages
        self._validate(distribution_dict=self.to_dict(validate=False), source_is_dict=False)

    @classmethod
    def _rate_scale_factor(cls):
        return 1

    def to_dict(self, validate: bool = True) -> Dict:
        # susceptibility distribution dicts MUST be in ages_days. objs must be in ages_years
        distribution_dict = {
            'ResultValues': self.susceptible_fraction,
            'DistributionValues': [years * 365 for years in self.ages_years],
            'ResultScaleFactor': self._rate_scale_factor()
        }
        if validate:
            self._validate(distribution_dict=distribution_dict, source_is_dict=False)
        return distribution_dict

    @classmethod
    def from_dict(cls, distribution_dict: Dict):
        # susceptibility distribution dicts MUST be in ages_days. objs must be in ages_years
        cls._validate(distribution_dict=distribution_dict, source_is_dict=True)
        ages_years = [days / 365 for days in distribution_dict['DistributionValues']]
        return cls(ages_years=ages_years,
                   susceptible_fraction=distribution_dict['ResultValues'])

    _validation_messages = {
        'fixed_value_check': {
            True: "key: %s value: %s does not match expected value: %s",
            False: None  # These are all properties of the obj and cannot be made invalid
        },
        'data_dimensionality_check_ages': {
            True: 'DistributionValues must be a 1-d array of floats',
            False: 'ages_years must be a 1-d array of floats'
        },
        'data_dimensionality_check_susceptibility': {
            True: 'ResultValues must be a 1-d array of floats',
            False: 'susceptible_fraction must be a 1-d array of floats'
        },
        'age_and_susceptibility_length_check': {
            True: 'DistributionValues and ResultValues must be the same length but are not',
            False: 'ages_years and susceptible_fraction must be the same length but are not'
        },
        'age_range_check': {
            True: "DistributionValues age values must be: 0 <= age <= 73000 in days. Out-of-range index:values : %s",
            False: "All ages_years values must be: 0 <= age <= 200 in years. Out-of-range index:values : %s"
        },
        'susceptibility_range_check': {
            True: "ResultValues susceptible fractions must be: 0 <= fraction <= 1. "
                  "Out-of-range index:values : %s",
            False: "All susceptible_fraction values must be: 0 <= fraction <= 1. "
                   "Out-of-range index:values : %s"
        },
        'age_monotonicity_check': {
            True: "DistributionValues ages in days must monotonically increase but do not, index: %d value: %s",
            False: "ages_years values must monotonically increase but do not, index: %d value: %s"
        }
    }

    @classmethod
    def _validate(cls, distribution_dict: Dict, source_is_dict: bool):
        """
        Validate a SusceptibilityDistribution in dict form

        Args:
            distribution_dict: (dict) the susceptibility distribution dict to validate
            source_is_dict: (bool) If true, report dict-relevant error messages. If false, report obj-relevant messages.

        Returns:
            Nothing
        """
        if source_is_dict is True:
            expected_values = {
                'ResultScaleFactor': cls._rate_scale_factor()
            }
            for key, expected_value in expected_values.items():
                value = distribution_dict[key]
                if value != expected_value:
                    message = cls._validation_messages['fixed_value_check'][source_is_dict] % (key, value, expected_value)
                    raise demog_ex.InvalidFixedValueException(message)

        # ensure the ages and distribution values are both 1-d iterables of the same length
        ages = distribution_dict['DistributionValues']
        susceptible_values = distribution_dict['ResultValues']

        is_1d = check_dimensionality(data=ages, dimensionality=1)
        if not is_1d:
            message = cls._validation_messages['data_dimensionality_check_ages'][source_is_dict]
            raise demog_ex.InvalidDataDimensionality(message)
        is_1d = check_dimensionality(data=susceptible_values, dimensionality=1)
        if not is_1d:
            message = cls._validation_messages['data_dimensionality_check_susceptibility'][source_is_dict]
            raise demog_ex.InvalidDataDimensionality(message)

        if len(ages) != len(susceptible_values):
            message = cls._validation_messages['age_and_susceptibility_length_check'][source_is_dict]
            raise demog_ex.InvalidDataDimensionLength(message)

        # ensure the age and susceptibility value lists are ascending and in reasonable ranges
        # record in days for dict-relevant messages, years for obj-relevant messages
        factor = 1 if source_is_dict is True else 1 / 365.0
        out_of_range = [f"{index}:{age * factor}" for index, age in enumerate(ages) if (age < 0 * 365) or (age > 200 * 365)]
        if len(out_of_range) > 0:
            oor_str = ', '.join(out_of_range)
            message = cls._validation_messages['age_range_check'][source_is_dict] % oor_str
            raise demog_ex.AgeOutOfRangeException(message)
        out_of_range = [f"{index}:{value}" for index, value in enumerate(susceptible_values)
                        if (value < 0) or (value > 1)]
        if len(out_of_range) > 0:
            oor_str = ', '.join(out_of_range)
            message = cls._validation_messages['susceptibility_range_check'][source_is_dict] % oor_str
            raise demog_ex.DistributionOutOfRangeException(message)

        for i in range(1, len(ages)):
            if ages[i] - ages[i - 1] <= 0:
                message = cls._validation_messages['age_monotonicity_check'][source_is_dict] % (i, ages[i])
                raise demog_ex.NonMonotonicAgeException(message)

__init__(ages_years, susceptible_fraction)

A by-age susceptibility to infection distribution in fraction units 0 to 1. This is used whenever an agent is created, such as during model initialization and when agents are born.

For Generic (GENERIC_SIM) simulations only.

The SusceptibilityDistribution provides a probability each agent will be initialized as susceptible to infection (or not). It models the effect of natural immunity in preventing infection entirely in (1-fraction) of the population. Those that are allowed to acquire an infection can also be affected by other interventions or immunity derived from getting the disease. Agents are identified at creation time as 'susceptible to infection' by a uniform random number draw that is compared to the susceptibility distribution value at the corresponding agent age. If an agents age lies between two provided ages, its chances of being susceptible to infection are linearly interpolated from the two closest corresponding ages. If the agents age lies beyond the provided ages, the closest age-corresponding susceptibility will be used.

WARNING: This complex distribution is different than when using a SimpleDistribution for this feature. The complex distribution makes people either completely susceptible or completely immune. In contrast, simple distributions give each person a probability of acquiring an infection (i.e. value between 0 and 1 versus just 0 or 1).

Parameters:

Name Type Description Default
ages_years List[float]

(List[float]) A list of ages (in years) that susceptibility fraction data will be provided for. Must be a list of monotonically increasing floats within range 0 <= age <= 200 years.

required
susceptible_fraction List[float]

(List[float]) A list of susceptibility fractions corresponding to the provided ages_years list. These represent the chances an initialized agent at a given age will be susceptible to infection. Must be a list of floats within range 0 <= fraction <= 1 .

required
Example

ages_years: [0, 10, 20, 50, 100] susceptible_fraction: [0.9, 0.7, 0.3, 0.5, 0.8]

Agent age 10 years susceptible chance: 0.7 Agent age 15 years: susceptible chance: 0.7 + (15 - 10) * ((0.3-0.7) / (20-10)) = 0.5 Agent age 1000 years (beyond provided age range) susceptible chance: 0.8 (nearest corresponding fraction)

Source code in emod_api/demographics/susceptibility_distribution.py
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
def __init__(self,
             ages_years: List[float],
             susceptible_fraction: List[float]):
    """

    A by-age susceptibility to infection distribution in fraction units 0 to 1. This is used whenever an agent is
    created, such as during model initialization and when agents are born.

    For Generic (GENERIC_SIM) simulations only.

    The SusceptibilityDistribution provides a probability each agent will be initialized as susceptible to
    infection (or not). It models the effect of natural immunity in preventing infection entirely in (1-fraction)
    of the population. Those that are allowed to acquire an infection can also be affected by other interventions
    or immunity derived from getting the disease. Agents are identified at creation time as 'susceptible to
    infection' by a uniform random number draw that is compared to the susceptibility distribution value at the
    corresponding agent age. If an agents age lies between two provided ages, its chances of being susceptible to
    infection are linearly interpolated from the two closest corresponding ages. If the agents age lies beyond the
    provided ages, the closest age-corresponding susceptibility will be used.

    WARNING: This complex distribution is different than when using a SimpleDistribution for this feature. The
    complex distribution makes people either completely susceptible or completely immune. In contrast, simple
    distributions give each person a probability of acquiring an infection (i.e. value between 0 and 1 versus
    just 0 or 1).

    Args:
        ages_years: (List[float]) A list of ages (in years) that susceptibility fraction data will be provided for.
            Must be a list of monotonically increasing floats within range 0 <= age <= 200 years.
        susceptible_fraction: (List[float]) A list of susceptibility fractions corresponding to the provided
            ages_years list. These represent the chances an initialized agent at a given age will be susceptible to
            infection. Must be a list of floats within range 0 <= fraction <= 1 .

    Example:
        ages_years: [0, 10, 20, 50, 100]
        susceptible_fraction: [0.9, 0.7, 0.3, 0.5, 0.8]

        Agent age 10 years
            susceptible chance: 0.7
        Agent age 15 years:
            susceptible chance: 0.7 + (15 - 10) * ((0.3-0.7) / (20-10)) = 0.5
        Agent age 1000 years (beyond provided age range)
            susceptible chance: 0.8 (nearest corresponding fraction)
    """
    super().__init__()
    self.ages_years = ages_years
    self.susceptible_fraction = susceptible_fraction
    # This will convert the object to an susceptibility distribution dictionary and then validate it reporting
    # object-relevant messages
    self._validate(distribution_dict=self.to_dict(validate=False), source_is_dict=False)