Censored data: right, left and interval censoring
What censored data are, how right, left and interval censoring differ, and why dropping censored observations biases survival estimates, on public trial data.
Updated . Written by Matt Gencer.
On this page
A value is censored when you know only that it lies above, below or between known bounds, not what it is. A woman still alive when a trial ends has a survival time of “at least this long”. That partial fact is real information, and the methods built for it use it. The usual mistake is to drop those rows or to treat the cut-off as if it were the event.
The post covers the three kinds of censoring, a worked example of the bias that dropping censored observations causes, and the assumption every method relies on. For the full workflow, see Survival analysis: Kaplan-Meier, Cox and AFT in R.
The three kinds of censoring
What differs between the kinds is which side of the true value you can see. Each one comes with a typical research setting.
Right censoring
The event has not happened by the time observation stops, so the true time is above a known value. It is the most common kind in time-to-event data. A participant is still alive at the last follow-up, a patient moves away, or the study ends while some units are still event-free. Plant ecologists see the same thing when a seedling has not flowered at the last visit.
Left censoring
The value is below a known threshold, or the event had already happened before observation began. A lab assay that reports “below the limit of quantification” is the standard case: the sample has some concentration, but the instrument cannot say how much. Values reported as “less than” a reporting limit are left-censored, and replacing them by a single invented number distorts the spread and the centre (Helsel 2006). The separate guide on values below the detection limit covers that situation, with the tests built for it. This post is about the same idea applied to follow-up times.
Interval censoring
The event happened between two known times, but not exactly when. A condition that is only checked at scheduled visits is interval-censored: the visit at month 6 was clear and the visit at month 12 was not, so onset fell somewhere in between. Right censoring is the special case where the upper bound is unlimited. Interval censoring needs methods that use both bounds, such as the non-parametric estimator of Turnbull (1976) or parametric models.
| Kind | What you know | Typical example |
|---|---|---|
| Right | The time is above a value | Still alive at the end of follow-up |
| Left | The value is below a threshold | Assay result reported as below the limit |
| Interval | The time is between two values | Event found at a visit, clear at the previous one |
In R, the Surv() function in the survival package builds all three kinds of outcome, using its type argument for left and interval data, and survreg() can fit parametric models to them (Therneau, package manual). Check the manual for the exact coding of each type before you use it.
Censoring is not missing data and not truncation
A missing value has no information about the outcome. A censored value has some: it tells you which side of a bound the truth lies on. That is why deleting a censored value throws real information away, and replacing it with a single number adds information that was never measured.
Censoring also differs from truncation. Under truncation, units outside a range never enter the data at all, so you do not even know they existed. Under censoring the unit is in the data and you know its row, with only the value incomplete.
A worked example: what dropping censored times does
The data are the public ovarian cancer trial shipped with R’s survival package: 26 women randomised to one of two regimens, with follow-up time in days to death or last contact. It is a small trial dataset used for teaching, shown here for how the numbers behave. Nothing in it is a statement about the outlook for any individual.
There were 12 deaths, so the other 14 women are right-censored, which is more than half of the sample. Their times run from 377 to 1,227 days. Ten of the 14 are censored at a time later than the last death in the data, 638 days. In other words, the ten longest follow-up times all belong to censored women.
The pattern that makes dropping them harmful is visible in those numbers. Here are three ways to summarise the same 26 rows with a median time:
| Approach | Median (days) |
|---|---|
| Kaplan-Meier estimate, censored women kept as censored | 638 |
| Censored women treated as if they had died at their last contact | 476 |
| Censored women dropped, median of the 12 death times | 359 |
The Kaplan-Meier estimate keeps each censored woman in the risk set until she leaves follow-up, so the long survivors count towards the estimate. Treating the cut-off as a death invents 14 deaths that did not happen. Dropping the censored women removes the longest follow-up times and leaves only the early deaths. Both shortcuts move the median down, by 162 and 279 days. The sizes belong to this sample, but the direction comes from the method, not from bad luck: a time treated as a death can only be shorter than the true one, and the deaths that remain after dropping are the early ones. A bigger sample would not remove the bias.
The three medians come from a few lines of R with the survival package on the same dataset (shown at the end), not from the engine run. The engine’s recorded result is the grouped comparison below.
Kaplan-Meier with censored times kept
The Kaplan-Meier estimator steps down at each death, multiplying the running estimate by the fraction of units still at risk that survive that moment (Kaplan and Meier 1958). A censored woman leaves the risk set without causing a step. She counted as at risk up to her last contact, and not after.
Run by regimen, the engine gives a median of 638 days for regimen 1, with a 95% confidence interval starting at 268 days and no upper limit that the data can estimate. For regimen 2 the curve had not fallen to one half by the end of follow-up, so the median was not reached: it cannot be estimated from these data, which is not the same as saying it is very long. The log-rank statistic is 1.06 on 1 degree of freedom (p = 0.303), and the hazard ratio of regimen 2 against regimen 1 is 0.551 (95% CI 0.174 to 1.74).
Read that for what it is. With 26 women and 12 deaths the interval for the hazard ratio is very wide and includes 1, so the data cannot separate a real difference from none. The engine’s check of proportional hazards found no evidence against it (p = 0.101), which with this few events says little. The result illustrates the method. It is not a finding about the two regimens. For how to read a ratio like 0.551, see how to interpret a hazard ratio.
Which method fits which kind
The kind of censoring decides the tool, because a method built for one kind silently misreads another.
| Kind | Describe and compare | Model |
|---|---|---|
| Right | Kaplan-Meier curves, log-rank test | Cox regression, accelerated failure time models |
| Left (values below a limit) | Peto-Peto test | Censored regression such as the Tobit model (Tobin 1958) |
| Interval | Turnbull estimator (1976) | Parametric survival regression |
Right-censored data are the case almost every survival tutorial assumes. If your outcome is a concentration with non-detects, start from the detection limit guide instead. If your events are found only at scheduled visits, midpoints of the intervals are not a free shortcut: they treat a bracket as a point and understate the uncertainty.
How to report censoring
A reader cannot judge a survival result without knowing how much of it rests on censored times. Report the number of units, the number of events and the number censored, say what ended follow-up, and give the median follow-up or the range of censoring times. State when no median was reached, as in regimen 2 above, instead of leaving the cell blank. If you chose a method for left or interval data, name it, because a table of “means” or “medians” with no method hides that values were bounded.
The assumption that carries everything
All of these methods assume that censoring is non-informative: given the variables in the analysis, a unit censored at time t has the same future risk as one that stays under observation (Clark and colleagues 2003). If people drop out because they are getting worse, those who remain are healthier than those who left, and the curve is too optimistic.
You cannot test this assumption from the censored data alone, because the missing part is exactly what you did not see. What you can do is ask why each unit was censored. Planned end-of-study censoring is usually safe. Withdrawal, loss to follow-up and competing events deserve a sentence in the methods section, and sometimes a sensitivity analysis.
What to do with your own data
- Record, for every row, the time and whether it is an event or a censored time. Never code the cut-off as an event.
- Say which kind of censoring you have. Mixed data are common, such as right-censored times and interval-censored visits in the same study.
- Keep censored rows in the analysis, and choose a method that uses the bound: Kaplan-Meier and the log-rank test for describing and comparing groups, Cox regression for adjusted ratios.
- For values below a detection limit, use the tools in the detection limit guide.
- State why units were censored, and report the number censored next to the number of events.
For the wider picture, with Kaplan-Meier, Cox and accelerated failure time models side by side, see the guide to Kaplan-Meier and Cox regression.
Rerunning the example
The engine’s grouped Kaplan-Meier run is reproducible from the script and data below. The three medians in the table above come from this separate R snippet, which needs only the survival package:
library(survival)
data(cancer, package = "survival")
fit <- survfit(Surv(futime, fustat) ~ 1, data = ovarian)
summary(fit)$table["median"] # 638
median(ovarian$futime[ovarian$fustat == 1]) # 359, deaths only
median(ovarian$futime) # 476, cut-off treated as death
Worked example
Data: Edmonson JH, Fleming TR, Decker DG, Malkasian GD, Jorgensen EO, Jefferies JA, et al. (1979). Different chemotherapeutic sensitivities and host factors affecting prognosis in advanced ovarian carcinoma versus minimal residual disease. Cancer Treatment Reports 63(2):241-247. Dataset documentation (cran.r-project.org). Seed: 20261028.
Run on Celeus engine 0.32.1, seed 20261028, public data.
Output
# Results - Test: Kaplan-Meier (log-rank test) - n: 26 - statistic: 1.063 - p-value: 0.303 - effect size: hazard ratio = 0.5508 (95% CI 0.1743 to 1.74) - seed: 20261028 ## Assumption checks - sample size: passed=TRUE (adequate n) - number of events: passed=TRUE (adequate events) - proportional hazards (cox.zph): passed=TRUE (no evidence against proportional hazards) ## Power - Power is not calculated for this test.
R script
# Celeus - reproduce.R (run from this package folder; bundled data in data/)
# Test: Kaplan-Meier (log-rank test) | engine 0.32.1 | catalog c39-d3cd
# Research context: not provided for this run.
# Reproduces the recorded result deterministically.
set.seed(20261028)
.data_path <- "data/censored-data.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.
.acols <- c("rx", "futime", "fustat")
data <- data[stats::complete.cases(data[, .acols, drop = FALSE]), , drop = FALSE]
result <- { .f <- survival::Surv(`futime`, `fustat`) ~ factor(`rx`); list(logrank = survival::survdiff(.f, data = data), km = summary(survival::survfit(.f, data = data))$table, cox = summary(survival::coxph(.f, data = data))) }
print(result)Download the files
Common questions
What is censored data?
Censored data are measurements where you know only a bound on the true value, not the value itself. A time may be known to exceed 400 days, to fall below a limit, or to lie between two visits. The partial information is kept in the analysis, not discarded.
What is the difference between right, left and interval censoring?
Right censoring means the event had not happened when observation stopped, so the time is above a known value. Left censoring means it had already happened or the value is below a limit. Interval censoring means it fell between two known times, such as two clinic visits.
Can I just remove the censored observations?
Not safely. In time-to-event data the censored units are often the ones that lasted longest, so removing them pulls every summary towards shorter times. The ovarian example in this post shows a median that is 279 days shorter when the censored women are dropped.
Is censoring the same as missing data?
No. A missing value carries no information about the outcome, whereas a censored value tells you that the outcome is above, below or between known bounds. Methods for censored data use that bound directly, while imputing a missing value rests on an assumption about why it is missing.
What assumption do survival methods make about censoring?
They assume censoring is non-informative: given the variables in the analysis, a unit censored at time t has the same future risk as a unit still under observation. If people leave the study because they are getting worse, that assumption fails and the estimates are biased.
Sources
- Kaplan EL, Meier P (1958). Nonparametric estimation from incomplete observations. Journal of the American Statistical Association 53(282):457-481. doi:10.1080/01621459.1958.10501452
- Clark TG, Bradburn MJ, Love SB, Altman DG (2003). Survival analysis part I: basic concepts and first analyses. British Journal of Cancer 89(2):232-238. doi:10.1038/sj.bjc.6601118
- Turnbull BW (1976). The empirical distribution function with arbitrarily grouped, censored and truncated data. Journal of the Royal Statistical Society: Series B (Methodological) 38(3):290-295. doi:10.1111/j.2517-6161.1976.tb01597.x
- Tobin J (1958). Estimation of relationships for limited dependent variables. Econometrica 26(1):24-36. doi:10.2307/1907382
- Helsel DR (2006). Fabricating data: how substituting values for nondetects can ruin results, and what can be done about it. Chemosphere 65(11):2434-2439. doi:10.1016/j.chemosphere.2006.04.051
- Therneau TM. survival: Survival Analysis. R package reference manual (Surv, survreg and ovarian 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