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.
Files changed (56) hide show
  1. heormodel-0.6.0/PKG-INFO +154 -0
  2. heormodel-0.6.0/README.md +111 -0
  3. heormodel-0.6.0/pyproject.toml +68 -0
  4. heormodel-0.6.0/setup.cfg +4 -0
  5. heormodel-0.6.0/src/heormodel/__init__.py +43 -0
  6. heormodel-0.6.0/src/heormodel/calibrate/__init__.py +16 -0
  7. heormodel-0.6.0/src/heormodel/calibrate/abc.py +182 -0
  8. heormodel-0.6.0/src/heormodel/cea/__init__.py +25 -0
  9. heormodel-0.6.0/src/heormodel/cea/ceac.py +130 -0
  10. heormodel-0.6.0/src/heormodel/cea/frontier.py +134 -0
  11. heormodel-0.6.0/src/heormodel/cea/nb.py +62 -0
  12. heormodel-0.6.0/src/heormodel/dsa/__init__.py +18 -0
  13. heormodel-0.6.0/src/heormodel/dsa/design.py +182 -0
  14. heormodel-0.6.0/src/heormodel/models/__init__.py +26 -0
  15. heormodel-0.6.0/src/heormodel/models/_accrual.py +142 -0
  16. heormodel-0.6.0/src/heormodel/models/des.py +497 -0
  17. heormodel-0.6.0/src/heormodel/models/markov.py +298 -0
  18. heormodel-0.6.0/src/heormodel/models/microsim.py +442 -0
  19. heormodel-0.6.0/src/heormodel/models/outcomes.py +221 -0
  20. heormodel-0.6.0/src/heormodel/models/protocol.py +51 -0
  21. heormodel-0.6.0/src/heormodel/params/__init__.py +37 -0
  22. heormodel-0.6.0/src/heormodel/params/distributions.py +307 -0
  23. heormodel-0.6.0/src/heormodel/params/inputs.py +174 -0
  24. heormodel-0.6.0/src/heormodel/params/mix.py +90 -0
  25. heormodel-0.6.0/src/heormodel/params/sampling.py +211 -0
  26. heormodel-0.6.0/src/heormodel/py.typed +0 -0
  27. heormodel-0.6.0/src/heormodel/report/__init__.py +31 -0
  28. heormodel-0.6.0/src/heormodel/report/plots.py +403 -0
  29. heormodel-0.6.0/src/heormodel/report/provenance.py +141 -0
  30. heormodel-0.6.0/src/heormodel/run/__init__.py +11 -0
  31. heormodel-0.6.0/src/heormodel/run/_progress.py +134 -0
  32. heormodel-0.6.0/src/heormodel/run/diagnostics.py +49 -0
  33. heormodel-0.6.0/src/heormodel/run/runner.py +209 -0
  34. heormodel-0.6.0/src/heormodel/run/seeds.py +81 -0
  35. heormodel-0.6.0/src/heormodel/voi/__init__.py +27 -0
  36. heormodel-0.6.0/src/heormodel/voi/_metamodel.py +89 -0
  37. heormodel-0.6.0/src/heormodel/voi/evpi.py +51 -0
  38. heormodel-0.6.0/src/heormodel/voi/evppi.py +107 -0
  39. heormodel-0.6.0/src/heormodel/voi/evsi.py +134 -0
  40. heormodel-0.6.0/src/heormodel.egg-info/PKG-INFO +154 -0
  41. heormodel-0.6.0/src/heormodel.egg-info/SOURCES.txt +54 -0
  42. heormodel-0.6.0/src/heormodel.egg-info/dependency_links.txt +1 -0
  43. heormodel-0.6.0/src/heormodel.egg-info/requires.txt +29 -0
  44. heormodel-0.6.0/src/heormodel.egg-info/top_level.txt +1 -0
  45. heormodel-0.6.0/tests/test_calibrate.py +48 -0
  46. heormodel-0.6.0/tests/test_cea.py +158 -0
  47. heormodel-0.6.0/tests/test_des.py +308 -0
  48. heormodel-0.6.0/tests/test_dsa.py +123 -0
  49. heormodel-0.6.0/tests/test_markov.py +257 -0
  50. heormodel-0.6.0/tests/test_markov_vs_microsim.py +122 -0
  51. heormodel-0.6.0/tests/test_microsim.py +297 -0
  52. heormodel-0.6.0/tests/test_outcomes_run.py +153 -0
  53. heormodel-0.6.0/tests/test_params.py +326 -0
  54. heormodel-0.6.0/tests/test_report.py +112 -0
  55. heormodel-0.6.0/tests/test_run_parallel.py +105 -0
  56. heormodel-0.6.0/tests/test_voi.py +192 -0
@@ -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
+ [![CI](https://github.com/pedroliman/heormodel/actions/workflows/ci.yml/badge.svg)](https://github.com/pedroliman/heormodel/actions/workflows/ci.yml)
47
+ [![codecov](https://codecov.io/gh/pedroliman/heormodel/branch/main/graph/badge.svg)](https://codecov.io/gh/pedroliman/heormodel)
48
+ [![PyPI](https://img.shields.io/pypi/v/heormodel.svg)](https://pypi.org/project/heormodel/)
49
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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
+ [![CI](https://github.com/pedroliman/heormodel/actions/workflows/ci.yml/badge.svg)](https://github.com/pedroliman/heormodel/actions/workflows/ci.yml)
4
+ [![codecov](https://codecov.io/gh/pedroliman/heormodel/branch/main/graph/badge.svg)](https://codecov.io/gh/pedroliman/heormodel)
5
+ [![PyPI](https://img.shields.io/pypi/v/heormodel.svg)](https://pypi.org/project/heormodel/)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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
+ ]