vivarium-public-health 4.2.6__py3-none-any.whl → 4.3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vivarium_public_health/__init__.py +7 -1
- vivarium_public_health/_version.py +1 -1
- vivarium_public_health/exposure/__init__.py +2 -0
- vivarium_public_health/exposure/data_transformations.py +254 -0
- vivarium_public_health/exposure/distributions.py +555 -0
- vivarium_public_health/exposure/effect.py +356 -0
- vivarium_public_health/exposure/exposure.py +254 -0
- vivarium_public_health/population/data_transformations.py +2 -2
- vivarium_public_health/results/__init__.py +1 -0
- vivarium_public_health/results/intervention.py +139 -0
- vivarium_public_health/risks/base_risk.py +34 -214
- vivarium_public_health/risks/data_transformations.py +23 -245
- vivarium_public_health/risks/distributions.py +17 -484
- vivarium_public_health/risks/effect.py +18 -323
- vivarium_public_health/risks/implementations/low_birth_weight_and_short_gestation.py +17 -12
- vivarium_public_health/treatment/__init__.py +1 -0
- vivarium_public_health/treatment/intervention.py +85 -0
- {vivarium_public_health-4.2.6.dist-info → vivarium_public_health-4.3.1.dist-info}/METADATA +1 -1
- {vivarium_public_health-4.2.6.dist-info → vivarium_public_health-4.3.1.dist-info}/RECORD +22 -15
- {vivarium_public_health-4.2.6.dist-info → vivarium_public_health-4.3.1.dist-info}/WHEEL +0 -0
- {vivarium_public_health-4.2.6.dist-info → vivarium_public_health-4.3.1.dist-info}/licenses/LICENSE.txt +0 -0
- {vivarium_public_health-4.2.6.dist-info → vivarium_public_health-4.3.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,139 @@
|
|
1
|
+
"""
|
2
|
+
======================
|
3
|
+
Intervention Observers
|
4
|
+
======================
|
5
|
+
|
6
|
+
This module contains tools for observing risk exposure during the simulation.
|
7
|
+
|
8
|
+
"""
|
9
|
+
|
10
|
+
import pandas as pd
|
11
|
+
from vivarium.framework.engine import Builder
|
12
|
+
|
13
|
+
from vivarium_public_health.results.columns import COLUMNS
|
14
|
+
from vivarium_public_health.results.observer import PublicHealthObserver
|
15
|
+
from vivarium_public_health.utilities import to_years
|
16
|
+
|
17
|
+
|
18
|
+
class CategoricalInterventionObserver(PublicHealthObserver):
|
19
|
+
"""
|
20
|
+
A class for observering interventions. This class has the same implementation as
|
21
|
+
the 'CategoricalRiskObserver' class.
|
22
|
+
|
23
|
+
"""
|
24
|
+
|
25
|
+
@property
|
26
|
+
def columns_required(self) -> list[str] | None:
|
27
|
+
"""The columns required by this observer."""
|
28
|
+
return ["alive"]
|
29
|
+
|
30
|
+
#####################
|
31
|
+
# Lifecycle methods #
|
32
|
+
#####################
|
33
|
+
|
34
|
+
def __init__(self, intervention: str) -> None:
|
35
|
+
"""Constructor for this observer.
|
36
|
+
|
37
|
+
Parameters
|
38
|
+
----------
|
39
|
+
intervention
|
40
|
+
The name of the intervention being observed
|
41
|
+
"""
|
42
|
+
super().__init__()
|
43
|
+
self.intervention = intervention
|
44
|
+
self.coverage_pipeline_name = f"{self.intervention}.coverage"
|
45
|
+
|
46
|
+
#################
|
47
|
+
# Setup methods #
|
48
|
+
#################
|
49
|
+
|
50
|
+
def setup(self, builder: Builder) -> None:
|
51
|
+
"""Set up the observer."""
|
52
|
+
self.step_size = builder.time.step_size()
|
53
|
+
self.categories = builder.data.load(f"intervention.{self.intervention}.categories")
|
54
|
+
|
55
|
+
def get_configuration_name(self) -> str:
|
56
|
+
return self.intervention
|
57
|
+
|
58
|
+
def register_observations(self, builder: Builder) -> None:
|
59
|
+
"""Register a stratification and observation.
|
60
|
+
|
61
|
+
Notes
|
62
|
+
-----
|
63
|
+
While it's typical for all stratification registrations to be encapsulated
|
64
|
+
in a single class (i.e. the
|
65
|
+
:class:ResultsStratifier <vivarium_public_health.results.stratification.ResultsStratifier),
|
66
|
+
this observer registers an additional one. While it could be registered
|
67
|
+
in the ``ResultsStratifier`` as well, it is specific to this observer and
|
68
|
+
so it is registered here while we have easy access to the required categories
|
69
|
+
and value names.
|
70
|
+
"""
|
71
|
+
builder.results.register_stratification(
|
72
|
+
f"{self.intervention}",
|
73
|
+
list(self.categories.keys()),
|
74
|
+
requires_values=[self.coverage_pipeline_name],
|
75
|
+
)
|
76
|
+
self.register_adding_observation(
|
77
|
+
builder=builder,
|
78
|
+
name=f"person_time_{self.intervention}",
|
79
|
+
pop_filter=f'alive == "alive" and tracked==True',
|
80
|
+
when="time_step__prepare",
|
81
|
+
requires_columns=["alive"],
|
82
|
+
requires_values=[self.coverage_pipeline_name],
|
83
|
+
additional_stratifications=self.configuration.include + [self.intervention],
|
84
|
+
excluded_stratifications=self.configuration.exclude,
|
85
|
+
aggregator=self.aggregate_intervention_category_person_time,
|
86
|
+
)
|
87
|
+
|
88
|
+
###############
|
89
|
+
# Aggregators #
|
90
|
+
###############
|
91
|
+
|
92
|
+
def aggregate_intervention_category_person_time(self, x: pd.DataFrame) -> float:
|
93
|
+
"""Aggregate the person time for this time step."""
|
94
|
+
return len(x) * to_years(self.step_size())
|
95
|
+
|
96
|
+
##############################
|
97
|
+
# Results formatting methods #
|
98
|
+
##############################
|
99
|
+
|
100
|
+
def format(self, measure: str, results: pd.DataFrame) -> pd.DataFrame:
|
101
|
+
"""Rename the appropriate column to 'sub_entity'.
|
102
|
+
|
103
|
+
The primary thing this method does is rename the risk column
|
104
|
+
to 'sub_entity'. We do this here instead of the 'get_sub_entity_column'
|
105
|
+
method simply because we do not want the risk column at all. If we keep
|
106
|
+
it here and then return it as the sub-entity column later, the final
|
107
|
+
results would have both.
|
108
|
+
|
109
|
+
Parameters
|
110
|
+
----------
|
111
|
+
measure
|
112
|
+
The measure.
|
113
|
+
results
|
114
|
+
The results to format.
|
115
|
+
|
116
|
+
Returns
|
117
|
+
-------
|
118
|
+
The formatted results.
|
119
|
+
"""
|
120
|
+
results = results.reset_index()
|
121
|
+
results.rename(columns={self.intervention: COLUMNS.SUB_ENTITY}, inplace=True)
|
122
|
+
return results
|
123
|
+
|
124
|
+
def get_measure_column(self, measure: str, results: pd.DataFrame) -> pd.Series:
|
125
|
+
"""Get the 'measure' column values."""
|
126
|
+
return pd.Series("person_time", index=results.index)
|
127
|
+
|
128
|
+
def get_entity_type_column(self, measure: str, results: pd.DataFrame) -> pd.Series:
|
129
|
+
"""Get the 'entity_type' column values."""
|
130
|
+
return pd.Series("rei", index=results.index)
|
131
|
+
|
132
|
+
def get_entity_column(self, measure: str, results: pd.DataFrame) -> pd.Series:
|
133
|
+
"""Get the 'entity' column values."""
|
134
|
+
return pd.Series(self.intervention, index=results.index)
|
135
|
+
|
136
|
+
def get_sub_entity_column(self, measure: str, results: pd.DataFrame) -> pd.Series:
|
137
|
+
"""Get the 'sub_entity' column values."""
|
138
|
+
# The sub-entity col was created in the 'format' method
|
139
|
+
return results[COLUMNS.SUB_ENTITY]
|
@@ -7,30 +7,15 @@ This module contains tools for modeling categorical and continuous risk
|
|
7
7
|
exposure.
|
8
8
|
|
9
9
|
"""
|
10
|
+
import warnings
|
11
|
+
from typing import NamedTuple
|
10
12
|
|
11
|
-
from typing import Any
|
12
|
-
|
13
|
-
import pandas as pd
|
14
|
-
from vivarium import Component
|
15
13
|
from vivarium.framework.engine import Builder
|
16
|
-
from vivarium.framework.event import Event
|
17
|
-
from vivarium.framework.population import SimulantData
|
18
|
-
from vivarium.framework.randomness import RandomnessStream
|
19
|
-
from vivarium.framework.resource import Resource
|
20
|
-
from vivarium.framework.values import Pipeline
|
21
14
|
|
22
|
-
from vivarium_public_health.
|
23
|
-
from vivarium_public_health.risks.distributions import (
|
24
|
-
ContinuousDistribution,
|
25
|
-
DichotomousDistribution,
|
26
|
-
EnsembleDistribution,
|
27
|
-
PolytomousDistribution,
|
28
|
-
RiskExposureDistribution,
|
29
|
-
)
|
30
|
-
from vivarium_public_health.utilities import EntityString, get_lookup_columns
|
15
|
+
from vivarium_public_health.exposure import Exposure
|
31
16
|
|
32
17
|
|
33
|
-
class Risk(
|
18
|
+
class Risk(Exposure):
|
34
19
|
"""A model for a risk factor defined by either a continuous or a categorical value.
|
35
20
|
|
36
21
|
For example,
|
@@ -89,217 +74,52 @@ class Risk(Component):
|
|
89
74
|
|
90
75
|
"""
|
91
76
|
|
92
|
-
exposure_distributions = {
|
93
|
-
"dichotomous": DichotomousDistribution,
|
94
|
-
"ordered_polytomous": PolytomousDistribution,
|
95
|
-
"unordered_polytomous": PolytomousDistribution,
|
96
|
-
"normal": ContinuousDistribution,
|
97
|
-
"lognormal": ContinuousDistribution,
|
98
|
-
"ensemble": EnsembleDistribution,
|
99
|
-
}
|
100
|
-
|
101
|
-
##############
|
102
|
-
# Properties #
|
103
|
-
##############
|
104
|
-
|
105
77
|
@property
|
106
|
-
def
|
107
|
-
|
78
|
+
def risk(self) -> str:
|
79
|
+
warnings.warn(
|
80
|
+
"The 'risk' attribute is deprecated. Use 'entity' instead.",
|
81
|
+
DeprecationWarning,
|
82
|
+
stacklevel=2,
|
83
|
+
)
|
84
|
+
return self.entity
|
85
|
+
|
86
|
+
@risk.setter
|
87
|
+
def risk(self, value: str) -> None:
|
88
|
+
warnings.warn(
|
89
|
+
"The 'risk' attribute is deprecated. Use 'entity' instead.",
|
90
|
+
DeprecationWarning,
|
91
|
+
stacklevel=2,
|
92
|
+
)
|
93
|
+
self.entity = value
|
108
94
|
|
109
95
|
@property
|
110
|
-
def
|
111
|
-
|
112
|
-
|
113
|
-
"data_sources": {
|
114
|
-
"exposure": f"{self.risk}.exposure",
|
115
|
-
"ensemble_distribution_weights": f"{self.risk}.exposure_distribution_weights",
|
116
|
-
"exposure_standard_deviation": f"{self.risk}.exposure_standard_deviation",
|
117
|
-
},
|
118
|
-
"distribution_type": f"{self.risk}.distribution",
|
119
|
-
# rebinned_exposed only used for DichotomousDistribution
|
120
|
-
"rebinned_exposed": [],
|
121
|
-
"category_thresholds": [],
|
122
|
-
}
|
123
|
-
}
|
96
|
+
def exposure_type(self) -> str:
|
97
|
+
"""The measure of the risk exposure."""
|
98
|
+
return "exposure"
|
124
99
|
|
125
100
|
@property
|
126
|
-
def
|
127
|
-
|
128
|
-
if self.create_exposure_column:
|
129
|
-
columns_to_create.append(self.exposure_column_name)
|
130
|
-
return columns_to_create
|
101
|
+
def dichotomous_exposure_category_names(self) -> NamedTuple:
|
102
|
+
"""The name of the exposed category for this intervention."""
|
131
103
|
|
132
|
-
|
133
|
-
|
134
|
-
|
104
|
+
class __Categories(NamedTuple):
|
105
|
+
exposed: str = "exposed"
|
106
|
+
unexposed: str = "unexposed"
|
107
|
+
|
108
|
+
categories = __Categories()
|
109
|
+
return categories
|
135
110
|
|
136
111
|
#####################
|
137
112
|
# Lifecycle methods #
|
138
113
|
#####################
|
139
114
|
|
140
|
-
def __init__(self, risk: str):
|
141
|
-
"""
|
142
|
-
|
143
|
-
Parameters
|
144
|
-
----------
|
145
|
-
risk
|
146
|
-
the type and name of a risk, specified as "type.name". Type is singular.
|
147
|
-
"""
|
148
|
-
super().__init__()
|
149
|
-
self.risk = EntityString(risk)
|
150
|
-
self.distribution_type = None
|
151
|
-
|
152
|
-
self.randomness_stream_name = f"initial_{self.risk.name}_propensity"
|
153
|
-
self.propensity_column_name = f"{self.risk.name}_propensity"
|
154
|
-
self.propensity_pipeline_name = f"{self.risk.name}.propensity"
|
155
|
-
self.exposure_pipeline_name = f"{self.risk.name}.exposure"
|
156
|
-
self.exposure_column_name = f"{self.risk.name}_exposure"
|
157
|
-
|
158
|
-
#################
|
159
|
-
# Setup methods #
|
160
|
-
#################
|
161
|
-
|
162
|
-
def build_all_lookup_tables(self, builder: "Builder") -> None:
|
163
|
-
# All lookup tables are built in the exposure distribution
|
164
|
-
pass
|
165
|
-
|
166
|
-
# noinspection PyAttributeOutsideInit
|
167
115
|
def setup(self, builder: Builder) -> None:
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
self.randomness = self.get_randomness_stream(builder)
|
172
|
-
self.propensity = self.get_propensity_pipeline(builder)
|
173
|
-
self.exposure = self.get_exposure_pipeline(builder)
|
174
|
-
|
175
|
-
# We want to set this to True iff there is a non-loglinear risk effect
|
116
|
+
super().setup(builder)
|
117
|
+
# We want to set this to True if there is a non-loglinear risk effect
|
176
118
|
# on this risk instance
|
177
119
|
self.create_exposure_column = bool(
|
178
120
|
[
|
179
121
|
component
|
180
122
|
for component in builder.components.list_components()
|
181
|
-
if component.startswith(f"non_log_linear_risk_effect.{self.
|
123
|
+
if component.startswith(f"non_log_linear_risk_effect.{self.entity.name}_on_")
|
182
124
|
]
|
183
125
|
)
|
184
|
-
|
185
|
-
def get_distribution_type(self, builder: Builder) -> str:
|
186
|
-
"""Get the distribution type for the risk from the configuration.
|
187
|
-
|
188
|
-
If the configured distribution type is not one of the supported types,
|
189
|
-
it is assumed to be a data source and the data is retrieved using the
|
190
|
-
get_data method.
|
191
|
-
|
192
|
-
Parameters
|
193
|
-
----------
|
194
|
-
builder
|
195
|
-
The builder object.
|
196
|
-
|
197
|
-
Returns
|
198
|
-
-------
|
199
|
-
The distribution type.
|
200
|
-
"""
|
201
|
-
if self.configuration is None:
|
202
|
-
self.configuration = self.get_configuration(builder)
|
203
|
-
|
204
|
-
distribution_type = self.configuration["distribution_type"]
|
205
|
-
if distribution_type not in self.exposure_distributions.keys():
|
206
|
-
# todo deal with incorrect typing
|
207
|
-
distribution_type = self.get_data(builder, distribution_type)
|
208
|
-
|
209
|
-
if self.configuration["rebinned_exposed"]:
|
210
|
-
if distribution_type != "dichotomous" or "polytomous" not in distribution_type:
|
211
|
-
raise ValueError(
|
212
|
-
f"Unsupported risk distribution type '{distribution_type}' "
|
213
|
-
f"for {self.name}. Rebinned exposed categories are only "
|
214
|
-
"supported for dichotomous and polytomous distributions."
|
215
|
-
)
|
216
|
-
distribution_type = "dichotomous"
|
217
|
-
return distribution_type
|
218
|
-
|
219
|
-
def get_exposure_distribution(self, builder: Builder) -> RiskExposureDistribution:
|
220
|
-
"""Creates and sets up the exposure distribution component for the Risk
|
221
|
-
based on its distribution type.
|
222
|
-
|
223
|
-
Parameters
|
224
|
-
----------
|
225
|
-
builder
|
226
|
-
The builder object.
|
227
|
-
|
228
|
-
Returns
|
229
|
-
-------
|
230
|
-
The exposure distribution.
|
231
|
-
|
232
|
-
Raises
|
233
|
-
------
|
234
|
-
NotImplementedError
|
235
|
-
If the distribution type is not supported.
|
236
|
-
"""
|
237
|
-
try:
|
238
|
-
exposure_distribution = self.exposure_distributions[self.distribution_type](
|
239
|
-
self.risk, self.distribution_type
|
240
|
-
)
|
241
|
-
except KeyError:
|
242
|
-
raise NotImplementedError(
|
243
|
-
f"Distribution type {self.distribution_type} is not supported."
|
244
|
-
)
|
245
|
-
|
246
|
-
exposure_distribution.setup_component(builder)
|
247
|
-
return exposure_distribution
|
248
|
-
|
249
|
-
def get_randomness_stream(self, builder: Builder) -> RandomnessStream:
|
250
|
-
return builder.randomness.get_stream(self.randomness_stream_name, component=self)
|
251
|
-
|
252
|
-
def get_propensity_pipeline(self, builder: Builder) -> Pipeline:
|
253
|
-
return builder.value.register_value_producer(
|
254
|
-
self.propensity_pipeline_name,
|
255
|
-
source=lambda index: (
|
256
|
-
self.population_view.subview([self.propensity_column_name])
|
257
|
-
.get(index)
|
258
|
-
.squeeze(axis=1)
|
259
|
-
),
|
260
|
-
component=self,
|
261
|
-
required_resources=[self.propensity_column_name],
|
262
|
-
)
|
263
|
-
|
264
|
-
def get_exposure_pipeline(self, builder: Builder) -> Pipeline:
|
265
|
-
required_columns = get_lookup_columns(
|
266
|
-
self.exposure_distribution.lookup_tables.values()
|
267
|
-
)
|
268
|
-
return builder.value.register_value_producer(
|
269
|
-
self.exposure_pipeline_name,
|
270
|
-
source=self.get_current_exposure,
|
271
|
-
component=self,
|
272
|
-
required_resources=required_columns
|
273
|
-
+ [
|
274
|
-
self.propensity,
|
275
|
-
self.exposure_distribution.exposure_parameters,
|
276
|
-
],
|
277
|
-
preferred_post_processor=get_exposure_post_processor(builder, self.name),
|
278
|
-
)
|
279
|
-
|
280
|
-
########################
|
281
|
-
# Event-driven methods #
|
282
|
-
########################
|
283
|
-
|
284
|
-
def on_initialize_simulants(self, pop_data: SimulantData) -> None:
|
285
|
-
propensity = pd.Series(
|
286
|
-
self.randomness.get_draw(pop_data.index), name=self.propensity_column_name
|
287
|
-
)
|
288
|
-
self.population_view.update(propensity)
|
289
|
-
self.update_exposure_column(pop_data.index)
|
290
|
-
|
291
|
-
def on_time_step_prepare(self, event: Event) -> None:
|
292
|
-
self.update_exposure_column(event.index)
|
293
|
-
|
294
|
-
def update_exposure_column(self, index: pd.Index) -> None:
|
295
|
-
if self.create_exposure_column:
|
296
|
-
exposure = pd.Series(self.exposure(index), name=self.exposure_column_name)
|
297
|
-
self.population_view.update(exposure)
|
298
|
-
|
299
|
-
##################################
|
300
|
-
# Pipeline sources and modifiers #
|
301
|
-
##################################
|
302
|
-
|
303
|
-
def get_current_exposure(self, index: pd.Index) -> pd.Series:
|
304
|
-
propensity = self.propensity(index)
|
305
|
-
return pd.Series(self.exposure_distribution.ppf(propensity), index=index)
|