gen_surv
Simulate survival data with a known truth.
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
- ORCID: https://orcid.org/0009-0001-2022-7072
- Email: dfr@esmad.ipp.pt · diogo.debastos.ribeiro@gmail.com
- GitHub: @DiogoRibeiro7
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)
| 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)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| 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 logRelease 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 logSecurity 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.
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 logA 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.