AFT

Parametric accelerated failure time model.

Usage

Source

AFT(
    dist="weibull",
    *,
    conf_level=0.95,
)

While the Cox proportional hazards model leaves the baseline hazard unspecified, AFT models assume a fully parametric distribution for survival times and model how covariates accelerate or decelerate the “clock” of failure. Specifically, \log(T) = \mu + \beta^\top x + \sigma\varepsilon, where T is survival time, \beta are log-time-scale coefficients, \sigma is a scale parameter, and \varepsilon follows a specified error distribution (e.g., extreme-value, logistic, normal). This means a unit increase in covariate x multiplies survival time by \exp(\beta).

AFT models are useful when you want explicit, interpretable survival time predictions or when the parametric assumptions are reasonable. Unlike Cox models, they require choosing a distributional family (Weibull, exponential, lognormal, or loglogistic). Call fit() with a right-censored Surv response and a design matrix. The model automatically adds an intercept and estimates coefficients (on the log-time scale), the scale parameter, and standard errors via maximum likelihood.

The implementation uses numerical optimization (typically Newton-Raphson) to maximize the likelihood. Coefficients on the log-time scale can be exponentiated to obtain time- acceleration ratios: \exp(\beta) is the multiplicative effect on median or mean survival. The model also supports prediction of survival probabilities and quantiles at future times given covariate values.

Parameters

dist: str = "weibull"

Error distribution: "weibull" (default), "exponential", "lognormal", or "loglogistic".

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

Returns

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

Details

Call fit(surv, covariates) with a right-censored Surv response and a covariate design (a 2-D array or a dataframe). An intercept is added automatically; rows with missing covariates are dropped. Results are exposed as arrays (coef_, scale_, std_error_, z_, p_value_) and as tidy frames via to_frame() (optionally format=) and greenwood.tidy.

Examples

Build a Surv response from the bundled lung dataset and fit a Weibull AFT model with age and sex as covariates. Printing the fitted object reports the coefficients (on the log-time scale), the scale, and the log-likelihood.

import greenwood as gw

# Load data and build a right-censored response
lung = gw.load_dataset("lung", backend="polars")
y = gw.Surv.right(lung["time"], event=(lung["status"] == 2))

# Fit a Weibull AFT model with age and sex as covariates
aft = gw.AFT("weibull").fit(y, lung[["age", "sex"]])
aft
AFT (accelerated failure time model, dist='weibull')

                 coef  se(coef)       z          p
(Intercept)     6.275    0.4814  13.036  7.671e-39
age          -0.01226  0.006957  -1.762    0.07811
sex            0.3821    0.1275   2.997   0.002723

Scale = 0.7541
n = 228, events = 165
Log-likelihood = -1147

Methods

Name Description
fit() Fit the accelerated failure time model to survival data.
predict() Predict survival times, quantiles, or survival probabilities from the AFT model.
to_frame() Return the coefficient table as a DataFrame.

fit()

Fit the accelerated failure time model to survival data.

Usage

Source

fit(surv, covariates, *, data=None)

Fits a parametric accelerated failure time (AFT) model to a right-censored response and covariates. The AFT models the log-survival time as a linear regression on covariates plus a random error from a specified parametric distribution (Weibull, exponential, log-normal, or log-logistic). An intercept is added automatically.

The AFT is a parametric alternative to Cox regression, providing a fully specified survival distribution at the cost of stronger distributional assumptions. Unlike Cox, AFT supports median survival predictions and is naturally interpreted on the log-time scale: a coefficient of 0.1 means the covariate multiplies survival time by \exp(0.1). Results are stored in the fitted object as coefficient arrays and can be exported to DataFrames.

Parameters
surv: Surv

A right-censored Surv response. Built with Surv.right(). Interval-censored or other response types raise NotImplementedError.

covariates: Any

A dataframe (pandas or polars), a 2-D array, or a formula string (e.g., "age + sex") evaluated against the data argument.

data: Any = None
A dataframe to evaluate the formula string (ignored if covariates is a dataframe or array).
Returns
AFT
The fitted estimator object itself (for method chaining) with cached coefficient arrays (coef_, std_error_, z_, p_value_), scale parameter (scale_), and log-likelihood (loglik_).
Details

The AFT model parameterizes log-survival time as \log(T) = X\beta + \sigma\varepsilon, where X is the design matrix, \beta are coefficients, \sigma is a scale parameter, and \varepsilon is an error term from the chosen distribution. The survival function is then S(t \mid X) = P(T > t \mid X) = G((\log(t) - X\beta) / \sigma), where G is the survival function of the error distribution.

Estimation uses maximum likelihood via numerical optimization. Exponential and Weibull models are nested special cases; log-normal and log-logistic offer different tail behaviors.

Examples

Fit a log-normal AFT model on the bundled lung dataset with age and sex as covariates:

import greenwood as gw

# Load data and build a right-censored response
lung = gw.load_dataset("lung", backend="polars")
y = gw.Surv.right(lung["time"], event=(lung["status"] == 2))

# Fit a log-normal AFT model
aft = gw.AFT(dist="lognormal").fit(y, lung[["age", "sex"]])
aft
AFT (accelerated failure time model, dist='lognormal')

                 coef  se(coef)       z          p
(Intercept)     6.408    0.5929  10.807  3.186e-27
age          -0.02336  0.008389  -2.784   0.005363
sex            0.5193    0.1552   3.347  0.0008176

Scale = 1.053
n = 228, events = 165
Log-likelihood = -1159

Use a formula string with the data argument:

# Fit via a formula string instead of a DataFrame
aft_formula = gw.AFT(dist="weibull").fit(y, "age + sex", data=lung)
aft_formula
AFT (accelerated failure time model, dist='weibull')

                 coef  se(coef)       z          p
(Intercept)     6.275    0.4814  13.036  7.671e-39
age          -0.01226  0.006957  -1.762    0.07811
sex            0.3821    0.1275   2.997   0.002723

Scale = 0.7541
n = 228, events = 165
Log-likelihood = -1147

predict()

Predict survival times, quantiles, or survival probabilities from the AFT model.

Usage

Source

predict(
    newdata=None,
    *,
    type="survival",
    times=None,
    p=0.5,
    tau=None,
    conditional_after=None,
    ci=False,
    conf_type="log-log",
    format=None
)

Generates predictions from a fitted accelerated failure time model. The AFT is a fully parametric survival model, so predictions require specifying both the predictor values (via newdata) and the type of prediction desired. Pass newdata=None to predict for the training data (fitted subjects).

Six prediction types are available:

  1. Linear predictor (type="lp"): the log-time location X\beta, showing how covariates shift the log-survival time distribution.

  2. Quantile (type="quantile"): predicted survival-time quantiles at specified failure probabilities (e.g., median survival when p=0.5). Useful for clinical summaries like “50% of subjects with these covariates survive to time X.”

  3. Survival (type="survival"): survival probabilities S(t \mid x) at specified times, returned as a DataFrame for easy visualization. Optionally condition on already having survived to a landmark time (conditional_after) for landmark-based predictions. With ci=True, confidence intervals are included.

  4. Mean (type="mean"): the expected survival time E[T] (unconditional) or the conditional mean E[T \mid T > t_0] when conditional_after is provided. Computed via closed-form formulas for all distributions. Returns an array of shape (n_subjects,).

  5. Mean remaining (type="mean_remaining"): expected remaining lifetime E[T - t_0 \mid T > t_0] for subjects known to have survived past t0. Requires conditional_after. Returns an array of shape (n_subjects,).

  6. RMST (type="rmst"): restricted mean survival time E[\min(T, \tau)] up to the restriction time tau. Requires the tau argument. Returns an array of shape (n_subjects,).

Parameters
newdata: Any = None

Covariate values for prediction. A DataFrame (Pandas or Polars), 2-D array, or None (default). If None, uses the training data (design matrix used at fit time). Must have the same columns/features as the training data.

type: str = "survival"

Prediction type (default "survival"):

  • "lp": Linear predictor X\beta (log-time location). Returns an array.
  • "quantile": Survival-time quantiles at failure probabilities p. Returns a frame with p column and one column per subject.
  • "survival": Survival probabilities S(t \mid x) at times in times. Returns a frame with time column and one column per subject (one per query time, one per subject in newdata).
times: Any = None

Query times for type="survival" (ignored for other types). An array-like of floats. If None (default), uses an automatic grid based on the fitted distribution (50 equally spaced times on the log scale, rounded).

p: Any = 0.5

Failure probabilities for type="quantile" (ignored for other types). Can be a scalar (e.g., 0.5 for median) or array-like. Default 0.5 (median). Must be in (0, 1).

tau: Any = None

Restriction time for type="rmst". Scalar float giving the upper limit of integration. Required when type="rmst", ignored otherwise.

conditional_after: Any = None

For type="survival", optionally compute conditional survival P(T > t \mid T > c) = S(t) / S(c). Also used with type="mean" and type="mean_remaining" to condition on surviving past a landmark time t_0. Scalar (same conditioning time for all subjects) or array-like (one per subject). Default None (unconditional).

ci: bool = False

If True (survival only), include confidence intervals (_lower and _upper columns per subject). Default is False. Not supported with conditional_after.

conf_type: str = "log-log"

Confidence interval transform (used only if ci=True and type="survival"):

  • "log-log" (default): Log-log transform. Bounds respect the constraint that survival S(t) \in (0, 1). Recommended.
  • "plain": Wald bounds without transform. Simple but may produce invalid bounds (survival < 0 or > 1).
format: str | None = None
Output format for the returned frame (type="quantile" or "survival"): None (default), "pandas", "polars", or "pyarrow". When None, a backend is auto-detected (Polars, then Pandas, then PyArrow). Ignored for type="lp", "mean", "mean_remaining", and "rmst" (which always return arrays).
Returns
ndarray or DataFrame
If type="lp": an array of shape (n_subjects,) containing log-time locations. If type="quantile": a DataFrame with columns p (failure probabilities) and subject_1, subject_2, etc. containing survival times at each p. If type="survival": a DataFrame with columns time (query times) and subject_1, subject_2, etc. containing survival probabilities at each time, optionally with _lower and _upper columns for confidence intervals. If type="mean": an array of shape (n_subjects,) with E[T] or E[T \mid T > t_0]. If type="mean_remaining": an array of shape (n_subjects,) with E[T - t_0 \mid T > t_0]. If type="rmst": an array of shape (n_subjects,) with E[\min(T, \tau)].
Details

The AFT model assumes \log(T) = X\beta + \sigma\varepsilon, where \varepsilon follows a parametric error distribution (Weibull, lognormal, etc.). Predictions are made by evaluating the CDF/survival function of this distribution at covariate-adjusted locations. All predictions respect the fitted distribution and scale parameter.

Predictions assume the model is well-specified. For flexible models, consider parametric bootstrap to quantify uncertainty.

Examples

Fit a Weibull AFT model on the bundled lung dataset, then predict the linear predictor (log-time location) for the first two subjects:

import greenwood as gw

# Load data and fit a Weibull AFT model
lung = gw.load_dataset("lung", backend="polars")
y = gw.Surv.right(lung["time"], event=(lung["status"] == 2))
aft = gw.AFT("weibull").fit(y, lung[["age", "sex"]])

# Predict the linear predictor for the first two subjects
aft.predict(lung[["age", "sex"]][:2], type="lp")
array([5.749914  , 5.82345845])

Predicted survival-time quantiles for the first two subjects at the lower quartile, median, and upper quartile (a table, so pass format=):

# Predict survival-time quartiles for the first two subjects
aft.predict(lung[["age", "sex"]][:2], type="quantile", p=[0.25, 0.5, 0.75],
            format="polars")
shape: (3, 3)
psubject_1subject_2
f64f64f64
0.25122.785883132.156457
0.5238.303358256.489806
0.75401.903889432.575736

Read survival probabilities off the fitted curves at chosen times. Here are the estimates at 180 and 365 days for those same two subjects:

# Predict survival probabilities at 180 and 365 days
aft.predict(lung[["age", "sex"]][:2], type="survival", times=[180, 365],
            format="polars")
shape: (2, 3)
timesubject_1subject_2
f64f64f64
180.00.6201630.648317
365.00.2952110.330653

Predict conditional survival given already having survived to 100 days:

# Predict conditional survival given survival to 100 days
aft.predict(lung[["age", "sex"]][:2], type="survival", times=[180, 365],
            conditional_after=100, format="polars")
shape: (2, 3)
timesubject_1subject_2
f64f64f64
180.00.7720930.790875
365.00.3675330.403359

Add confidence intervals with ci=True:

# Include confidence intervals for the survival predictions
aft.predict(lung[["age", "sex"]][:2], type="survival", times=[180, 365],
            ci=True, format="polars")
shape: (2, 7)
timesubject_1subject_1_lowersubject_1_uppersubject_2subject_2_lowersubject_2_upper
f64f64f64f64f64f64f64
180.00.6201630.5359480.6935180.6483170.5877870.70227
365.00.2952110.2033650.3927510.3306530.2574350.405532

Unconditional mean survival time per subject:

# Predict unconditional mean survival time
aft.predict(lung[["age", "sex"]][:2], type="mean")
array([289.02746597, 311.08499373])

Expected remaining lifetime given the subject has already survived 100 days:

# Predict expected remaining lifetime given survival past 100 days
aft.predict(lung[["age", "sex"]][:2], type="mean_remaining",
            conditional_after=100)
array([246.28738556, 267.29476956])

Restricted mean survival time up to 365 days:

# Predict restricted mean survival time up to 365 days
aft.predict(lung[["age", "sex"]][:2], type="rmst", tau=365)
array([230.17701765, 239.03377795])

to_frame()

Return the coefficient table as a DataFrame.

Usage

Source

to_frame(*, format=None)

Exports one row per term, including the intercept, with coefficient estimates, standard errors, Wald statistics, p-values, and 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).
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 Weibull AFT model on the bundled lung dataset, then export its coefficient table as a Polars frame:

import greenwood as gw

# Load data and fit a Weibull AFT model
lung = gw.load_dataset("lung", backend="polars")
y = gw.Surv.right(lung["time"], event=(lung["status"] == 2))
aft = gw.AFT("weibull").fit(y, lung[["age", "sex"]])

# Export the coefficient table as a Polars DataFrame
aft.to_frame(format="polars")
shape: (3, 7)
termestimatestd_errorstatisticp_valueconf_lowconf_high
strf64f64f64f64f64f64
"(Intercept)"6.2748770.48136313.035657.6711e-395.3314237.21833
"age"-0.0122570.006957-1.7617770.078107-0.0258940.001379
"sex"0.3820860.1274732.9973820.0027230.1322430.631928

Request a different backend with format=:

# Export as a Pandas DataFrame instead
aft.to_frame(format="pandas")
term estimate std_error statistic p_value conf_low conf_high
0 (Intercept) 6.274877 0.481363 13.035650 7.671080e-39 5.331423 7.218330
1 age -0.012257 0.006957 -1.761777 7.810698e-02 -0.025894 0.001379
2 sex 0.382086 0.127473 2.997382 2.723096e-03 0.132243 0.631928