vivarium-public-health 4.2.5__py3-none-any.whl → 4.3.0__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/{risks → exposure}/distributions.py +102 -38
- 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 +16 -215
- vivarium_public_health/risks/effect.py +18 -323
- vivarium_public_health/risks/implementations/low_birth_weight_and_short_gestation.py +16 -11
- vivarium_public_health/treatment/__init__.py +1 -0
- vivarium_public_health/treatment/intervention.py +85 -0
- {vivarium_public_health-4.2.5.dist-info → vivarium_public_health-4.3.0.dist-info}/METADATA +2 -2
- {vivarium_public_health-4.2.5.dist-info → vivarium_public_health-4.3.0.dist-info}/RECORD +19 -14
- {vivarium_public_health-4.2.5.dist-info → vivarium_public_health-4.3.0.dist-info}/WHEEL +0 -0
- {vivarium_public_health-4.2.5.dist-info → vivarium_public_health-4.3.0.dist-info}/licenses/LICENSE.txt +0 -0
- {vivarium_public_health-4.2.5.dist-info → vivarium_public_health-4.3.0.dist-info}/top_level.txt +0 -0
@@ -3,339 +3,32 @@
|
|
3
3
|
Risk Effect Models
|
4
4
|
==================
|
5
5
|
|
6
|
-
This module contains tools for modeling the relationship between risk
|
7
|
-
exposure models and disease models.
|
8
|
-
|
9
6
|
"""
|
7
|
+
|
10
8
|
from collections.abc import Callable
|
11
|
-
from importlib import import_module
|
12
9
|
from typing import Any
|
13
10
|
|
14
11
|
import numpy as np
|
15
12
|
import pandas as pd
|
16
13
|
import scipy
|
17
|
-
from layered_config_tree import ConfigurationError
|
18
|
-
from vivarium import Component
|
19
14
|
from vivarium.framework.engine import Builder
|
20
|
-
from vivarium.framework.values import Pipeline
|
21
15
|
|
22
|
-
from vivarium_public_health.
|
23
|
-
from vivarium_public_health.
|
24
|
-
|
25
|
-
pivot_categorical,
|
26
|
-
)
|
27
|
-
from vivarium_public_health.risks.distributions import MissingDataError
|
28
|
-
from vivarium_public_health.utilities import EntityString, TargetString, get_lookup_columns
|
16
|
+
from vivarium_public_health.exposure import ExposureEffect
|
17
|
+
from vivarium_public_health.exposure.distributions import MissingDataError
|
18
|
+
from vivarium_public_health.utilities import EntityString, TargetString
|
29
19
|
|
30
20
|
|
31
|
-
class RiskEffect(
|
21
|
+
class RiskEffect(ExposureEffect):
|
32
22
|
"""A component to model the effect of a risk factor on an affected entity's target rate.
|
33
23
|
|
34
24
|
This component can source data either from builder.data or from parameters
|
35
25
|
supplied in the configuration.
|
36
26
|
|
37
|
-
For a risk named 'risk' that affects 'affected_risk' and 'affected_cause',
|
38
|
-
the configuration would look like:
|
39
|
-
|
40
|
-
.. code-block:: yaml
|
41
|
-
|
42
|
-
configuration:
|
43
|
-
risk_effect.risk_name_on_affected_target:
|
44
|
-
exposure_parameters: 2
|
45
|
-
incidence_rate: 10
|
46
|
-
|
47
27
|
"""
|
48
28
|
|
49
|
-
|
50
|
-
# Properties #
|
51
|
-
##############
|
52
|
-
|
53
|
-
@property
|
54
|
-
def name(self) -> str:
|
55
|
-
return self.get_name(self.risk, self.target)
|
56
|
-
|
57
|
-
@staticmethod
|
58
|
-
def get_name(risk: EntityString, target: TargetString) -> str:
|
29
|
+
def get_name(self, risk: EntityString, target: TargetString) -> str:
|
59
30
|
return f"risk_effect.{risk.name}_on_{target}"
|
60
31
|
|
61
|
-
@property
|
62
|
-
def configuration_defaults(self) -> dict[str, Any]:
|
63
|
-
"""Default values for any configurations managed by this component."""
|
64
|
-
return {
|
65
|
-
self.name: {
|
66
|
-
"data_sources": {
|
67
|
-
"relative_risk": f"{self.risk}.relative_risk",
|
68
|
-
"population_attributable_fraction": f"{self.risk}.population_attributable_fraction",
|
69
|
-
},
|
70
|
-
"data_source_parameters": {
|
71
|
-
"relative_risk": {},
|
72
|
-
},
|
73
|
-
}
|
74
|
-
}
|
75
|
-
|
76
|
-
@property
|
77
|
-
def is_exposure_categorical(self) -> bool:
|
78
|
-
return self._exposure_distribution_type in [
|
79
|
-
"dichotomous",
|
80
|
-
"ordered_polytomous",
|
81
|
-
"unordered_polytomous",
|
82
|
-
]
|
83
|
-
|
84
|
-
#####################
|
85
|
-
# Lifecycle methods #
|
86
|
-
#####################
|
87
|
-
|
88
|
-
def __init__(self, risk: str, target: str):
|
89
|
-
"""
|
90
|
-
|
91
|
-
Parameters
|
92
|
-
----------
|
93
|
-
risk
|
94
|
-
Type and name of risk factor, supplied in the form
|
95
|
-
"risk_type.risk_name" where risk_type should be singular (e.g.,
|
96
|
-
risk_factor instead of risk_factors).
|
97
|
-
target
|
98
|
-
Type, name, and target rate of entity to be affected by risk factor,
|
99
|
-
supplied in the form "entity_type.entity_name.measure"
|
100
|
-
where entity_type should be singular (e.g., cause instead of causes).
|
101
|
-
"""
|
102
|
-
super().__init__()
|
103
|
-
self.risk = EntityString(risk)
|
104
|
-
self.target = TargetString(target)
|
105
|
-
|
106
|
-
self._exposure_distribution_type = None
|
107
|
-
|
108
|
-
self.exposure_pipeline_name = f"{self.risk.name}.exposure"
|
109
|
-
self.target_pipeline_name = f"{self.target.name}.{self.target.measure}"
|
110
|
-
self.target_paf_pipeline_name = f"{self.target_pipeline_name}.paf"
|
111
|
-
|
112
|
-
# noinspection PyAttributeOutsideInit
|
113
|
-
def setup(self, builder: Builder) -> None:
|
114
|
-
self.exposure = self.get_risk_exposure(builder)
|
115
|
-
|
116
|
-
self._relative_risk_source = self.get_relative_risk_source(builder)
|
117
|
-
self.relative_risk = self.get_relative_risk_pipeline(builder)
|
118
|
-
|
119
|
-
self.register_target_modifier(builder)
|
120
|
-
self.register_paf_modifier(builder)
|
121
|
-
|
122
|
-
#################
|
123
|
-
# Setup methods #
|
124
|
-
#################
|
125
|
-
|
126
|
-
def build_all_lookup_tables(self, builder: Builder) -> None:
|
127
|
-
self._exposure_distribution_type = self.get_distribution_type(builder)
|
128
|
-
|
129
|
-
rr_data = self.load_relative_risk(builder)
|
130
|
-
rr_value_cols = None
|
131
|
-
if self.is_exposure_categorical:
|
132
|
-
rr_data, rr_value_cols = self.process_categorical_data(builder, rr_data)
|
133
|
-
self.lookup_tables["relative_risk"] = self.build_lookup_table(
|
134
|
-
builder, rr_data, rr_value_cols
|
135
|
-
)
|
136
|
-
|
137
|
-
paf_data = self.get_filtered_data(
|
138
|
-
builder, self.configuration.data_sources.population_attributable_fraction
|
139
|
-
)
|
140
|
-
self.lookup_tables["population_attributable_fraction"] = self.build_lookup_table(
|
141
|
-
builder, paf_data
|
142
|
-
)
|
143
|
-
|
144
|
-
def get_distribution_type(self, builder: Builder) -> str:
|
145
|
-
"""Get the distribution type for the risk from the configuration."""
|
146
|
-
risk_exposure_component = self._get_risk_exposure_class(builder)
|
147
|
-
if risk_exposure_component.distribution_type:
|
148
|
-
return risk_exposure_component.distribution_type
|
149
|
-
return risk_exposure_component.get_distribution_type(builder)
|
150
|
-
|
151
|
-
def load_relative_risk(
|
152
|
-
self,
|
153
|
-
builder: Builder,
|
154
|
-
configuration=None,
|
155
|
-
) -> str | float | pd.DataFrame:
|
156
|
-
if configuration is None:
|
157
|
-
configuration = self.configuration
|
158
|
-
|
159
|
-
rr_source = configuration.data_sources.relative_risk
|
160
|
-
rr_dist_parameters = configuration.data_source_parameters.relative_risk.to_dict()
|
161
|
-
|
162
|
-
if isinstance(rr_source, str):
|
163
|
-
try:
|
164
|
-
distribution = getattr(import_module("scipy.stats"), rr_source)
|
165
|
-
rng = np.random.default_rng(builder.randomness.get_seed(self.name))
|
166
|
-
rr_data = distribution(**rr_dist_parameters).ppf(rng.random())
|
167
|
-
except AttributeError:
|
168
|
-
rr_data = self.get_filtered_data(builder, rr_source)
|
169
|
-
except TypeError:
|
170
|
-
raise ConfigurationError(
|
171
|
-
f"Parameters {rr_dist_parameters} are not valid for distribution {rr_source}."
|
172
|
-
)
|
173
|
-
else:
|
174
|
-
rr_data = self.get_filtered_data(builder, rr_source)
|
175
|
-
return rr_data
|
176
|
-
|
177
|
-
def get_filtered_data(
|
178
|
-
self, builder: "Builder", data_source: str | float | pd.DataFrame
|
179
|
-
) -> float | pd.DataFrame:
|
180
|
-
data = super().get_data(builder, data_source)
|
181
|
-
|
182
|
-
if isinstance(data, pd.DataFrame):
|
183
|
-
# filter data to only include the target entity and measure
|
184
|
-
correct_target_mask = True
|
185
|
-
columns_to_drop = []
|
186
|
-
if "affected_entity" in data.columns:
|
187
|
-
correct_target_mask &= data["affected_entity"] == self.target.name
|
188
|
-
columns_to_drop.append("affected_entity")
|
189
|
-
if "affected_measure" in data.columns:
|
190
|
-
correct_target_mask &= data["affected_measure"] == self.target.measure
|
191
|
-
columns_to_drop.append("affected_measure")
|
192
|
-
data = data[correct_target_mask].drop(columns=columns_to_drop)
|
193
|
-
return data
|
194
|
-
|
195
|
-
def process_categorical_data(
|
196
|
-
self, builder: Builder, rr_data: str | float | pd.DataFrame
|
197
|
-
) -> tuple[str | float | pd.DataFrame, list[str]]:
|
198
|
-
if not isinstance(rr_data, pd.DataFrame):
|
199
|
-
cat1 = builder.data.load("population.demographic_dimensions")
|
200
|
-
cat1["parameter"] = "cat1"
|
201
|
-
cat1["value"] = rr_data
|
202
|
-
cat2 = cat1.copy()
|
203
|
-
cat2["parameter"] = "cat2"
|
204
|
-
cat2["value"] = 1
|
205
|
-
rr_data = pd.concat([cat1, cat2], ignore_index=True)
|
206
|
-
if "parameter" in rr_data.index.names:
|
207
|
-
rr_data = rr_data.reset_index("parameter")
|
208
|
-
|
209
|
-
rr_value_cols = list(rr_data["parameter"].unique())
|
210
|
-
rr_data = pivot_categorical(builder, self.risk, rr_data, "parameter")
|
211
|
-
return rr_data, rr_value_cols
|
212
|
-
|
213
|
-
# todo currently this isn't being called. we need to properly set rrs if
|
214
|
-
# the exposure has been rebinned
|
215
|
-
def rebin_relative_risk_data(
|
216
|
-
self, builder, relative_risk_data: pd.DataFrame
|
217
|
-
) -> pd.DataFrame:
|
218
|
-
"""Rebin relative risk data.
|
219
|
-
|
220
|
-
When the polytomous risk is rebinned, matching relative risk needs to be rebinned.
|
221
|
-
After rebinning, rr for both exposed and unexposed categories should be the weighted sum of relative risk
|
222
|
-
of the component categories where weights are relative proportions of exposure of those categories.
|
223
|
-
For example, if cat1, cat2, cat3 are exposed categories and cat4 is unexposed with exposure [0.1,0.2,0.3,0.4],
|
224
|
-
for the matching rr = [rr1, rr2, rr3, 1], rebinned rr for the rebinned cat1 should be:
|
225
|
-
(0.1 *rr1 + 0.2 * rr2 + 0.3* rr3) / (0.1+0.2+0.3)
|
226
|
-
"""
|
227
|
-
if not self.risk in builder.configuration.to_dict():
|
228
|
-
return relative_risk_data
|
229
|
-
|
230
|
-
rebin_exposed_categories = set(builder.configuration[self.risk]["rebinned_exposed"])
|
231
|
-
|
232
|
-
if rebin_exposed_categories:
|
233
|
-
# todo make sure this works
|
234
|
-
exposure_data = load_exposure_data(builder, self.risk)
|
235
|
-
relative_risk_data = self._rebin_relative_risk_data(
|
236
|
-
relative_risk_data, exposure_data, rebin_exposed_categories
|
237
|
-
)
|
238
|
-
|
239
|
-
return relative_risk_data
|
240
|
-
|
241
|
-
def _rebin_relative_risk_data(
|
242
|
-
self,
|
243
|
-
relative_risk_data: pd.DataFrame,
|
244
|
-
exposure_data: pd.DataFrame,
|
245
|
-
rebin_exposed_categories: set,
|
246
|
-
) -> pd.DataFrame:
|
247
|
-
cols = list(exposure_data.columns.difference(["value"]))
|
248
|
-
|
249
|
-
relative_risk_data = relative_risk_data.merge(exposure_data, on=cols)
|
250
|
-
relative_risk_data["value_x"] = relative_risk_data.value_x.multiply(
|
251
|
-
relative_risk_data.value_y
|
252
|
-
)
|
253
|
-
relative_risk_data.parameter = relative_risk_data["parameter"].map(
|
254
|
-
lambda p: "cat1" if p in rebin_exposed_categories else "cat2"
|
255
|
-
)
|
256
|
-
relative_risk_data = relative_risk_data.groupby(cols).sum().reset_index()
|
257
|
-
relative_risk_data["value"] = relative_risk_data.value_x.divide(
|
258
|
-
relative_risk_data.value_y
|
259
|
-
).fillna(0)
|
260
|
-
return relative_risk_data.drop(columns=["value_x", "value_y"])
|
261
|
-
|
262
|
-
def get_risk_exposure(self, builder: Builder) -> Callable[[pd.Index], pd.Series]:
|
263
|
-
return builder.value.get_value(self.exposure_pipeline_name)
|
264
|
-
|
265
|
-
def adjust_target(self, index: pd.Index, target: pd.Series) -> pd.Series:
|
266
|
-
relative_risk = self.relative_risk(index)
|
267
|
-
return target * relative_risk
|
268
|
-
|
269
|
-
def get_relative_risk_source(self, builder: Builder) -> Callable[[pd.Index], pd.Series]:
|
270
|
-
|
271
|
-
if not self.is_exposure_categorical:
|
272
|
-
tmred = builder.data.load(f"{self.risk}.tmred")
|
273
|
-
tmrel = 0.5 * (tmred["min"] + tmred["max"])
|
274
|
-
scale = builder.data.load(f"{self.risk}.relative_risk_scalar")
|
275
|
-
|
276
|
-
def generate_relative_risk(index: pd.Index) -> pd.Series:
|
277
|
-
rr = self.lookup_tables["relative_risk"](index)
|
278
|
-
exposure = self.exposure(index)
|
279
|
-
relative_risk = np.maximum(rr.values ** ((exposure - tmrel) / scale), 1)
|
280
|
-
return relative_risk
|
281
|
-
|
282
|
-
else:
|
283
|
-
index_columns = ["index", self.risk.name]
|
284
|
-
|
285
|
-
def generate_relative_risk(index: pd.Index) -> pd.Series:
|
286
|
-
rr = self.lookup_tables["relative_risk"](index)
|
287
|
-
exposure = self.exposure(index).reset_index()
|
288
|
-
exposure.columns = index_columns
|
289
|
-
exposure = exposure.set_index(index_columns)
|
290
|
-
|
291
|
-
relative_risk = rr.stack().reset_index()
|
292
|
-
relative_risk.columns = index_columns + ["value"]
|
293
|
-
relative_risk = relative_risk.set_index(index_columns)
|
294
|
-
|
295
|
-
effect = relative_risk.loc[exposure.index, "value"].droplevel(self.risk.name)
|
296
|
-
return effect
|
297
|
-
|
298
|
-
return generate_relative_risk
|
299
|
-
|
300
|
-
def get_relative_risk_pipeline(self, builder: Builder) -> Pipeline:
|
301
|
-
return builder.value.register_value_producer(
|
302
|
-
f"{self.risk.name}_on_{self.target.name}.relative_risk",
|
303
|
-
self._relative_risk_source,
|
304
|
-
component=self,
|
305
|
-
required_resources=[self.exposure],
|
306
|
-
)
|
307
|
-
|
308
|
-
def register_target_modifier(self, builder: Builder) -> None:
|
309
|
-
builder.value.register_value_modifier(
|
310
|
-
self.target_pipeline_name,
|
311
|
-
modifier=self.adjust_target,
|
312
|
-
component=self,
|
313
|
-
required_resources=[self.relative_risk],
|
314
|
-
)
|
315
|
-
|
316
|
-
def register_paf_modifier(self, builder: Builder) -> None:
|
317
|
-
required_columns = get_lookup_columns(
|
318
|
-
[self.lookup_tables["population_attributable_fraction"]]
|
319
|
-
)
|
320
|
-
builder.value.register_value_modifier(
|
321
|
-
self.target_paf_pipeline_name,
|
322
|
-
modifier=self.lookup_tables["population_attributable_fraction"],
|
323
|
-
component=self,
|
324
|
-
required_resources=required_columns,
|
325
|
-
)
|
326
|
-
|
327
|
-
##################
|
328
|
-
# Helper methods #
|
329
|
-
##################
|
330
|
-
|
331
|
-
def _get_risk_exposure_class(self, builder: Builder) -> Risk:
|
332
|
-
risk_exposure_component = builder.components.get_component(self.risk)
|
333
|
-
if not isinstance(risk_exposure_component, Risk):
|
334
|
-
raise ValueError(
|
335
|
-
f"Risk effect model {self.name} requires a Risk component named {self.risk}"
|
336
|
-
)
|
337
|
-
return risk_exposure_component
|
338
|
-
|
339
32
|
|
340
33
|
class NonLogLinearRiskEffect(RiskEffect):
|
341
34
|
"""A component to model the exposure-parametrized effect of a risk factor.
|
@@ -366,15 +59,15 @@ class NonLogLinearRiskEffect(RiskEffect):
|
|
366
59
|
return {
|
367
60
|
self.name: {
|
368
61
|
"data_sources": {
|
369
|
-
"relative_risk": f"{self.
|
370
|
-
"population_attributable_fraction": f"{self.
|
62
|
+
"relative_risk": f"{self.entity}.relative_risk",
|
63
|
+
"population_attributable_fraction": f"{self.entity}.population_attributable_fraction",
|
371
64
|
},
|
372
65
|
}
|
373
66
|
}
|
374
67
|
|
375
68
|
@property
|
376
69
|
def columns_required(self) -> list[str]:
|
377
|
-
return [f"{self.
|
70
|
+
return [f"{self.entity.name}_exposure"]
|
378
71
|
|
379
72
|
#################
|
380
73
|
# Setup methods #
|
@@ -414,8 +107,8 @@ class NonLogLinearRiskEffect(RiskEffect):
|
|
414
107
|
.reset_index()
|
415
108
|
)
|
416
109
|
rr_data = rr_data.drop("parameter", axis=1)
|
417
|
-
rr_data[f"{self.
|
418
|
-
rr_data[f"{self.
|
110
|
+
rr_data[f"{self.entity.name}_exposure_start"] = rr_data["left_exposure"]
|
111
|
+
rr_data[f"{self.entity.name}_exposure_end"] = rr_data["right_exposure"]
|
419
112
|
# build lookup table
|
420
113
|
rr_value_cols = ["left_exposure", "left_rr", "right_exposure", "right_rr"]
|
421
114
|
self.lookup_tables["relative_risk"] = self.build_lookup_table(
|
@@ -438,17 +131,19 @@ class NonLogLinearRiskEffect(RiskEffect):
|
|
438
131
|
configuration = self.configuration
|
439
132
|
|
440
133
|
# get TMREL
|
441
|
-
tmred = builder.data.load(f"{self.
|
134
|
+
tmred = builder.data.load(f"{self.entity}.tmred")
|
442
135
|
if tmred["distribution"] == "uniform":
|
443
136
|
draw = builder.configuration.input_data.input_draw_number
|
444
137
|
rng = np.random.default_rng(builder.randomness.get_seed(self.name + str(draw)))
|
445
138
|
self.tmrel = rng.uniform(tmred["min"], tmred["max"])
|
446
139
|
elif tmred["distribution"] == "draws": # currently only for iron deficiency
|
447
140
|
raise MissingDataError(
|
448
|
-
f"This data has draw-level TMRELs. You will need to contact the research team that models {self.
|
141
|
+
f"This data has draw-level TMRELs. You will need to contact the research team that models {self.entity.name} to get this data."
|
449
142
|
)
|
450
143
|
else:
|
451
|
-
raise MissingDataError(
|
144
|
+
raise MissingDataError(
|
145
|
+
f"No TMRED found in gbd_mapping for risk {self.entity.name}"
|
146
|
+
)
|
452
147
|
|
453
148
|
# calculate RR at TMREL
|
454
149
|
rr_source = configuration.data_sources.relative_risk
|
@@ -489,7 +184,7 @@ class NonLogLinearRiskEffect(RiskEffect):
|
|
489
184
|
def get_relative_risk_source(self, builder: Builder) -> Callable[[pd.Index], pd.Series]:
|
490
185
|
def generate_relative_risk(index: pd.Index) -> pd.Series:
|
491
186
|
rr_intervals = self.lookup_tables["relative_risk"](index)
|
492
|
-
exposure = self.population_view.get(index)[f"{self.
|
187
|
+
exposure = self.population_view.get(index)[f"{self.entity.name}_exposure"]
|
493
188
|
x1, x2 = (
|
494
189
|
rr_intervals["left_exposure"].values,
|
495
190
|
rr_intervals["right_exposure"].values,
|
@@ -512,7 +207,7 @@ class NonLogLinearRiskEffect(RiskEffect):
|
|
512
207
|
parameter_data_is_numeric = rr_data["parameter"].dtype.kind in "biufc"
|
513
208
|
if not parameter_data_is_numeric:
|
514
209
|
raise ValueError(
|
515
|
-
f"The parameter column in your {self.
|
210
|
+
f"The parameter column in your {self.entity.name} relative risk data must contain numeric data. Its dtype is {rr_data['parameter'].dtype} instead."
|
516
211
|
)
|
517
212
|
|
518
213
|
# and that these RR values are monotonically increasing within each demographic group
|
@@ -22,12 +22,12 @@ from vivarium.framework.population import SimulantData
|
|
22
22
|
from vivarium.framework.resource import Resource
|
23
23
|
from vivarium.framework.values import Pipeline
|
24
24
|
|
25
|
+
from vivarium_public_health.exposure.distributions import PolytomousDistribution
|
25
26
|
from vivarium_public_health.risks import Risk, RiskEffect
|
26
27
|
from vivarium_public_health.risks.data_transformations import (
|
27
28
|
get_exposure_post_processor,
|
28
29
|
pivot_categorical,
|
29
30
|
)
|
30
|
-
from vivarium_public_health.risks.distributions import PolytomousDistribution
|
31
31
|
from vivarium_public_health.utilities import EntityString, get_lookup_columns, to_snake_case
|
32
32
|
|
33
33
|
CATEGORICAL = "categorical"
|
@@ -71,7 +71,10 @@ class LBWSGDistribution(PolytomousDistribution):
|
|
71
71
|
|
72
72
|
if isinstance(birth_exposure_data, pd.DataFrame):
|
73
73
|
birth_exposure_data = pivot_categorical(
|
74
|
-
builder,
|
74
|
+
builder,
|
75
|
+
self.exposure_component.entity,
|
76
|
+
birth_exposure_data,
|
77
|
+
"parameter",
|
75
78
|
)
|
76
79
|
|
77
80
|
self.lookup_tables["birth_exposure"] = self.build_lookup_table(
|
@@ -118,7 +121,9 @@ class LBWSGDistribution(PolytomousDistribution):
|
|
118
121
|
-------
|
119
122
|
The intervals for each category.
|
120
123
|
"""
|
121
|
-
categories: dict[str, str] = builder.data.load(
|
124
|
+
categories: dict[str, str] = builder.data.load(
|
125
|
+
f"{self.exposure_component.entity}.categories"
|
126
|
+
)
|
122
127
|
category_intervals = {
|
123
128
|
axis: {
|
124
129
|
category: self._parse_description(axis, description)
|
@@ -271,7 +276,7 @@ class LBWSGRisk(Risk):
|
|
271
276
|
# Add birth exposure data source
|
272
277
|
configuration_defaults[self.name]["data_sources"][
|
273
278
|
"birth_exposure"
|
274
|
-
] = f"{self.
|
279
|
+
] = f"{self.entity}.birth_exposure"
|
275
280
|
configuration_defaults[self.name]["distribution_type"] = "lbwsg"
|
276
281
|
return configuration_defaults
|
277
282
|
|
@@ -300,7 +305,7 @@ class LBWSGRisk(Risk):
|
|
300
305
|
# Propensity only used on initialization; not being saved to avoid a cycle
|
301
306
|
return None
|
302
307
|
|
303
|
-
def
|
308
|
+
def get_exposure_callable(self, builder: Builder) -> Pipeline | None:
|
304
309
|
# Exposure only used on initialization; not being saved to avoid a cycle
|
305
310
|
return None
|
306
311
|
|
@@ -400,12 +405,12 @@ class LBWSGRiskEffect(RiskEffect):
|
|
400
405
|
LBWSGRisk.get_exposure_column_name(axis) for axis in LBWSGRisk.AXES
|
401
406
|
]
|
402
407
|
self.relative_risk_pipeline_name = (
|
403
|
-
f"effect_of_{self.
|
408
|
+
f"effect_of_{self.entity.name}_on_{self.target.name}.relative_risk"
|
404
409
|
)
|
405
410
|
|
406
411
|
def relative_risk_column_name(self, age_group_id: str) -> str:
|
407
412
|
return (
|
408
|
-
f"effect_of_{self.
|
413
|
+
f"effect_of_{self.entity.name}_on_{age_group_id}_{self.target.name}_relative_risk"
|
409
414
|
)
|
410
415
|
|
411
416
|
# noinspection PyAttributeOutsideInit
|
@@ -426,7 +431,7 @@ class LBWSGRiskEffect(RiskEffect):
|
|
426
431
|
builder, paf_data, paf_value_cols
|
427
432
|
)
|
428
433
|
|
429
|
-
def
|
434
|
+
def get_exposure_callable(self, builder: Builder) -> Callable[[pd.Index], pd.DataFrame]:
|
430
435
|
def exposure(index: pd.Index) -> pd.DataFrame:
|
431
436
|
return self.population_view.subview(self.lbwsg_exposure_column_names).get(index)
|
432
437
|
|
@@ -435,7 +440,7 @@ class LBWSGRiskEffect(RiskEffect):
|
|
435
440
|
def get_population_attributable_fraction_source(
|
436
441
|
self, builder: Builder
|
437
442
|
) -> tuple[pd.DataFrame, list[str]]:
|
438
|
-
paf_key = f"{self.
|
443
|
+
paf_key = f"{self.entity}.population_attributable_fraction"
|
439
444
|
paf_data = builder.data.load(paf_key)
|
440
445
|
return paf_data, builder.data.value_columns()(paf_key)
|
441
446
|
|
@@ -449,7 +454,7 @@ class LBWSGRiskEffect(RiskEffect):
|
|
449
454
|
|
450
455
|
def get_age_intervals(self, builder: Builder) -> dict[str, pd.Interval]:
|
451
456
|
age_bins = builder.data.load("population.age_bins").set_index("age_start")
|
452
|
-
relative_risks = builder.data.load(f"{self.
|
457
|
+
relative_risks = builder.data.load(f"{self.entity}.relative_risk")
|
453
458
|
exposed_age_group_starts = (
|
454
459
|
relative_risks.groupby("age_start")["value"].any().reset_index()["age_start"]
|
455
460
|
)
|
@@ -476,7 +481,7 @@ class LBWSGRiskEffect(RiskEffect):
|
|
476
481
|
}
|
477
482
|
|
478
483
|
# get relative risk data for target
|
479
|
-
interpolators = builder.data.load(f"{self.
|
484
|
+
interpolators = builder.data.load(f"{self.entity}.relative_risk_interpolator")
|
480
485
|
interpolators = (
|
481
486
|
# isolate RRs for target and drop non-neonatal age groups since they have RR == 1.0
|
482
487
|
interpolators[
|
@@ -1,3 +1,4 @@
|
|
1
|
+
from vivarium_public_health.treatment.intervention import Intervention, InterventionEffect
|
1
2
|
from vivarium_public_health.treatment.magic_wand import AbsoluteShift
|
2
3
|
from vivarium_public_health.treatment.scale_up import LinearScaleUp
|
3
4
|
from vivarium_public_health.treatment.therapeutic_inertia import TherapeuticInertia
|
@@ -0,0 +1,85 @@
|
|
1
|
+
from typing import NamedTuple
|
2
|
+
|
3
|
+
from vivarium_public_health.exposure import Exposure
|
4
|
+
from vivarium_public_health.exposure.effect import ExposureEffect
|
5
|
+
from vivarium_public_health.utilities import EntityString, TargetString
|
6
|
+
|
7
|
+
|
8
|
+
class Intervention(Exposure):
|
9
|
+
"""A model for an intervention defined by coverage (access to intervention).
|
10
|
+
|
11
|
+
This component models access to an intervention as a dichotomous exposure where
|
12
|
+
simulants are either covered or uncovered by the intervention.
|
13
|
+
|
14
|
+
For example,
|
15
|
+
|
16
|
+
#. vaccination coverage where simulants are either vaccinated (covered) or
|
17
|
+
unvaccinated (uncovered).
|
18
|
+
#. treatment access where simulants either have access to treatment (covered)
|
19
|
+
or do not have access (uncovered).
|
20
|
+
|
21
|
+
This component can source data either from a key in an Artifact
|
22
|
+
("intervention.intervention_name.coverage") or from parameters
|
23
|
+
supplied in the configuration. If data is derived from the configuration, it
|
24
|
+
must be an integer or float expressing the desired coverage level or a
|
25
|
+
covariate name that is intended to be used as a proxy. For example, for an
|
26
|
+
intervention named "intervention", the configuration could look like this:
|
27
|
+
|
28
|
+
.. code-block:: yaml
|
29
|
+
|
30
|
+
configuration:
|
31
|
+
intervention.intervention_name:
|
32
|
+
coverage: 0.8
|
33
|
+
|
34
|
+
Interventions should be configured with names in the format of
|
35
|
+
"intervention.intervention_name".
|
36
|
+
|
37
|
+
"""
|
38
|
+
|
39
|
+
@property
|
40
|
+
def exposure_type(self) -> str:
|
41
|
+
"""The measure of the intervention access."""
|
42
|
+
return "coverage"
|
43
|
+
|
44
|
+
@property
|
45
|
+
def dichotomous_exposure_category_names(self) -> NamedTuple:
|
46
|
+
"""The name of the covered and uncovered categories for this intervention."""
|
47
|
+
|
48
|
+
class __Categories(NamedTuple):
|
49
|
+
exposed: str = "covered"
|
50
|
+
unexposed: str = "uncovered"
|
51
|
+
|
52
|
+
categories = __Categories()
|
53
|
+
return categories
|
54
|
+
|
55
|
+
|
56
|
+
class InterventionEffect(ExposureEffect):
|
57
|
+
"""A component to model the effect of an intervention on an affected entity's target rate.
|
58
|
+
|
59
|
+
This component models how intervention coverage affects the rate of some target
|
60
|
+
entity (e.g., disease incidence, mortality, disability). The effect is typically
|
61
|
+
protective, reducing the target rate for covered simulants compared to uncovered
|
62
|
+
simulants.
|
63
|
+
|
64
|
+
This component can source data either from builder.data or from parameters
|
65
|
+
supplied in the configuration. The data should specify the relative risk or
|
66
|
+
rate ratio associated with intervention coverage.
|
67
|
+
|
68
|
+
For example, an intervention effect might model how vaccination coverage affects
|
69
|
+
disease incidence, where vaccinated individuals have a lower risk of disease
|
70
|
+
compared to unvaccinated individuals.
|
71
|
+
|
72
|
+
For an exposure named 'exposure_name' that affects 'affected_entity' and 'affected_cause',
|
73
|
+
the configuration would look like:
|
74
|
+
|
75
|
+
.. code-block:: yaml
|
76
|
+
|
77
|
+
configuration:
|
78
|
+
intervention_effect.exposure_name_on_affected_target:
|
79
|
+
exposure_parameters: 2
|
80
|
+
incidence_rate: 10
|
81
|
+
|
82
|
+
"""
|
83
|
+
|
84
|
+
def get_name(self, intervention: EntityString, target: TargetString) -> str:
|
85
|
+
return f"intervention_effect.{intervention.name}_on_{target}"
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: vivarium_public_health
|
3
|
-
Version: 4.
|
3
|
+
Version: 4.3.0
|
4
4
|
Summary: Components for modelling diseases, risks, and interventions with ``vivarium``
|
5
5
|
Home-page: https://github.com/ihmeuw/vivarium_public_health
|
6
6
|
Author: The vivarium developers
|
@@ -26,7 +26,7 @@ Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
|
|
26
26
|
Classifier: Topic :: Scientific/Engineering :: Physics
|
27
27
|
Classifier: Topic :: Software Development :: Libraries
|
28
28
|
License-File: LICENSE.txt
|
29
|
-
Requires-Dist: vivarium_build_utils<
|
29
|
+
Requires-Dist: vivarium_build_utils<3.0.0,>=2.0.1
|
30
30
|
Requires-Dist: vivarium>=3.4.3
|
31
31
|
Requires-Dist: layered_config_tree>=3.2.0
|
32
32
|
Requires-Dist: loguru
|