![Logo](assets/logo-light.png) ![Logo](assets/logo-dark.png)

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

### Modern survival analysis for Python

Greenwood turns time-to-event data into estimates, tests, models, and figures. Whether you're tracking patient outcomes, equipment failure times, or any event that takes time to happen, Greenwood provides a complete toolkit for survival analysis.

Unlike many statistical libraries, Greenwood is built on [Narwhals](https://narwhals-dev.github.io/narwhals/) for backend-agnostic compute, validated to tolerance against R's gold-standard **survival** package, and visualized with publication-quality interactive plots and tables. It works with Pandas, Polars, PyArrow, or any dataframe library you prefer.

### Why Greenwood?

- **Works with your dataframe library**: Pandas, Polars, PyArrow, or any Narwhals-compatible frame (use the `format=` argument to control output types).
- **Rigorously validated**: every statistic is tested to tolerance against R's **survival** package, so results match what the field already trusts.
- **Beautiful visualizations**: interactive survival curves, forest plots, and cumulative incidence plots with a choice of plotting backends.
- **Publication-ready tables**: built-in integration with [Great Tables](https://posit-co.github.io/great-tables/) for production-quality reporting.
- **Complete toolkit**: from simple Kaplan-Meier curves to Cox regression, competing risks, and multi-state models (everything you need, all validated)\`.

### The analytical pipeline

Every Greenwood analysis follows a consistent workflow:

1.  **Represent the data** with a formula such as `"Surv(time, status) ~ age"` or an [Outcome](./reference/Outcome.md#greenwood.Outcome) that names your columns, or with [Surv()](./reference/Surv.md#greenwood.Surv) for values already in hand: capture survival outcomes and censoring (right, left, interval, or counting-process form) in a validated response object.
2.  **Estimate and summarize**: use [KaplanMeier](./reference/KaplanMeier.md#greenwood.KaplanMeier), [NelsonAalen](./reference/NelsonAalen.md#greenwood.NelsonAalen), or [Turnbull](./reference/Turnbull.md#greenwood.Turnbull) (for interval-censored data) to describe the data, then compare groups with [logrank_test](./reference/logrank_test.md#greenwood.logrank_test), trend tests, or RMST comparisons.
3.  **Build and fit models**: fit Cox proportional hazards, parametric AFT models, or competing-risks models to examine covariate effects.
4.  **Visualize and evaluate**: create publication-quality plots with [plot_survival](./reference/plot_survival.md#greenwood.plot_survival), `forest_plot`, or `cif_plot`, and assess model performance with concordance, Brier scores, or cross-validation.
5.  **Report results**: tidy model summaries and publication tables are built in via broom-compatible [tidy()](./reference/tidy.md#greenwood.tidy) functions and Great Tables.

### A first look

Here's a complete example: load survival data, estimate a curve, test for differences, and visualize.

**Step 1: Describe the response by column names**

``` python
import greenwood as gw

# Load data and describe the survival response (a status of 2 marks a death)
lung = gw.load_dataset("lung", backend="polars")
death = gw.Outcome.surv(time="time", event="status")

# Evaluate it against the frame to see the Surv object it produces
death.bind(lung)
```

    Surv(type=right, n=228, events=165)

**Step 2: Estimate Kaplan-Meier survival curves, stratified by sex**

``` python
# Fit stratified Kaplan-Meier with log-log confidence intervals
km = gw.KaplanMeier(conf_type="log-log").fit(death, by="sex", data=lung)

# Get median survival per group with 95% confidence intervals
km.median(format="polars")
```

shape: (2, 4)

| strata  | time  | conf_low | conf_high |
|---------|-------|----------|-----------|
| str     | f64   | f64      | f64       |
| "sex=1" | 270.0 | 210.0    | 306.0     |
| "sex=2" | 426.0 | 345.0    | 524.0     |

**Step 3: Compare survival between groups with a log-rank test**

``` python
# Test for differences in survival between sexes
test_result = gw.logrank_test("Surv(time, status) ~ sex", data=lung)
test_result
```

    TestResult(method='Log-rank test', statistic=10.3267, df=1, p_value=0.001311)

**Step 4: Visualize with an interactive plot and at-risk table**

``` python
# Create publication-quality survival curves
gw.plot_survival(km, risk_table=True)
```

**Step 5: Fit a Cox proportional hazards model**

``` python
# Model covariate effects on hazard, written as an R-style formula
cox = gw.CoxPH().fit("Surv(time, status) ~ age + sex", data=lung)

# Get hazard ratios with confidence intervals
gw.tidy(cox, exponentiate=True, format="polars")
```

shape: (2, 7)

| term  | estimate | std_error | statistic | p_value  | conf_low | conf_high |
|-------|----------|-----------|-----------|----------|----------|-----------|
| str   | f64      | f64       | f64       | f64      | f64      | f64       |
| "age" | 1.017191 | 0.009223  | 1.848078  | 0.064591 | 0.998969 | 1.035747  |
| "sex" | 0.598566 | 0.167458  | -3.06476  | 0.002178 | 0.431094 | 0.831099  |

### What's inside

**Descriptive statistics**: Kaplan-Meier curves with Greenwood confidence intervals, restricted mean survival time (RMST), quantiles, and medians. Nelson-Aalen cumulative hazard. Turnbull's NPMLE for interval-censored data. Event tables and risk sets.

**Group comparisons**: Log-rank and Peto-Peto tests, Fleming-Harrington tests for weighted comparisons, linear trend tests across ordered groups, and RMST hypothesis tests with pairwise comparisons.

**Regression models**: Cox proportional hazards with stratification, baseline hazard prediction with confidence intervals, and full diagnostics. Parametric accelerated failure time (AFT) models with Weibull, exponential, log-normal, and log-logistic distributions. Competing risks via Aalen-Johansen and Fine-Gray models. Multi-state transition probabilities.

**Visualization**: interactive survival curves with confidence bands, censoring marks, and aligned at-risk tables. Forest plots with hazard ratios. Cumulative incidence plots for competing risks. All plots are interactive and publication-ready.

**Model evaluation**: Concordance index (Harrell's C-statistic), IPCW Brier scores, integrated Brier scores, and k-fold cross-validation with stratification support.

**Built-in data**: load bundled datasets (`lung`, `veteran`, `ovarian`, `pbc`, `colon`, `mgus2`) in any supported backend.

### Next steps

- **Installation**: get started with `pip install greenwood` or see [Installation](user-guide/installation.md).
- **Quick tour**: new to Greenwood? Start with the [Quick start](user-guide/quick-start.md) for a fast hands-on tour (5 min read).
- **Learn survival analysis**: new to survival analysis? Begin with [Survival data and the Surv object](user-guide/survival-data.md) to understand censoring.
- **Build models**: jump to [Cox regression](user-guide/cox-regression.md) or [Parametric models](user-guide/parametric-models.md).
- **Explore everything**: see the full [User Guide](user-guide/get-started.md) for comprehensive coverage of all features.
- **API reference**: browse the [Python API reference](reference/) for function signatures and detailed documentation.
