heormodel 0.6.0__tar.gz
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.
- heormodel-0.6.0/PKG-INFO +154 -0
- heormodel-0.6.0/README.md +111 -0
- heormodel-0.6.0/pyproject.toml +68 -0
- heormodel-0.6.0/setup.cfg +4 -0
- heormodel-0.6.0/src/heormodel/__init__.py +43 -0
- heormodel-0.6.0/src/heormodel/calibrate/__init__.py +16 -0
- heormodel-0.6.0/src/heormodel/calibrate/abc.py +182 -0
- heormodel-0.6.0/src/heormodel/cea/__init__.py +25 -0
- heormodel-0.6.0/src/heormodel/cea/ceac.py +130 -0
- heormodel-0.6.0/src/heormodel/cea/frontier.py +134 -0
- heormodel-0.6.0/src/heormodel/cea/nb.py +62 -0
- heormodel-0.6.0/src/heormodel/dsa/__init__.py +18 -0
- heormodel-0.6.0/src/heormodel/dsa/design.py +182 -0
- heormodel-0.6.0/src/heormodel/models/__init__.py +26 -0
- heormodel-0.6.0/src/heormodel/models/_accrual.py +142 -0
- heormodel-0.6.0/src/heormodel/models/des.py +497 -0
- heormodel-0.6.0/src/heormodel/models/markov.py +298 -0
- heormodel-0.6.0/src/heormodel/models/microsim.py +442 -0
- heormodel-0.6.0/src/heormodel/models/outcomes.py +221 -0
- heormodel-0.6.0/src/heormodel/models/protocol.py +51 -0
- heormodel-0.6.0/src/heormodel/params/__init__.py +37 -0
- heormodel-0.6.0/src/heormodel/params/distributions.py +307 -0
- heormodel-0.6.0/src/heormodel/params/inputs.py +174 -0
- heormodel-0.6.0/src/heormodel/params/mix.py +90 -0
- heormodel-0.6.0/src/heormodel/params/sampling.py +211 -0
- heormodel-0.6.0/src/heormodel/py.typed +0 -0
- heormodel-0.6.0/src/heormodel/report/__init__.py +31 -0
- heormodel-0.6.0/src/heormodel/report/plots.py +403 -0
- heormodel-0.6.0/src/heormodel/report/provenance.py +141 -0
- heormodel-0.6.0/src/heormodel/run/__init__.py +11 -0
- heormodel-0.6.0/src/heormodel/run/_progress.py +134 -0
- heormodel-0.6.0/src/heormodel/run/diagnostics.py +49 -0
- heormodel-0.6.0/src/heormodel/run/runner.py +209 -0
- heormodel-0.6.0/src/heormodel/run/seeds.py +81 -0
- heormodel-0.6.0/src/heormodel/voi/__init__.py +27 -0
- heormodel-0.6.0/src/heormodel/voi/_metamodel.py +89 -0
- heormodel-0.6.0/src/heormodel/voi/evpi.py +51 -0
- heormodel-0.6.0/src/heormodel/voi/evppi.py +107 -0
- heormodel-0.6.0/src/heormodel/voi/evsi.py +134 -0
- heormodel-0.6.0/src/heormodel.egg-info/PKG-INFO +154 -0
- heormodel-0.6.0/src/heormodel.egg-info/SOURCES.txt +54 -0
- heormodel-0.6.0/src/heormodel.egg-info/dependency_links.txt +1 -0
- heormodel-0.6.0/src/heormodel.egg-info/requires.txt +29 -0
- heormodel-0.6.0/src/heormodel.egg-info/top_level.txt +1 -0
- heormodel-0.6.0/tests/test_calibrate.py +48 -0
- heormodel-0.6.0/tests/test_cea.py +158 -0
- heormodel-0.6.0/tests/test_des.py +308 -0
- heormodel-0.6.0/tests/test_dsa.py +123 -0
- heormodel-0.6.0/tests/test_markov.py +257 -0
- heormodel-0.6.0/tests/test_markov_vs_microsim.py +122 -0
- heormodel-0.6.0/tests/test_microsim.py +297 -0
- heormodel-0.6.0/tests/test_outcomes_run.py +153 -0
- heormodel-0.6.0/tests/test_params.py +326 -0
- heormodel-0.6.0/tests/test_report.py +112 -0
- heormodel-0.6.0/tests/test_run_parallel.py +105 -0
- heormodel-0.6.0/tests/test_voi.py +192 -0
heormodel-0.6.0/PKG-INFO
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: heormodel
|
|
3
|
+
Version: 0.6.0
|
|
4
|
+
Summary: Health economic evaluation in Python: PSA parameters, swappable model engines, cost-effectiveness analysis, and value of information
|
|
5
|
+
Author: Pedro Nascimento de Lima
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Documentation, https://pedroliman.github.io/heormodel/
|
|
8
|
+
Project-URL: Repository, https://github.com/pedroliman/heormodel
|
|
9
|
+
Project-URL: Changelog, https://github.com/pedroliman/heormodel/blob/main/CHANGELOG.md
|
|
10
|
+
Keywords: health economics,HEOR,HTA,cost-effectiveness,value of information,PSA
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
|
|
16
|
+
Requires-Python: >=3.11
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
Requires-Dist: numpy>=1.26
|
|
19
|
+
Requires-Dist: scipy>=1.11
|
|
20
|
+
Requires-Dist: pandas>=2.1
|
|
21
|
+
Requires-Dist: joblib>=1.3
|
|
22
|
+
Requires-Dist: matplotlib>=3.8
|
|
23
|
+
Requires-Dist: scikit-learn>=1.3
|
|
24
|
+
Provides-Extra: calibration
|
|
25
|
+
Requires-Dist: pyabc>=0.12; extra == "calibration"
|
|
26
|
+
Provides-Extra: des
|
|
27
|
+
Requires-Dist: simpy>=4.0; extra == "des"
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
30
|
+
Requires-Dist: pytest-cov>=5; extra == "dev"
|
|
31
|
+
Requires-Dist: ruff>=0.5; extra == "dev"
|
|
32
|
+
Requires-Dist: mypy>=1.10; extra == "dev"
|
|
33
|
+
Requires-Dist: pyabc>=0.12; extra == "dev"
|
|
34
|
+
Requires-Dist: simpy>=4.0; extra == "dev"
|
|
35
|
+
Provides-Extra: docs
|
|
36
|
+
Requires-Dist: quartodoc>=0.11; extra == "docs"
|
|
37
|
+
Requires-Dist: ipykernel<7,>=6.29; extra == "docs"
|
|
38
|
+
Requires-Dist: nbclient>=0.10; extra == "docs"
|
|
39
|
+
Requires-Dist: nbformat>=5.10; extra == "docs"
|
|
40
|
+
Requires-Dist: pyyaml>=6; extra == "docs"
|
|
41
|
+
Requires-Dist: pyabc>=0.12; extra == "docs"
|
|
42
|
+
Requires-Dist: simpy>=4.0; extra == "docs"
|
|
43
|
+
|
|
44
|
+
# heormodel
|
|
45
|
+
|
|
46
|
+
[](https://github.com/pedroliman/heormodel/actions/workflows/ci.yml)
|
|
47
|
+
[](https://codecov.io/gh/pedroliman/heormodel)
|
|
48
|
+
[](https://pypi.org/project/heormodel/)
|
|
49
|
+
[](https://opensource.org/licenses/MIT)
|
|
50
|
+
|
|
51
|
+
Health economic evaluation in Python: parameter specification and probabilistic sampling, simulation across model types, cost-effectiveness analysis (CEA), and value-of-information (VoI) analysis for model-based HEOR and HTA work.
|
|
52
|
+
|
|
53
|
+
Every model engine returns costs and effects in one standardized structure, so CEA and VoI run the same on outputs from any source. You do not need a built-in engine to start: a PSA table from a spreadsheet or a legacy simulator enters the pipeline through one call.
|
|
54
|
+
|
|
55
|
+
Documentation: [pedroliman.github.io/heormodel](https://pedroliman.github.io/heormodel/)
|
|
56
|
+
|
|
57
|
+
## Install
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pip install heormodel
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The `calibration` extra adds ABC-SMC calibration: `pip install "heormodel[calibration]"`. Development uses [`uv`](https://docs.astral.sh/uv/); see [Development](#development).
|
|
64
|
+
|
|
65
|
+
## Quickstart
|
|
66
|
+
|
|
67
|
+
Load a costs/effects PSA table with `as_outcomes`, then run incremental analysis and value of information from the same object:
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
import numpy as np
|
|
71
|
+
import pandas as pd
|
|
72
|
+
from heormodel.run import as_outcomes
|
|
73
|
+
from heormodel.cea import icer_table
|
|
74
|
+
from heormodel.voi import evpi
|
|
75
|
+
|
|
76
|
+
rng = np.random.default_rng(7)
|
|
77
|
+
n = 2_000
|
|
78
|
+
df = pd.concat(
|
|
79
|
+
pd.DataFrame({
|
|
80
|
+
"strategy": name, "iteration": range(n),
|
|
81
|
+
"cost": rng.normal(cost, 2_000, n), "qaly": rng.normal(q, 0.4, n),
|
|
82
|
+
})
|
|
83
|
+
for name, cost, q in [("Standard care", 40_000, 8.0), ("New drug", 52_000, 8.6)]
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
outcomes = as_outcomes(df) # accepts a DataFrame or a CSV path
|
|
87
|
+
icer_table(outcomes).round(1)
|
|
88
|
+
# cost effect inc_cost inc_effect icer status
|
|
89
|
+
# strategy
|
|
90
|
+
# Standard care 39920.1 8.0 NaN NaN NaN ND
|
|
91
|
+
# New drug 51985.1 8.6 12065.0 0.6 20606.0 ND
|
|
92
|
+
|
|
93
|
+
round(evpi(outcomes, wtp=30_000), 1)
|
|
94
|
+
# 4339.3
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
[`examples/byoo_example.py`](examples/byoo_example.py) runs the same wedge end to end: an external table through CEA, VoI, plots, and a run report, plus the full pipeline (`ParameterSet` sampling, `SeedManager`, `run_psa`).
|
|
98
|
+
|
|
99
|
+
The `examples/mdm_*.py` scripts replicate three published Sick-Sicker cost-effectiveness tutorials and match their deterministic results: a cohort state-transition model (`mdm_cohort.py`), its time-dependent version with age-varying mortality (`mdm_cohort_timedep.py`), and a microsimulation (`mdm_microsim.py`). Each has a companion page in the [replication gallery](https://pedroliman.github.io/heormodel/tutorials/replication-gallery.html).
|
|
100
|
+
|
|
101
|
+
[`examples/microsim.py`](examples/microsim.py) builds an individual-level model with `MicrosimModel`: frailty heterogeneity and a mortality risk that rises with time spent sick, two features a cohort average cannot carry, then runs it through CEA and VoI. Run it with `uv run python examples/microsim.py`.
|
|
102
|
+
|
|
103
|
+
[`examples/markov_vs_microsim.py`](examples/markov_vs_microsim.py) builds one Sick-Sicker-style model as both a `MarkovModel` cohort trace and a `MicrosimModel` individual simulation from the same rates. The homogeneous microsimulation converges to the cohort trace (a cross-engine cross-validation), then a mean-1 frailty raises the microsimulation QALYs about 8% above the cohort on unchanged mean rates, the heterogeneity a cohort averages away. Run it with `uv run python examples/markov_vs_microsim.py`.
|
|
104
|
+
|
|
105
|
+
[`examples/des.py`](examples/des.py) builds a resource-constrained clinic with `DESModel`, a thin SimPy wrapper: patients queue for a scarce specialist, so added capacity buys QALYs by cutting waiting time, a coupling a cohort model cannot carry. Run it with `uv run python examples/des.py` after installing the `des` extra.
|
|
106
|
+
|
|
107
|
+
[`examples/calibration_workflow.py`](examples/calibration_workflow.py) mixes parameter sources: a natural-history model's transition rates are calibrated to observed prevalence with `abc_calibrate`, utilities and costs come from the literature, and `mix_draws` joins the two into one PSA that flows through CEA and VoI. Run it with `uv run python examples/calibration_workflow.py` after installing the `calibration` extra.
|
|
108
|
+
|
|
109
|
+
[`examples/parameter_inputs.py`](examples/parameter_inputs.py) shows the three ways parameters enter the run loop besides distribution sampling: `single_draw` (and `ParameterSet.at_means`) for a base-case run, `read_draws` for a draw matrix from a CSV or DataFrame, and `resample_posterior` for a weighted posterior resampled with replacement. Run it with `uv run python examples/parameter_inputs.py`.
|
|
110
|
+
|
|
111
|
+
## Package layout
|
|
112
|
+
|
|
113
|
+
| Subpackage | Status | Contents |
|
|
114
|
+
|---|---|---|
|
|
115
|
+
| `heormodel.params` | done | Distribution specs with mean/SE constructors; correlated sampling; draw matrices from point values, CSVs, and weighted posteriors |
|
|
116
|
+
| `heormodel.models` | done | `Outcomes` schema, `ModelEngine` protocol; cohort state-transition, microsimulation, and discrete-event engines built |
|
|
117
|
+
| `heormodel.run` | done | `SeedManager`, `run_psa`, `as_outcomes`, running-mean diagnostics |
|
|
118
|
+
| `heormodel.cea` | done | ICERs, dominance, extended dominance, frontier, NMB/NHB, CEAC/CEAF |
|
|
119
|
+
| `heormodel.dsa` | done | One-way, one-at-a-time, and full-factorial grid deterministic sensitivity designs |
|
|
120
|
+
| `heormodel.voi` | done | EVPI; EVPPI (spline/GP metamodels); EVSI (regression; others stubbed) |
|
|
121
|
+
| `heormodel.calibrate` | done, optional | ABC-SMC via `pyabc`; posterior as an iteration-indexed draw matrix |
|
|
122
|
+
| `heormodel.report` | done | CE plane, CEAC/CEAF, frontier, tornado plots; provenance, run report |
|
|
123
|
+
|
|
124
|
+
Full docs are at [pedroliman.github.io/heormodel](https://pedroliman.github.io/heormodel/). Next steps are prioritized in [`roadmap/`](roadmap/README.md); shipped changes are in [CHANGELOG.md](CHANGELOG.md); the release process is in [RELEASING.md](RELEASING.md). Prose follows [`guidance/writing_style.md`](guidance/writing_style.md).
|
|
125
|
+
|
|
126
|
+
## Development
|
|
127
|
+
|
|
128
|
+
Requires Python 3.11+ and [`uv`](https://docs.astral.sh/uv/):
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
uv venv && uv pip install -e ".[dev]" # extras: calibration (pyabc), des (simpy), dev
|
|
132
|
+
uv run pytest # suite incl. validation checks
|
|
133
|
+
uv run pytest --doctest-modules src # every docstring example runs
|
|
134
|
+
uv run ruff check . && uv run mypy
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Two validation checks anchor the suite: a hand-verified five-strategy dominance and ICER example (`tests/test_cea.py`), and analytic Gaussian EVPI/EVPPI/EVSI recovered within Monte Carlo error at 80,000 iterations (`tests/test_voi.py`).
|
|
138
|
+
|
|
139
|
+
The documentation site lives in `docs/` and builds with [Quarto](https://quarto.org) and [quartodoc](https://machow.github.io/quartodoc/); tutorials execute at render time, so the build doubles as a test. With Quarto installed and the `docs` extra synced (`uv sync --extra docs`):
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
uv run quartodoc build --config docs/_quarto.yml # generate the API reference
|
|
143
|
+
quarto preview docs # or: quarto render docs
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
CI (`.github/workflows/docs.yml`) rebuilds and publishes the site to GitHub Pages on every push to `main`.
|
|
147
|
+
|
|
148
|
+
## Design notes
|
|
149
|
+
|
|
150
|
+
- VoI metamodeling uses scikit-learn; `method=` leaves room for other backends.
|
|
151
|
+
- `heormodel.calibrate` is a seventh subpackage beyond the original six; calibrated draws re-enter the pipeline as a standard draw matrix.
|
|
152
|
+
- Mean/SE constructors turn published estimates into sampling distributions; direct parameterisation remains available.
|
|
153
|
+
- Dirichlet vectors sample as normalised independent Gammas; leave their correlation targets at zero.
|
|
154
|
+
- Tornado diagrams are PSA-based (linear fits at outer percentiles); phase 1 has no deterministic engine to re-run.
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# heormodel
|
|
2
|
+
|
|
3
|
+
[](https://github.com/pedroliman/heormodel/actions/workflows/ci.yml)
|
|
4
|
+
[](https://codecov.io/gh/pedroliman/heormodel)
|
|
5
|
+
[](https://pypi.org/project/heormodel/)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
Health economic evaluation in Python: parameter specification and probabilistic sampling, simulation across model types, cost-effectiveness analysis (CEA), and value-of-information (VoI) analysis for model-based HEOR and HTA work.
|
|
9
|
+
|
|
10
|
+
Every model engine returns costs and effects in one standardized structure, so CEA and VoI run the same on outputs from any source. You do not need a built-in engine to start: a PSA table from a spreadsheet or a legacy simulator enters the pipeline through one call.
|
|
11
|
+
|
|
12
|
+
Documentation: [pedroliman.github.io/heormodel](https://pedroliman.github.io/heormodel/)
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install heormodel
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The `calibration` extra adds ABC-SMC calibration: `pip install "heormodel[calibration]"`. Development uses [`uv`](https://docs.astral.sh/uv/); see [Development](#development).
|
|
21
|
+
|
|
22
|
+
## Quickstart
|
|
23
|
+
|
|
24
|
+
Load a costs/effects PSA table with `as_outcomes`, then run incremental analysis and value of information from the same object:
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
import numpy as np
|
|
28
|
+
import pandas as pd
|
|
29
|
+
from heormodel.run import as_outcomes
|
|
30
|
+
from heormodel.cea import icer_table
|
|
31
|
+
from heormodel.voi import evpi
|
|
32
|
+
|
|
33
|
+
rng = np.random.default_rng(7)
|
|
34
|
+
n = 2_000
|
|
35
|
+
df = pd.concat(
|
|
36
|
+
pd.DataFrame({
|
|
37
|
+
"strategy": name, "iteration": range(n),
|
|
38
|
+
"cost": rng.normal(cost, 2_000, n), "qaly": rng.normal(q, 0.4, n),
|
|
39
|
+
})
|
|
40
|
+
for name, cost, q in [("Standard care", 40_000, 8.0), ("New drug", 52_000, 8.6)]
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
outcomes = as_outcomes(df) # accepts a DataFrame or a CSV path
|
|
44
|
+
icer_table(outcomes).round(1)
|
|
45
|
+
# cost effect inc_cost inc_effect icer status
|
|
46
|
+
# strategy
|
|
47
|
+
# Standard care 39920.1 8.0 NaN NaN NaN ND
|
|
48
|
+
# New drug 51985.1 8.6 12065.0 0.6 20606.0 ND
|
|
49
|
+
|
|
50
|
+
round(evpi(outcomes, wtp=30_000), 1)
|
|
51
|
+
# 4339.3
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
[`examples/byoo_example.py`](examples/byoo_example.py) runs the same wedge end to end: an external table through CEA, VoI, plots, and a run report, plus the full pipeline (`ParameterSet` sampling, `SeedManager`, `run_psa`).
|
|
55
|
+
|
|
56
|
+
The `examples/mdm_*.py` scripts replicate three published Sick-Sicker cost-effectiveness tutorials and match their deterministic results: a cohort state-transition model (`mdm_cohort.py`), its time-dependent version with age-varying mortality (`mdm_cohort_timedep.py`), and a microsimulation (`mdm_microsim.py`). Each has a companion page in the [replication gallery](https://pedroliman.github.io/heormodel/tutorials/replication-gallery.html).
|
|
57
|
+
|
|
58
|
+
[`examples/microsim.py`](examples/microsim.py) builds an individual-level model with `MicrosimModel`: frailty heterogeneity and a mortality risk that rises with time spent sick, two features a cohort average cannot carry, then runs it through CEA and VoI. Run it with `uv run python examples/microsim.py`.
|
|
59
|
+
|
|
60
|
+
[`examples/markov_vs_microsim.py`](examples/markov_vs_microsim.py) builds one Sick-Sicker-style model as both a `MarkovModel` cohort trace and a `MicrosimModel` individual simulation from the same rates. The homogeneous microsimulation converges to the cohort trace (a cross-engine cross-validation), then a mean-1 frailty raises the microsimulation QALYs about 8% above the cohort on unchanged mean rates, the heterogeneity a cohort averages away. Run it with `uv run python examples/markov_vs_microsim.py`.
|
|
61
|
+
|
|
62
|
+
[`examples/des.py`](examples/des.py) builds a resource-constrained clinic with `DESModel`, a thin SimPy wrapper: patients queue for a scarce specialist, so added capacity buys QALYs by cutting waiting time, a coupling a cohort model cannot carry. Run it with `uv run python examples/des.py` after installing the `des` extra.
|
|
63
|
+
|
|
64
|
+
[`examples/calibration_workflow.py`](examples/calibration_workflow.py) mixes parameter sources: a natural-history model's transition rates are calibrated to observed prevalence with `abc_calibrate`, utilities and costs come from the literature, and `mix_draws` joins the two into one PSA that flows through CEA and VoI. Run it with `uv run python examples/calibration_workflow.py` after installing the `calibration` extra.
|
|
65
|
+
|
|
66
|
+
[`examples/parameter_inputs.py`](examples/parameter_inputs.py) shows the three ways parameters enter the run loop besides distribution sampling: `single_draw` (and `ParameterSet.at_means`) for a base-case run, `read_draws` for a draw matrix from a CSV or DataFrame, and `resample_posterior` for a weighted posterior resampled with replacement. Run it with `uv run python examples/parameter_inputs.py`.
|
|
67
|
+
|
|
68
|
+
## Package layout
|
|
69
|
+
|
|
70
|
+
| Subpackage | Status | Contents |
|
|
71
|
+
|---|---|---|
|
|
72
|
+
| `heormodel.params` | done | Distribution specs with mean/SE constructors; correlated sampling; draw matrices from point values, CSVs, and weighted posteriors |
|
|
73
|
+
| `heormodel.models` | done | `Outcomes` schema, `ModelEngine` protocol; cohort state-transition, microsimulation, and discrete-event engines built |
|
|
74
|
+
| `heormodel.run` | done | `SeedManager`, `run_psa`, `as_outcomes`, running-mean diagnostics |
|
|
75
|
+
| `heormodel.cea` | done | ICERs, dominance, extended dominance, frontier, NMB/NHB, CEAC/CEAF |
|
|
76
|
+
| `heormodel.dsa` | done | One-way, one-at-a-time, and full-factorial grid deterministic sensitivity designs |
|
|
77
|
+
| `heormodel.voi` | done | EVPI; EVPPI (spline/GP metamodels); EVSI (regression; others stubbed) |
|
|
78
|
+
| `heormodel.calibrate` | done, optional | ABC-SMC via `pyabc`; posterior as an iteration-indexed draw matrix |
|
|
79
|
+
| `heormodel.report` | done | CE plane, CEAC/CEAF, frontier, tornado plots; provenance, run report |
|
|
80
|
+
|
|
81
|
+
Full docs are at [pedroliman.github.io/heormodel](https://pedroliman.github.io/heormodel/). Next steps are prioritized in [`roadmap/`](roadmap/README.md); shipped changes are in [CHANGELOG.md](CHANGELOG.md); the release process is in [RELEASING.md](RELEASING.md). Prose follows [`guidance/writing_style.md`](guidance/writing_style.md).
|
|
82
|
+
|
|
83
|
+
## Development
|
|
84
|
+
|
|
85
|
+
Requires Python 3.11+ and [`uv`](https://docs.astral.sh/uv/):
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
uv venv && uv pip install -e ".[dev]" # extras: calibration (pyabc), des (simpy), dev
|
|
89
|
+
uv run pytest # suite incl. validation checks
|
|
90
|
+
uv run pytest --doctest-modules src # every docstring example runs
|
|
91
|
+
uv run ruff check . && uv run mypy
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Two validation checks anchor the suite: a hand-verified five-strategy dominance and ICER example (`tests/test_cea.py`), and analytic Gaussian EVPI/EVPPI/EVSI recovered within Monte Carlo error at 80,000 iterations (`tests/test_voi.py`).
|
|
95
|
+
|
|
96
|
+
The documentation site lives in `docs/` and builds with [Quarto](https://quarto.org) and [quartodoc](https://machow.github.io/quartodoc/); tutorials execute at render time, so the build doubles as a test. With Quarto installed and the `docs` extra synced (`uv sync --extra docs`):
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
uv run quartodoc build --config docs/_quarto.yml # generate the API reference
|
|
100
|
+
quarto preview docs # or: quarto render docs
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
CI (`.github/workflows/docs.yml`) rebuilds and publishes the site to GitHub Pages on every push to `main`.
|
|
104
|
+
|
|
105
|
+
## Design notes
|
|
106
|
+
|
|
107
|
+
- VoI metamodeling uses scikit-learn; `method=` leaves room for other backends.
|
|
108
|
+
- `heormodel.calibrate` is a seventh subpackage beyond the original six; calibrated draws re-enter the pipeline as a standard draw matrix.
|
|
109
|
+
- Mean/SE constructors turn published estimates into sampling distributions; direct parameterisation remains available.
|
|
110
|
+
- Dirichlet vectors sample as normalised independent Gammas; leave their correlation targets at zero.
|
|
111
|
+
- Tornado diagrams are PSA-based (linear fits at outer percentiles); phase 1 has no deterministic engine to re-run.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "heormodel"
|
|
7
|
+
version = "0.6.0"
|
|
8
|
+
description = "Health economic evaluation in Python: PSA parameters, swappable model engines, cost-effectiveness analysis, and value of information"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Pedro Nascimento de Lima" }]
|
|
13
|
+
keywords = ["health economics", "HEOR", "HTA", "cost-effectiveness", "value of information", "PSA"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Intended Audience :: Science/Research",
|
|
17
|
+
"Programming Language :: Python :: 3.11",
|
|
18
|
+
"Programming Language :: Python :: 3.12",
|
|
19
|
+
"Topic :: Scientific/Engineering :: Medical Science Apps.",
|
|
20
|
+
]
|
|
21
|
+
dependencies = [
|
|
22
|
+
"numpy>=1.26",
|
|
23
|
+
"scipy>=1.11",
|
|
24
|
+
"pandas>=2.1",
|
|
25
|
+
"joblib>=1.3",
|
|
26
|
+
"matplotlib>=3.8",
|
|
27
|
+
"scikit-learn>=1.3",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Documentation = "https://pedroliman.github.io/heormodel/"
|
|
32
|
+
Repository = "https://github.com/pedroliman/heormodel"
|
|
33
|
+
Changelog = "https://github.com/pedroliman/heormodel/blob/main/CHANGELOG.md"
|
|
34
|
+
|
|
35
|
+
[project.optional-dependencies]
|
|
36
|
+
calibration = ["pyabc>=0.12"]
|
|
37
|
+
des = ["simpy>=4.0"]
|
|
38
|
+
dev = ["pytest>=8", "pytest-cov>=5", "ruff>=0.5", "mypy>=1.10", "pyabc>=0.12", "simpy>=4.0"]
|
|
39
|
+
docs = ["quartodoc>=0.11", "ipykernel>=6.29,<7", "nbclient>=0.10", "nbformat>=5.10", "pyyaml>=6", "pyabc>=0.12", "simpy>=4.0"]
|
|
40
|
+
|
|
41
|
+
[tool.setuptools.packages.find]
|
|
42
|
+
where = ["src"]
|
|
43
|
+
|
|
44
|
+
[tool.setuptools.package-data]
|
|
45
|
+
heormodel = ["py.typed"]
|
|
46
|
+
|
|
47
|
+
[tool.ruff]
|
|
48
|
+
line-length = 100
|
|
49
|
+
target-version = "py311"
|
|
50
|
+
src = ["src", "tests"]
|
|
51
|
+
|
|
52
|
+
[tool.ruff.lint]
|
|
53
|
+
select = ["E", "F", "W", "I", "UP", "B", "NPY"]
|
|
54
|
+
|
|
55
|
+
[tool.mypy]
|
|
56
|
+
ignore_missing_imports = true
|
|
57
|
+
disallow_untyped_defs = true
|
|
58
|
+
warn_unused_ignores = false
|
|
59
|
+
files = ["src/heormodel"]
|
|
60
|
+
|
|
61
|
+
[tool.pytest.ini_options]
|
|
62
|
+
testpaths = ["tests"]
|
|
63
|
+
|
|
64
|
+
[tool.coverage.run]
|
|
65
|
+
source = ["src/heormodel"]
|
|
66
|
+
|
|
67
|
+
[tool.coverage.report]
|
|
68
|
+
exclude_lines = ["pragma: no cover"]
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""heval: health economic evaluation in Python.
|
|
2
|
+
|
|
3
|
+
One parameter draw matrix flows through swappable model engines into a
|
|
4
|
+
shared analysis layer. Engines differ internally but share a contract on
|
|
5
|
+
their outputs, the `Outcomes` schema indexed by
|
|
6
|
+
``(strategy, iteration)``, which makes cost-effectiveness and
|
|
7
|
+
value-of-information analysis engine-agnostic. Outputs from any external
|
|
8
|
+
model enter the same pipeline via `as_outcomes`.
|
|
9
|
+
|
|
10
|
+
Subpackages:
|
|
11
|
+
- `heval.params`: distributions and correlated PSA sampling
|
|
12
|
+
- `heval.models`: engines behind the output contract
|
|
13
|
+
- `heval.run`: seeds, run loop, bring-your-own-outputs ingestion
|
|
14
|
+
- `heval.cea`: incremental analysis, frontier, NMB/NHB, CEAC/CEAF
|
|
15
|
+
- `heval.dsa`: one-way, one-at-a-time, and grid deterministic sensitivity designs
|
|
16
|
+
- `heval.voi`: EVPI, EVPPI, EVSI
|
|
17
|
+
- `heval.calibrate`: ABC calibration (optional ``pyabc`` extra)
|
|
18
|
+
- `heval.report`: plots and reproducibility scaffolding
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from importlib.metadata import PackageNotFoundError
|
|
22
|
+
from importlib.metadata import version as _version
|
|
23
|
+
|
|
24
|
+
from heormodel.models import ModelEngine, ModelFn, Outcomes
|
|
25
|
+
from heormodel.params import ParameterSet, mix_draws
|
|
26
|
+
from heormodel.run import SeedManager, as_outcomes, run_psa
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
__version__ = _version("heval")
|
|
30
|
+
except PackageNotFoundError: # pragma: no cover - not installed, e.g. running from source
|
|
31
|
+
__version__ = "0.0.0"
|
|
32
|
+
|
|
33
|
+
__all__ = [
|
|
34
|
+
"ModelEngine",
|
|
35
|
+
"ModelFn",
|
|
36
|
+
"Outcomes",
|
|
37
|
+
"ParameterSet",
|
|
38
|
+
"SeedManager",
|
|
39
|
+
"__version__",
|
|
40
|
+
"as_outcomes",
|
|
41
|
+
"mix_draws",
|
|
42
|
+
"run_psa",
|
|
43
|
+
]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Model calibration (`heval.calibrate`).
|
|
2
|
+
|
|
3
|
+
Approximate Bayesian computation via ``pyabc`` (optional dependency):
|
|
4
|
+
translate ``heval`` priors, calibrate a simulator to observed targets, and
|
|
5
|
+
get back a posterior parameter draw matrix that flows into the standard
|
|
6
|
+
PSA pipeline through the shared iteration index.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from heormodel.calibrate.abc import (
|
|
10
|
+
CalibrationResult,
|
|
11
|
+
TargetSimulator,
|
|
12
|
+
abc_calibrate,
|
|
13
|
+
to_pyabc_prior,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
__all__ = ["CalibrationResult", "TargetSimulator", "abc_calibrate", "to_pyabc_prior"]
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""Model calibration via approximate Bayesian computation (pyabc).
|
|
2
|
+
|
|
3
|
+
Bridges ``heval`` parameter specs to ``pyabc`` priors, runs ABC-SMC against
|
|
4
|
+
observed calibration targets, and returns the posterior as an
|
|
5
|
+
equally-weighted parameter draw matrix carrying the standard ``iteration``
|
|
6
|
+
index, so calibrated draws flow through `heval.run.run_psa` and the
|
|
7
|
+
analysis layer exactly like draws from `heval.params.ParameterSet.sample`.
|
|
8
|
+
|
|
9
|
+
``pyabc`` is an optional dependency: install with ``uv pip install
|
|
10
|
+
'heval[calibration]'``.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import tempfile
|
|
16
|
+
from collections.abc import Callable, Mapping
|
|
17
|
+
from dataclasses import dataclass
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Any
|
|
20
|
+
|
|
21
|
+
import numpy as np
|
|
22
|
+
import pandas as pd
|
|
23
|
+
|
|
24
|
+
from heormodel.params.distributions import (
|
|
25
|
+
Beta,
|
|
26
|
+
Dirichlet,
|
|
27
|
+
Distribution,
|
|
28
|
+
Gamma,
|
|
29
|
+
LogNormal,
|
|
30
|
+
Normal,
|
|
31
|
+
Uniform,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
#: A calibration simulator: parameter values -> simulated calibration targets.
|
|
35
|
+
TargetSimulator = Callable[[dict[str, float]], dict[str, float]]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _require_pyabc() -> Any:
|
|
39
|
+
try:
|
|
40
|
+
import pyabc
|
|
41
|
+
except ImportError as err: # pragma: no cover
|
|
42
|
+
raise ImportError(
|
|
43
|
+
"Calibration requires pyabc; install it with uv pip install 'heval[calibration]'."
|
|
44
|
+
) from err
|
|
45
|
+
return pyabc
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def to_pyabc_prior(distributions: Mapping[str, Distribution | Dirichlet]) -> Any:
|
|
49
|
+
"""Translate ``heval`` distribution specs into a ``pyabc`` prior.
|
|
50
|
+
|
|
51
|
+
Supported: `Beta`, `Gamma`, `LogNormal`,
|
|
52
|
+
`Normal`, `Uniform`. Dirichlet and Fixed parameters cannot
|
|
53
|
+
be calibrated directly; hold them constant inside the simulator instead.
|
|
54
|
+
|
|
55
|
+
Example:
|
|
56
|
+
>>> from heormodel.calibrate import to_pyabc_prior # doctest: +SKIP
|
|
57
|
+
>>> from heormodel.params import Beta
|
|
58
|
+
>>> prior = to_pyabc_prior({"p": Beta(2, 8)}) # doctest: +SKIP
|
|
59
|
+
"""
|
|
60
|
+
pyabc = _require_pyabc()
|
|
61
|
+
rvs: dict[str, Any] = {}
|
|
62
|
+
for name, dist in distributions.items():
|
|
63
|
+
if isinstance(dist, Beta):
|
|
64
|
+
rvs[name] = pyabc.RV("beta", dist.alpha, dist.beta)
|
|
65
|
+
elif isinstance(dist, Gamma):
|
|
66
|
+
rvs[name] = pyabc.RV("gamma", dist.shape, scale=dist.scale)
|
|
67
|
+
elif isinstance(dist, LogNormal):
|
|
68
|
+
rvs[name] = pyabc.RV("lognorm", dist.sigma, scale=float(np.exp(dist.mu)))
|
|
69
|
+
elif isinstance(dist, Normal):
|
|
70
|
+
rvs[name] = pyabc.RV("norm", dist.mean_, dist.sd_)
|
|
71
|
+
elif isinstance(dist, Uniform):
|
|
72
|
+
rvs[name] = pyabc.RV("uniform", dist.low, dist.high - dist.low)
|
|
73
|
+
else:
|
|
74
|
+
raise TypeError(
|
|
75
|
+
f"Parameter {name!r}: {type(dist).__name__} priors are not supported "
|
|
76
|
+
"for ABC calibration; hold it constant inside the simulator."
|
|
77
|
+
)
|
|
78
|
+
return pyabc.Distribution(**rvs)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@dataclass
|
|
82
|
+
class CalibrationResult:
|
|
83
|
+
"""Posterior draws and diagnostics from an ABC-SMC calibration.
|
|
84
|
+
|
|
85
|
+
Attributes:
|
|
86
|
+
posterior: Equally-weighted posterior draw matrix with a
|
|
87
|
+
``RangeIndex`` named ``iteration``, ready for
|
|
88
|
+
`heval.run.run_psa`.
|
|
89
|
+
weighted: The raw weighted particle population (columns = parameters,
|
|
90
|
+
plus a ``weight`` column).
|
|
91
|
+
n_populations: Number of ABC-SMC populations run.
|
|
92
|
+
final_epsilon: Acceptance threshold of the final population.
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
posterior: pd.DataFrame
|
|
96
|
+
weighted: pd.DataFrame
|
|
97
|
+
n_populations: int
|
|
98
|
+
final_epsilon: float
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def abc_calibrate(
|
|
102
|
+
simulator: TargetSimulator,
|
|
103
|
+
priors: Mapping[str, Distribution | Dirichlet],
|
|
104
|
+
observed: Mapping[str, float],
|
|
105
|
+
*,
|
|
106
|
+
population_size: int = 200,
|
|
107
|
+
max_populations: int = 8,
|
|
108
|
+
min_epsilon: float = 0.0,
|
|
109
|
+
n_posterior: int | None = None,
|
|
110
|
+
seed: int | None = None,
|
|
111
|
+
db_path: str | Path | None = None,
|
|
112
|
+
) -> CalibrationResult:
|
|
113
|
+
"""Calibrate model parameters to observed targets with ABC-SMC.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
simulator: Maps a parameter dict to simulated calibration targets
|
|
117
|
+
(same keys as ``observed``).
|
|
118
|
+
priors: Parameter priors as ``heval`` distribution specs.
|
|
119
|
+
observed: Observed calibration target values.
|
|
120
|
+
population_size: Particles per ABC-SMC population.
|
|
121
|
+
max_populations: Maximum number of populations.
|
|
122
|
+
min_epsilon: Stop once the acceptance threshold reaches this value.
|
|
123
|
+
n_posterior: Rows in the returned equally-weighted posterior matrix
|
|
124
|
+
(default: the final population size).
|
|
125
|
+
seed: Seed for the weighted-to-equal resampling step. (The ABC run
|
|
126
|
+
itself uses pyabc's internal randomness.)
|
|
127
|
+
db_path: Where to store pyabc's bookkeeping database (default: a
|
|
128
|
+
temporary file).
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
A `CalibrationResult` whose ``posterior`` plugs directly into
|
|
132
|
+
the PSA pipeline.
|
|
133
|
+
|
|
134
|
+
Example:
|
|
135
|
+
>>> from heormodel.calibrate import abc_calibrate # doctest: +SKIP
|
|
136
|
+
>>> from heormodel.params import Uniform
|
|
137
|
+
>>> result = abc_calibrate( # doctest: +SKIP
|
|
138
|
+
... simulator=lambda p: {"prevalence": p["risk"] * 0.5},
|
|
139
|
+
... priors={"risk": Uniform(0.0, 1.0)},
|
|
140
|
+
... observed={"prevalence": 0.15},
|
|
141
|
+
... )
|
|
142
|
+
"""
|
|
143
|
+
pyabc = _require_pyabc()
|
|
144
|
+
from pyabc.sampler import SingleCoreSampler
|
|
145
|
+
|
|
146
|
+
prior = to_pyabc_prior(priors)
|
|
147
|
+
keys = sorted(observed)
|
|
148
|
+
|
|
149
|
+
def model(parameter: Mapping[str, float]) -> dict[str, float]:
|
|
150
|
+
return dict(simulator(dict(parameter)))
|
|
151
|
+
|
|
152
|
+
distance = pyabc.PNormDistance(p=2)
|
|
153
|
+
abc = pyabc.ABCSMC(
|
|
154
|
+
model,
|
|
155
|
+
prior,
|
|
156
|
+
distance,
|
|
157
|
+
population_size=population_size,
|
|
158
|
+
sampler=SingleCoreSampler(),
|
|
159
|
+
)
|
|
160
|
+
if db_path is None:
|
|
161
|
+
db_file = Path(tempfile.mkdtemp()) / "heval_abc.db"
|
|
162
|
+
else:
|
|
163
|
+
db_file = Path(db_path)
|
|
164
|
+
abc.new("sqlite:///" + str(db_file), {k: float(observed[k]) for k in keys})
|
|
165
|
+
history = abc.run(minimum_epsilon=min_epsilon, max_nr_populations=max_populations)
|
|
166
|
+
|
|
167
|
+
particles, weights = history.get_distribution()
|
|
168
|
+
weighted = particles.copy()
|
|
169
|
+
weighted["weight"] = weights
|
|
170
|
+
rng = np.random.default_rng(seed)
|
|
171
|
+
n_out = n_posterior or len(particles)
|
|
172
|
+
picks = rng.choice(len(particles), size=n_out, p=np.asarray(weights) / np.sum(weights))
|
|
173
|
+
posterior = particles.iloc[picks].reset_index(drop=True)
|
|
174
|
+
posterior.index = pd.RangeIndex(n_out, name="iteration")
|
|
175
|
+
posterior.columns.name = None
|
|
176
|
+
epsilons = history.get_all_populations()["epsilon"]
|
|
177
|
+
return CalibrationResult(
|
|
178
|
+
posterior=posterior,
|
|
179
|
+
weighted=weighted,
|
|
180
|
+
n_populations=history.max_t + 1,
|
|
181
|
+
final_epsilon=float(epsilons.iloc[-1]),
|
|
182
|
+
)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Cost-effectiveness analysis (`heval.cea`).
|
|
2
|
+
|
|
3
|
+
Engine-agnostic decision analysis on the standard outcome schema:
|
|
4
|
+
incremental analysis with dominance and extended dominance, the efficiency
|
|
5
|
+
frontier, net monetary/health benefit, acceptability curves (CEAC), the
|
|
6
|
+
acceptability frontier (CEAF), and cost-effectiveness-plane data.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from heormodel.cea.ceac import ce_plane, ceac, ceaf
|
|
10
|
+
from heormodel.cea.frontier import STATUS_D, STATUS_ED, STATUS_ND, frontier, icer_table
|
|
11
|
+
from heormodel.cea.nb import expected_nmb, nhb, nmb
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"STATUS_D",
|
|
15
|
+
"STATUS_ED",
|
|
16
|
+
"STATUS_ND",
|
|
17
|
+
"ce_plane",
|
|
18
|
+
"ceac",
|
|
19
|
+
"ceaf",
|
|
20
|
+
"expected_nmb",
|
|
21
|
+
"frontier",
|
|
22
|
+
"icer_table",
|
|
23
|
+
"nhb",
|
|
24
|
+
"nmb",
|
|
25
|
+
]
|