FineGray

Fine-Gray subdistribution hazard model for a competing-risks endpoint.

Usage

Source

FineGray(
    cause,
    *,
    conf_level=0.95,
)

The Fine-Gray model extends Cox regression to competing-risks settings where multiple event types (e.g., disease progression vs. death) can occur and only the first is observed. Rather than modeling the cause-specific hazard (as in standard Cox), it models the subdistribution hazard: the rate at which subjects experience the target cause as if competing events did not occur. This allows inference directly on the cumulative incidence function, making it ideal for policy-relevant questions like “what is the effect of treatment on my probability of experiencing event A?”

Technically, the Fine-Gray model uses a weighted Cox-like approach: subjects who experience a competing event remain in the risk set but with decreasing inverse-probability-of-censoring weights, reflecting their reduced ability to contribute information about the target cause. Call fit() with a multi-state response (an Outcome or formula with data=, or a Surv with a categorical event) and specify the target cause of interest. Coefficients, hazard ratios, and standard errors are computed via weighted partial likelihood, with robust (clustered) standard errors accounting for the weighting scheme.

The implementation automatically computes event weights and handles censoring. Standard errors use the Lin-Wei robust (sandwich) estimator, validated against R’s survival package. Unlike the Aalen-Johansen non-parametric approach, Fine-Gray provides covariate-adjusted estimates and supports prediction of cumulative incidence at specified times.

Parameters

cause: Any

The target cause-of-interest label from the multi-state Surv response.

conf_level: float = 0.95
Confidence level for coefficient intervals (default 0.95).

Returns

Fitted estimator
Call fit() to produce a fitted estimator with cached results (coef_, hazard_ratio_, std_error_, z_, p_value_, conf_low_, conf_high_), accessible as arrays or exported to DataFrames.

Details

Call fit(surv, covariates, data=...) with a multi-state response (an Outcome, a formula, or a Surv with a categorical event) and specify the target cause. The model uses weighted Cox-like optimization with robust standard errors. Results can be tidy frames via to_frame() (optionally format=).

Examples

Using the same competing-risks setup as AalenJohansen, model the cumulative incidence of plasma-cell malignancy ("pcm") as a function of age and sex. The age and sex columns of the mgus2 frame form the covariate design. Printing the fitted object reports the subdistribution-hazard coefficient table.

import greenwood as gw

# Load data and name the competing endpoints
mg = gw.load_dataset("mgus2", backend="pandas")
cr = gw.Outcome.first_event(
    endpoints={"pcm": ("ptime", "pstat"), "death": ("futime", "death")}
)

# Fit the Fine-Gray subdistribution hazard model for pcm
fg = gw.FineGray(cause="pcm").fit(cr, covariates=["age", "sex"], data=mg)
fg
FineGray (Fine-Gray subdistribution hazard model, cause='pcm')

         coef  exp(coef)  se(coef)       z         p
age   -0.0173     0.9828  0.005701  -3.035  0.002408
sexM  -0.2597     0.7713    0.1856  -1.399    0.1617

n = 1384, events = 115
Standard errors: robust (clustered)

Passing exponentiate=True to tidy reports the subdistribution hazard ratios (with their confidence limits) instead of the log-scale coefficients. Pass format= to choose the backend (here, Polars):

# Report subdistribution hazard ratios as a Polars DataFrame
gw.tidy(fg, exponentiate=True, format="polars")
shape: (2, 7)
termestimatestd_errorstatisticp_valueconf_lowconf_high
strf64f64f64f64f64f64
"age"0.9828480.005701-3.0347060.0024080.9719270.993892
"sexM"0.7712820.185579-1.3994070.1616910.5361031.109631

Methods

Name Description
fit() Fit the Fine-Gray subdistribution hazard model to competing-risks data.
predict() Predict linear predictor or risk from the fitted Fine-Gray model.
predict_cumulative_incidence() Predict cumulative incidence F(t \mid x) for new subjects.
to_frame() Return the Fine-Gray coefficient table as a DataFrame.

fit()

Fit the Fine-Gray subdistribution hazard model to competing-risks data.

Usage

Source

fit(
    surv,
    covariates=None,
    *,
    data=None,
    max_iter=30,
    tol=1e-09,
)

Fits a Cox-like regression model to the subdistribution hazard of a target cause in a competing-risks setting. Subjects who experience a competing event remain in the risk set with inverse-probability-of-censoring weights, allowing direct inference on cumulative incidence of the target cause. The model reports coefficients, hazard ratios, and clustered robust (Lin-Wei) standard errors.

Parameters

surv: Surv | Outcome | str

A multi-state response: a Surv with a categorical event, an Outcome (such as gw.Outcome.first_event(...)), or a formula string such as "Surv(etime, factor(cause, c(0, 1, 2), c('censor', 'pcm', 'death'))) ~ age + sex". Must have multiple causes-of-interest. Raises ValueError if a single-event response is passed. An Outcome or formula reads its columns from data. The formula’s right-hand side sets covariates.

covariates: Any = None

A dataframe (pandas or polars) or 2-D array of covariates to adjust for in the subdistribution hazard. An intercept is added automatically. Must have the same number of rows as surv. A list of column names in data also works.

max_iter: int = 30

Maximum number of Newton-Raphson iterations (default 30).

tol: float = 1e-09

Convergence tolerance for coefficient changes (default 1e-9).

data: Any = None
A data frame (pandas, Polars, PyArrow, DuckDB, a lazy frame, …) holding the columns named by the response and covariates. When surv is an Outcome or a formula, rows with a missing value in any column used are dropped before fitting. Covariate formula strings and lists of column names are also resolved here.

Returns

FineGray
The fitted estimator object itself (for method chaining) with cached coefficient arrays (coef_, std_error_, hazard_ratio_, z_, p_value_), event counts, and model fit statistics.

Details

The Fine-Gray model estimates the subdistribution hazard h_j(t) using weighted partial likelihood. Weights are the inverse of the Kaplan-Meier estimate of the censoring distribution, computed just before each target event time. Subjects with competing events receive time-decreasing weights reflecting reduced ability to contribute information.

Coefficients are interpreted as log-hazard ratios on the subdistribution hazard scale, not the cause-specific hazard. The model directly targets cumulative incidence (F_j), making it ideal for policy-relevant questions about the probability of experiencing a specific cause.

Examples

Fit a Fine-Gray model on the competing-risks mgus2 dataset, modeling the cumulative incidence of plasma-cell malignancy (pcm) as a function of age and sex:

import greenwood as gw

# Load data and name the competing endpoints
mg = gw.load_dataset("mgus2", backend="pandas")
cr = gw.Outcome.first_event(
    endpoints={"pcm": ("ptime", "pstat"), "death": ("futime", "death")}
)

# Fit the Fine-Gray subdistribution hazard model for pcm
fg = gw.FineGray(cause="pcm").fit(cr, covariates=["age", "sex"], data=mg)
fg
FineGray (Fine-Gray subdistribution hazard model, cause='pcm')

         coef  exp(coef)  se(coef)       z         p
age   -0.0173     0.9828  0.005701  -3.035  0.002408
sexM  -0.2597     0.7713    0.1856  -1.399    0.1617

n = 1384, events = 115
Standard errors: robust (clustered)

Extract hazard ratios with exponentiation for interpretation; pass format= to choose the backend (here, Polars):

# Report subdistribution hazard ratios as a Polars DataFrame
gw.tidy(fg, exponentiate=True, format="polars")
shape: (2, 7)
termestimatestd_errorstatisticp_valueconf_lowconf_high
strf64f64f64f64f64f64
"age"0.9828480.005701-3.0347060.0024080.9719270.993892
"sexM"0.7712820.185579-1.3994070.1616910.5361031.109631

predict()

Predict linear predictor or risk from the fitted Fine-Gray model.

Usage

Source

predict(
    newdata=None,
    *,
    type="lp",
    format=None,
)

Parameters

newdata: Any = None

Covariate values for prediction. A dataframe or 2-D array with the same columns as the training data. If None, uses the training data. A data frame may also hold other columns. The covariates are picked out by name and coded as they were at fit time, so the frame the model was fit on can be passed as is.

type: str = "lp"

"lp" (default) returns the centered linear predictor X\beta. "risk" returns the relative subdistribution hazard \exp(X\beta).

format: str | None = None
Ignored (present for API consistency).

Returns

ndarray
Array of shape (n_subjects,).

predict_cumulative_incidence()

Predict cumulative incidence F(t \mid x) for new subjects.

Usage

Source

predict_cumulative_incidence(
    newdata=None,
    *,
    times=None,
    format=None,
)

Uses the baseline subdistribution cumulative hazard from the training data and the fitted coefficients to compute the predicted cumulative incidence function: F(t \mid x) = 1 - \exp\bigl(-H_0(t)\,\exp(x\beta)\bigr).

Parameters

newdata: Any = None

Covariate values for prediction. A dataframe or 2-D array with the same columns as the training data. If None, uses the training data. A data frame may also hold other columns. The covariates are picked out by name and coded as they were at fit time, so the frame the model was fit on can be passed as is.

times: Any = None

Time points at which to evaluate the cumulative incidence. If None, uses the target-event times from the training data.

format: str | None = None
Output format: None (default), "pandas", "polars", or "pyarrow".

Returns

DataFrame
A table with a time column and one column per subject (subject_1, subject_2, …) containing cumulative incidence probabilities.

to_frame()

Return the Fine-Gray coefficient table as a DataFrame.

Usage

Source

to_frame(
    *,
    format=None,
    exponentiate=False,
)

Exports one row per term with coefficient estimates, robust standard errors, test statistics, p-values, and confidence limits. Set exponentiate=True to return subdistribution hazard ratios and exponentiated confidence limits.

Parameters

format: str | None = None

Output format: None (default), "pandas", "polars", or "pyarrow". When None, a backend is auto-detected (Polars, then Pandas, then PyArrow).

exponentiate: bool = False
Whether to return subdistribution hazard ratios instead of log-scale coefficients. Default False.

Returns

pandas.DataFrame, polars.DataFrame, or pyarrow.Table
A tidy table with columns term, estimate, std_error, statistic, p_value, conf_low, and conf_high.

Raises

ImportError
If the requested (or, when auto-detecting, any) DataFrame library is not installed.

Examples

Fit a Fine-Gray model on the competing-risks mgus2 data and export the coefficient table as a Polars frame:

import greenwood as gw

# Load data and name the competing endpoints
mg = gw.load_dataset("mgus2", backend="pandas")
cr = gw.Outcome.first_event(
    endpoints={"pcm": ("ptime", "pstat"), "death": ("futime", "death")}
)
fg = gw.FineGray(cause="pcm").fit(cr, covariates=["age", "sex"], data=mg)

# Export the coefficient table as a Polars DataFrame
fg.to_frame(format="polars")
shape: (2, 7)
termestimatestd_errorstatisticp_valueconf_lowconf_high
strf64f64f64f64f64f64
"age"-0.0173010.005701-3.0347060.002408-0.028474-0.006127
"sexM"-0.2597010.185579-1.3994070.161691-0.623430.104028

To report subdistribution hazard ratios instead of coefficients:

# Report hazard ratios instead of log-scale coefficients
fg.to_frame(format="polars", exponentiate=True)
shape: (2, 7)
termestimatestd_errorstatisticp_valueconf_lowconf_high
strf64f64f64f64f64f64
"age"0.9828480.005701-3.0347060.0024080.9719270.993892
"sexM"0.7712820.185579-1.3994070.1616910.5361031.109631