Weibull AFT vs Cox: when time ratios fit better
Fit a Weibull AFT model and a Cox model to the same lung data, read time ratios beside hazard ratios, and see when each describes the effect better.
On this page
A Cox model and a Weibull accelerated failure time (AFT) model answer the same question in two languages. Cox reports a hazard ratio, a statement about event rates. The Weibull AFT model reports a time ratio, a statement about how long survival lasts. This post fits both to the same people with the same predictors, puts the two sets of ratios side by side, and shows when the time ratio is the easier one to say out loud.
The data are the North Central Cancer Treatment Group (NCCTG) lung cancer data that ship with the R survival package, a standard teaching dataset. They are used only to illustrate the statistics, and nothing here is a statement about any person’s outlook or about treatment. For the background on censoring and the Cox model, start with the guide to Kaplan-Meier and Cox regression or the full survival analysis walkthrough.
The core difference in one paragraph
A hazard is the instantaneous event rate among people still at risk. The Cox model says each predictor multiplies that rate by a constant factor, whatever the follow-up time, and leaves the shape of the baseline hazard unspecified. An AFT model works on the time axis instead. It says each predictor stretches or shrinks survival time by a constant factor, the time ratio. A time ratio above 1 means longer survival and below 1 shorter, which is the reverse direction of a hazard ratio.
The choice matters because a Cox model needs no assumption about the shape of the hazard, but its answer is a rate ratio, which many readers misread as a ratio of risks or of survival times. The post on interpreting a hazard ratio covers that misreading. A parametric AFT model gives an answer in time, which is plainer, at the price of assuming a distribution for the survival times.
The data and the question
The lung data frame has 228 rows, one per person, so each person appears once. People were enrolled at several institutions, which can introduce clustering, a point the pitfalls section returns to. time is survival in days and status is 1 for censored and 2 for dead. Censoring is right-censoring: a censored person was still alive at last contact. Three predictors are used:
ph.ecog, the physician-rated ECOG performance score, entered as a number, so its ratio is per one-point step.sex, coded 1 for male and 2 for female, so its ratio contrasts female with male.age, in years.
One row has a missing ECOG score, so both models use the same 227 of 228 rows, and 164 of those people died during follow-up. Events set the precision of both models, and 164 events for 3 predictors is comfortably above the usual rule-of-thumb floor of ten per predictor. The question is: adjusting for age and sex, what does each ECOG step do to survival, described first as a time ratio and then as a hazard ratio?
The two models in R
The Cox and Weibull AFT models are each one line in the survival package, with the same formula:
library(survival)
fit_cox <- coxph(Surv(time, status) ~ ph.ecog + sex + age, data = lung)
fit_aft <- survreg(Surv(time, status) ~ ph.ecog + sex + age, data = lung,
dist = "weibull")
exp(coef(fit_cox)) # hazard ratios
exp(coef(fit_aft)) # time ratios (the first value is the intercept)
fit_aft$scale # the Weibull scale; the shape is 1 / scale
survreg() fits the log of the survival time to the predictors, so exponentiating a coefficient gives a time ratio. The first coefficient is the intercept, a baseline time rather than a ratio. Both functions read the status coded 1 and 2 with 2 as the event, as the survival manual describes. The numbers that follow come from two recorded Celeus engine runs of these models, whose R script for the Weibull model you can download below the article. The Cox numbers come from a companion run on the same rows by the same engine.
The Weibull AFT result
Engine run, Weibull AFT:
| Term | Time ratio | 95% CI | Wald p |
|---|---|---|---|
| ECOG score (per point) | 0.712 | 0.605 to 0.839 | < 0.001 |
| Sex (female vs male) | 1.493 | 1.172 to 1.903 | 0.00119 |
| Age (per year) | 0.993 | 0.979 to 1.006 | 0.269 |
Read in time: holding sex and age fixed, each one-point higher ECOG score is associated with survival times about 0.712 times as long, a 28.8% shorter expected survival time, with the data compatible with factors from 0.605 to 0.839. Women’s survival times were estimated to be 1.493 times as long as men’s at the same ECOG score and age, 49.35% longer. The age interval includes 1, so these data cannot separate no age effect from a small one.
The p values in the table are Wald tests, while the per-term tests in the engine output are likelihood ratio tests, so the two can differ slightly. The whole-model likelihood ratio test was 29.98 on 3 degrees of freedom, and the per-term likelihood ratio tests were 16.64 for ECOG score, 11.34 for sex and 1.244 for age, each on 1 degree of freedom.
The Cox result on the same rows
Engine run, Cox proportional hazards:
| Term | Hazard ratio | 95% CI | Wald p |
|---|---|---|---|
| ECOG score (per point) | 1.590 | 1.273 to 1.986 | < 0.001 |
| Sex (female vs male) | 0.575 | 0.414 to 0.799 | < 0.001 |
| Age (per year) | 1.011 | 0.993 to 1.030 | 0.232 |
Read in rates: each one-point higher ECOG score is associated with a 59% higher hazard of death, and women’s hazard was estimated at 42.46% lower than men’s. The whole-model likelihood ratio test was 30.50 on 3 degrees of freedom, close to the AFT model’s 29.98. The post on Cox regression in R reads this output line by line.
Reading the two tables together
Put the tables next to each other and three things stand out.
The direction flips for every term, as it should. ECOG score has a hazard ratio above 1 and a time ratio below 1: a higher score raises the event rate and shortens survival. Sex has a hazard ratio of 0.575 and a time ratio of 1.493: a lower rate and longer survival. For a term clearly separated from 1, a hazard ratio above 1 together with a time ratio above 1 would signal a mistake in the coding or the reference level.
The evidence is about the same. ECOG score and sex are clearly separated from 1 in both models, and age is not in either. The per-term likelihood ratio statistics are close: 16.64 against 16.62 for ECOG score, and 11.34 against 11.44 for sex. When two reasonable models agree this closely, the choice between them is about how you want to describe the result, not about what the data say.
The sizes are not on the same scale. A time ratio of 0.712 and a hazard ratio of 1.590 both describe the ECOG effect, but they cannot be compared directly, because they measure different things. A reader who sees only the second might conclude that the ECOG effect is “59% worse survival”, which it is not. It is a 59% higher rate at every moment, and what that does to the length of survival is exactly what the time ratio states.
How the two are linked under a Weibull model
For the Weibull distribution, and only for it among the common choices, the AFT model and the proportional hazards model are the same model written two ways (Kalbfleisch and Prentice, 2002, listed under the sources). The link runs through the Weibull shape. The recorded Weibull run reported a log scale parameter of -0.313, so the survreg() scale was exp(-0.313), a value below 1, and the shape, which is 1 divided by the scale, was therefore estimated to be above 1. A shape above 1 means a hazard that rises over follow-up.
The conversion is then: the AFT coefficient equals the negative of the proportional hazards coefficient multiplied by the scale (equivalently, divided by the shape). In ratio terms, a time ratio is the hazard ratio raised to the power of minus the scale. I checked this against the two recorded runs using only the numbers in their result files, on the log scale. The values predicted from the Cox coefficients came out close to the fitted AFT coefficients for all three terms, with the largest gap under a hundredth on the log scale.
Exact agreement is not expected. The Cox model estimates its coefficients without assuming any baseline hazard, while the Weibull model also fits the baseline, so a small gap is expected because the two estimators differ. If the two gave very different answers, that would be evidence that the Weibull shape is a poor fit, which is a reason to look at the fitted curve against the Kaplan-Meier curve before trusting the AFT result.
When time ratios describe the effect better
A time ratio is easier to use in three situations.
- The reader thinks in time. A statement like “survival times about 0.712 times as long” or “about 29 percent shorter” is more direct than a rate ratio, and it avoids the common misreading of a hazard ratio as a ratio of risks. Hernán makes the case that hazard ratios are hard to interpret causally, because they compare people who survived to each time.
- You want to predict a time. A parametric model gives a full fitted survival curve, so a median or another quantile follows from the fit. A Cox model needs an extra step to estimate its baseline.
- A constant stretch of time is more plausible than a constant rate ratio. For some exposures it is more natural to think that the whole course runs slower or faster, and that is exactly what an AFT model assumes.
Wei describes the AFT model as a useful alternative to the Cox model for this reason: it regresses the logarithm of survival time on the predictors, which gives an intuitive interpretation.
When Cox is the better choice
Choose Cox when you do not want to assume the shape of the hazard, when your field reports hazard ratios and a reviewer will expect one, or when you are comparing with earlier Cox results. It is also the default in most published survival work, so a hazard ratio is easier to put beside the literature. The log-rank test belongs to the same family, since it is the unadjusted partner of the Cox model for comparing groups.
The honest limit of the Weibull AFT model deserves its own warning. Because it is also a proportional hazards model, it does not rescue an analysis in which proportional hazards fails. If the hazard ratios drift over follow-up, a Weibull AFT model has the same flaw as the Cox model. Other AFT distributions, such as the log-normal and log-logistic, do not assume proportional hazards; they assume a constant time ratio instead. The Celeus engine’s survival AFT method uses only the Weibull distribution, so for those other distributions you would use survreg() directly with the dist argument.
Checking the assumptions
Each model has its own check, and the engine records what it can.
For the Cox model the assumption is proportional hazards, tested with cox.zph(). The recorded run gave a global chi-square of 4.464 on 3 degrees of freedom (p = 0.216), with no single term below 0.05, and the engine records “no evidence against proportional hazards”. That is weaker than “the assumption holds”: a test with 164 events can miss a modest drift, so also look at the plot. The post on checking the proportional hazards assumption works through the plot and the options when the check fails.
For the Weibull model the assumption is the Weibull shape itself. The engine run records the events-per-variable check, about 55 events per predictor here, but no test of the Weibull distribution. Two checks are still available by hand. Because the Weibull model implies proportional hazards, the cox.zph() result, which found no evidence against proportional hazards, is consistent with it. And you can compare the fitted Weibull survival curve with the Kaplan-Meier curve for the same groups: a systematic gap at early or late times says the distribution is wrong.
Reporting both models
Name the model, the reference level and the unit, give the interval, and say which model was primary. A defensible sentence for the Weibull model is:
Adjusting for age and sex, each one-point higher ECOG performance score was associated with survival times 0.712 times as long (time ratio 0.712, 95% CI 0.605 to 0.839, p < 0.001, 164 events among 227 people).
For the Cox model the matching sentence reports a hazard ratio of 1.59 (95% CI 1.27 to 1.99), described as a rate and not as a time. Decide the primary model before looking at the results, and treat the other as a sensitivity analysis. Showing both, as here, tells a reader the finding does not depend on the modelling language. The guide to reproducible statistical analysis explains why keeping the seed, the versions and the script together makes that claim checkable.
What the engine runs add
The Weibull and Cox runs use the same engine, the same rows and the same predictors in the same order, so the only difference is the model. Both are recorded, seeded analyses; the Weibull script is below the article. The engine also adds notes the hand-written code does not: the events-per-variable check, a note that the event indicator was recoded so that 2 means an event and 1 means censored, and a descriptive note on values beyond the 1.5 times interquartile range fence in ph.ecog (1 value) and time (10 values). Those notes change nothing in the data. They only flag values worth a second look.
For the method details in the Celeus engine, see the reference pages for the Weibull AFT model and for Cox regression.
Pitfalls to avoid
- Reading a time ratio as a hazard ratio. The two go in opposite directions. Check the sign of each estimate against the question before writing the sentence.
- Reading
exp(coef)fromsurvreg()including the intercept. The first value is a baseline time, not a ratio. - Treating a coded category as a number.
sexis 1 and 2 and works as a single contrast. A variable with three or more categories coded 1, 2, 3 must be a factor, or the model assumes equal steps. - Believing the Weibull AFT is a way round non-proportional hazards. It is not, as explained above.
- Ignoring clustering. People in this dataset were enrolled at several institutions, and a model that treats them as independent can understate the standard errors if people at one site resemble each other.
- Picking the model after seeing the results. Choose the primary model first.
- Too few events. Count events, not rows.
Treat all of this as research statistics, not clinical decision support: a time ratio or hazard ratio in a public teaching dataset describes an association in that sample and is not an estimate for any individual.
Reproduce this
Worked example
Data: Loprinzi CL, Laurie JA, Wieand HS, Krook JE, Novotny PJ, Kugler JW, et al. (1994). Prospective evaluation of prognostic variables from patient-completed questionnaires. Journal of Clinical Oncology 12(3):601-607. doi:10.1200/JCO.1994.12.3.601. Seed: 20261029.
Run on Celeus engine 0.36.4, seed 20261029, public data.
Output
# Results - Test: Weibull accelerated failure time regression - n: 227 - statistic: -4.069 - p-value: < 0.001 - effect size: time ratio = 0.712 (95% CI 0.6046 to 0.8386) - 95% CI (ph.ecog): [0.6046, 0.8386] - rows used: 227 of 228 (1 dropped) - seed: 20261029 ## Assumption checks - events per variable: passed=TRUE (at or above the rule-of-thumb floor (a convention, not a guarantee of a stable model)) ## Term tests | Term | Test | Statistic | df | Residual df | p-value | |---|---|---|---|---|---| | ph.ecog | LR | 16.64 | 1 | - | < 0.001 | | sex | LR | 11.34 | 1 | - | < 0.001 | | age | LR | 1.244 | 1 | - | 0.265 | ## Power - Power is not calculated for this test.
R script
# Celeus - reproduce.R (run from this package folder; bundled data in data/)
# Test: Weibull accelerated failure time regression | engine 0.36.4 | catalog c39-d3cd
# Research context: not provided for this run.
# Reproduces the recorded result deterministically.
set.seed(20261029)
.data_path <- "data/aft-weibull-vs-cox.csv"
if (!file.exists(.data_path)) stop("Run reproduce.R from the package folder (the bundled data is in data/).")
data <- readr::read_csv(.data_path, show_col_types = FALSE) # needs the readr package
clean_cells <- function (df)
{
ctrl_collapse <- "[\r\n\t\v\f]+"
zero_width_chars <- intToUtf8(c(8203L, 65279L), multiple = TRUE)
uni_space_chars <- intToUtf8(c(160L, 8239L, 8199L), multiple = TRUE)
clean_text <- function(x) {
out <- x
for (ch in zero_width_chars) out <- gsub(ch, "", out,
fixed = TRUE)
out <- gsub(ctrl_collapse, " ", out)
for (ch in uni_space_chars) out <- gsub(ch, " ", out,
fixed = TRUE)
trimws(gsub(" +", " ", out))
}
changed_cols <- character(0)
cells_cleaned <- 0L
retyped <- character(0)
for (j in seq_along(df)) {
col <- df[[j]]
if (!is.character(col))
next
cleaned <- clean_text(col)
n_changed <- sum(!is.na(col) & !is.na(cleaned) & col !=
cleaned, na.rm = TRUE)
if (n_changed > 0L) {
changed_cols <- c(changed_cols, names(df)[j])
cells_cleaned <- cells_cleaned + n_changed
}
df[[j]] <- cleaned
non_blank <- cleaned[!is.na(cleaned) & nzchar(cleaned)]
if (length(non_blank) && all(grepl("^[+-]?(0|[1-9][0-9]*)([.][0-9]+)?$",
non_blank))) {
parsed <- suppressWarnings(as.numeric(cleaned))
was_blank <- is.na(cleaned) | !nzchar(cleaned)
lost <- (is.na(parsed) | is.infinite(parsed)) & !was_blank
if (!any(lost)) {
finite <- parsed[!is.na(parsed)]
all_whole <- all(finite == floor(finite))
in_range <- all(abs(finite) <= .Machine$integer.max)
df[[j]] <- if (all_whole && in_range)
as.integer(parsed)
else parsed
retyped <- c(retyped, names(df)[j])
}
}
}
names(df) <- clean_text(names(df))
if (length(changed_cols)) {
attr(df, "ingest_clean_columns") <- changed_cols
attr(df, "ingest_clean_cells_cleaned") <- cells_cleaned
}
if (length(retyped))
attr(df, "ingest_clean_retyped") <- retyped
df
}
data <- clean_cells(data)
# Complete-case analysis: drop rows missing any analysis value.
data[["status"]] <- as.integer(factor(data[["status"]], levels = c("1", "2"))) - 1L
.acols <- c("ph.ecog", "sex", "age", "time", "status")
data <- data[stats::complete.cases(data[, .acols, drop = FALSE]), , drop = FALSE]
result <- { .fit <- survival::survreg(survival::Surv(`time`, `status`) ~ `ph.ecog` + `sex` + `age`, data = data, dist = "weibull"); list(summary = summary(.fit), term_tests = stats::drop1(.fit, test = "Chisq")) }
print(result)Download the files
Common questions
Is a time ratio just the inverse of a hazard ratio?
No. A hazard ratio compares event rates at each moment, and a time ratio compares how long survival lasts. They point in opposite directions, so a hazard ratio above 1 goes with a time ratio below 1. Only under a Weibull model are they linked, through the fitted shape, and even then the link is not a plain inverse unless the shape is exactly 1, which is the exponential model.
Can I fit both a Cox model and an AFT model to the same data?
Yes, and it is a useful check. Fit both with the same predictors on the same rows and compare the direction and strength of each term. Choose one as the primary model before looking at the results, and report the other as a sensitivity analysis rather than choosing whichever result looks better.
Which distribution should I use for an AFT model?
The Weibull is the usual starting point because it is simple, allows a hazard that rises or falls steadily, and has a hazard ratio form as well. Log-normal and log-logistic models are other common choices. Compare the fitted survival curve with a Kaplan-Meier curve before settling on one, and state the choice.
Does a Weibull AFT model fix a failed proportional hazards check?
No. A Weibull AFT model is also a proportional hazards model, so if the hazard ratios drift over follow-up the Weibull fit has the same problem. A different AFT distribution, a stratified or time-varying Cox model, or a summary that needs no proportional hazards are the routes to try.
How do I read a time ratio of 0.712?
It means expected survival time is estimated to be 0.712 times as long for a one-unit increase in the predictor, holding the other terms fixed, which is about 29 percent shorter. Always give the unit, the confidence interval, and the reference group for a category, as with any ratio.
How many events do I need for an AFT model?
The events, not the rows, set the precision, as with a Cox model. The same rule of thumb of about ten events per estimated parameter is a floor, not a guarantee. A parametric model also leans on the assumed distribution being right, which more events help you check.
Sources
- Wei LJ (1992). The accelerated failure time model: a useful alternative to the Cox regression model in survival analysis. Statistics in Medicine 11(14-15):1871-1879. doi:10.1002/sim.4780111409
- Cox DR (1972). Regression models and life-tables. Journal of the Royal Statistical Society: Series B (Methodological) 34(2):187-202. doi:10.1111/j.2517-6161.1972.tb00899.x
- Kalbfleisch JD, Prentice RL (2002). The Statistical Analysis of Failure Time Data, 2nd edition. Wiley. doi:10.1002/9781118032985
- Hernán MA (2010). The hazards of hazard ratios. Epidemiology 21(1):13-15. doi:10.1097/EDE.0b013e3181c1ea43
- Therneau TM. survival: Survival Analysis. R package reference manual (survreg, coxph and lung help pages), version 3.8-6. CRAN. cran.r-project.org
Methods discussed
Try it on your own data
Upload a dataset and Celeus suggests a method, checks its assumptions, runs it in R, and gives you the script to rerun it.
Try it in Celeus