Metadata-Version: 2.4
Name: survival
Version: 1.3.0
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Science/Research
Classifier: Intended Audience :: Healthcare Industry
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: POSIX :: Linux
Classifier: Operating System :: MacOS
Classifier: Operating System :: Microsoft :: Windows
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Rust
Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
Classifier: Topic :: Scientific/Engineering :: Mathematics
Classifier: Typing :: Typed
Requires-Dist: numpy>=1.20.0
Requires-Dist: pre-commit==4.6.2 ; extra == 'dev'
Requires-Dist: pytest==9.1.1 ; extra == 'dev'
Requires-Dist: pytest-cov==7.1.0 ; extra == 'dev'
Requires-Dist: ruff==0.16.3 ; extra == 'dev'
Requires-Dist: numpy==2.4.6 ; extra == 'dev'
Requires-Dist: mypy==2.3.0 ; extra == 'dev'
Requires-Dist: scikit-learn>=1.0.0 ; extra == 'sklearn'
Requires-Dist: pytest==9.1.1 ; extra == 'test'
Requires-Dist: pytest-cov==7.1.0 ; extra == 'test'
Requires-Dist: numpy==2.4.6 ; extra == 'test'
Requires-Dist: pandas==3.0.5 ; extra == 'test'
Requires-Dist: polars==1.43.2 ; extra == 'test'
Provides-Extra: dev
Provides-Extra: sklearn
Provides-Extra: test
License-File: LICENSE
Summary: A high-performance survival analysis library written in Rust with Python bindings
Keywords: survival-analysis,kaplan-meier,cox-regression,statistics,biostatistics,rust
Author-email: Cameron Lyons <cameron.lyons2@gmail.com>
Maintainer-email: Cameron Lyons <cameron.lyons2@gmail.com>
License: MIT
Requires-Python: >=3.11
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Documentation, https://github.com/Cameron-Lyons/survival#python-package-layout
Project-URL: Issues, https://github.com/Cameron-Lyons/survival/issues
Project-URL: Repository, https://github.com/Cameron-Lyons/survival

# survival

[![Crates.io](https://img.shields.io/crates/v/survival.svg)](https://crates.io/crates/survival)
[![PyPI version](https://img.shields.io/pypi/v/survival.svg)](https://pypi.org/project/survival/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

A high-performance survival analysis library written in Rust, with a Python API powered by [PyO3](https://github.com/PyO3/pyo3) and [maturin](https://github.com/PyO3/maturin).

## Features

- Core survival analysis routines
- Cox proportional hazards models with frailty
- Kaplan-Meier and Aalen-Johansen (multi-state) survival curves
- Nelson-Aalen estimator
- Parametric accelerated failure time models
- Fine-Gray competing risks model
- Penalized splines (P-splines) for smooth covariate effects
- Concordance index calculations
- Person-years calculations
- Score calculations for survival models
- Residual analysis (martingale, Schoenfeld, score residuals)
- Bootstrap confidence intervals
- Cross-validation for model assessment
- Statistical tests (log-rank, likelihood ratio, Wald, score, proportional hazards)
- Sample size and power calculations
- RMST (Restricted Mean Survival Time) analysis
- Landmark analysis
- Calibration and risk stratification
- Time-dependent AUC
- Conditional logistic regression
- Time-splitting utilities

## Installation

### From PyPI (Recommended)

```sh
pip install survival
```

### From Source

#### Prerequisites

- Python 3.11+
- Rust 1.94+ (see [rustup.rs](https://rustup.rs/))
- [maturin](https://github.com/PyO3/maturin)

Install maturin:
```sh
pip install maturin
```

#### Build and Install

Build the Python wheel:
```sh
maturin build --release
```

The default source build keeps optional ML bindings out of the extension. To
build the full Python surface locally, include the ML feature explicitly:

```sh
maturin build --release --features extension-module,ml
```

Install the wheel:
```sh
pip install target/wheels/survival-*.whl
```

For development:
```sh
maturin develop --release
```

For development against ML bindings:
```sh
maturin develop --release --features extension-module,ml
```

## Python Package Layout

Prefer domain modules in new code:

```python
from survival import core, datasets, regression, surv_analysis, validation

lung = datasets.load_lung()
fit = regression.survreg(...)
km = surv_analysis.survfitkm(...)
score = validation.rmst(...)
```

R-style entry points are intentionally available from the package root for users
porting code from R's `survival` package:

```python
from survival import (
    Surv,
    aic,
    as_data_frame,
    basehaz,
    clogit,
    coxph,
    fitted,
    predict,
    survdiff,
    survfit,
    survreg,
)

data = {
    "time": [1.0, 2.0, 3.0, 4.0],
    "status": [1, 1, 0, 1],
    "group": ["control", "control", "treated", "treated"],
    "age": [52.0, 61.0, 58.0, 63.0],
}

km = survfit("Surv(time, status) ~ group", data=data)
km_table = as_data_frame(km)
cox_model = coxph("Surv(time, status) ~ group + age", data=data)
risk_scores = predict(cox_model, [[1.0, 60.0]], type="risk")
training_lp = fitted(cox_model)
model_aic = aic(cox_model)
hazard_times, cumulative_hazard = basehaz(cox_model)
aft_model = survreg("Surv(time, status) ~ group + age", data=data)
```

Formula support is intentionally conservative: `+` terms, `.` expansion,
`-` exclusions, backtick-quoted column names, categorical treatment coding,
`factor(...)` / `as.factor(...)`, `strata(...)`, interaction terms with `:`
or `*`, and numeric `offset(...)` terms are supported, along with one-column
numeric transforms `log(...)`, `sqrt(...)`, and `exp(...)`, plus
`I(...)`/`identity(...)` arithmetic with `+`, `-`, `*`, `/`, and `^`;
time transforms should use the lower-level matrix APIs until they have
dedicated Rust-backed support.
Formula calls also accept `subset=` as a boolean mask or zero-based row indices
and `na_action="omit"` for row-wise missing-data omission across formula
columns and external row-aligned arrays such as `weights`, `offset`, and
`strata`.
R `survobrien` formula expansion preserves factor keeper columns while applying
the risk-set transform only to continuous terms.
R `finegray` formulas use the same Python formula engine and Rust interval
expansion, with sorted censoring-risk sweeps and R-compatible factor classes.
Kaplan-Meier `survfit` calls honor `conf_level=`, R-style `conf_type=`
choices for confidence intervals, `start_time=` for conditional curves, and
`time0=True` to include the starting row.
They support right-censored `Surv(time, event)` data and counting-process
`Surv(start, stop, event)` data with delayed-entry risk sets. Direct and
formula `Surv(...)` calls also accept R-style named aliases including `time=`,
`time1=`, `start=`, `time2=`, `stop=`, `event=`, and `status=`.
Factor-valued event responses produce multi-state Aalen--Johansen curves.
These curves support subject histories through `id=`, observed initial states
through `istate=`, event-type conversion through `etype=`, user-supplied
initial distributions through `p0=`, and entry counts through `entry=True`.
`survfit0(...)` inserts the initial state-probability row into existing
multi-state curves while preserving their typed count, hazard, and uncertainty
outputs.
Multi-state fits with retained model frames also support influence residuals
and pseudo-values for state probabilities, cumulative transition hazards, and
integrated state occupancy, including grouped, weighted, and subject-collapsed
counting-process results.
Fitted Cox models can also be passed to `survfit(...)` with optional `newdata=`
to produce model-based survival curves.
The R facade's low-level `coxsurv.fit` and `survfitcoxph.fit` entry points use
an `O(n log n)` Rust risk-set sweep for weighted, stratified, tied-event, and
counting-process baselines, while retaining R-compatible curve and uncertainty
shapes for ordinary predictions and individual time-dependent trajectories.
`survdiff` uses the same right-censored and delayed-entry response forms.
`coxph` uses Efron's tie handling by default, matching R, and also accepts
`ties="breslow"` or the compatibility alias `method="breslow"`.
Formula fits support `tt(...)` time-varying coefficient terms for right-censored
and counting-process responses, including R's default O'Brien rank transform
and custom `tt(x, time, riskset, weights)` callables.
`clogit("case ~ exposure + strata(set)", data=...)` fits matched case-control
models through the exact stratified Cox likelihood; `method="approximate"`
maps to Breslow handling as it does in R.
`cch("Surv(time, status) ~ exposure + group", data=..., subcoh="sampled",
id="subject", cohort_size=...)` fits case-cohort models with the native
Prentice, Self-Prentice, or Lin--Ying estimators. Sampling-stratified designs
also support I.Borgan and II.Borgan with per-stratum population sizes.
Right-censored and counting-process responses share the Cox optimizer, formula
expansion supports numeric, factor, and interaction terms, and `robust=True`
selects Lin--Ying's robust variance. The risk-set, residual, and phase-two
covariance sweeps stay in Rust; Python performs only formula preparation and
result labeling.
R-style `coxph.control(...)` and `survreg.control(...)` helpers are available
in the bridge and pass named control lists through to the Python API.
Time-dependent start/stop data can be built with the R-compatible `tmerge`
workflow. Its update builders preserve R's `(tstart, tstop]` boundary rules,
event placement, cumulative updates, missing-value handling, and classification
metadata while using the native linear-time sweeps underneath:

```python
from survival import cumevent, cumtdc, event, tdc, tmerge

baseline = {"id": [1, 2], "group": ["control", "treated"]}
spans = {"id": [1, 2], "stop": [10.0, 8.0]}
updates = {
    "id": [1, 1, 2],
    "time": [2.0, 6.0, 4.0],
    "dose": [5.0, 3.0, 4.0],
    "status": [0, 1, 1],
}

timeline = tmerge(baseline, spans, "id", tstop="stop")
timeline = tmerge(
    timeline,
    updates,
    "id",
    dose=tdc("time", "dose", init=0.0),
    total_dose=cumtdc("time", "dose", init=0.0),
    endpoint=event("time", "status"),
    endpoint_count=cumevent("time", "status"),
)
```

The raw `tmerge`, `tmerge2`, and `tmerge3` sweeps remain available from
`survival.data_prep` for callers that already manage sorted numeric arrays.
The R-style `predict(...)` and `fitted(...)` generics support Cox linear
predictors, relative risk scores, term contributions, survival curves, and
expected event counts.
For `survreg` fits it supports response-scale predictions, linear predictors,
term contributions, and quantile predictions via `type="quantile"`.
The AFT optimizer uses positive-definite observed-information Newton steps when
available and falls back to the stable outer-product system otherwise. The R
bridge also routes built-in `survreg.fit` matrix calls through this kernel,
including fixed or stratified scales and interval-censored responses.
Model helpers include `model_formula`, `model_weights`, `df_residual`,
`loglik`, `aic`, `bic`, `extract_aic`, coefficient, variance-covariance,
confidence-interval, model-matrix/model-frame, and summary accessors for fitted
Cox and `survreg` models.
Common result objects can be converted to column-oriented tables with
`as_data_frame(...)`; the experimental R bridge exposes the same path through
`as.data.frame(...)`, `summary(...)`, and `print(...)` methods.
`Surv` responses also support table conversion for quick data inspection.
The `survival.residuals` name remains the residual diagnostics module; the
R-style residual generic is available as `survival.r_api.residuals(...)` for
fitted Cox and `survreg` models.

Other historical root-level algorithm names remain available for compatibility,
but module imports are the preferred style because they match the current repo
layout and keep the API easier to navigate. Legacy root-level algorithm names
are resolved lazily instead of being copied into the package namespace at import
time.

`survival.__all__` and `dir(survival)` expose the curated package surface:
domain modules, R-style entry points, and scikit-learn helpers. Legacy
root-level algorithm exports are still available for compatibility and are listed in
`survival.__deprecated_root_exports__`. In lean source builds, symbols that
require the Rust `ml` feature are omitted from their domain module until the
extension is built with `--features extension-module,ml`.

Common modules:
- `survival.datasets`: built-in example and benchmark datasets
- `survival.data_prep`: time splitting and data transformation helpers
- `survival.core`: shared concordance, spline, and low-level core routines
- `survival.regression`: Cox, AFT, competing-risks, cure, and recurrent-event models
- `survival.surv_analysis`: Kaplan-Meier, Nelson-Aalen, multistate, and log-rank helpers
- `survival.validation`: metrics, calibration, conformal, RMST, and statistical tests
- `survival.residuals`: martingale, Schoenfeld, and related residual diagnostics
- `survival.population`: expected-survival and rate-table routines
- `survival.monitoring`: drift and monitoring utilities
- `survival.ml`: neural, tree, and modern ML-oriented survival models
- `survival.reliability_tools`: reliability utilities; the top-level
  `survival.reliability` name remains the callable function

See [`docs/repo-layout.md`](docs/repo-layout.md) for the full Rust and Python
layout and [`examples/python_package_layout.py`](examples/python_package_layout.py)
for a runnable module-oriented example.

## Usage

### Aalen's Additive Regression Model

```python
import survival

data = {
    "time": [1.0, 2.0, 2.0, 3.0, 4.0, 4.0],
    "status": [1, 1, 1, 1, 0, 1],
    "age": [42.0, 55.0, 61.0, 49.0, 67.0, 38.0],
    "treatment": ["control", "treated", "control", "treated", "control", "treated"],
}

fit = survival.aareg(
    "Surv(time, status) ~ age + treatment",
    data=data,
    nmin=1,
)
print(fit.coefficient_names)
print(fit.coefficient)
```

The formula interface supports right-censored and counting-process responses,
case weights, factors and interactions, clustered influence estimates, tapering,
and retained model, design, and response data. The risk-set sweep and linear
algebra are implemented in Rust.

### Penalized Splines (P-splines)

```python
from survival import core

x = [0.1 * i for i in range(100)]
pspline = core.PSpline(
    x=x,
    df=10,
    theta=1.0,
    eps=1e-6,
    method="GCV",
    boundary_knots=(0.0, 10.0),
    intercept=True,
    penalty=True,
)
pspline.fit()
```

### Concordance Index

```python
from survival import core

time_data = [1.0, 2.0, 3.0, 4.0, 5.0, 1.0, 2.0, 3.0, 4.0, 5.0]
weights = [1.0, 1.0, 1.0, 1.0, 1.0]
indices = [0, 1, 2, 3, 4]
ntree = 5

result = core.perform_concordance1_calculation(time_data, weights, indices, ntree)
print(f"Concordance index: {result['concordance_index']}")
```

### Cox Regression with Frailty

```python
from survival import regression

result = regression.perform_cox_regression_frailty(
    time=[1.0, 2.0, 3.0, 4.0],
    event=[1, 1, 0, 1],
    covariates=[
        [0.2, 1.0],
        [0.1, 0.5],
        [0.4, 1.2],
        [0.3, 0.7],
    ],
    max_iter=20,
    eps=1e-5,
)
print(result["coefficients"])
```

### Person-Years Calculation

The high-level API accepts a `tcut` result directly for time-changing groups:

```python
import survival

response = survival.Surv([25.0, 8.0], [1, 0])
attained = survival.tcut([0.0, 5.0], [0.0, 10.0, 20.0, 30.0])
result = survival.pyears(response, group=attained, scale=1)
```

```python
from survival import pybridge

# Low-level API: inputs should match ratetable-style dimensions/cuts.
result = pybridge.perform_pyears_calculation(
    time_data=[1.0, 2.0, 3.0, 1.0, 0.0, 1.0],  # [times..., events...], ny=2
    weights=[1.0, 1.0, 1.0],
    expected_dim=1,
    expected_factors=[0],
    expected_dims=[2],
    expected_cuts=[0.0, 2.0],
    expected_rates=[0.01, 0.02],
    expected_data=[0.5, 1.5, 0.5],
    observed_dim=1,
    observed_factors=[0],
    observed_dims=[2],
    observed_cuts=[0.0, 1.5, 3.0],
    method=0,
    observed_data=[0.5, 1.0, 2.0],
    do_event=1,
    ny=2,
)
print(result.keys())
```

### Kaplan-Meier Survival Curves

```python
from survival import surv_analysis

# Example survival data
time = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]
status = [1.0, 1.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0]  # 1 = event, 0 = censored
weights = [1.0] * len(time)  # Optional: equal weights

result = surv_analysis.survfitkm(
    time=time,
    status=status,
    weights=weights,
    entry_times=None,  # Optional: entry times for left-truncation
    position=None,     # Optional: position flags
    reverse=False,     # Optional: estimate the censoring distribution
    computation_type=0 # Optional: computation type
)

print(f"Time points: {result.time}")
print(f"Survival estimates: {result.estimate}")
print(f"Standard errors: {result.std_err}")
print(f"Number at risk: {result.n_risk}")
```

### Fine-Gray Competing Risks Model

```python
from survival import finegray

data = {
    "time": [1.0, 2.0, 3.0, 4.0],
    "event": ["target", "competing", "censor", "target"],
    "x": [0.2, 0.4, 0.1, 0.8],
}

# String labels use a recognized censor label as the censoring state. For
# pandas categoricals, the declared category order is preserved exactly.
expanded = finegray(
    "Surv(time, event) ~ x",
    data=data,
    etype="target",
    count="replication",
)

print(expanded.event)
print(expanded["fgstart"], expanded["fgstop"], expanded["fgwt"])
```

The checked six-vector interval splitter remains available for lower-level
workflows:

```python
from survival import regression

# Example competing risks data
tstart = [0.0, 0.0, 0.0, 0.0]
tstop = [1.0, 2.0, 3.0, 4.0]
ctime = [0.5, 1.5, 2.5, 3.5]  # Cut points
cprob = [0.1, 0.2, 0.3, 0.4]  # Cumulative probabilities
extend = [True, True, False, False]  # Whether to extend intervals
keep = [True, True, True, True]      # Which cut points to keep

result = regression.finegray(
    tstart=tstart,
    tstop=tstop,
    ctime=ctime,
    cprob=cprob,
    extend=extend,
    keep=keep
)

print(f"Row indices: {result.row}")
print(f"Start times: {result.start}")
print(f"End times: {result.end}")
print(f"Weights: {result.wt}")
```

### Parametric Survival Regression (Accelerated Failure Time Models)

```python
from survival import regression

# Example survival data
time = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]
status = [1.0, 1.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0]  # 1 = event, 0 = censored
covariates = [
    [1.0, 2.0],
    [1.5, 2.5],
    [2.0, 3.0],
    [2.5, 3.5],
    [3.0, 4.0],
    [3.5, 4.5],
    [4.0, 5.0],
    [4.5, 5.5],
]

# Fit parametric survival model
result = regression.survreg(
    time=time,
    status=status,
    covariates=covariates,
    weights=None,          # Optional: observation weights
    offsets=None,          # Optional: offset values
    initial_beta=None,     # Optional: initial coefficient values
    strata=None,           # Optional: stratification variable
    distribution="weibull",  # "extreme_value", "logistic", "gaussian", "weibull", or "lognormal"
    max_iter=20,          # Optional: maximum iterations
    eps=1e-5,             # Optional: convergence tolerance
    tol_chol=1e-9,        # Optional: Cholesky tolerance
)

print(f"Coefficients: {result.coefficients}")
print(f"Log-likelihood: {result.log_likelihood}")
print(f"Iterations: {result.iterations}")
print(f"Variance matrix: {result.variance_matrix}")
print(f"Convergence flag: {result.convergence_flag}")
```

### Cox Proportional Hazards Model

```python
from survival import regression

# Create a Cox PH model
model = regression.CoxPHModel()

# Or create with data
covariates = [[1.0, 2.0], [2.0, 3.0], [1.5, 2.5]]
event_times = [1.0, 2.0, 3.0]
censoring = [1, 1, 0]  # 1 = event, 0 = censored

model = regression.CoxPHModel.new_with_data(covariates, event_times, censoring)

# Fit the model
model.fit(n_iters=10)

# Get results
print(f"Baseline hazard: {model.baseline_hazard}")
print(f"Risk scores: {model.risk_scores}")
print(f"Coefficients: {model.coefficients}")

# Predict on new data
new_covariates = [[1.0, 2.0], [2.0, 3.0]]
predictions = model.predict(new_covariates)
print(f"Predictions: {predictions}")

# Calculate an IPCW Brier score at a common horizon. If omitted, `time`
# defaults to the middle distinct event time in the training data.
brier = model.brier_score(time=2.0)
print(f"Brier score: {brier}")

# Compute survival curves for new covariates
new_covariates = [[1.0, 2.0], [2.0, 3.0]]
time_points = [0.0, 1.0, 2.0, 3.0, 4.0, 5.0]  # Optional: specific time points
times, survival_curves = model.survival_curve(new_covariates, time_points)
print(f"Time points: {times}")
print(f"Survival curves: {survival_curves}")  # One curve per covariate set

# Create and add subjects
subject = regression.Subject(
    id=1,
    covariates=[1.0, 2.0],
    is_case=True,
    is_subcohort=True,
    stratum=0
)
model.add_subject(subject)
```

### Cox Martingale Residuals

```python
from survival import residuals

# Example survival data
time = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]
status = [1, 1, 0, 1, 0, 1, 1, 0]  # 1 = event, 0 = censored
score = [0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2]  # Risk scores

# Calculate martingale residuals
martingale_residuals = residuals.coxmart(
    time=time,
    status=status,
    score=score,
    weights=None,      # Optional: observation weights
    strata=None,       # Optional: stratification variable
    method=0,          # Optional: method (0 = Breslow, 1 = Efron)
)

print(f"Martingale residuals: {martingale_residuals}")
```

### Survival Difference Tests (Log-Rank Test)

```python
from survival import surv_analysis

# Example: Compare survival between two groups
time = [1.0, 2.0, 3.0, 4.0, 5.0, 1.5, 2.5, 3.5, 4.5, 5.5]
status = [1, 1, 0, 1, 0, 1, 1, 1, 0, 1]
group = [1, 1, 1, 1, 1, 2, 2, 2, 2, 2]  # Group 1 and Group 2

# Perform log-rank test (rho=0 for standard log-rank)
result = surv_analysis.compute_logrank_components(
    time=time,
    status=status,
    group=group,
    strata=None,  # Optional: stratification variable
    rho=0.0,      # 0.0 = log-rank; nonzero values use G-rho weights
)

print(f"Observed events: {result.observed}")
print(f"Expected events: {result.expected}")
print(f"Chi-squared statistic: {result.chi_squared}")
print(f"Degrees of freedom: {result.degrees_of_freedom}")
print(f"Variance matrix: {result.variance}")
```

### Built-in Datasets

The library includes 33 classic survival analysis datasets:

```python
from survival import datasets

# Load the lung cancer dataset
lung = datasets.load_lung()
columns = [name for name in lung if not name.startswith("_")]
print(f"Columns: {columns}")
print(f"Number of rows: {lung['_nrow']}")

# Load the acute myelogenous leukemia dataset
aml = datasets.load_aml()

# Load the veteran's lung cancer dataset
veteran = datasets.load_veteran()
```

Datasets are returned as column-oriented dictionaries with `_nrow` and `_ncol`
metadata.

**Available datasets:**
- `load_lung()` - NCCTG Lung Cancer Data
- `load_aml()` - Acute Myelogenous Leukemia Survival Data
- `load_veteran()` - Veterans' Administration Lung Cancer Study
- `load_ovarian()` - Ovarian Cancer Survival Data
- `load_colon()` - Colon Cancer Data
- `load_pbc()` - Primary Biliary Cholangitis Data
- `load_cgd()` - Chronic Granulomatous Disease Data
- `load_bladder()` - Bladder Cancer Recurrences
- `load_heart()` - Stanford Heart Transplant Data
- `load_kidney()` - Kidney Catheter Data
- `load_rats()` - Rat Treatment Data
- `load_stanford2()` - Stanford Heart Transplant Data (Extended)
- `load_udca()` - UDCA Clinical Trial Data
- `load_myeloid()` - Acute Myeloid Leukemia Clinical Trial
- `load_flchain()` - Free Light Chain Data
- `load_transplant()` - Liver Transplant Data
- `load_mgus()` - Monoclonal Gammopathy Data
- `load_mgus2()` - Monoclonal Gammopathy Data (Updated)
- `load_diabetic()` - Diabetic Retinopathy Data
- `load_retinopathy()` - Retinopathy Data
- `load_gbsg()` - German Breast Cancer Study Group Data
- `load_rotterdam()` - Rotterdam Tumor Bank Data
- `load_logan()` - Logan Unemployment Data
- `load_nwtco()` - National Wilms Tumor Study Data
- `load_solder()` - Solder Joint Data
- `load_tobin()` - Tobin's Tobit Data
- `load_rats2()` - Rat Tumorigenesis Data
- `load_nafld()` - Non-Alcoholic Fatty Liver Disease Data
- `load_cgd0()` - CGD Baseline Data
- `load_pbcseq()` - PBC Sequential Data
- `load_hoel()` - Hoel's Cancer Survival Data
- `load_myeloma()` - Myeloma Survival Data
- `load_rhdnase()` - rhDNase Clinical Trial Data

## API Reference

The public Python surface is broad and evolves quickly. For the most accurate,
version-matched signatures, use the checked-in type stubs:

`import survival` exposes the curated package API via domain modules. Legacy
root-level algorithm symbols remain available lazily for compatibility, but new
code should import from the relevant domain module. For lower-level or
experimental extension symbols, import from `survival._survival` explicitly.

- [`python/survival/__init__.pyi`](python/survival/__init__.pyi): package-level
  typed surface, including the new domain modules.
- [`python/survival/_survival.pyi`](python/survival/_survival.pyi): core
  PyO3 bindings exposed by `survival._survival`.
- [`python/survival/*.py`](python/survival): curated domain modules layered on
  top of the generated bindings.
- [`python/survival/sklearn_compat.py`](python/survival/sklearn_compat.py):
  scikit-learn-compatible estimators and streaming wrappers.

To inspect available symbols at runtime:

```python
import survival

public_names = [name for name in dir(survival) if not name.startswith("_")]
print(public_names)
print(survival.__deprecated_root_export_reason__)
```

Or inspect a specific domain module:

```python
from survival import regression, validation

print(regression.__all__[:10])
print(validation.__all__[:10])
```

## PSpline Options

The `PSpline` class provides penalized spline smoothing:

**Constructor Parameters:**
- `x`: Covariate vector (list of floats)
- `df`: Degrees of freedom (integer)
- `theta`: Roughness penalty (float)
- `eps`: Accuracy for degrees of freedom (float)
- `method`: Penalty method for tuning parameter selection. Supported methods:
  - `"GCV"` - Generalized Cross-Validation
  - `"UBRE"` - Unbiased Risk Estimator
  - `"REML"` - Restricted Maximum Likelihood
  - `"AIC"` - Akaike Information Criterion
  - `"BIC"` - Bayesian Information Criterion
- `boundary_knots`: Tuple of (min, max) for the spline basis
- `intercept`: Whether to include an intercept in the basis
- `penalty`: Whether or not to apply the penalty

**Methods:**
- `fit()`: Fit the spline model, returns coefficients
- `predict(new_x)`: Predict values at new x points

**Properties:**
- `coefficients`: Fitted coefficients (None if not fitted)
- `fitted`: Whether the model has been fitted
- `df`: Degrees of freedom
- `eps`: Convergence tolerance

## Development

See [`CONTRIBUTING.md`](CONTRIBUTING.md) for the full local development
workflow, feature-test matrix, and binding/stub update process.

Install development dependencies:
```sh
uv sync --extra dev --extra test --extra sklearn --no-install-project
```

Build the extension in your current environment:
```sh
maturin develop --release
```

Build with optional ML bindings:
```sh
maturin develop --release --features extension-module,ml
```

`Cargo.toml` is the source of truth for the published package version.

GitHub Actions publishes from an explicit tag or full commit SHA. PyPI/TestPyPI publishing is configured for trusted publishing rather than a long-lived API token.

Build the Rust library:
```sh
cargo build
```

Run Rust tests:
```sh
cargo test
```

Run Python tests:
```sh
uv run --no-sync pytest python/tests -v
```

Smoke-test benchmarks:
```sh
cargo bench -- --test
```

Format and lint:
```sh
cargo fmt
uv run --no-sync ruff format python/ test/ --check
uv run --no-sync ruff check python/ test/
uv run --no-sync mypy python/survival/__init__.pyi python/survival/_survival.pyi --ignore-missing-imports
```

The codebase is organized with:
- Domain-oriented Rust modules in `src/`
- Matching Python domain modules in `python/survival/`
- Experimental R bridge package in `r/survivalr/`
- Package/type stubs in `python/survival/__init__.pyi`,
  `python/survival/_survival.pyi`, and `survival.pyi`
- Runnable examples in `examples/`
- Developer-facing layout notes in `docs/`
- Rust unit/integration tests in `src/tests/`
- Python binding tests in `python/tests/`
- R validation fixtures and archived reference cases in `test/`

## Dependencies

Primary dependencies are defined in [`Cargo.toml`](Cargo.toml) and
[`pyproject.toml`](pyproject.toml), including:

- [PyO3](https://github.com/PyO3/pyo3) and [maturin](https://github.com/PyO3/maturin) for Python bindings
- [reticulate](https://rstudio.github.io/reticulate/) for the experimental R bridge package
- [numpy](https://numpy.org/) and [ndarray](https://github.com/rust-ndarray/ndarray) for array interop
- [faer](https://github.com/sarah-ek/faer-rs), [rayon](https://github.com/rayon-rs/rayon), and [burn](https://github.com/tracel-ai/burn) for numerical compute

## Compatibility

- Native extendr bindings are currently disabled. The experimental
  `r/survivalr` package provides an R facade through reticulate and the Python
  `survival.r_api` module.
- Python 3.11+ and Rust 1.94+ are required.
- macOS users: Ensure you are using the correct Python version and have Homebrew-installed Python if using Apple Silicon.

## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

