vivarium-public-health 4.2.6__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.6.dist-info → vivarium_public_health-4.3.0.dist-info}/METADATA +1 -1
- {vivarium_public_health-4.2.6.dist-info → vivarium_public_health-4.3.0.dist-info}/RECORD +19 -14
- {vivarium_public_health-4.2.6.dist-info → vivarium_public_health-4.3.0.dist-info}/WHEEL +0 -0
- {vivarium_public_health-4.2.6.dist-info → vivarium_public_health-4.3.0.dist-info}/licenses/LICENSE.txt +0 -0
- {vivarium_public_health-4.2.6.dist-info → vivarium_public_health-4.3.0.dist-info}/top_level.txt +0 -0
@@ -46,7 +46,13 @@ from vivarium_public_health.risks import (
|
|
46
46
|
Risk,
|
47
47
|
RiskEffect,
|
48
48
|
)
|
49
|
-
from vivarium_public_health.treatment import
|
49
|
+
from vivarium_public_health.treatment import (
|
50
|
+
AbsoluteShift,
|
51
|
+
Intervention,
|
52
|
+
InterventionEffect,
|
53
|
+
LinearScaleUp,
|
54
|
+
TherapeuticInertia,
|
55
|
+
)
|
50
56
|
|
51
57
|
__all__ = [
|
52
58
|
__author__,
|
@@ -1 +1 @@
|
|
1
|
-
__version__ = "4.
|
1
|
+
__version__ = "4.3.0"
|
@@ -1,6 +1,6 @@
|
|
1
1
|
"""
|
2
2
|
=================================
|
3
|
-
|
3
|
+
Exposure Distribution Models
|
4
4
|
=================================
|
5
5
|
|
6
6
|
This module contains tools for modeling several different risk
|
@@ -8,8 +8,12 @@ exposure distributions.
|
|
8
8
|
|
9
9
|
"""
|
10
10
|
|
11
|
+
from __future__ import annotations
|
12
|
+
|
13
|
+
import warnings
|
11
14
|
from abc import ABC, abstractmethod
|
12
15
|
from collections.abc import Callable
|
16
|
+
from typing import TYPE_CHECKING
|
13
17
|
|
14
18
|
import numpy as np
|
15
19
|
import pandas as pd
|
@@ -24,12 +28,15 @@ from vivarium.framework.values import Pipeline, list_combiner, union_post_proces
|
|
24
28
|
from vivarium_public_health.risks.data_transformations import pivot_categorical
|
25
29
|
from vivarium_public_health.utilities import EntityString, get_lookup_columns
|
26
30
|
|
31
|
+
if TYPE_CHECKING:
|
32
|
+
from vivarium_public_health.exposure import Exposure
|
33
|
+
|
27
34
|
|
28
35
|
class MissingDataError(Exception):
|
29
36
|
pass
|
30
37
|
|
31
38
|
|
32
|
-
class
|
39
|
+
class ExposureDistribution(Component, ABC):
|
33
40
|
|
34
41
|
#####################
|
35
42
|
# Lifecycle methods #
|
@@ -37,23 +44,32 @@ class RiskExposureDistribution(Component, ABC):
|
|
37
44
|
|
38
45
|
def __init__(
|
39
46
|
self,
|
40
|
-
|
47
|
+
exposure_component: Exposure,
|
41
48
|
distribution_type: str,
|
42
49
|
exposure_data: int | float | pd.DataFrame | None = None,
|
43
50
|
) -> None:
|
44
51
|
super().__init__()
|
45
|
-
self.
|
52
|
+
self.exposure_component = exposure_component
|
46
53
|
self.distribution_type = distribution_type
|
54
|
+
if (
|
55
|
+
self.distribution_type != "dichotomous"
|
56
|
+
and self.exposure_component.entity.type == "intervention"
|
57
|
+
):
|
58
|
+
raise NotImplementedError(
|
59
|
+
f"Distribution type {self.distribution_type} is not supported for interventions."
|
60
|
+
)
|
47
61
|
self._exposure_data = exposure_data
|
48
62
|
|
49
|
-
self.parameters_pipeline_name =
|
63
|
+
self.parameters_pipeline_name = (
|
64
|
+
f"{self.exposure_component.entity}.exposure_parameters"
|
65
|
+
)
|
50
66
|
|
51
67
|
#################
|
52
68
|
# Setup methods #
|
53
69
|
#################
|
54
70
|
|
55
71
|
def get_configuration(self, builder: "Builder") -> LayeredConfigTree | None:
|
56
|
-
return builder.configuration[self.
|
72
|
+
return builder.configuration[self.exposure_component.entity]
|
57
73
|
|
58
74
|
@abstractmethod
|
59
75
|
def build_all_lookup_tables(self, builder: "Builder") -> None:
|
@@ -62,7 +78,9 @@ class RiskExposureDistribution(Component, ABC):
|
|
62
78
|
def get_exposure_data(self, builder: Builder) -> int | float | pd.DataFrame:
|
63
79
|
if self._exposure_data is not None:
|
64
80
|
return self._exposure_data
|
65
|
-
return self.get_data(
|
81
|
+
return self.get_data(
|
82
|
+
builder, self.configuration["data_sources"][self.exposure_component.exposure_type]
|
83
|
+
)
|
66
84
|
|
67
85
|
# noinspection PyAttributeOutsideInit
|
68
86
|
def setup(self, builder: Builder) -> None:
|
@@ -87,7 +105,7 @@ class RiskExposureDistribution(Component, ABC):
|
|
87
105
|
raise NotImplementedError
|
88
106
|
|
89
107
|
|
90
|
-
class EnsembleDistribution(
|
108
|
+
class EnsembleDistribution(ExposureDistribution):
|
91
109
|
##############
|
92
110
|
# Properties #
|
93
111
|
##############
|
@@ -106,7 +124,7 @@ class EnsembleDistribution(RiskExposureDistribution):
|
|
106
124
|
|
107
125
|
def __init__(self, risk: EntityString, distribution_type: str = "ensemble") -> None:
|
108
126
|
super().__init__(risk, distribution_type)
|
109
|
-
self._propensity = f"ensemble_propensity_{self.
|
127
|
+
self._propensity = f"ensemble_propensity_{self.exposure_component.entity}"
|
110
128
|
|
111
129
|
#################
|
112
130
|
# Setup methods #
|
@@ -129,7 +147,11 @@ class EnsembleDistribution(RiskExposureDistribution):
|
|
129
147
|
distributions = list(raw_weights["parameter"].unique())
|
130
148
|
|
131
149
|
raw_weights = pivot_categorical(
|
132
|
-
builder,
|
150
|
+
builder,
|
151
|
+
self.exposure_component.entity,
|
152
|
+
raw_weights,
|
153
|
+
pivot_column="parameter",
|
154
|
+
reset_index=False,
|
133
155
|
)
|
134
156
|
|
135
157
|
weights, parameters = rd.EnsembleDistribution.get_parameters(
|
@@ -201,7 +223,7 @@ class EnsembleDistribution(RiskExposureDistribution):
|
|
201
223
|
return x
|
202
224
|
|
203
225
|
|
204
|
-
class ContinuousDistribution(
|
226
|
+
class ContinuousDistribution(ExposureDistribution):
|
205
227
|
#####################
|
206
228
|
# Lifecycle methods #
|
207
229
|
#####################
|
@@ -261,12 +283,12 @@ class ContinuousDistribution(RiskExposureDistribution):
|
|
261
283
|
return x
|
262
284
|
|
263
285
|
|
264
|
-
class PolytomousDistribution(
|
286
|
+
class PolytomousDistribution(ExposureDistribution):
|
265
287
|
@property
|
266
288
|
def categories(self) -> list[str]:
|
267
|
-
# These need to be sorted so the cumulative sum is in the
|
289
|
+
# These need to be sorted so the cumulative sum is in the correct order of categories
|
268
290
|
# and results are therefore reproducible and correct
|
269
|
-
return sorted(self.lookup_tables[
|
291
|
+
return sorted(self.lookup_tables[self.exposure_component.exposure_type].value_columns)
|
270
292
|
|
271
293
|
#################
|
272
294
|
# Setup methods #
|
@@ -277,9 +299,11 @@ class PolytomousDistribution(RiskExposureDistribution):
|
|
277
299
|
exposure_value_columns = self.get_exposure_value_columns(exposure_data)
|
278
300
|
|
279
301
|
if isinstance(exposure_data, pd.DataFrame):
|
280
|
-
exposure_data = pivot_categorical(
|
302
|
+
exposure_data = pivot_categorical(
|
303
|
+
builder, self.exposure_component.entity, exposure_data, "parameter"
|
304
|
+
)
|
281
305
|
|
282
|
-
self.lookup_tables[
|
306
|
+
self.lookup_tables[self.exposure_component.exposure_type] = self.build_lookup_table(
|
283
307
|
builder, exposure_data, exposure_value_columns
|
284
308
|
)
|
285
309
|
|
@@ -293,9 +317,11 @@ class PolytomousDistribution(RiskExposureDistribution):
|
|
293
317
|
def get_exposure_parameter_pipeline(self, builder: Builder) -> Pipeline:
|
294
318
|
return builder.value.register_value_producer(
|
295
319
|
self.parameters_pipeline_name,
|
296
|
-
source=self.lookup_tables[
|
320
|
+
source=self.lookup_tables[self.exposure_component.exposure_type],
|
297
321
|
component=self,
|
298
|
-
required_resources=get_lookup_columns(
|
322
|
+
required_resources=get_lookup_columns(
|
323
|
+
[self.lookup_tables[self.exposure_component.exposure_type]]
|
324
|
+
),
|
299
325
|
)
|
300
326
|
|
301
327
|
##################
|
@@ -313,12 +339,12 @@ class PolytomousDistribution(RiskExposureDistribution):
|
|
313
339
|
).sum(axis=1)
|
314
340
|
return pd.Series(
|
315
341
|
np.array(self.categories)[category_index],
|
316
|
-
name=self.
|
342
|
+
name=f"{self.exposure_component.entity}.exposure",
|
317
343
|
index=quantiles.index,
|
318
344
|
)
|
319
345
|
|
320
346
|
|
321
|
-
class DichotomousDistribution(
|
347
|
+
class DichotomousDistribution(ExposureDistribution):
|
322
348
|
|
323
349
|
#################
|
324
350
|
# Setup methods #
|
@@ -332,11 +358,15 @@ class DichotomousDistribution(RiskExposureDistribution):
|
|
332
358
|
any_negatives = (exposure_data[exposure_value_columns] < 0).any().any()
|
333
359
|
any_over_one = (exposure_data[exposure_value_columns] > 1).any().any()
|
334
360
|
if any_negatives or any_over_one:
|
335
|
-
raise ValueError(
|
361
|
+
raise ValueError(
|
362
|
+
f"All exposures must be in the range [0, 1] for {self.exposure_component.entity}"
|
363
|
+
)
|
336
364
|
elif exposure_data < 0 or exposure_data > 1:
|
337
|
-
raise ValueError(
|
365
|
+
raise ValueError(
|
366
|
+
f"Exposure must be in the range [0, 1] for {self.exposure_component.entity}"
|
367
|
+
)
|
338
368
|
|
339
|
-
self.lookup_tables[
|
369
|
+
self.lookup_tables[self.exposure_component.exposure_type] = self.build_lookup_table(
|
340
370
|
builder, exposure_data, exposure_value_columns
|
341
371
|
)
|
342
372
|
self.lookup_tables["paf"] = self.build_lookup_table(builder, 0.0)
|
@@ -350,20 +380,43 @@ class DichotomousDistribution(RiskExposureDistribution):
|
|
350
380
|
# rebin exposure categories
|
351
381
|
self.validate_rebin_source(builder, exposure_data)
|
352
382
|
rebin_exposed_categories = set(self.configuration["rebinned_exposed"])
|
383
|
+
# Check if risk exposure is exposed vs cat1
|
384
|
+
if (
|
385
|
+
"cat1" in exposure_data["parameter"].unique()
|
386
|
+
and self.exposure_component.entity.type == "risk_factor"
|
387
|
+
):
|
388
|
+
warnings.warn(
|
389
|
+
"Using 'cat1' and 'cat2' for dichotomous exposure is deprecated and will be removed in a future release. Use 'exposed' and 'unexposed' instead.",
|
390
|
+
FutureWarning,
|
391
|
+
stacklevel=2,
|
392
|
+
)
|
393
|
+
exposure_data["parameter"] = exposure_data["parameter"].replace(
|
394
|
+
{
|
395
|
+
"cat1": self.exposure_component.dichotomous_exposure_category_names.exposed,
|
396
|
+
"cat2": self.exposure_component.dichotomous_exposure_category_names.unexposed,
|
397
|
+
}
|
398
|
+
)
|
353
399
|
if rebin_exposed_categories:
|
354
|
-
exposure_data = self._rebin_exposure_data(
|
400
|
+
exposure_data = self._rebin_exposure_data(
|
401
|
+
exposure_data,
|
402
|
+
rebin_exposed_categories,
|
403
|
+
self.exposure_component.dichotomous_exposure_category_names.exposed,
|
404
|
+
)
|
355
405
|
|
356
|
-
exposure_data = exposure_data[
|
406
|
+
exposure_data = exposure_data[
|
407
|
+
exposure_data["parameter"]
|
408
|
+
== self.exposure_component.dichotomous_exposure_category_names.exposed
|
409
|
+
]
|
357
410
|
return exposure_data.drop(columns="parameter")
|
358
411
|
|
359
412
|
@staticmethod
|
360
413
|
def _rebin_exposure_data(
|
361
|
-
exposure_data: pd.DataFrame, rebin_exposed_categories: set
|
414
|
+
exposure_data: pd.DataFrame, rebin_exposed_categories: set, exposed_category_name: str
|
362
415
|
) -> pd.DataFrame:
|
363
416
|
exposure_data = exposure_data[
|
364
417
|
exposure_data["parameter"].isin(rebin_exposed_categories)
|
365
418
|
]
|
366
|
-
exposure_data["parameter"] =
|
419
|
+
exposure_data["parameter"] = exposed_category_name
|
367
420
|
exposure_data = (
|
368
421
|
exposure_data.groupby(list(exposure_data.columns.difference(["value"])))
|
369
422
|
.sum()
|
@@ -382,7 +435,7 @@ class DichotomousDistribution(RiskExposureDistribution):
|
|
382
435
|
def setup(self, builder: Builder) -> None:
|
383
436
|
super().setup(builder)
|
384
437
|
self.joint_paf = builder.value.register_value_producer(
|
385
|
-
f"{self.
|
438
|
+
f"{self.exposure_component.entity}.exposure_parameters.paf",
|
386
439
|
source=lambda index: [self.lookup_tables["paf"](index)],
|
387
440
|
component=self,
|
388
441
|
preferred_combiner=list_combiner,
|
@@ -391,10 +444,12 @@ class DichotomousDistribution(RiskExposureDistribution):
|
|
391
444
|
|
392
445
|
def get_exposure_parameter_pipeline(self, builder: Builder) -> Pipeline:
|
393
446
|
return builder.value.register_value_producer(
|
394
|
-
f"{self.
|
447
|
+
f"{self.exposure_component.entity}.exposure_parameters",
|
395
448
|
source=self.exposure_parameter_source,
|
396
449
|
component=self,
|
397
|
-
required_resources=get_lookup_columns(
|
450
|
+
required_resources=get_lookup_columns(
|
451
|
+
[self.lookup_tables[self.exposure_component.exposure_type]]
|
452
|
+
),
|
398
453
|
)
|
399
454
|
|
400
455
|
##############
|
@@ -405,29 +460,31 @@ class DichotomousDistribution(RiskExposureDistribution):
|
|
405
460
|
if not isinstance(data, pd.DataFrame):
|
406
461
|
return
|
407
462
|
|
408
|
-
rebin_exposed_categories = set(
|
463
|
+
rebin_exposed_categories = set(
|
464
|
+
builder.configuration[self.exposure_component.entity]["rebinned_exposed"]
|
465
|
+
)
|
409
466
|
|
410
467
|
if (
|
411
468
|
rebin_exposed_categories
|
412
|
-
and builder.configuration[self.
|
469
|
+
and builder.configuration[self.exposure_component.entity]["category_thresholds"]
|
413
470
|
):
|
414
471
|
raise ValueError(
|
415
472
|
f"Rebinning and category thresholds are mutually exclusive. "
|
416
|
-
f"You provided both for {self.
|
473
|
+
f"You provided both for {self.exposure_component.entity.name}."
|
417
474
|
)
|
418
475
|
|
419
476
|
invalid_cats = rebin_exposed_categories.difference(set(data.parameter))
|
420
477
|
if invalid_cats:
|
421
478
|
raise ValueError(
|
422
479
|
f"The following provided categories for the rebinned exposed "
|
423
|
-
f"category of {self.
|
480
|
+
f"category of {self.exposure_component.entity.name} are not found in the exposure data: "
|
424
481
|
f"{invalid_cats}."
|
425
482
|
)
|
426
483
|
|
427
484
|
if rebin_exposed_categories == set(data.parameter):
|
428
485
|
raise ValueError(
|
429
486
|
f"The provided categories for the rebinned exposed category of "
|
430
|
-
f"{self.
|
487
|
+
f"{self.exposure_component.entity.name} comprise all categories for the exposure data. "
|
431
488
|
f"At least one category must be left out of the provided categories "
|
432
489
|
f"to be rebinned into the unexposed category."
|
433
490
|
)
|
@@ -437,7 +494,9 @@ class DichotomousDistribution(RiskExposureDistribution):
|
|
437
494
|
##################################
|
438
495
|
|
439
496
|
def exposure_parameter_source(self, index: pd.Index) -> pd.Series:
|
440
|
-
base_exposure = self.lookup_tables[
|
497
|
+
base_exposure = self.lookup_tables[self.exposure_component.exposure_type](
|
498
|
+
index
|
499
|
+
).values
|
441
500
|
joint_paf = self.joint_paf(index).values
|
442
501
|
return pd.Series(base_exposure * (1 - joint_paf), index=index, name="values")
|
443
502
|
|
@@ -448,8 +507,13 @@ class DichotomousDistribution(RiskExposureDistribution):
|
|
448
507
|
def ppf(self, quantiles: pd.Series) -> pd.Series:
|
449
508
|
exposed = quantiles < self.exposure_parameters(quantiles.index)
|
450
509
|
return pd.Series(
|
451
|
-
exposed.replace(
|
452
|
-
|
510
|
+
exposed.replace(
|
511
|
+
{
|
512
|
+
True: self.exposure_component.dichotomous_exposure_category_names.exposed,
|
513
|
+
False: self.exposure_component.dichotomous_exposure_category_names.unexposed,
|
514
|
+
}
|
515
|
+
),
|
516
|
+
name=f"{self.exposure_component.entity}.{self.exposure_component.exposure_type}",
|
453
517
|
index=quantiles.index,
|
454
518
|
)
|
455
519
|
|