arframe TFL Gallery
  1. Figures
  2. Kaplan-Meier Plot
  • Getting Started
    • Installation

  • Tables
    • Study Conduct
    • Enrollment by Country and Site

    • Study Population
    • Demographics Summary
    • Medical History
    • Prior Medication
    • Disposition Summary
    • Analysis Populations

    • Extent of Exposure
    • Concomitant Medications
    • Extent of Exposure

    • Safety
    • Adverse Events by System Organ Class and Preferred Term
    • AEs Related to Study Drug
    • Common Adverse Events
    • Adverse Events by Grade / Intensity
    • Overall Safety Summary
    • Adverse Events with Event Counts
    • Exposure-Adjusted Adverse Events
    • Adverse Events by Subgroup
    • Serious Adverse Events by SOC and PT
    • AEs Leading to Study Drug Discontinuation
    • Death Summary
    • Vital Signs
    • Laboratory Results - Chemistry
    • Laboratory Shift Table
    • Laboratory Worst Toxicity Grade
    • Laboratory Marked Abnormalities
    • Electrocardiogram Summary

    • Efficacy
    • Time to Event Summary
    • Best Overall Response

  • Listings
    • Adverse Event Listing
    • Demographic Characteristics Listing
    • Medical History Listing
    • Vital Signs Listing
    • Laboratory Test Results Listing
    • Concomitant Medications Listing

  • Figures
    • Kaplan-Meier Plot
    • Swimmer Plot
    • Waterfall Plot

On this page

  • Setup
  • Plot Construction
  • arframe Pipeline
  • Rendered Figure
  1. Figures
  2. Kaplan-Meier Plot

Kaplan-Meier Plot

Progression-Free Survival

Setup

See Prerequisites for installation instructions.

library(arframe)
library(ggplot2)
library(survival)
library(pharmaverseadam)
library(dplyr, warn.conflicts = FALSE)

arm_levels <- c("Placebo", "Xanomeline Low Dose", "Xanomeline High Dose")
arm_colors <- c("#1b9e77", "#d95f02", "#7570b3")

pfs <- pharmaverseadam::adtte_onco |>
  filter(PARAMCD == "PFS", ARM != "Screen Failure") |>
  mutate(
    ARM = factor(ARM, levels = arm_levels),
    AVAL_MONTHS = AVAL / 30.4375,
    EVENT = 1 - CNSR
  )

Plot Construction

# Fit survival curves
fit <- survfit(Surv(AVAL_MONTHS, EVENT) ~ ARM, data = pfs)

# Build plot data for ggplot2
surv_df <- broom::tidy(fit) |>
  mutate(strata = gsub("ARM=", "", strata))

# Number at risk
nar_times <- seq(0, 6, by = 1)
s <- summary(fit, times = nar_times, extend = TRUE)
nar_df <- data.frame(
  time   = s$time,
  strata = gsub("ARM=", "", s$strata),
  n.risk = s$n.risk,
  stringsAsFactors = FALSE
) |>
  mutate(strata = factor(strata, levels = rev(arm_levels)))

# KM curves
p_km <- ggplot(surv_df, aes(x = time, y = estimate, color = strata)) +
  geom_step(linewidth = 0.8) +
  geom_point(
    data = surv_df |> filter(n.censor > 0),
    aes(x = time, y = estimate),
    shape = 3, size = 2
  ) +
  scale_color_manual(values = setNames(arm_colors, arm_levels)) +
  scale_x_continuous(breaks = nar_times, limits = c(0, 6)) +
  scale_y_continuous(labels = scales::percent, limits = c(0, 1)) +
  labs(x = "Time (Months)", y = "Survival Probability", color = "Treatment") +
  theme_minimal(base_size = 11) +
  theme(
    legend.position = "bottom",
    panel.grid.minor = element_blank()
  )

# Number at risk table
p_nar <- ggplot(nar_df, aes(x = time, y = strata, label = n.risk)) +
  geom_text(size = 3.2) +
  scale_x_continuous(breaks = nar_times, limits = c(-0.1, 6.1)) +
  labs(x = NULL, y = NULL, title = "Number at Risk") +
  theme_minimal(base_size = 10) +
  theme(
    panel.grid = element_blank(),
    axis.text.x = element_blank(),
    axis.text.y = element_text(size = 8),
    plot.title = element_text(size = 10, face = "bold")
  )

# Combine with patchwork
km_combined <- patchwork::wrap_plots(p_km, p_nar, ncol = 1, heights = c(4, 1))

arframe Pipeline

km_combined |>
  fr_figure(width = 7, height = 5.5) |>
  fr_titles(
    "Figure 14.2.1",
    "Kaplan-Meier Plot for Progression-Free Survival",
    "Efficacy Population"
  ) |>
  fr_footnotes(
    "PFS is calculated from date of first dose to date of disease progression or death.",
    "Patients without progression or death are censored at date of last assessment.",
    "Censored observations are indicated by + marks."
  )

Rendered Figure

Figure 14.2.1
Kaplan-Meier Plot for Progression-Free Survival
Efficacy Population
figure
PFS is calculated from date of first dose to date of disease progression or death.
Patients without progression or death are censored at date of last assessment.
Censored observations are indicated by + marks.
/opt/quarto/share/rmd/rmd.R 01APR2026 09:50:26
Source Code
---
title: "Kaplan-Meier Plot"
subtitle: "Progression-Free Survival"
execute:
  echo: true
  eval: true
---


```{r}
#| label: prereqs
#| include: false
library(arframe)
fr_theme(hlines = "header", font_family = "Courier New")
```

## Setup

See [Prerequisites](../install.qmd) for installation instructions.

```{r}
#| label: setup
library(arframe)
library(ggplot2)
library(survival)
library(pharmaverseadam)
library(dplyr, warn.conflicts = FALSE)

arm_levels <- c("Placebo", "Xanomeline Low Dose", "Xanomeline High Dose")
arm_colors <- c("#1b9e77", "#d95f02", "#7570b3")

pfs <- pharmaverseadam::adtte_onco |>
  filter(PARAMCD == "PFS", ARM != "Screen Failure") |>
  mutate(
    ARM = factor(ARM, levels = arm_levels),
    AVAL_MONTHS = AVAL / 30.4375,
    EVENT = 1 - CNSR
  )
```


## Plot Construction

```{r}
#| label: build-plot

# Fit survival curves
fit <- survfit(Surv(AVAL_MONTHS, EVENT) ~ ARM, data = pfs)

# Build plot data for ggplot2
surv_df <- broom::tidy(fit) |>
  mutate(strata = gsub("ARM=", "", strata))

# Number at risk
nar_times <- seq(0, 6, by = 1)
s <- summary(fit, times = nar_times, extend = TRUE)
nar_df <- data.frame(
  time   = s$time,
  strata = gsub("ARM=", "", s$strata),
  n.risk = s$n.risk,
  stringsAsFactors = FALSE
) |>
  mutate(strata = factor(strata, levels = rev(arm_levels)))

# KM curves
p_km <- ggplot(surv_df, aes(x = time, y = estimate, color = strata)) +
  geom_step(linewidth = 0.8) +
  geom_point(
    data = surv_df |> filter(n.censor > 0),
    aes(x = time, y = estimate),
    shape = 3, size = 2
  ) +
  scale_color_manual(values = setNames(arm_colors, arm_levels)) +
  scale_x_continuous(breaks = nar_times, limits = c(0, 6)) +
  scale_y_continuous(labels = scales::percent, limits = c(0, 1)) +
  labs(x = "Time (Months)", y = "Survival Probability", color = "Treatment") +
  theme_minimal(base_size = 11) +
  theme(
    legend.position = "bottom",
    panel.grid.minor = element_blank()
  )

# Number at risk table
p_nar <- ggplot(nar_df, aes(x = time, y = strata, label = n.risk)) +
  geom_text(size = 3.2) +
  scale_x_continuous(breaks = nar_times, limits = c(-0.1, 6.1)) +
  labs(x = NULL, y = NULL, title = "Number at Risk") +
  theme_minimal(base_size = 10) +
  theme(
    panel.grid = element_blank(),
    axis.text.x = element_blank(),
    axis.text.y = element_text(size = 8),
    plot.title = element_text(size = 10, face = "bold")
  )

# Combine with patchwork
km_combined <- patchwork::wrap_plots(p_km, p_nar, ncol = 1, heights = c(4, 1))
```


## arframe Pipeline

```{r}
#| label: pipeline
#| eval: false
km_combined |>
  fr_figure(width = 7, height = 5.5) |>
  fr_titles(
    "Figure 14.2.1",
    "Kaplan-Meier Plot for Progression-Free Survival",
    "Efficacy Population"
  ) |>
  fr_footnotes(
    "PFS is calculated from date of first dose to date of disease progression or death.",
    "Patients without progression or death are censored at date of last assessment.",
    "Censored observations are indicated by + marks."
  )
```


## Rendered Figure

```{r}
#| label: pre-render
#| include: false
km_combined <- patchwork::wrap_plots(p_km, p_nar, ncol = 1, heights = c(4, 1))
```

```{r}
#| label: rendered-figure
#| echo: false
#| ref.label: pipeline
```

Open-source TFL reference collection

 

CDISC Pilot Study (CDISCPILOT01) • pharmaverseadam datasets