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 uses the order in survival_endpoints as the nesting order (e.g. ["PFS", "PFS2", "OS"]). At each time, it preserves the final curve and works backward: S[j] = min(S[j], S[j+1]), using the already corrected next curve. State occupancy, plots and Excel exports use the corrected curves. Base-case and PSA calculations follow the same rule; PSA keeps every sampled parameter set. Individual curves must still be finite, within [0, 1], and nonincreasing. This adjustment changes state time and rewards where curves cross; inspect substantial crossovers when selecting extrapolations.

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.5.0

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.5.0
File Size Uploaded Provenance Actions
pyheor-0.5.0.tar.gz 154.1 kB With provenance Details for pyheor-0.5.0.tar.gz

Built distribution (wheel)

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

Total release size: 269.6 kB

Release files / pyheor-0.5.0.tar.gz

Download URL pyheor-0.5.0.tar.gz
Size 154.1 kB
Tags Source
SHA-256 checksum
How to use checksums
45c1cd7ed8418d04e16a002e393af7698948769f9d375cfafd7cdf23126f4d0a
BLAKE2b-256 checksum
How to use checksums
3784e2cc602a50978537b33a2656c094337d050589208f5e1c78f5e1c7978674
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 31, 2026.

Transparency log

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

Download URL pyheor-0.5.0-py3-none-any.whl
Size 115.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e2aac3e4c6b55b3b44e6873ff7809486c6aed76001c917bed45337f9e7fd646a
BLAKE2b-256 checksum
How to use checksums
b12abbb1027d35d44969aaf461a87a68315444a2f53ae5b4cabc8aa8790eecb6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 31, 2026.

Transparency log

Security for pyheor 0.5.0

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.5.0.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 31, 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