Skip to main content

gen_surv

Simulate survival data with a known truth.

PyPI Python Tests Coverage Docs License

gen_surv generates synthetic time-to-event datasets from twelve models — proportional hazards, accelerated failure time, competing risks, cure fractions, piecewise hazards, recurrent events and two illness-death processes — so you can test an estimator against parameters you chose yourself.

It is a Python port of the R package genSurv, extended well past the original's four models.

📖 Documentation · 🚀 Quickstart · 🧪 Choosing a model

Install

pip install gen-surv

Python 3.11, 3.12 and 3.13. Everything is included except scikit-survival, which is optional and needed only for the two conversion helpers:

pip install scikit-survival

The package ships py.typed, so mypy and pyright check your calls into it rather than treating it as untyped.

Thirty seconds

from gen_surv import generate

df = generate(
    model="cphm",           # Cox proportional hazards
    n=6,
    beta=0.5,               # log hazard ratio
    covariate_range=2.0,    # X0 ~ Uniform(0, 2)
    model_cens="uniform",
    cens_par=1.0,
    seed=42,
)
print(df)
       time  status        X0
0  0.438878     0.0  1.547912
1  0.094177     0.0  1.394736
2  0.037041     1.0  1.522279
3  0.370798     0.0  0.900772
4  0.646901     1.0  1.287730
5  0.251113     1.0  0.454477

You picked beta = 0.5, so you know what a correct estimator should recover:

from lifelines import CoxPHFitter

df = generate(model="cphm", n=5000, beta=0.5, covariate_range=2.0,
              model_cens="uniform", cens_par=1.0, seed=7)

CoxPHFitter().fit(df, duration_col="time", event_col="status").params_
# X0    0.501

That is the whole idea. Every column was produced by a mechanism you specified, so anything an estimator gets wrong is the estimator's fault.

The twelve models

model= Family Rows per subject
cphm Cox proportional hazards 1
aft_ln Log-normal AFT 1
aft_weibull Weibull AFT 1
aft_log_logistic Log-logistic AFT 1
piecewise_exponential Piecewise constant hazard 1
competing_risks Cause-specific constant hazards 1
competing_risks_weibull Cause-specific Weibull hazards 1
mixture_cure Logistic cure + exponential failure 1
cmm Illness-death, counting-process intervals 2 or 3
thmm Illness-death, observed state panel 2 or 3
tdcm Cox with a time-dependent covariate 1
recurrent_events Repeated events: Andersen-Gill, PWP 1 per at-risk interval

Every model has a page covering its parameters, the mathematics, a worked example, and a check that the parameters can be recovered from the data it generates — start at Choosing a model.

The output shape is not the same for every model. Multi-state generators return several rows per subject, and column names differ between families. See Output schemas before writing code that consumes a generated frame.

A thirteenth, outside generate()

The twelve above each fix a state structure. When yours is not among them, gen_multistate takes the graph itself:

from gen_surv import ExponentialBaseline, Transition, WeibullBaseline, gen_multistate

multistate = gen_multistate(
    n=500,
    transitions=[
        Transition(1, 2, WeibullBaseline(shape=1.2, scale=3.0), [0.4]),  # fall ill
        Transition(2, 1, ExponentialBaseline(rate=0.6), [0.0]),          # recover
        Transition(2, 3, ExponentialBaseline(rate=0.2), [0.6]),          # die while ill
    ],
    clock="reset",
    seed=1,
)

Every edge carries its own baseline hazard and coefficients. A state with no outgoing edge is absorbing, and cycles are allowed - recovery above is a transition like any other. clock="forward" measures the hazard from entry to the study, giving a Markov process; clock="reset" restarts it at each state, giving a semi-Markov one.

cmm and thmm are configurations of this engine rather than separate implementations. It takes a list of objects rather than scalars, so it has no model= string and no command-line form - import it directly. See The multistate engine.

The ground truth, not just the data

A generated frame looks like a real one — which means it hides the same things. simulate() hands back what a real dataset never could:

from gen_surv import simulate

result = simulate("cphm", n=1000, beta=0.5, covariate_range=2.0,
                  model_cens="uniform", cens_par=1.0, seed=42)

result.data                      # the frame generate() would return
result.config                    # model, parameters, seed, gen_surv version
result.truth["event_time"]       # when each subject would have failed
result.truth["censoring_time"]   # what censoring hid

Several models draw their coefficients for you when you leave them out. result.truth["betas"] is the only way to learn what they were — without it those datasets cannot validate anything.

Any hazard shape

Every generator that draws a time inverts a cumulative hazard, so the shape is a parameter rather than a fork in the code:

from gen_surv import generate, LogLogisticBaseline

recurrent = generate(model="recurrent_events", n=500,
                     baseline=LogLogisticBaseline(shape=2.0, scale=1.5),
                     betas=[0.4, -0.2], followup_time=6.0, seed=1)

Exponential, Weibull, Gompertz, log-logistic and piecewise-constant are built in, and anything implementing hazard, cumulative_hazard and its inverse works too.

Beyond generating

from gen_surv import describe_survival, plot_survival_curve, export_dataset, to_sksurv

describe_survival(df)              # events, censoring, median follow-up
plot_survival_curve(df)            # Kaplan-Meier, optionally stratified
export_dataset(df, "data.rds")     # csv, json, feather or rds
to_sksurv(df)                      # structured array for scikit-survival
  • Ground truth — configurations, latent times, the coefficients actually used
  • Baseline hazards — the five families, and writing your own
  • Censoring — the built-in mechanisms, hitting a target event rate, applying your own distribution
  • Covariates — the three schemes across model families
  • Summaries — event counts, quality checks, dataset comparison
  • Plotting — survival curves, hazard comparisons, covariate effects
  • Fitting models — lifelines, scikit-survival, scikit-learn, R

Command line

gen_surv dataset cphm --n 1000 --beta 0.5 --seed 42 -o survival.csv
gen_surv visualize survival.csv --group-col X0 --output km.png

Repeat a flag for list arguments — --beta 0.5 --beta -0.3. Every one of the twelve models is reachable from the command line. Full option reference in the CLI guide.

Reproducibility

Every generator takes a seed, accepting an int or a numpy.random.Generator. The same seed on the same version always gives the same frame, on any platform.

A bug fix in a sampler changes the draws a seed produces, so pin the version alongside the seed for anything that must reproduce:

gen-surv==3.1.2

See Reproducibility.

Documentation

https://diogoribeiro7.github.io/genSurvPy/

Section Contents
Getting started Installation, quickstart, output schemas, reproducibility
Models Per-model parameters, mathematics, examples, recovery checks
Guides Censoring, covariates, summaries, plotting, export, interoperability, CLI
Theory The mathematics behind every generator, plus the bibliography
API Full reference, generated from the source

Built with Material for MkDocs and mkdocstrings, and rebuilt from the release tag by the Pages workflow — so it documents the version on PyPI, not unreleased work.

Development

git clone https://github.com/DiogoRibeiro7/genSurvPy.git
cd genSurvPy
poetry install --with dev

pre-commit install
pre-commit run --all-files     # black, isort, flake8, mypy
pytest                         # tests needing optional packages skip themselves

Docs:

poetry install --with docs
poetry run mkdocs serve        # live reload on http://127.0.0.1:8000

On Debian and Ubuntu, building scikit-survival may need build-essential gfortran libopenblas-dev.

Work happens on develop; main carries releases. See CONTRIBUTING.md.

Citation

@software{ribeiro_gensurv,
  title   = {gen_surv: Survival Data Simulation in Python},
  author  = {Diogo Ribeiro},
  url     = {https://github.com/DiogoRibeiro7/genSurvPy},
  version = {3.1.2}
}

Machine-readable metadata: CITATION.cff and .zenodo.json.

License

MIT — see LICENSE.

Author

Diogo Ribeiro — ESMAD, Instituto Politécnico do Porto

GitHub stars GitHub forks

Metadata

Release files for gen-surv 3.1.2

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

Source distribution (sdist)

Source distribution for gen-surv 3.1.2
File Size Uploaded Provenance Actions
gen_surv-3.1.2.tar.gz 64.1 kB With provenance Details for gen_surv-3.1.2.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for gen-surv 3.1.2
File Interpreter ABI Platform Actions
gen_surv-3.1.2-py3-none-any.whl Python 3 none any Details for gen_surv-3.1.2-py3-none-any.whl

Total release size: 139.5 kB

Release files / gen_surv-3.1.2.tar.gz

Download URL gen_surv-3.1.2.tar.gz
Size 64.1 kB
Tags Source
SHA-256 checksum
How to use checksums
45d90a1b77f489ad7dfcb3870e60c2e180980c698c68a624fc7c39a06fddc36f
BLAKE2b-256 checksum
How to use checksums
628d1b2ac456a43ec8a610cd1f78a254804385b2a7c81f033a9b00d2c016f356
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 Aug 30, 2026.

Transparency log

Release files / gen_surv-3.1.2-py3-none-any.whl

Download URL gen_surv-3.1.2-py3-none-any.whl
Size 75.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c56f0c143b423e8a9c35498693a2d71f5e3c652a2577fa7e28bfef2f96db1f46
BLAKE2b-256 checksum
How to use checksums
5575fdd96f7e0fd69b87945faea3f16bee7a09b8126c2220c3fe6a2d952b9503
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 Aug 30, 2026.

Transparency log

Security for gen-surv 3.1.2

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

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for gen_surv-3.1.2.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 Aug 30, 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