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
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"]])
aft
AFT (accelerated failure time model, dist='weibull')
coef se(coef) z p
(Intercept) 6.275 0.4814 13.036 7.65e-39
age -0.01226 0.006957 -1.762 0.0781
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
lung = gw.load_dataset("lung", backend="polars")
y = gw.Surv.right(lung["time"], event=(lung["status"] == 2))
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.808 3.152e-27
age -0.02336 0.008388 -2.785 0.005359
sex 0.5193 0.1551 3.347 0.0008175
Scale = 1.053
n = 228, events = 165
Log-likelihood = -1159
Use a formula string with the data argument:
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.65e-39
age -0.01226 0.006957 -1.762 0.0781
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,
conditional_after=None,
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).
Three 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.
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).
conditional_after: Any = None
-
For type="survival", optionally compute conditional survival: \(P(T > t \mid T > c) = S(t) / S(c)\). Scalar (same conditioning time for all subjects) or array-like (one per subject). Default None (unconditional). Predictions before the landmark time return 1.0.
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".
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. Column names match the input row index if newdata has a row index.
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
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"]])
aft.predict(lung[["age", "sex"]][:2], type="lp")
array([5.74991419, 5.82345873])
Predicted survival-time quantiles for the first two subjects at the lower quartile, median, and upper quartile (a table, so pass format=):
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.785921 | 132.156509 |
| 0.5 | 238.303411 | 256.489886 |
| 0.75 | 401.903952 | 432.575842 |
Read survival probabilities off the fitted curves at chosen times. Here are the estimates at 180 and 365 days for those same two subjects:
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.648318 |
| 365.0 | 0.295211 | 0.330653 |
Predict conditional survival given already having survived 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.40336 |
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
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"]])
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.274879 | 0.481355 | 13.035859 | 7.6500e-39 | 5.33144 | 7.218318 |
| "age" | -0.012257 | 0.006957 | -1.761794 | 0.078104 | -0.025894 | 0.001379 |
| "sex" | 0.382085 | 0.127473 | 2.997374 | 0.002723 | 0.132242 | 0.631927 |
Request a different backend with format=:
aft.to_frame(format="pandas")
|
term |
estimate |
std_error |
statistic |
p_value |
conf_low |
conf_high |
| 0 |
(Intercept) |
6.274879 |
0.481355 |
13.035859 |
7.650039e-39 |
5.331440 |
7.218318 |
| 1 |
age |
-0.012257 |
0.006957 |
-1.761794 |
7.810410e-02 |
-0.025894 |
0.001379 |
| 2 |
sex |
0.382085 |
0.127473 |
2.997374 |
2.723163e-03 |
0.132242 |
0.631927 |