Tutorials › Biostatistics › Survival Analysis in R: The survival Package

Time-to-Event Analysis

Survival Analysis in R: The survival Package

A practical guide to time-to-event analysis in R using the survival package, including survival objects, censoring, Kaplan–Meier estimation, log-rank tests, Cox proportional hazards models, hazard ratios, confidence intervals, model diagnostics, clinical-trial interpretation, and reproducible workflows.

Intermediate 22 min read

What You'll Learn

  • What survival analysis means in a clinical-trial setting
  • How censoring is represented with Surv()
  • How to estimate and interpret Kaplan–Meier survival curves
  • How to compare survival curves with the log-rank test
  • How to fit and interpret Cox proportional hazards models
  • How survival analysis is used in clinical study reports

Introduction

Many clinical-trial endpoints are fundamentally about time.

Investigators may want to know how long patients remain alive, how long they remain free of disease progression, or how long a treatment response lasts.

Examples include:

  • Overall survival (OS)
  • Progression-free survival (PFS)
  • Disease-free survival (DFS)
  • Event-free survival (EFS)
  • Time to progression (TTP)
  • Duration of response (DoR)

These endpoints are commonly analyzed using survival analysis, a family of statistical methods designed for time-to-event data.

In R, one of the foundational tools for this work is the survival package.

Key idea: Survival analysis is not simply an analysis of how long patients survive. It is a framework for analyzing the time until a prespecified event while properly handling patients whose event time is not observed during the observation period.

Why Ordinary Statistical Methods Are Not Enough

Suppose a clinical trial follows 100 patients for two years.

Some patients die during follow-up. Others remain alive at the end of the study. Still others withdraw, are lost to follow-up, or remain alive when their individual follow-up ends.

For those patients, the exact time of death is unknown.

We only know that they survived for at least the amount of time they were observed.

This is called right censoring.

What Is a Time-to-Event Endpoint?

A time-to-event endpoint contains two fundamental pieces of information:

Component Meaning
Time How long the patient was observed until the event or censoring
Event indicator Whether the event actually occurred

For example:

Patient Time Event
001 14 months 1 — death
002 24 months 0 — censored
003 9 months 1 — death
004 18 months 0 — censored

Patient 002 did not necessarily survive exactly 24 months. We know only that the patient survived at least 24 months without the event being observed.

The Survival Function

The central quantity in survival analysis is the survival function.

It is commonly written as:

$$ S(t)=P(T>t) $$

where \(T\) is the event time.

In words, \(S(t)\) is the probability of surviving beyond time \(t\).

For example, if:

$$ S(12)=0.80 $$

then the estimated probability of remaining event-free beyond 12 months is 80%.

Important: "Survival" in survival analysis does not necessarily mean death. The event can be progression, relapse, treatment failure, hospitalization, response loss, or another prespecified endpoint.

The Event Indicator

The event indicator is commonly coded:

  • 1: event occurred
  • 0: right censored

For example:

time  status
14    1
24    0
9     1
18    0

This convention is especially convenient when using Surv().

The survival Package

The R package survival provides the core tools needed for many standard survival analyses.

It includes functionality for:

  • Creating survival objects
  • Kaplan–Meier estimation
  • Survival-curve comparisons
  • Cox proportional hazards regression
  • Stratified models
  • Time-dependent covariates
  • Model diagnostics
  • Predicted survival quantities

The package is widely used in medical research and clinical-trial analysis.

Loading the Package

library(survival)

If the package has not yet been installed:

install.packages("survival")

The Surv() Function

The most important starting point is the Surv() function.

For right-censored data, the basic form is:

Surv(time, status)

For example:

Surv(
  time = data$time,
  event = data$status
)

This creates a survival object that can be passed to functions such as survfit() and coxph().

A Small Example Dataset

Consider a simulated oncology study with 12 patients.

trial <- data.frame(
  id = sprintf("P%03d", 1:12),
  treatment = c(
    "Control","Control","Control","Control",
    "Control","Control",
    "Experimental","Experimental","Experimental",
    "Experimental","Experimental","Experimental"
  ),
  time = c(
    8, 12, 15, 18, 21, 24,
    10, 16, 20, 26, 30, 34
  ),
  status = c(
    1, 1, 1, 0, 1, 0,
    1, 1, 0, 1, 0, 0
  )
)

Here, status = 1 represents the event and status = 0 represents censoring.

Creating the Survival Object

surv_obj <- Surv(
  time = trial$time,
  event = trial$status
)

surv_obj

The result encodes both the observed time and whether the observation ended with an event or censoring.

Think of Surv() as the bridge between your dataset and the survival-analysis model. You generally do not pass raw time and event columns independently to every survival-analysis function. Instead, you construct a survival object that encodes the time-to-event structure.

Kaplan–Meier Estimation

The Kaplan–Meier estimator is one of the most important tools in survival analysis.

It estimates the probability of remaining event-free over time while accounting for censored observations.

In R, Kaplan–Meier estimation is performed with survfit().

km_fit <- survfit(
  Surv(time, status) ~ 1,
  data = trial
)

km_fit

The notation:

Surv(time, status) ~ 1

means that we are estimating one overall survival curve without comparing groups.

The Kaplan–Meier Estimator

Suppose event times are:

$$ t_1,t_2,\ldots,t_k $$

At each event time, the Kaplan–Meier estimator updates the estimated survival probability.

The estimator can be written as:

$$ \hat S(t) = \prod_{t_i\le t} \left( 1-\frac{d_i}{n_i} \right) $$

where:

  • \(d_i\) = number of events at time \(t_i\)
  • \(n_i\) = number at risk immediately before \(t_i\)

The resulting curve is a step function.

An Actual Kaplan–Meier Plot

The following simulated figure illustrates two treatment groups.

Figure 1. Simulated Kaplan–Meier Survival Curves
Illustrative event-free survival curves for an experimental treatment and control group. Tick marks indicate censored observations.
Experimental
Control
50% survival reference

The curves are simulated for teaching purposes and do not represent an actual clinical trial.

How to Read a Kaplan–Meier Curve

The x-axis represents time.

The y-axis represents the estimated probability of surviving beyond that time.

For example, if a curve is at:

$$ S(12)=0.75 $$

then approximately 75% of the population is estimated to remain event-free beyond 12 time units.

The vertical drops occur at event times.

Censoring does not cause a vertical drop.

What Does a Censoring Tick Mean?

A censoring mark means that the patient's event was not observed after that point of follow-up.

For example, if a patient is censored at 18 months, we know that the patient was event-free through 18 months.

We do not know what happened afterward.

Censoring is not an event. A common beginner mistake is to treat every endpoint of follow-up as if it were an event. Kaplan–Meier estimation distinguishes between observed events and censored observations.

Kaplan–Meier by Treatment Group

To estimate separate curves for treatment groups:

km_fit <- survfit(
  Surv(time, status) ~ treatment,
  data = trial
)

km_fit

The right-hand side of the formula determines the grouping variable.

Plotting the Kaplan–Meier Estimate

The base R plotting system can directly display a survfit object.

plot(
  km_fit,
  xlab = "Time",
  ylab = "Survival Probability",
  lwd = 2
)

For clinical reporting, analysts often use ggplot2 or other specialized visualization tools for greater control over typography, risk tables, confidence intervals, and annotations.

Confidence Intervals

A Kaplan–Meier estimate is uncertain because it is based on a finite sample.

Confidence intervals can therefore be displayed around the estimated curve.

In survfit(), confidence intervals are available from the fitted object.

km_fit <- survfit(
  Surv(time, status) ~ treatment,
  data = trial,
  conf.type = "log"
)

The exact confidence-interval transformation should be selected according to the statistical analysis specification.

Median Survival

A commonly reported summary is the median survival time.

The median survival is the time at which:

$$ S(t)=0.50 $$

In R:

summary(km_fit)$table

For a group whose survival curve never falls below 50% during observed follow-up, the median may not be estimable.

Do not report "median not reached" as zero. If fewer than half of the patients have experienced the event, the Kaplan–Meier curve has not crossed 0.50, so the median survival is not estimable from the observed data.

Restricted Mean Survival Time

When the median is not estimable or when the investigator wants an alternative summary, the restricted mean survival time (RMST) can be useful.

Conceptually, RMST is the area under the survival curve up to a specified time horizon \(\tau\):

$$ RMST(\tau) = \int_0^\tau S(t)\,dt $$

RMST can be particularly informative when survival curves do not cross 50% during the available follow-up.

Number at Risk

A Kaplan–Meier curve should generally be interpreted alongside the number of patients remaining at risk.

Time Experimental Control
0 120 118
6 months 103 91
12 months 86 69
18 months 70 48
24 months 55 31

When very few patients remain at risk, the tail of the Kaplan–Meier curve may be based on limited information.

Always inspect the risk set. A visually impressive separation between curves late in follow-up can be unstable when only a small number of patients remain under observation.

The Log-Rank Test

If the trial compares two or more treatment groups, investigators often use the log-rank test to compare their survival distributions.

In R:

logrank <- survdiff(
  Surv(time, status) ~ treatment,
  data = trial
)

logrank

The log-rank test evaluates whether the observed event patterns differ between groups under its underlying assumptions.

Interpreting the Log-Rank Test

Suppose the analysis produces:

$$ p=0.018 $$

A small p-value provides evidence against the null hypothesis of equal survival distributions.

It does not, by itself, tell you:

  • How large the treatment effect is
  • At what time the treatment effect occurs
  • Whether proportional hazards holds
  • Whether the difference is clinically meaningful

For those questions, effect estimates and graphical displays are important.

Why the Log-Rank Test Is Not Enough

Consider two treatment groups whose survival curves separate only after 18 months.

Another study might show an early difference that disappears later.

A single p-value does not adequately describe these patterns.

Therefore, clinical-trial reporting generally combines:

  • Kaplan–Meier curves
  • Numbers at risk
  • Median survival where estimable
  • Hazard ratios
  • Confidence intervals
  • Statistical tests
  • Clinical interpretation

The Hazard Function

Another central concept is the hazard.

The hazard function can be described informally as the instantaneous event rate among patients who have survived to a particular time.

It is commonly represented mathematically as:

$$ h(t) = \lim_{\Delta t\rightarrow0} \frac{ P(t\le T

The hazard is not the same thing as the probability of an event.

Important distinction: Survival probability answers "What proportion remain event-free beyond time \(t\)?" Hazard describes the instantaneous event rate among patients who have remained event-free up to that time.

Hazard Ratio

The hazard ratio compares hazards between two groups.

For an experimental treatment compared with control:

$$ HR = \frac{h_{\text{Experimental}}(t)} {h_{\text{Control}}(t)} $$

under the proportional-hazards interpretation.

For example:

$$ HR=0.70 $$

is commonly interpreted as the experimental group having an estimated hazard 70% that of the reference group, corresponding to an approximately 30% lower instantaneous event hazard under the proportional-hazards model.

Hazard Ratio Is Not Risk Ratio

This distinction is essential.

A hazard ratio is not generally equivalent to:

$$ \frac{\text{probability of event in treatment}} {\text{probability of event in control}} $$

The hazard ratio incorporates the timing of events.

It should therefore not be casually described as a relative reduction in "risk" without appropriate qualification.

The Cox Proportional Hazards Model

The Cox proportional hazards model is one of the most widely used regression models for survival data.

The model is commonly written:

$$ h(t\mid X) = h_0(t) \exp(\beta_1X_1+\beta_2X_2+\cdots+\beta_pX_p) $$

where:

  • \(h_0(t)\) is the baseline hazard
  • \(X_1,\ldots,X_p\) are covariates
  • \(\beta_1,\ldots,\beta_p\) are regression coefficients

The baseline hazard is left unspecified, which is why the Cox model is often called a semi-parametric model.

Fitting a Cox Model in R

The coxph() function fits a Cox proportional hazards model.

cox_fit <- coxph(
  Surv(time, status) ~ treatment,
  data = trial
)

summary(cox_fit)

This estimates the coefficient for treatment on the log-hazard scale.

Interpreting the Cox Coefficient

Suppose the fitted coefficient is:

$$ \hat\beta=-0.357 $$

The corresponding hazard ratio is:

$$ HR=e^{-0.357}\approx0.70 $$

Thus:

exp(coef(cox_fit))

returns the hazard ratio.

Confidence Intervals for the Hazard Ratio

A common approach is:

exp(confint(cox_fit))

A complete summary can also be obtained from:

summary(cox_fit)

The resulting table typically contains the coefficient, exponentiated coefficient, standard error, test statistic, and p-value.

Example Hazard-Ratio Interpretation

Suppose a clinical trial reports:

Measure Estimate
Hazard ratio 0.72
95% CI 0.58–0.89
p-value 0.002

A suitable interpretation would be:

Example interpretation: The estimated hazard of the event was lower in the experimental treatment group than in the control group, with a hazard ratio of 0.72 (95% CI: 0.58–0.89). Under the proportional-hazards model, this corresponds to an estimated hazard approximately 28% lower in the experimental group.

The confidence interval provides information about the precision of the estimated treatment effect.

The Reference Group Matters

If treatment is coded as a factor, R uses one level as the reference category.

For example:

trial$treatment <-
  factor(
    trial$treatment,
    levels = c("Control", "Experimental")
  )

The resulting coefficient for Experimental is interpreted relative to Control.

Always verify the reference category. A hazard ratio of 0.70 means something very different from a hazard ratio of 1.43. The numerical reciprocal can arise simply from reversing the reference group.

Multiple Covariates

A Cox model can include several prognostic or treatment-related variables.

cox_fit <- coxph(
  Surv(time, status) ~
    treatment +
    age +
    stage +
    biomarker,
  data = trial
)

summary(cox_fit)

This produces adjusted hazard ratios for the included covariates.

Continuous Covariates

Suppose age is entered as a continuous variable.

coxph(
  Surv(time, status) ~ age,
  data = trial
)

The hazard ratio is then associated with a one-unit increase in age.

If:

$$ HR=1.03 $$

the model estimates a 3% higher hazard per one-unit increase in age, assuming the model is otherwise correctly specified.

Sometimes a more interpretable contrast is a 10-year increase.

If the coefficient is \(\beta\), the hazard ratio for a 10-unit increase is:

$$ HR_{10}=e^{10\beta} $$

Categorical Covariates

Categorical variables should generally be explicitly coded as factors.

trial$stage <-
  factor(
    trial$stage,
    levels = c("I", "II", "III", "IV")
  )

R then estimates each non-reference category relative to the reference.

Stratification

Sometimes a covariate does not satisfy the proportional-hazards assumption but is important for controlling the baseline hazard.

The Cox model can use stratification:

cox_fit <- coxph(
  Surv(time, status) ~
    treatment +
    age +
    strata(region),
  data = trial
)

A stratified variable receives a separate baseline hazard for each stratum, while its own coefficient is not estimated.

The Proportional Hazards Assumption

The Cox model assumes that hazard ratios remain proportional over time.

For two patients or groups:

$$ \frac{h_1(t)}{h_2(t)} = \text{constant} $$

over the relevant follow-up period.

This does not mean that the hazards themselves are constant.

It means their ratio is assumed to be constant.

Common misconception: "Proportional hazards" does not mean that the event rate is constant over time. It means that the relative hazard between groups is assumed to remain approximately constant over time.

Testing Proportional Hazards in R

The cox.zph() function is commonly used to assess the proportional-hazards assumption.

ph_check <- cox.zph(cox_fit)

ph_check

A graphical assessment is also useful:

plot(ph_check)

The analysis should not rely exclusively on a single p-value.

Clinical interpretation, graphical diagnostics, and knowledge of the endpoint should all contribute to the assessment.

What If Proportional Hazards Does Not Hold?

If the proportional-hazards assumption is substantially violated, several approaches may be considered.

  • Include time-dependent effects
  • Stratify on an appropriate variable
  • Report time-specific effects
  • Use RMST
  • Use alternative survival models
  • Describe the non-proportional pattern directly

The appropriate strategy depends on the estimand, endpoint, trial design, and statistical analysis plan.

Kaplan–Meier and Cox Models Answer Different Questions

Method Primary Purpose
Kaplan–Meier Estimate survival over time
Log-rank test Compare survival distributions
Cox model Estimate covariate effects on hazard
RMST Compare average event-free time through a specified horizon

These methods complement one another.

Kaplan–Meier vs. Cox Regression

A Kaplan–Meier curve can show:

  • How survival changes over time
  • Where events occur
  • Where censoring occurs
  • Whether curves appear to separate

A Cox model can additionally provide:

  • Hazard ratios
  • Confidence intervals
  • Covariate adjustment
  • Multivariable modeling

In a clinical study report, the two are frequently presented together.

A Typical Clinical-Trial Workflow

1
Define the time-to-event endpoint and event rules.
2
Derive analysis time and event/censoring status.
3
Create the survival object using Surv().
4
Estimate Kaplan–Meier curves.
5
Calculate survival summaries and confidence intervals.
6
Perform the prespecified treatment-group comparison.
7
Fit the prespecified Cox proportional hazards model.
8
Assess model assumptions.
9
Validate event and censoring derivations.
10
Generate validated tables, listings, and figures.

Clinical Trial Data Structure

A simplified survival-analysis dataset might look like:

USUBJID TRT01P ADT ADT_EVENT AVAL CNSR
001 Control 2026-03-15 2026-03-15 182 0
002 Experimental 2026-08-21 NA 335 1
003 Experimental 2026-06-12 2026-06-12 265 0
004 Control 2026-09-01 NA 346 1

The exact variables depend on the clinical-trial data standard and analysis dataset.

Censoring Indicators in Clinical Trials

Clinical-trial datasets often use a censoring variable rather than directly coding the event as 1.

For example:

  • CNSR = 0 may indicate an event
  • CNSR = 1 may indicate censoring

If that convention is used, the event indicator for Surv() may need to be derived as:

event <- 1 - CNSR

Surv(
  time = AVAL,
  event = event
)
Be extremely careful with event coding. The meaning of 0 and 1 must be confirmed from the analysis dataset and statistical analysis plan. Reversing the event indicator can fundamentally change the analysis.

Time Units Matter

Survival time may be expressed in:

  • Days
  • Weeks
  • Months
  • Years

The model itself does not require a particular unit.

However, the chosen unit must be consistently defined and clearly documented.

time_months <-
  (event_date - baseline_date) / 30.4375

The exact derivation should follow the analysis specification rather than an arbitrary conversion chosen during plotting.

Overall Survival

For overall survival, the event is commonly death from any cause.

The survival object might therefore be:

os <- Surv(
  time = OS_TIME,
  event = OS_EVENT
)

A Kaplan–Meier estimate can then be obtained with:

os_km <- survfit(
  os ~ TRT01P,
  data = adtte
)

The exact event and censoring rules should come from the prespecified endpoint definition.

Progression-Free Survival

Progression-free survival is more complicated because the event may consist of either:

  • Disease progression
  • Death

The endpoint derivation therefore typically requires integration of tumor assessment data, clinical events, treatment discontinuation rules, and censoring conventions.

Do not derive PFS simply by looking for the first tumor progression date. PFS definitions can include death and prespecified censoring rules. The analysis dataset should contain the validated endpoint derivation rather than requiring the plotting program to independently reconstruct the endpoint.

Duration of Response

Duration of response is generally defined only among patients who achieve a prespecified response.

The event may be progression or death, depending on the endpoint definition.

A typical structure is:

$$ DoR = \text{date of progression/death} - \text{date response criteria first met} $$

The exact definition, however, must follow the protocol and SAP.

Competing Risks

Standard Kaplan–Meier methods can be inappropriate when another event prevents the event of interest from occurring and that competing event is handled as ordinary censoring.

For example, if the endpoint is time to relapse and death without relapse prevents relapse from occurring, death can be a competing event.

Competing-risk analyses may require methods such as cumulative incidence functions rather than treating competing events as non-informative censoring.

Important: The standard Surv() + Kaplan–Meier workflow is not automatically appropriate for every time-to-event problem. The event structure should be considered before selecting the statistical method.

Left Truncation and Delayed Entry

Some survival studies include patients who enter the risk set after time zero.

This is sometimes called left truncation or delayed entry.

The survival object can represent entry and exit times:

Surv(
  time = entry,
  time2 = exit,
  event = status
)

For example:

Surv(
  entry_time,
  exit_time,
  event
)

This tells the analysis that the patient was only under observation and at risk from the entry time onward.

Time-Dependent Covariates

Some covariates change over time.

Examples include:

  • Biomarker measurements
  • Treatment exposure
  • Time-varying disease status
  • Transplant status

The Cox framework can accommodate time-dependent covariates, but the dataset must be structured appropriately.

For example:

coxph(
  Surv(start, stop, event) ~
    treatment +
    biomarker,
  data = time_varying_data
)

This is substantially more complex than a standard one-row-per-patient analysis and should be specified carefully.

One Row Per Patient Is Not Always Enough

For a basic right-censored endpoint, a dataset may contain one row per patient.

For time-dependent analyses, a patient may contribute multiple observation intervals.

Patient Start Stop Biomarker Event
001 0 90 Low 0
001 90 180 High 0
001 180 240 High 1

The interval structure allows the covariate to change over time.

Stratified Kaplan–Meier Curves

Sometimes the analyst wants curves within another grouping variable.

survfit(
  Surv(time, status) ~ treatment + stage,
  data = trial
)

This can produce separate curves for combinations of treatment and stage.

For reporting, however, the display should be designed deliberately so that the number of curves does not become excessive.

Confidence Bands in a Kaplan–Meier Plot

Confidence bands can communicate uncertainty around the survival estimate.

A basic base-R display can include confidence intervals:

plot(
  km_fit,
  conf.int = TRUE,
  xlab = "Time",
  ylab = "Survival Probability",
  lwd = 2
)

For publication or CSR graphics, analysts often use a more customizable plotting workflow.

Extracting Kaplan–Meier Results

The fitted object contains the estimated survival information.

summary(km_fit)

Useful components include:

km_fit$time
km_fit$surv
km_fit$n.risk
km_fit$n.event
km_fit$n.censor

These components can be useful when creating custom tables or figures.

Extracting the Cox Model Results

summary(cox_fit)

Useful quantities include:

coef(cox_fit)
exp(coef(cox_fit))
confint(cox_fit)
exp(confint(cox_fit))

This allows a programmer to construct a formatted hazard-ratio table.

A Simple Hazard-Ratio Table

A clinical analysis table might contain:

Covariate Hazard Ratio 95% CI p-value
Experimental vs Control 0.72 0.58–0.89 0.002
Age, per 10 years 1.08 0.99–1.18 0.081
Stage III vs I/II 1.54 1.16–2.05 0.003

Forest Plots

Hazard ratios are often presented graphically in a forest plot.

A forest plot makes it easy to see:

  • The direction of effects
  • The magnitude of hazard ratios
  • Confidence intervals
  • The reference value of HR = 1

The survival package supplies the model; the actual forest-plot visualization can be constructed using additional R packages or custom plotting code.

Adjusted vs Unadjusted Analyses

A treatment-only Cox model:

coxph(
  Surv(time, status) ~ treatment,
  data = trial
)

produces an unadjusted treatment effect.

A model containing baseline covariates:

coxph(
  Surv(time, status) ~
    treatment +
    age +
    stage +
    biomarker,
  data = trial
)

produces adjusted estimates.

The appropriate primary analysis depends on the prespecified statistical methodology.

Do Not Select Covariates Solely by p-Value

A common mistake is to fit many variables and retain only those with small p-values.

Clinical-trial models should instead follow the prespecified analysis strategy, including decisions about:

  • Baseline covariates
  • Stratification factors
  • Treatment effects
  • Interactions
  • Missing data
  • Model assumptions

Exploratory model selection and confirmatory analysis should not be confused.

Interactions in Cox Models

An interaction can be modeled with:

coxph(
  Surv(time, status) ~
    treatment * biomarker,
  data = trial
)

This evaluates whether the treatment effect varies according to the biomarker.

Interaction analyses require careful interpretation and are especially sensitive to sample size and multiplicity.

Clinical Interpretation of a Hazard Ratio

Suppose:

$$ HR=0.65 \qquad 95\%\,CI=(0.50,0.85) $$

The estimated hazard is lower in the treatment group.

However, the statement:

"Treatment increases survival by 35%"

is generally too simplistic.

A more accurate description is that the estimated hazard ratio is 0.65, corresponding to an approximately 35% lower hazard under the proportional hazards interpretation.

Survival Probability Is Often Easier to Explain

Suppose the Kaplan–Meier estimates are:

Time Experimental Control
12 months 82% 71%
24 months 68% 51%

These values can provide an intuitive description of the observed survival experience.

They complement rather than replace the hazard ratio.

Crossing Kaplan–Meier Curves

A particularly important situation occurs when survival curves cross.

For example:

  • Treatment A may initially have worse survival.
  • The curves may cross after 12 months.
  • Treatment A may subsequently have better survival.

This pattern can indicate that a single proportional hazard ratio does not adequately summarize the treatment effect.

When curves cross, look beyond the hazard ratio. Consider the graphical pattern, clinically relevant time points, RMST, and other estimands appropriate to the trial question.

Non-Proportional Hazards

Non-proportional hazards can arise from:

  • Delayed treatment effects
  • Early treatment toxicity
  • Immunotherapy-related delayed effects
  • Treatment switching
  • Changing disease risk over time

In these settings, the hazard ratio may be difficult to interpret as a single summary of the entire follow-up period.

Survival Analysis in Immuno-Oncology

Immunotherapy trials can provide examples where the proportional-hazards assumption deserves particular attention.

Some trials may show:

  • Delayed separation of curves
  • Early similar event rates
  • Later separation
  • Long-term survival plateaus

The analysis should therefore consider whether the prespecified estimand and statistical model appropriately capture the treatment effect.

Censoring Mechanisms

Standard survival methods generally rely on assumptions about censoring.

In simplified terms, censoring should not systematically provide information about the future event time after accounting for relevant observed information.

Examples of potentially informative censoring include:

  • Patients withdrawing because they are deteriorating
  • Patients discontinuing because they are doing particularly well
  • Loss to follow-up associated with prognosis

Clinical-trial censoring rules and sensitivity analyses should therefore be prespecified.

Administrative Censoring

Administrative censoring occurs because the study observation period ends before a patient experiences the event.

For example, a patient might remain alive when the database cutoff is reached.

That patient contributes all of their available follow-up without being classified as having experienced the event.

Informative vs Non-Informative Censoring

The distinction is important.

Type Concept
Administrative Follow-up ends because the observation period ends
Loss to follow-up Patient cannot be observed after a particular point
Potentially informative Censoring probability may be related to future event risk

The statistical method does not automatically solve an informative-censoring problem.

Clinical Study Report Considerations

Survival analyses in a CSR typically require clear documentation of:

  • Endpoint definition
  • Event definition
  • Censoring definition
  • Analysis population
  • Analysis cutoff date
  • Time origin
  • Time units
  • Kaplan–Meier methodology
  • Confidence-interval methodology
  • Comparison method
  • Cox model specification
  • Covariate handling
  • Missing-data rules

Time Origin Is Critical

The starting point of the survival clock must be clearly defined.

Possible time origins include:

  • Randomization
  • First dose
  • Diagnosis
  • Surgery
  • Response date
  • Transplantation

The appropriate time origin depends on the endpoint.

Never assume time zero. Two analyses of the same patients can produce different survival estimates if they use different time origins.

Analysis Cutoff Dates

Clinical-trial survival analyses are often based on a prespecified data cutoff.

Patients who are still event-free at the cutoff are generally censored according to the endpoint-specific rules.

This means that survival analysis is inherently linked to the timing of the database snapshot.

Example: Building a Kaplan–Meier Analysis

library(survival)

km_fit <- survfit(
  Surv(
    OS_TIME,
    OS_EVENT
  ) ~ TRT01P,
  data = adtte
)

summary(km_fit)

plot(
  km_fit,
  xlab = "Months from Randomization",
  ylab = "Overall Survival Probability",
  lwd = 2,
  conf.int = TRUE
)

This basic workflow can be expanded into a fully specified clinical-trial analysis.

Example: Cox Analysis

cox_fit <- coxph(
  Surv(
    OS_TIME,
    OS_EVENT
  ) ~ TRT01P + AGE + SEX + STAGE,
  data = adtte
)

summary(cox_fit)

The treatment coefficient provides the adjusted treatment hazard ratio, assuming the model is appropriately specified.

Checking the Cox Model

ph_test <- cox.zph(cox_fit)

print(ph_test)

plot(ph_test)

The output should be reviewed together with the Kaplan–Meier curves and the scientific context.

Creating a Survival Analysis Dataset

A robust programming workflow separates endpoint derivation from statistical modeling.

1
Start with the validated clinical event and follow-up data.
2
Determine the endpoint-specific time origin.
3
Determine the event date according to the endpoint definition.
4
Determine the censoring date when no event is observed.
5
Calculate the analysis duration.
6
Create the event indicator.
7
Validate the derived endpoint against source data.
8
Pass the validated endpoint to Surv().

Validation of Event Dates

For each patient, the programmer should be able to answer:

  • What was the time origin?
  • What was the event date?
  • If there was no event, what was the censoring date?
  • Why was the patient censored?
  • Was the event date after the time origin?
  • Does the analysis time agree with the source dates?

A survival curve can be mathematically correct while still being clinically wrong if the endpoint derivation is incorrect.

Common Data-Programming Mistakes

  1. Reversing the event indicator. This can turn events into censored observations and vice versa.
  2. Using the wrong time origin. All subsequent survival times become misaligned.
  3. Ignoring endpoint-specific censoring rules. Different endpoints can require different handling of events and follow-up.
  4. Using the database cutoff incorrectly. This can produce incorrect censoring dates.
  5. Mixing time units. Days, months, and years should not be mixed without explicit conversion.
  6. Ignoring competing events. Some endpoints require competing-risk methods rather than ordinary Kaplan–Meier estimation.
  7. Reporting an unstable tail. Very few patients at risk can make late estimates highly uncertain.
  8. Ignoring proportional-hazards diagnostics. A Cox model should not automatically be interpreted as a constant treatment effect.

Kaplan–Meier Curve vs. Cumulative Incidence

These displays answer different questions.

Display Primary Quantity
Kaplan–Meier Probability of remaining event-free
Cumulative incidence Probability of experiencing a particular competing event by time t

The distinction becomes important in competing-risk settings.

Survival Probability vs. Cumulative Event Probability

For a simple single-event setting:

$$ P(T\le t)=1-S(t) $$

Thus, if:

$$ S(12)=0.80 $$

then the estimated probability of experiencing the event by 12 months is approximately:

$$ 1-0.80=0.20 $$

or 20%, under the corresponding single-event framework.

Why the Kaplan–Meier Curve Is a Step Function

The Kaplan–Meier estimator changes only when an event occurs.

Censoring changes the number at risk but does not itself reduce the estimated survival probability.

Therefore, the curve:

  • Steps downward at events
  • Remains flat between events
  • Does not drop at censoring times

An Example of the Risk Set

Suppose there are 100 patients at risk immediately before an event time, and two events occur.

The survival estimate is multiplied by:

$$ 1-\frac{2}{100}=0.98 $$

If the survival estimate immediately before the event was 0.90, then after the event:

$$ 0.90\times0.98=0.882 $$

The curve therefore falls to approximately 88.2%.

Multiple Events at the Same Time

If multiple events occur at the same time, the Kaplan–Meier estimator accounts for all of them at that event time.

This is one reason why understanding the risk set is important when validating survival estimates.

Survival Analysis and Ties

In clinical-trial data, multiple patients may experience events on the same day.

The Cox model therefore needs a method for handling tied event times.

The coxph() function supports established approaches for ties, and the analysis should use the method specified by the statistical methodology.

Reproducible R Code

A clean survival-analysis program should separate:

  • Data preparation
  • Endpoint derivation
  • Statistical modeling
  • Figure generation
  • Output formatting
  • Quality control

For example:

# 1. Prepare analysis data

adtte <- analysis_data


# 2. Create survival object

os_surv <- with(
  adtte,
  Surv(OS_TIME, OS_EVENT)
)


# 3. Kaplan-Meier model

km_fit <- survfit(
  os_surv ~ TRT01P,
  data = adtte
)


# 4. Cox model

cox_fit <- coxph(
  os_surv ~ TRT01P + AGE + SEX,
  data = adtte
)


# 5. Diagnostics

ph_check <- cox.zph(cox_fit)


# 6. Review outputs

summary(km_fit)
summary(cox_fit)
print(ph_check)

Using Formula Syntax Correctly

R survival models use formula notation.

For example:

Surv(time, status) ~ treatment

can be read as:

$$ \text{survival outcome} \sim \text{treatment} $$

The left side contains the survival outcome. The right side contains predictors or grouping variables.

Multiple Groups

Kaplan–Meier estimation can accommodate more than two groups.

km_fit <- survfit(
  Surv(time, status) ~ dose_group,
  data = trial
)

The resulting object contains one survival curve per group.

If there are many groups, the figure can quickly become difficult to read.

Stratified Randomized Trials

Some clinical trials use stratified randomization.

The Cox model may incorporate those stratification factors according to the prespecified analysis.

cox_fit <- coxph(
  Surv(time, status) ~
    treatment +
    strata(randomization_factor),
  data = trial
)

The exact model should follow the SAP rather than being chosen after inspecting the results.

Missing Baseline Covariates

Missing covariates can cause patients to be excluded from a complete-case Cox model unless missingness is explicitly handled.

For example:

coxph(
  Surv(time, status) ~
    treatment + biomarker,
  data = trial
)

may use only observations with complete values for the variables included in the model.

Always check the analysis population. The number of observations used in a Cox model may differ from the number of patients in the Kaplan–Meier analysis if covariate data are missing.

Landmark Analyses

Sometimes investigators want to examine survival after a defined landmark time.

For example, patients might be classified according to response status at Week 12 and then followed thereafter.

Such analyses require careful handling of selection and guarantee-time bias.

They should be prespecified or clearly labeled as exploratory.

Immortal Time Bias

A related issue is immortal time bias.

This can occur when patients must survive event-free for a period before they can be classified into an exposure group, but that guaranteed survival period is not handled correctly in the analysis.

Time-dependent methods or landmark approaches may sometimes be more appropriate.

Clinical Interpretation of Kaplan–Meier Curves

When reviewing a Kaplan–Meier figure, ask:

  • What is the time origin?
  • What is the event?
  • How many events occurred?
  • How much censoring occurred?
  • How many patients remain at risk?
  • Do the curves separate early or late?
  • Do the curves cross?
  • Is the median estimable?
  • Is the tail based on many or few patients?

Clinical Interpretation of Cox Results

When reviewing a hazard ratio, ask:

  • Which group is the reference?
  • What is the hazard ratio?
  • What is the confidence interval?
  • Does the confidence interval include 1?
  • Was the model adjusted?
  • Were the proportional-hazards assumptions evaluated?
  • Is the estimated effect clinically meaningful?
  • Does the HR adequately summarize the observed curve pattern?

Statistical Significance vs. Clinical Significance

A small p-value does not necessarily imply a clinically important treatment effect.

For example:

$$ HR=0.94 $$

could be statistically significant in a very large trial while representing a relatively modest treatment effect.

Conversely, a clinically important hazard ratio may have a wide confidence interval in a small study.

Effect size, precision, and clinical context should therefore be considered together.

A Practical Reporting Framework

Component What It Communicates
Kaplan–Meier curve Longitudinal survival experience
Number at risk Amount of information remaining over time
Median survival Time at which estimated survival reaches 50%, if estimable
Hazard ratio Relative event hazard under the model
95% CI Precision of the effect estimate
Log-rank p-value Statistical comparison of survival distributions
RMST Average event-free time through a specified horizon

Survival Analysis Quality Control

Survival-analysis programming should undergo independent validation or other appropriate quality-control procedures.

At minimum, verify:

  • Correct analysis population
  • Correct treatment assignment
  • Correct time origin
  • Correct event date
  • Correct censoring date
  • Correct event indicator
  • Correct analysis time
  • Correct time units
  • Correct Kaplan–Meier estimates
  • Correct number-at-risk counts
  • Correct median survival estimates
  • Correct hazard ratios
  • Correct confidence intervals
  • Correct p-values
  • Correct reference category
  • Correct model covariates
  • Appropriate assessment of proportional hazards

Patient-Level Validation

A useful QC strategy is to trace individual patients through the survival derivation.

For selected patients, verify:

Patient Time Origin Event/Censor Date Time Event
001 Day 0 Day 180 180 1
002 Day 0 Day 365 365 0
003 Day 0 Day 240 240 1

This type of patient-level review can detect derivation errors that may not be obvious from the final Kaplan–Meier curve.

Cross-Checking Kaplan–Meier Output

For important outputs, independently verify selected survival estimates.

For example, confirm that:

  • The survival probability changes only at event times.
  • Censoring does not directly cause a drop.
  • The number at risk decreases appropriately.
  • The reported median corresponds to the curve crossing 0.50.

Common Mistakes With Surv()

  1. Using the wrong event coding. Always confirm what 0 and 1 mean.
  2. Passing dates directly without defining analysis time. The survival model should receive a meaningful time scale.
  3. Using event date as time without subtracting the time origin. The time variable must represent elapsed follow-up.
  4. Ignoring delayed entry. Some datasets require start-stop survival objects.
  5. Assuming all endpoints use the same censoring rule. OS, PFS, DoR, and other endpoints can have different definitions.

Common Mistakes With Kaplan–Meier Curves

  1. Ignoring the number at risk. Late curves can be based on very few patients.
  2. Overinterpreting crossing curves. A single hazard ratio may not adequately summarize non-proportional effects.
  3. Reporting a median when it was not reached. The median is not automatically the final follow-up time.
  4. Confusing censoring with an event. Censored patients remain part of the risk-set calculation up to their censoring time.
  5. Using the wrong time origin. This changes the endpoint itself.

Common Mistakes With Cox Models

  1. Interpreting HR as a risk ratio. The hazard ratio describes relative hazard, not simply cumulative event probability.
  2. Ignoring the reference group. The direction of the HR depends on which group is the reference.
  3. Assuming proportional hazards automatically holds. It should be assessed.
  4. Using post hoc variable selection for a confirmatory model. The model should follow the prespecified analysis strategy.
  5. Reporting only the p-value. The effect estimate and confidence interval are essential.

Survival Analysis in a Statistical Analysis Plan

An SAP should clearly describe the planned survival analysis.

Important components may include:

  • Endpoint definition
  • Time origin
  • Event definition
  • Censoring rules
  • Analysis population
  • Kaplan–Meier methodology
  • Confidence intervals
  • Median survival estimation
  • Treatment comparison
  • Cox model specification
  • Covariates
  • Stratification
  • Proportional-hazards assessment
  • Sensitivity analyses

Example SAP Language

Illustrative language: Time-to-event endpoints will be summarized using Kaplan–Meier methodology. Patients without an observed event by the analysis cutoff will be censored according to the endpoint-specific censoring rules. Kaplan–Meier estimates with corresponding confidence intervals will be presented at prespecified time points. Median event-free time will be summarized when estimable. Treatment groups will be compared using the prespecified statistical test. A Cox proportional hazards model will be used to estimate the hazard ratio and corresponding confidence interval according to the prespecified model specification.

The actual wording should reflect the trial-specific estimand and SAP.

Survival Curves Are Not Just "Lines"

A Kaplan–Meier figure contains substantial statistical information.

The curve reflects:

  • Event timing
  • Risk-set sizes
  • Censoring
  • Accumulated survival probability

This is why simply connecting observed event proportions with ordinary lines would not produce a valid Kaplan–Meier estimate.

Why Censoring Does Not Cause a Drop

Suppose a patient is censored at 10 months.

That patient was event-free through month 10.

Therefore, the patient's information contributes to the risk set before 10 months.

After censoring, the patient is removed from the risk set.

But because no event occurred, the survival probability does not drop at that time.

An Intuitive Example

Suppose five patients are followed:

Patient Time Status
A 3 Event
B 5 Censored
C 7 Event
D 10 Event
E 12 Censored

At month 3, five patients are initially at risk and one event occurs.

The survival estimate becomes:

$$ \hat S(3)=1-\frac{1}{5}=0.80 $$

At month 5, Patient B is censored. There is no survival drop.

At month 7, another event occurs among the remaining patients, producing the next drop.

Survival Analysis and Reproducibility

The survival package makes the statistical analysis reproducible because the same analysis can be regenerated directly from the analysis dataset and R code.

A reproducible workflow should retain:

  • Source data version
  • Analysis dataset
  • Endpoint derivation code
  • Statistical model code
  • Figure code
  • Output version
  • Software/package versions where required

Minimal End-to-End Example

library(survival)

# Kaplan-Meier analysis

km <- survfit(
  Surv(time, status) ~ treatment,
  data = trial
)

summary(km)

# Treatment comparison

logrank <- survdiff(
  Surv(time, status) ~ treatment,
  data = trial
)

logrank

# Cox proportional hazards model

cox <- coxph(
  Surv(time, status) ~ treatment,
  data = trial
)

summary(cox)

# Hazard ratio

exp(coef(cox))

# 95% CI

exp(confint(cox))

# Proportional hazards diagnostic

cox.zph(cox)

A More Complete Clinical-Trial Workflow

library(survival)

# -------------------------------
# Analysis endpoint
# -------------------------------

adtte$event <- 1 - adtte$CNSR

adtte$time <-
  adtte$AVAL / 30.4375


# -------------------------------
# Kaplan-Meier
# -------------------------------

km_fit <- survfit(
  Surv(time, event) ~ TRT01P,
  data = adtte
)


# -------------------------------
# Log-rank comparison
# -------------------------------

logrank <- survdiff(
  Surv(time, event) ~ TRT01P,
  data = adtte
)


# -------------------------------
# Cox model
# -------------------------------

cox_fit <- coxph(
  Surv(time, event) ~
    TRT01P +
    AGE +
    SEX +
    STAGE,
  data = adtte
)


# -------------------------------
# Hazard ratios
# -------------------------------

hr <- exp(coef(cox_fit))

ci <- exp(
  confint(cox_fit)
)


# -------------------------------
# PH assessment
# -------------------------------

ph <- cox.zph(cox_fit)

print(ph)

What the survival Package Does Not Do Automatically

The package provides statistical methods, but it does not know your clinical endpoint definition.

It does not automatically determine:

  • What constitutes progression
  • Which death dates belong to the endpoint
  • Which assessments qualify as valid
  • When censoring should occur
  • What the analysis cutoff should be
  • Which population belongs in the analysis
Key programming principle: The survival package analyzes the endpoint you provide. It cannot determine whether the endpoint itself has been correctly derived.

Statistical Programming vs. Clinical Endpoint Derivation

This distinction is particularly important in clinical trials.

A programmer may write:

survfit(
  Surv(AVAL, EVENT) ~ TRT01P,
  data = adtte
)

perfectly.

If EVENT or AVAL is incorrectly derived, however, the final result remains incorrect.

Therefore, survival-analysis quality depends on both:

  • Correct endpoint derivation
  • Correct statistical implementation

Interactive Figure: What the Curve Is Telling You

The following simplified figure emphasizes the relationship between survival probability and follow-up.

Figure 2. Interpreting Survival Probability Over Time
A conceptual Kaplan–Meier curve showing event-related drops and censored observations.
Estimated survival
50% survival reference

The downward steps correspond to events. Censoring marks indicate the end of individual follow-up without an observed event.

Median Survival From the Figure

The median is the time at which the estimated survival curve first reaches or falls below 50%.

If the curve never reaches 50%:

$$ \hat S(t)>0.50 \quad \text{for all observed }t $$

then the median is not estimable from the observed follow-up.

A Second Important Survival Concept: The Tail

The right-hand tail of a Kaplan–Meier curve deserves special attention.

Imagine a study beginning with 500 patients. At 36 months, perhaps only 20 patients remain at risk.

The estimated curve may still be mathematically valid, but the amount of information supporting the estimate is much smaller.

This is why number-at-risk tables are essential.

Why a Large Hazard Ratio CI Matters

Suppose:

$$ HR=0.60 \qquad 95\%\,CI=(0.25,1.45) $$

The point estimate suggests lower hazard, but the confidence interval is wide and includes 1.

This indicates substantial uncertainty about the treatment effect.

The point estimate should not be interpreted in isolation.

Sample Size and Number of Events

Survival-analysis precision is often strongly influenced by the number of observed events.

A trial can have many enrolled patients but relatively few events.

In that situation:

  • Confidence intervals may be wide.
  • Median survival may not be reached.
  • Late Kaplan–Meier estimates may be unstable.
  • Cox-model estimates may be imprecise.

The number of events should therefore be considered alongside the total sample size.

Survival Analysis and Interim Analyses

In event-driven trials, interim analyses may occur after a specified number of events rather than at a fixed calendar date.

This creates additional considerations for:

  • Database cutoff
  • Event counts
  • Information fraction
  • Statistical boundaries
  • Multiplicity

The basic survival functions can calculate the survival estimates, but interim-analysis methodology requires additional statistical planning.

Survival Analysis and Treatment Switching

Treatment switching can complicate interpretation of overall survival.

If patients assigned to control later receive experimental treatment, the observed survival comparison may no longer represent the treatment effect under simple intention-to-treat interpretation.

Potential approaches include prespecified sensitivity analyses and specialized methods for treatment-switching adjustment.

These analyses should not be improvised solely during programming.

Survival Analysis and Multiplicity

A trial may evaluate several time-to-event endpoints:

  • Overall survival
  • Progression-free survival
  • Event-free survival
  • Time to response
  • Duration of response

If multiple confirmatory hypotheses are tested, multiplicity control may be required.

The survival package does not decide the multiplicity strategy.

How Survival Analysis Fits Into a Clinical Reporting Package

A
Table: summary of events, censoring, median survival, and treatment effects.
B
Kaplan–Meier figure: visualizes survival distributions over time.
C
Risk table: shows the number of patients remaining at risk.
D
Cox model: summarizes the prespecified treatment effect and covariate effects.
E
Listings: provide patient-level traceability for events and censoring.

Recommended Survival-Analysis Checklist

1
Confirm the endpoint definition.
2
Confirm the time origin.
3
Confirm the event definition.
4
Confirm the censoring rules.
5
Confirm event coding.
6
Create the Surv() object.
7
Generate Kaplan–Meier estimates.
8
Review confidence intervals and number-at-risk counts.
9
Perform the prespecified treatment comparison.
10
Fit and validate the Cox model where applicable.
11
Assess proportional-hazards assumptions.
12
Trace final results back to patient-level data.

Key R Functions to Remember

Function Purpose
Surv() Create a survival response object
survfit() Estimate Kaplan–Meier survival curves
survdiff() Perform a log-rank-type comparison
coxph() Fit a Cox proportional hazards model
cox.zph() Assess proportional-hazards assumptions
summary() Inspect fitted survival and Cox-model results
confint() Obtain confidence intervals for model coefficients

The Most Important Concept

The most important idea in survival analysis is that time and event status must be analyzed together.

A patient who experiences an event after 24 months and a patient who is censored after 24 months are not equivalent observations.

Both contribute 24 months of follow-up, but only the first contributes an observed event at 24 months.

Survival-analysis methods are designed specifically to preserve this distinction.

Bottom line: The survival package provides the core R tools for many standard time-to-event analyses. The fundamental workflow is to create a validated Surv() object, estimate Kaplan–Meier curves with survfit(), compare groups using an appropriate prespecified method, and estimate covariate effects with coxph(). In clinical trials, however, the most important work often occurs before these functions are called: correctly defining the endpoint, time origin, event, censoring rules, and analysis population. The statistical model can only be as correct as the endpoint data provided to it.

Summary

Survival analysis provides a framework for analyzing time-to-event endpoints in the presence of censoring.

The survival package makes the core methodology accessible through a small number of powerful functions.

The essential workflow is:

$$ \text{Validated endpoint} \rightarrow \text{Surv()} \rightarrow \text{Kaplan–Meier} \rightarrow \text{Treatment comparison} \rightarrow \text{Cox model} \rightarrow \text{Diagnostics} $$

For clinical-trial statisticians, the key concepts to master are:

  • Right censoring
  • Risk sets
  • Kaplan–Meier estimation
  • Median survival
  • Number at risk
  • Log-rank testing
  • Hazard functions
  • Hazard ratios
  • Cox proportional hazards regression
  • Proportional-hazards diagnostics
  • Non-proportional hazards
  • Endpoint-specific censoring
  • Clinical-trial validation and traceability

Once these concepts are understood, the R syntax becomes comparatively straightforward.

References

Therneau, T.M. (2024). A Package for Survival Analysis in R. R package documentation for survival.
Therneau, T.M. and Grambsch, P.M. (2000). Modeling Survival Data: Extending the Cox Model. Springer.
Klein, J.P. and Moeschberger, M.L. (2003). Survival Analysis: Techniques for Censored and Truncated Data. Springer.
Collett, D. (2015). Modelling Survival Data in Medical Research. CRC Press.
Cox, D.R. (1972). Regression Models and Life-Tables. Journal of the Royal Statistical Society: Series B, 34(2), 187–220.
Kaplan, E.L. and Meier, P. (1958). Nonparametric Estimation from Incomplete Observations. Journal of the American Statistical Association, 53(282), 457–481.