Censoring and the structure of survival data
Why "the event has not happened yet" is information rather than a missing value, what right, left and interval censoring each look like, how the risk set shrinks over time, the non-informative censoring assumption the whole toolkit rests on, and how immortal time bias turns a treatment with no detected effect into a miracle cure.
What problem this page solves
You have enrolled 228 patients with advanced lung cancer and followed them to the end of the study. Some have died, and you know the exact date. Some were still alive at their last clinic visit. One moved away and no longer answers the phone. Now you have to answer the question: how long do these people survive?
The first instinct is to average the survival times. That stalls immediately. What number do you enter for the patients who are still alive? Enter the date of their last visit and you have assumed they died that day. Leave them out and you have deleted every patient who survived the longest.
This is not a data quality problem. It is the shape this kind of data has. A patient who has been followed for 400 days without dying is not a missing value; he is an explicit statement: this person’s survival time is longer than 400 days. The entire survival analysis toolkit exists to use observations where only one bound is known, instead of discarding them.
The worked example is survival::lung: 228 patients, of whom 165 died during follow-up and 63 (27.6%) reached the end of the study with no event observed.
figures/scripts/B3-01-censoring.RThree kinds of censoring, and they do not look alike
Censoring means the event time was not observed exactly — all you know is the range it falls in. In clinical research you will meet the first kind almost exclusively, but knowing the other two exist is what tells you which one you are actually holding.
| Type | What you know | Typical situation |
|---|---|---|
| Right-censoring | Event time > some known point | Alive at the end of the study, lost to follow-up, withdrew |
| Left-censoring | Event time < some known point | Already seropositive at the first test; the infection date is unknown |
| Interval-censoring | Event time lies between two observations | Imaging every six months, and this scan shows recurrence — it happened somewhere between the two scans |
The other thing to separate from censoring is truncation. Censoring says “this person is in the data, but their time is incomplete”. Truncation says “this person never appears in the data at all”. A registry that enrols only patients who reached a tertiary centre will never contain those who died in the emergency department on presentation — that is left truncation, and it leaves no visible hole in the data, only a population that has already been filtered.
Left truncation has a milder and far more common form: people do get in, they just get in late — a registry whose enrolment date follows the diagnosis date, or an age-as-time-scale cohort where each person enters the risk set at their own age. In that situation Surv(time, event) raises no error and simply overstates survival. How to handle it, what it costs, and how large the bias actually is: left truncation and delayed entry.
The risk set: the denominator shrinks as time passes
Every estimate in survival analysis is, underneath, the same question asked at a series of time points: among the people who could still have been observed to have the event at this instant, how many did? That group is the risk set at that time point.
The risk set only shrinks, for two reasons: people have the event (and leave), and people are censored (and also leave). In the figure above, the dashed line at day 300 illustrates this — only the patients whose lines cross that dashed line belong to the risk set at day 300.
Here is how the risk set actually shrinks in survival::lung:
| Time (days) | Still at risk | KM survival estimate |
|---|---|---|
| 100 | 196 | 86.4% |
| 300 | 92 | 53.1% |
| 500 | 41 | 29.3% |
| 800 | 8 | 7.8% |
Non-informative censoring: the foundation the whole method sits on
Everything above rests on one assumption: non-informative censoring (also called independent censoring).
In plain terms: the fact that a person was censored at time must carry no information about how high their risk was at time . More precisely, a censored person’s prognosis at the moment of censoring should be the same as that of comparable people who were still under follow-up at that moment.
When it holds, and when it breaks:
| Reason for censoring | Does the assumption hold? | Why |
|---|---|---|
| Study ended, everyone stops together (administrative censoring) | ✅ Usually holds | The closing date has nothing to do with the patient’s condition |
| Loss to follow-up at random (moved house, changed hospital) | ✅ Roughly holds | Provided moving is unrelated to the illness |
| Withdrew because the disease got worse | ❌ Breaks | Those censored are at higher risk than those who stayed, so KM overestimates survival |
| Stopped attending because they felt better | ❌ Breaks | Same failure in the opposite direction: KM underestimates survival |
| Follow-up stopped on switching to another treatment | ❌ Breaks | The reason for switching is usually that the treatment was not working |
Median follow-up: a number a lot of people compute incorrectly
The first sentence of a Results section is often “median follow-up was X months”. That number is not the median of everyone’s observation time — because for a patient who died, the observation time is their survival time, not how long they were followed.
The correct approach is the reverse Kaplan-Meier: flip the event indicator so that “still under follow-up” becomes the event, then take the median.
In survival::lung the two calculations are far apart:
- Median of the raw observation times: 255.5 days (the wrong one)
- Reverse-KM median follow-up: 588 days (95% CI 511–1010)
More than a twofold difference. The first number is in fact closer to the median survival time, and quoting it as follow-up leaves readers thinking the study followed people for far less time than it did.
Immortal time bias: turning a treatment with no detected effect into a miracle cure
This is the design error you will meet most often in the clinical literature, and the one you are most likely to commit in your own analysis. The mechanism is simple: if the information that determines group membership only becomes available after baseline, then during the interval before it becomes available, the people who end up in one particular group cannot possibly have died — otherwise they would never have been put in that group.
That interval is called immortal time. It gets wrongly counted as “survival time” for the treated group, so the treated group is handed a stretch of guaranteed-alive time out of thin air.
The cleanest teaching example is the Stanford heart transplant study (survival::jasa): 103 patients joined the waiting list, of whom 69 eventually received a transplant and 34 did not.
figures/scripts/B3-01-censoring.RTwo analyses, one dataset:
| Analysis | HR for transplant | 95% CI | p |
|---|---|---|---|
| Naive: group at baseline by “ever transplanted” | 0.27 | 0.17–0.43 | < 0.001 |
| Correct: transplant as a time-dependent covariate | 1.14 | 0.63–2.05 | 0.67 |
The naive analysis says transplantation cuts the hazard of death to 26.6% of its previous level, with a p-value small enough to look unanswerable. The correct analysis does not detect a difference — its confidence interval crosses 1. The mechanism behind that gap is the 2600 person-days of waiting time (median 25 days) being credited to the transplant group’s survival — time during which, by definition, those patients could not die.
Run it yourself
library(survival)
data(cancer, package = "survival") # lung ships inside the cancer help page
data(heart, package = "survival") # loads both jasa and heart
# The atom of survival data: Surv(time, event indicator)
# lung's status: 1 = censored, 2 = dead
head(Surv(lung$time, lung$status)) # the entries with "+" are censored
# How the risk set shrinks: the n.risk column of the KM object
fit <- survfit(Surv(time, status) ~ 1, data = lung)
summary(fit, times = c(100, 300, 500, 800))
# Median follow-up: flip the event indicator so "still followed" is the event
survfit(Surv(time, 1 - (status == 2)) ~ 1, data = lung)
# -- immortal time bias --
# Wrong: treat "ever transplanted" as a baseline group
jasa$tx <- factor(jasa$transplant, 0:1, c("No", "Yes"))
coxph(Surv(futime, fustat) ~ tx, data = jasa)
# Right: heart is already a (start, stop] long table, transplant varies with time
head(heart[heart$id %in% c(2, 4), ])
coxph(Surv(start, stop, event) ~ transplant, data = heart)Verified with R 4.6.0 and survival 3.8.6
import statsmodels.api as sm
from lifelines import KaplanMeierFitter
from lifelines.utils import median_survival_times
# Naming trap: on Rdatasets, lung sits under the "cancer" help page
lung = sm.datasets.get_rdataset("cancer", "survival").data
lung["event"] = (lung["status"] == 2).astype(int)
kmf = KaplanMeierFitter().fit(lung["time"], lung["event"])
# The risk set. Not event_table.loc[[100, ...]] -- those times need not be
# event times and it raises KeyError -- and not .asof() either, which
# returns the last observed time <= t and is off by one row.
for t in (100, 300, 500, 800):
print(t, int((lung["time"] >= t).sum()))
# Median follow-up: reverse the event indicator
rev = KaplanMeierFitter().fit(lung["time"], 1 - lung["event"])
print("median follow-up:", rev.median_survival_time_)
# Time-dependent covariates (the fix for immortal time) go through
# CoxTimeVaryingFitter, which takes a (start, stop] long table with the
# same column names as R's heart
from lifelines import CoxTimeVaryingFitter
# ctv = CoxTimeVaryingFitter().fit(long_df, id_col="id",
# event_col="event", start_col="start", stop_col="stop")lifelines uses the same data structure as R: one duration column and one 0/1 event column.
How to read the report
When you pick up a survival analysis paper, this page maps onto the Methods and the opening sentences of the Results. Four things to check:
- Which day is time zero, and is it the same day for everybody? Date of randomisation? Date of diagnosis? Date of surgery? If the two groups have different definitions of time zero, nothing downstream is worth reading.
- Is median follow-up reported, and how was it computed? Some papers write “median follow-up X months (IQR …)” but have actually taken the median of the observation times. If that number is noticeably smaller than the median survival, it is usually the wrong calculation.
- How many were lost to follow-up, and why? The CONSORT or STROBE flow diagram should show it. A high proportion with no explanation leaves the non-informative censoring assumption hanging in mid-air.
- Was group membership knowable at baseline? As soon as you see a title contrasting “responders vs non-responders”, “completed vs did not complete therapy”, or “underwent surgery vs did not”, assume immortal time bias until you find, in the Methods, a landmark analysis or a time-dependent covariate.
Common misuses
| Misuse | Why it is wrong |
|---|---|
| Treating censored patients as “no event” and computing a plain proportion | Underestimates risk; they may have had the event after you stopped looking |
| Dropping censored patients entirely | Usually overestimates risk — only those with events remain — and destroys randomisation |
| Using the median observation time as median follow-up | For those who died, observation time is survival time, not follow-up; use the reverse KM |
| Grouping by a status only knowable after baseline (response / completion / surgery) | Immortal time bias, which systematically exaggerates the effect |
| Starting follow-up at diagnosis while defining exposure by the first prescription fill | The same error, in its most common pharmacoepidemiological form |
| Taking the date of a scheduled test as the exact event time without stating the interval | The data are really interval-censored, event times are pushed systematically later, and arms tested on different schedules end up differing for that reason alone |
| High loss to follow-up with no sensitivity analysis | The non-informative censoring assumption cannot be tested; only a sensitivity analysis shows how far the conclusion could move |
| A KM figure with no numbers-at-risk table | The reader cannot tell how many patients the tail of the curve rests on |
| Arguing that censoring is non-informative because those lost and those retained had similar baseline characteristics | Similar at baseline does not mean similar in prognosis at the moment of censoring, which is what the assumption is about |
Reproducing every number on this page
/opt/homebrew/bin/Rscript figures/scripts/B3-01-censoring.RRead the figure
The answer comes from the same statistical output that produced this page's figures, not from a number typed in beside them.
Output prints several numbers measured in days. Which one answers "how long did this study actually watch people for"?
Show the answer and why
Correct answer: 588.0 days - the median follow-up obtained by swapping the roles of events and censoring
Median follow-up asks how long the people still under observation were watched for, so it comes from re-running KM with censoring treated as the event: 588.0 days. Median observed time gives 255.5, because a patient lost to follow-up on day three contributes three days, which makes the study look far shorter than it was - an under-followed study then reads as merely short. And 511.0 is only the lower confidence bound on 588.0; it moves with the sample, and it is not the time anybody was followed for.
The same heart-transplant data is analysed once with transplant as a fixed group and once with it time-dependent. Which result can be used to describe the effect of transplantation?
Show the answer and why
Correct answer: Hazard ratio 1.14, from the time-dependent form - each patient counts as untransplanted until the day of surgery
The fixed grouping gives 0.27, which reads as though transplantation cut mortality to a quarter. But during the wait for an organ those patients cannot die by definition, and that immortal time is credited to the transplant group. 0.43 is the upper bound on the same biased estimate and inherits the bias. Letting the variable vary with time gives 1.14, whose 95% confidence interval spans both appreciable benefit and appreciable harm: it does not reach statistical significance, so these data neither support nor rule out an effect of transplantation on mortality.
Someone looks at how many patients were censored and says that this many survived. Which statement actually holds?
Show the answer and why
Correct answer: 63 patients were censored, and censoring only says that from that point on we stop knowing what happened
Censoring means one thing: after this point we do not know what happened. Those 63 include patients still alive when follow-up closed and patients who stopped coming back, some of whom may have died unrecorded, so they cannot be counted as survivors as a block. 165 is the number observed to die, but "everybody else is alive" is the same misreading in other words; 228 minus 165 likewise gives the censored count, not a survivor count. A high censored share means little information, not good prognosis.
Chapters that use this method
Watch next
Censoring and Truncation [Survival Analysis 2/8]
Survival Analysis [Simply Explained]
存活分析(Survival Analysis)
【Lecture】L19 Survival Analysis (1)Sources and licences
This page is original writing