Nature 复现 | 患者泳道图 Swimmer’s plot

Page content

Nature 复现 | 患者泳道图 Swimmer’s plot by MsTt笔记

请尊重原创劳动成果🙏

未经允许,不可抄袭,转载请注明出处!

今天复现的图是 Swimmer's plot,其在医学/临床研究领域通常叫做 "游泳图" 或 "泳道图"。

  • 这是一种用于可视化临床试验中个体患者治疗历程的图表,横轴通常表示时间,纵轴表示每个患者,用不同颜色的条形(泳道)表示不同的治疗阶段,并用符号标记响应事件、疾病进展、治疗终止等关键时间点。
  • 它在肿瘤学临床试验报告中尤为常见,用于展示患者层面的治疗暴露时间、响应持续时间和结局。

Nature 文章中 (Safonov, A., Lee, M., Brown, D.N. et al. Homologous recombination deficiency and hemizygosity drive resistance in breast cancer. Nature (2026). https://doi.org/10.1038/s41586-026-10197-0) 的 Fig 2.d

原图

这张图清晰地展示了 41 名 患者在接受了 CDK4/6抑制剂 + 内分泌治疗(红色长条) 之后,序贯接受 PARP抑制剂(蓝色长条) 的治疗历程、响应情况及生存结局。

1. 治疗顺序与持续时间

  • 每个患者用两条水平“泳道”表示:红色代表 CDK4/6i+ET 治疗阶段,蓝色代表后续的 PARPi 治疗阶段。
  • 横轴为时间(单位:月),泳道的长度直观反映每位患者接受各方案治疗的持续时间。
  • 通过对比同一患者红蓝两条的长度,可以看出部分患者从 PARPi 中获得了比前线治疗更长的治疗获益。

2. 响应标记

  • 图中使用不同符号标记每位患者在两种治疗下的最佳响应:
    • CR(完全缓解)/ PR(部分缓解)/ SD(疾病稳定)/ PD(疾病进展)
  • 可以直观地看到:许多患者在红色条(CDK4/6i+ET)上标记为 SD 或 PD,但在蓝色条(PARPi)上转为 PR 或 CR,体现了 PARPi 的后线治疗价值

3. 治疗终止原因

  • 泳道末端的符号或箭头表示终止原因(如疾病进展、毒性、仍在治疗中等)
  • MD(physician)可能表示医生决策终止

4. 患者排序

  • 患者通常按某种临床特征(如响应类型、生物标志物状态或前线治疗时长)排序,便于发现模式

复现图

游泳图的独特价值:

  • 个体化呈现:不仅展示群体统计结果,还能看到每个患者的具体治疗轨迹
  • 异质性可视化:直观显示不同患者对同一治疗方案的响应差异
  • 时间维度清晰:同时展示治疗顺序、持续时间和事件发生时间点
  • 前后对比:同一患者红蓝条并排,便于比较两种方案的个体获益

加载包,读入数据

# 加载必要的包library(tidyverse) 
# 读入数据data <- readr::read_csv("data_1.csv")
# 泳道颜色(按不同治疗方案分组)main_colors <- c("cdk" = "#f06b6c", "parp" = "#6c90c9", "other" = "#dbdada")# 最佳响应颜色(圆形点)response_colors <- c("bet" = "#F4F4F4", "SD" = "#6b8590", "PR" = "#9cc0cd", "CR" = "#FFFDEC")# 治疗终止原因颜色(菱形点和箭头)status_colors <- c(  "Toxicity"             = "#b9b9ba",  "Disease Progression"  = "black",  "Ongoing"              = "black")
# 元数据 - Treatment_linemeta_colors_1 <- c("1" = "#dfebf3", "2" = "#8eb8d0", ">2" = "#1f7386")# 元数据 - LOH_statusmeta_colors_2 <- c("AI_LOH_ALT" = "#ef75ad", "No_LOH" = "#FAC1E3", "Non_evaluable" = "#FFFFFF")# 元数据 - CDKmeta_colors_3 <- c("Palbociclib" = "#f8ae82", "Abemaciclib" = "#f06b6c", "Ribociclib" = "#ce5455")# 元数据 - AI.SERDmeta_colors_4 <- c("SERD" = "#a060a7", "AI" = "#e4c9e0")# 元数据 - Interim.Platinummeta_colors_5 <- c("Yes" = "#08b06e", "No" = "#b3dcc2")

此数据为模拟数据,无实际意义,如需文末可以自助获取。

绘图

ggplot() +  # ------------------- A. 泳道主体(长矩形) -------------------  # 每个治疗阶段用一个矩形条表示  geom_rect(data = data,            aes(xmin = Start.Date.norm, xmax = End.Date.norm,                 ymin = index - 0.3, ymax = index + 0.3, fill = Treatment),            linewidth = 0.2, alpha = 1) +  # 填充颜色映射到治疗方案,并添加图例(order=1表示图例排在最前)  scale_fill_manual(name = "Treatment\n", values = main_colors,                    guide = guide_legend(order = 1,                                          override.aes = list(alpha = 1))) +  # ------------------- B. 基线到最末时间的连线 -------------------  # 为每个患者(index)画一条从时间0到最晚治疗结束时间的灰色线段,表示观察总时长  geom_segment(data = data %>%                  group_by(index) %>%                  summarise(Max_End = max(End.Date.norm, na.rm = TRUE)),               aes(x = 0, xend = Max_End, y = index, yend = index),               color = "grey30", linewidth = 0.4) +
  # ------------------- C. 最佳响应(圆形点) -------------------  # 使用新的填充标度(不影响之前的fill标度)  ggnewscale::new_scale_fill() +  geom_point(data = filter(data, Rapid.Prog == 0 & Best.Response %in% c("CR", "PR", "SD")),             aes(x = Scan.Date.Norm, y = index, fill = Best.Response),             shape = 21, size = 3.5, color = "black", stroke = 0.3) +  # 响应颜色映射,添加图例(order=2)  scale_fill_manual(name = "Best\nresponse", values = response_colors,                    guide = guide_legend(order = 2,                                          override.aes = list(size = 6))) +
  # ------------------- D. 终止原因标记 -------------------  # 新的颜色标度  ggnewscale::new_scale_color() +  # 菱形(shape=18)表示因毒性或疾病进展而停止治疗的患者  geom_point(data = filter(data, Treatment %in% c("cdk", "parp") &                              Reason.Stop %in% names(status_colors) &                              Reason.Stop != "Ongoing"),             aes(x = End.Date.norm, y = index, color = Reason.Stop),              shape = 18, size = 3) +
  # ------------------- E. 仍在治疗中的箭头 -------------------  # 对于Reason.Stop为"Ongoing"的患者,在治疗结束时间点绘制向右的箭头,表示治疗仍在继续  geom_segment(data = filter(data, Reason.Stop == "Ongoing"),               aes(x = End.Date.norm, y = index, xend = End.Date.norm + 2, yend = index,                    color = Reason.Stop),                arrow = arrow(length = unit(0.15, "cm")), linewidth = 0.5) +
  # 终止原因颜色映射,添加图例(order=3)  scale_color_manual(name = "Reason\nfor stop", values = status_colors,                     guide = guide_legend(order = 3,                                           override.aes = list(size = 6))) +
  # ------------------- F. 左侧元数据方块(5个分类变量) -------------------    # F1. 治疗线数 (Treatment_line)  ggnewscale::new_scale_fill() +  geom_point(data = filter(meta_data, category == "Treatment_line"),              aes(x = x_pos, y = index, fill = value), shape = 22, size = 4.5, alpha = 0.8) +  scale_fill_manual(name = "Treatment line\nCDK4/6i", values = meta_colors_1,                    guide = guide_legend(order = 8,                                          override.aes = list(size = 6))) +
  # F2. LOH状态 (LOH_status)  ggnewscale::new_scale_fill() +  geom_point(data = filter(meta_data, category == "LOH_status"),              aes(x = x_pos, y = index, fill = value), shape = 22, size = 4.5, alpha = 0.8) +  scale_fill_manual(name = "BRCA2\nzygosity", values = meta_colors_2,                    guide = guide_legend(order = 7,                                         override.aes = list(size = 6))) +
  # F3. CDK4/6抑制剂具体药物 (CDK)  ggnewscale::new_scale_fill() +  geom_point(data = filter(meta_data, category == "CDK"),              aes(x = x_pos, y = index, fill = value), shape = 22, size = 4.5, alpha = 0.8) +  scale_fill_manual(name = "CDK4/6i\nagent", values = meta_colors_3,                    guide = guide_legend(order = 6,                                         override.aes = list(size = 6))) +
  # F4. 内分泌治疗药物类型 (AI.SERD)  ggnewscale::new_scale_fill() +  geom_point(data = filter(meta_data, category == "AI.SERD"),              aes(x = x_pos, y = index, fill = value), shape = 22, size = 4.5, alpha = 0.8) +  scale_fill_manual(name = "Endocrine\nagent", values = meta_colors_4,                    guide = guide_legend(order = 5,                                         override.aes = list(size = 6))) +
  # F5. 既往铂类化疗 (Interim.Platinum)  ggnewscale::new_scale_fill() +  geom_point(data = filter(meta_data, category == "Interim.Platinum"),              aes(x = x_pos, y = index, fill = value), shape = 22, size = 4.5, alpha = 0.8) +  scale_fill_manual(name = "Previous\nplatinum", values = meta_colors_5,                    guide = guide_legend(order = 4,                                         override.aes = list(size = 6))) +  # ------------------- G. 坐标轴与视图范围设置 -------------------  coord_cartesian(xlim = c(0, max(data$End.Date.norm, na.rm = TRUE) + 5), clip = "off") +
  # 设置 X 轴刻度,只显示正数部分  scale_x_continuous(breaks = seq(0, 80, by = 20), expand = c(0, 0)) +  # Y轴:上下各扩展0.5个单位,避免图形紧贴边缘  scale_y_continuous(expand = c(0, 0.5)) +

  # ------------------- H. 主题与标签设置 -------------------  theme_classic()

更多可视化案例,复现思路大同小异,大家可以参考样式:

https://link.springer.com/article/10.1186/s12916-022-02696-4

https://www.nature.com/articles/s41586-025-09580-0

https://www.nature.com/articles/s41586-025-09203-8

https://www.nature.com/articles/s41586-025-10004-2

https://link.springer.com/article/10.1186/s13045-020-01019-5

获取以 RProject 形式保存的绘图代码+注释+数据模板,后台回复关键词: 260409_swimmer_plot


原文链接