Skip to main content

PyHEOR

Python tools for health economic modeling and cost-effectiveness analysis.

中文 · Français

Example results

Figures generated by the synthetic examples in this repository. Click an image to view it at full size.

Survival modeling Parameter uncertainty
PFS and OS survival curves for two strategies PSA incremental cost and QALY scatter plot
Compare fitted PFS and OS curves. Explore incremental costs and QALYs across PSA draws.
Cost-effectiveness acceptability (CEAC) Multi-strategy decision analysis (CEAF)
Two-strategy cost-effectiveness acceptability curves Cost-effectiveness acceptability frontier with strategy switches
See how cost-effectiveness probabilities vary with WTP. Track the recommended strategy and its cost-effectiveness probability.

Reproduce these figures with the PSM oncology and multi-strategy comparison examples. CEAC/CEAF show labeled display smoothing; analysis and exports retain raw probabilities.

Install

pip install pyheor

Requires Python 3.9+, NumPy, SciPy, pandas, matplotlib and openpyxl.

Models

Model Time Rewards
MarkovModel Explicit fixed cycles Cohort state and transition rewards
PSMModel Explicit fixed cycles Survival-based occupancy, optional Terminal bookkeeping
MicroSimModel Explicit fixed cycles Individual state and transition rewards, heterogeneity and common random numbers
DESModel Continuous, declared time_unit State reward rates integrated over time; event rewards at actual times

Base-case analysis, sensitivity analysis, strategy comparisons, CE planes, CEACs, traces and report exports are available. DES retains base-case and PSA support.

Quick start

State costs and QALYs are per cycle. Discount rates are effective rates per cycle, and the first cycle is undiscounted. Utility weights require an explicit conversion to QALYs.

import pyheor as ph

cycle = ph.Cycle(1, "month")
model = ph.MarkovModel(
    states=["Alive", "Dead"], strategies=["SOC", "TRT"],
    n_cycles=120, cycle=cycle, method="life-table",
    dr_cost=ph.rescale_discount_rate(.03, ph.Cycle(1, "year"), cycle),
    dr_qaly=ph.rescale_discount_rate(.03, ph.Cycle(1, "year"), cycle),
)
model.add_param("u_alive", base=.8, dist=ph.Beta(mean=.8, sd=.05))
model.set_transitions("SOC", [[.98, .02], [0, 1]])
model.set_transitions("TRT", [[.985, .015], [0, 1]])
model.set_state_cost("care", {
    "SOC": {"Alive": 1000}, "TRT": {"Alive": 1500},
})
model.set_state_qaly("health", {
    "Alive": lambda p, k: ph.qaly(p["u_alive"], cycle),
})
model.set_starting_cost("test", 200)
model.set_state_cost("loading", {"Alive": 500}, cycles=1)
model.set_transition_cost("terminal", "Alive", "Dead", 10000)

base = model.run_base_case()
print(base.summary())
print(base.icer())
psa = model.run_psa(n_sim=100, seed=42)

Reward APIs

Costs and QALYs have parallel state, starting, entry, transition and custom interfaces. State rewards accept cycles=1 or a list of cycle numbers. Callbacks receive parameters and optionally a 1-based cycle index and patient attributes. Starting rewards are unweighted time-zero amounts; first-cycle state rewards are weighted by occupancy.

method is beginning, end, or life-table (default). State counts and event flows follow heemod conventions. PSM cannot identify pairwise transition flows, so entry/transition reward registration is unavailable there; use an explicit Terminal state for the documented end-of-life accounting rule.

PSM PSA resamples draws when ordered survival endpoints cross within a strategy, and warns with the rejected-draw count. n_sim counts valid simulations; max_attempts defaults to 10 * n_sim. Base-case runs remain strict. Resampling conditions the parameter distribution on valid curve ordering.

Practical tools

Use these tools through import pyheor as ph. Model engines require explicit units; the helpers perform the conversions you request.

Tool Purpose and input convention
Cycle(length, unit) Define a period; .years, .in_unit(unit) and .time(k, unit=...) convert durations and cycle-boundary times
qaly(utility, duration, unit=None) Utility × duration in years; accepts a Cycle or numeric duration with an explicit unit, utility arrays and decrements
rescale_discount_rate(rate, from_period, to_period) Effective-rate conversion: (1 + rate) ** (target duration / source duration) - 1; use two Cycle objects or two numeric periods in the same unit
rescale_survival(curve, from_unit=..., to_period=...) Convert fitted survival time to model periods, including hazards and quantiles
from_flexsurv(distribution, **parameters) Construct a survival distribution from natural-scale R/flexsurv parameters; does not accept optimizer coefficients or convert time units
ScaledSurvival(curve, factor) Direct time scaling: S_new(t) = S_old(t * factor); prefer rescale_survival() when units are known
ProportionalHazards(curve, hr) Apply S_new(t) = S_old(t) ** hr; HR does not change time units
AcceleratedFailureTime(curve, af) Apply S_new(t) = S_old(t / af); af=1.2 extends survival times by 20%
Beta, Gamma, LogNormal with mean/sd Derive sampling parameters from natural-scale moments; LogNormal also accepts meanlog/sdlog
C Fill a transition row's remaining probability; at most one per row, e.g. [ph.C, .02] gives C=.98
import pyheor as ph

month = ph.Cycle(1, "month")
year = ph.Cycle(1, "year")
monthly_qaly = ph.qaly(.8, month)                    # 0.066667 QALY
monthly_cost = 12000 * month.years                  # Annual cost → monthly: 1000
monthly_dr = ph.rescale_discount_rate(.03, year, month)
three_month_qaly = ph.qaly(.8, 3, unit="month")      # 0.2 QALY
month_boundaries = month.time([0, 1, 12], unit="year")

# Fitted in years; after conversion t=1 means one month.
curve = ph.from_flexsurv("weibull", shape=1.3, scale=1.5)
monthly_curve = ph.rescale_survival(
    curve, from_unit="year", to_period=month,
)
treated_curve = ph.ProportionalHazards(monthly_curve, hr=.75)

Supported from_flexsurv() inputs:

Distribution Parameters
exp rate
weibull, weibullPH, llogis shape, scale
lnorm meanlog, sdlog
gompertz shape, rate
gengamma mu, sigma, Q
gengamma.orig shape, scale, k

GeneralizedGamma (including from_flexsurv("gengamma", ...)) uses the log-normal limit with meanlog=mu and sdlog=sigma when abs(Q) < 1e-5, to avoid numerical precision loss. This is exact at Q=0 and an approximation for nonzero Q. Excel export uses the same threshold; its positive-Q Gamma upper tails use 1-CDF, which can round very small probabilities to zero.

Parameters follow the respective R distribution definitions; names are not interchangeable across distributions. In particular, weibullPH uses a PH coefficient named scale, which the adapter converts to PyHEOR's Weibull scale.

Duration conventions are 12 months, 52 weeks or 365 days per year, rather than calendar date arithmetic. Multiply annual state cost rates by cycle years; enter one-time costs at their occurrence amounts. Put parameter-dependent QALY, discount-rate and survival conversions inside PSA/OWSA callbacks so they are recomputed for each draw.

Plots

Plot Purpose Entry point
State traces State occupancy over time; Markov also supports stacked areas Markov, PSM, MicroSim: base.plot_trace()
Survival curves Compare survival across strategies PSM, MicroSim, DES: base.plot_survival()
Partitioned survival area PFS, progressed and dead occupancy PSM: base.plot_state_area()
Individual outcome histograms Cost, QALY or life-year distributions MicroSim, DES: base.plot_outcomes_histogram()
Model / transition diagrams State structure and transitions Markov: base.plot_model_diagram(), base.plot_transition_diagram()
Tornado Rank parameter impacts owsa.plot_tornado()
PSA cost-effectiveness scatter Incremental costs and QALYs psa.plot_scatter()
CEAC Cost-effectiveness probability by strategy psa.plot_ceac()
PSA convergence Inspect simulation stability Markov, PSM: psa.plot_convergence()
Efficiency frontier / NMB curves Compare strategies and WTP thresholds cea.plot_frontier(), cea.plot_nmb_curve()
CEAF / EVPI curves Recommendation uncertainty and value of perfect information PSA-backed cea.plot_ceaf(), cea.plot_evpi()

Plots return Matplotlib Figure objects for customization and export:

fig = psa.plot_ceac(wtp_range=(0, 100000))
fig.savefig("ceac.png", dpi=150, bbox_inches="tight")

cea = ph.CEAnalysis.from_psa(psa)
cea.plot_ceaf(wtp_range=(0, 100000))

Display smoothing does not change analysis or exported values; empirical survival curves retain steps.

Results and export

base.summary() and base.icer() report totals and incremental analysis. reward_components provides cost/QALY components; cycle_rewards and state_occupancy provide cycle detail. DES also provides event logs and time in state; metadata records units and calculation conventions.

Use ph.export_to_excel(base, "results.xlsx") for result tables, ph.export_excel_model(base, "model.xlsx") for Markov/PSM formula workbooks, and ph.generate_report(model, "report.md") for an analysis report.

Examples

Runnable examples cover Markov, PSM, MicroSim and strategy comparisons. See CHANGELOG for version history.

Development

See development and versioning rules.

pip install -e '.[dev]'
pytest

License: AGPL-3.0-or-later.

Metadata

Release files for pyheor 0.4.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pyheor 0.4.1
File Size Uploaded Provenance Actions
pyheor-0.4.1.tar.gz 152.4 kB With provenance Details for pyheor-0.4.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for pyheor 0.4.1
File Interpreter ABI Platform Actions
pyheor-0.4.1-py3-none-any.whl Python 3 none any Details for pyheor-0.4.1-py3-none-any.whl

Total release size: 268.1 kB

Release files / pyheor-0.4.1.tar.gz

Download URL pyheor-0.4.1.tar.gz
Size 152.4 kB
Tags Source
SHA-256 checksum
How to use checksums
e816fadbd350b69994685c0eef62e7d60e4066fcea45b9da96400d5c10af42e4
BLAKE2b-256 checksum
How to use checksums
eb1feae0df5c0787edeb6b0cfaad354f9f80088b2ac8b1d35f58f7a519e745a0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 8, 2026.

Transparency log

Release files / pyheor-0.4.1-py3-none-any.whl

Download URL pyheor-0.4.1-py3-none-any.whl
Size 115.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
554fb1a75d503776f5506a5faf8508ff84dbcaeb1d08f36486161d8fbaee8af5
BLAKE2b-256 checksum
How to use checksums
e55dad63f0a6363ed015f7a2ec72e4548914c0347c1c7cac4acfd0aed0aa006b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 8, 2026.

Transparency log

Security for pyheor 0.4.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for pyheor-0.4.1.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 8, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page