Fine-Gray subdistribution hazard model for a competing-risks endpoint.
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)| term | estimate | std_error | statistic | p_value | conf_low | conf_high |
|---|
| str | f64 | f64 | f64 | f64 | f64 | f64 |
| "age" | 0.982848 | 0.005701 | -3.034706 | 0.002408 | 0.971927 | 0.993892 |
| "sexM" | 0.771282 | 0.185579 | -1.399407 | 0.161691 | 0.536103 | 1.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.
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)| term | estimate | std_error | statistic | p_value | conf_low | conf_high |
|---|
| str | f64 | f64 | f64 | f64 | f64 | f64 |
| "age" | 0.982848 | 0.005701 | -3.034706 | 0.002408 | 0.971927 | 0.993892 |
| "sexM" | 0.771282 | 0.185579 | -1.399407 | 0.161691 | 0.536103 | 1.109631 |
predict()
Predict linear predictor or risk from the fitted Fine-Gray model.
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.
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.
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)| term | estimate | std_error | statistic | p_value | conf_low | conf_high |
|---|
| str | f64 | f64 | f64 | f64 | f64 | f64 |
| "age" | -0.017301 | 0.005701 | -3.034706 | 0.002408 | -0.028474 | -0.006127 |
| "sexM" | -0.259701 | 0.185579 | -1.399407 | 0.161691 | -0.62343 | 0.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)| term | estimate | std_error | statistic | p_value | conf_low | conf_high |
|---|
| str | f64 | f64 | f64 | f64 | f64 | f64 |
| "age" | 0.982848 | 0.005701 | -3.034706 | 0.002408 | 0.971927 | 0.993892 |
| "sexM" | 0.771282 | 0.185579 | -1.399407 | 0.161691 | 0.536103 | 1.109631 |