Modern survival analysis for Python: Narwhals-native, R-validated, beautifully visualized.

AI / Agents

Skills
llms.txt
llms-full.txt

Developers

Richard Iannone

Maintainer

Community

Contributing guide
Code of conduct
Project roadmap
Security policy
Full license MIT
Citing greenwood

Meta

Requires: Python >=3.10
Provides-Extra: pd, pl, pa, formula, plotnine, altair, tables, fast, all, dev, docs
Package Info

Modern survival analysis for Python.

Greenwood is a time-to-event modeling library built for 2026: it works on pandas, Polars, PyArrow, and anything else Narwhals supports; it is validated to tolerance against R’s survival package; it visualizes beautifully, with a choice of plotting backends; and it plugs into the Great Tables ecosystem for publication-quality tables. The name is a nod to Greenwood’s formula, the classic variance estimator for the Kaplan-Meier curve.

What’s present

This release centers on the Surv response object, which handles right, left, interval, and counting-process censoring (including left truncation and multi-state endpoints).

The core estimators are KaplanMeier (survival curves with Greenwood confidence intervals, medians, quantiles, and restricted mean survival time) and NelsonAalen (cumulative hazard). For group comparisons, the logrank_test implements the log-rank test and the G-rho/Fleming-Harrington family. You also get CoxPH for proportional hazards regression, with interactive curves and aligned risk tables.

Every statistic is validated to tolerance against R’s survival package. Parametric models, competing risks, and Cox model extensions will arrive in later releases. See the project roadmap.

A first look

import greenwood as gw

# Load a bundled dataset and build a response (lung codes 1 = censored, 2 = dead).
df = gw.load_dataset("lung")
y = gw.Surv.right(df["time"], event=(df["status"] == 2))

# Kaplan-Meier survival, stratified, with log-log confidence intervals.
km = gw.KaplanMeier(conf_type="log-log").fit(y, by=df["sex"])
km.to_frame(format="pandas").head()
km.median(ci=True)

# Compare groups with the log-rank test.
gw.logrank_test(y, group=df["sex"])

# Publication-quality curves with an aligned numbers-at-risk table.
gw.plot_survival(km, risk_table=True)

# Cox proportional hazards, with broom-style hazard ratios.
cox = gw.CoxPH().fit(y, df[["age", "sex"]])
gw.tidy(cox, exponentiate=True)