Parametric accelerated failure time model.
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.
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.
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:
Linear predictor (type="lp"): the log-time location X\beta, showing how covariates shift the log-survival time distribution.
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.”
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.
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,).
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,).
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)| p | subject_1 | subject_2 |
|---|
| f64 | f64 | f64 |
| 0.25 | 122.785883 | 132.156457 |
| 0.5 | 238.303358 | 256.489806 |
| 0.75 | 401.903889 | 432.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)| time | subject_1 | subject_2 |
|---|
| f64 | f64 | f64 |
| 180.0 | 0.620163 | 0.648317 |
| 365.0 | 0.295211 | 0.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)| time | subject_1 | subject_2 |
|---|
| f64 | f64 | f64 |
| 180.0 | 0.772093 | 0.790875 |
| 365.0 | 0.367533 | 0.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)| time | subject_1 | subject_1_lower | subject_1_upper | subject_2 | subject_2_lower | subject_2_upper |
|---|
| f64 | f64 | f64 | f64 | f64 | f64 | f64 |
| 180.0 | 0.620163 | 0.535948 | 0.693518 | 0.648317 | 0.587787 | 0.70227 |
| 365.0 | 0.295211 | 0.203365 | 0.392751 | 0.330653 | 0.257435 | 0.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.
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)| term | estimate | std_error | statistic | p_value | conf_low | conf_high |
|---|
| str | f64 | f64 | f64 | f64 | f64 | f64 |
| "(Intercept)" | 6.274877 | 0.481363 | 13.03565 | 7.6711e-39 | 5.331423 | 7.21833 |
| "age" | -0.012257 | 0.006957 | -1.761777 | 0.078107 | -0.025894 | 0.001379 |
| "sex" | 0.382086 | 0.127473 | 2.997382 | 0.002723 | 0.132243 | 0.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 |