Kaplan–Meier survival curves in R with canvasXpress

R
canvasXpress
survival
bioinformatics
visualization
Turn a survival table into an interactive, reproducible Kaplan–Meier plot stratified by treatment arm — with censoring ticks — in a few lines of R.
Author

Isaac Neuhaus

Published

September 23, 2026

Kaplan–Meier curves summarize time-to-event data: the step-down survival probability over time, usually split by a group like treatment arm. In this post we build one in R with canvasXpress. canvasXpress computes the survival steps for you — you hand it a time column and a censoring column, and it does the rest — and the result is an interactive figure that serializes to one portable, reproducible spec.

A survival table

Swap this simulated block for your own data: you need, per subject, a follow-up time, a censoring indicator (1 = censored, 0 = event), and the grouping variable.

library(canvasXpress)

set.seed(1)
n    <- 200
arm  <- sample(c("Treatment", "Control"), n, replace = TRUE)
base <- ifelse(arm == "Treatment", 60, 40)      # treatment survives longer
time <- round(rexp(n, rate = 1 / base), 1)
cens <- rbinom(n, 1, 0.3)                        # 1 = censored, 0 = event
subj <- paste0("Subj", seq_len(n))

canvasXpress reads two columns for a Kaplan–Meier plot — the time and its matching censor flag — plus a per-subject annotation carrying the group.

y <- data.frame(
  "Time"        = time,
  "Time-Censor" = cens,
  row.names     = subj,
  check.names   = FALSE
)

varAnnot <- data.frame(Arm = arm, row.names = subj, check.names = FALSE)

The plot

The key is graphType = "KaplanMeier" and passing both columns to xAxis (time first, then the censor column). colorBy stratifies the curves.

canvasXpress(
  data     = y,
  varAnnot = varAnnot,
  graphType = "KaplanMeier",
  xAxis    = list("Time", "Time-Censor"),
  colorBy  = "Arm",
  title    = "Overall survival by treatment arm",
  xAxisTitle = "Time (weeks)",
  yAxisTitle = "Probability of survival",
  invertCensored = TRUE,     # draw censoring ticks
  showKMConfidenceIntervals = FALSE,
  width  = 650,
  height = 500
)

Hover any step for the time and survival probability; the tick marks are censored subjects. Because canvasXpress derives the survival curve from the raw table, you never precompute the steps — and the same spec re-renders the identical curve from Python or JavaScript.

A common gotcha

The survival transform only fires with graphType = "KaplanMeier". If you use a bare "Scatter2D" you’ll get a raw scatter of the censor values instead — survival probability is computed by the engine, it is not a column in your data.

Next