To run a survival analysis in R, you use the survival package: build the outcome with Surv(time, event), estimate the Kaplan-Meier curve with survfit(), compare groups with the log-rank test, and model predictors with a Cox proportional hazards regression in coxph(). The analysis that holds up depends on coding censoring correctly and checking the proportional hazards assumption. This guide covers the full workflow with code.
What survival analysis handles that regression cannot
Survival analysis models the time until an event, such as relapse, death, or equipment failure, while correctly handling censoring, the participants who leave the study or reach the end without the event. A plain linear regression in R cannot represent censored cases, and a logistic regression throws away the timing by reducing the outcome to a yes or no. Survival methods keep both.
Coding time and the censoring indicator
You need a time column and an event indicator coded 1 for the event and 0 for censored. The Surv() object pairs them:
library(survival)
df$event <- ifelse(df$status == "relapsed", 1, 0)
surv_obj <- Surv(time = df$months, event = df$event)Coding censoring as if it were the event, or dropping censored cases entirely, biases every result that follows. This is the single most common error in applied survival work.
The Kaplan-Meier estimate
Estimate and plot the survival function overall, then split by a grouping variable to compare curves:
fit <- survfit(surv_obj ~ group, data = df)
summary(fit) # survival probabilities over time
library(survminer)
ggsurvplot(fit, data = df, pval = TRUE, risk.table = TRUE)The survminer package draws a publication-ready curve with the number at risk beneath it and the log-rank p-value on the plot, which is exactly what a results chapter needs.
Comparing groups with the log-rank test
The log-rank test asks whether two or more survival curves differ across the whole follow-up:
survdiff(surv_obj ~ group, data = df)It is the survival counterpart of comparing group means, but it weighs the entire curve rather than a single time point, so it detects differences that a snapshot comparison would miss.
The Cox proportional hazards model
To estimate the effect of predictors on the hazard, fit a Cox model. Its coefficients exponentiate into hazard ratios, much as logistic coefficients become odds ratios:
cox <- coxph(Surv(months, event) ~ group + age + stage, data = df)
summary(cox) # exp(coef) column is the hazard ratioA hazard ratio above 1 means a higher instantaneous risk of the event; below 1 means a lower risk. The conceptual detail behind the Cox model is covered in the interpretation of regression coefficients more broadly.
Checking the proportional hazards assumption
The Cox model assumes the hazard ratio is constant over time. Test it with the scaled Schoenfeld residuals:
ph <- cox.zph(cox)
ph # a significant p flags a violation
plot(ph) # residuals should be flat over timeWhen the assumption fails for a predictor, the usual remedies are to add a time interaction for that term, stratify on it, or fit a model that does not require proportional hazards. Report the check; reviewers look for it.
Reporting survival analysis
Report the Kaplan-Meier median survival per group with the log-rank p, then each Cox predictor as a hazard ratio with its 95% confidence interval and p-value. A clean sentence reads: the treatment group had longer survival than control (log-rank p = .008), and in the Cox model treatment was associated with a lower hazard, HR = 0.62, 95% CI [0.44, 0.88], p = .007. The formatting rules sit in presenting statistics in APA style, and the narrative belongs in your results section of a research paper.