You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在R中绘制含多事件的Kaplan-Meier时间-事件图?

Great question! Kaplan-Meier is commonly used for binary survival outcomes, but it absolutely can be adapted for multi-state scenarios like tracking time to sequential medication switches. Let's break down where you went wrong and how to fix it.

Why Your Initial Code Failed

  • The core issue is how you constructed your Surv object. The event argument in Surv(time, event) expects a binary indicator (0 = censored, 1 = event occurred), but you passed episode (the stage number). This confused the survival package, which marked those non-binary event values as uncertain (hence the ? in your S_input) and dropped those observations entirely.
  • Your data is in long format (one row per stage per patient), but you need to restructure it to focus on time to each switch event rather than stage numbers.

Step 1: Restructure Your Data

First, we need to calculate the cumulative time to each medication switch for each patient. Let's fix the data to get clear time-to-event values for first, second, and third switches:

library(data.table)
data <- data.table(
  ID = c(1,1,2,3,4,5,6,6,7,7,7,8,9,10,11,11,11,12,12,13,14,14,14,15,15,16),
  episode = c(1,2,1,1,1,1,1,2,1,2,3,1,1,1,1,2,3,1,2,1,1,2,3,1,2,1),
  time_till_next_ep = c(0,5,0,0,0,0,0,8,0,4,14,0,0,0,0,6,17,0,9,0,0,4,9,0,8,0)
)

# Sort data by patient and stage
data <- data[order(ID, episode)]

# Calculate cumulative time to each stage (time from enrollment to switch)
data[, cum_time := cumsum(time_till_next_ep), by = ID]

# Convert to wide format to get time to each switch per patient
wide_data <- dcast(data, ID ~ paste0("stage_", episode), value.var = "cum_time")
wide_data[, paste0("stage_", 1:3) := lapply(.SD, function(x) ifelse(is.na(x), 0, x)), .SDcols = paste0("stage_", 1:3)]

# Define time to each switch and event indicators (1 = switch occurred, 0 = no switch)
wide_data[, time_to_1st_switch := stage_2]  # Time from enrollment to first switch
wide_data[, time_to_2nd_switch := stage_3]  # Time from enrollment to second switch
wide_data[, event_1st := as.integer(time_to_1st_switch > 0)]
wide_data[, event_2nd := as.integer(time_to_2nd_switch > 0)]

Step 2: Fit Multi-State Survival Curves

Now we can fit survival curves for each switch event, or use a multi-state model to track transitions between stages.

Option 1: Separate Kaplan-Meier Curves for Each Switch

This shows the proportion of patients who haven't switched to the next stage yet at each time point:

library(survival)
library(survminer)

# Curve for time to first switch (remaining in Stage 1)
km_1st <- survfit(Surv(time_to_1st_switch, event_1st) ~ 1, data = wide_data)
ggsurvplot(km_1st, data = wide_data,
           main = "Time to First Medication Switch",
           xlab = "Days Since Enrollment",
           ylab = "Proportion Remaining in Stage 1",
           tables.theme = theme_cleantable())

# Curve for time to second switch (only patients who switched once)
km_2nd <- survfit(Surv(time_to_2nd_switch - time_to_1st_switch, event_2nd) ~ 1, 
                  data = wide_data[event_1st == 1])
ggsurvplot(km_2nd, data = wide_data[event_1st == 1],
           main = "Time to Second Medication Switch (After First Switch)",
           xlab = "Days Since First Switch",
           ylab = "Proportion Remaining in Stage 2",
           tables.theme = theme_cleantable())

Option 2: Multi-State Curve Tracking All Stages

This shows the probability of being in each stage at any given time, which aligns with your request for a plot that "drops a step" when patients move to a new stage:

# Build multi-state transition data
ms_data <- data.table()
for (id in unique(data$ID)) {
  sub <- data[ID == id]
  max_stage <- max(sub$episode)
  
  if (max_stage == 1) {
    # Patient stayed in Stage 1 (censored)
    ms_data <- rbind(ms_data,
                     data.table(ID = id, start = 0, stop = 0, status = 0, from = 1, to = 1))
  } else if (max_stage == 2) {
    # Transition from Stage 1 to 2, then censored in Stage 2
    switch_time_1 <- sub[episode == 2, cum_time]
    ms_data <- rbind(ms_data,
                     data.table(ID = id, start = 0, stop = switch_time_1, status = 1, from = 1, to = 2),
                     data.table(ID = id, start = switch_time_1, stop = switch_time_1, status = 0, from = 2, to = 2))
  } else if (max_stage == 3) {
    # Transition 1->2, then 2->3, then censored in Stage 3
    switch_time_1 <- sub[episode == 2, cum_time]
    switch_time_2 <- sub[episode == 3, cum_time]
    ms_data <- rbind(ms_data,
                     data.table(ID = id, start = 0, stop = switch_time_1, status = 1, from = 1, to = 2),
                     data.table(ID = id, start = switch_time_1, stop = switch_time_2, status = 1, from = 2, to = 3),
                     data.table(ID = id, start = switch_time_2, stop = switch_time_2, status = 0, from = 3, to = 3))
  }
}

# Fit multi-state survival model
ms_fit <- survfit(Surv(start, stop, status, type = "counting") ~ 1, data = ms_data, id = ID)

# Plot multi-state curves
ggsurvplot_ms(ms_fit, data = ms_data,
              palette = "jco",
              main = "Multi-State Survival: Medication Stages Over Time",
              xlab = "Days Since Enrollment",
              ylab = "Probability of Being in Stage")

Key Takeaways

  • Kaplan-Meier can be extended to multi-state/sequential event scenarios—you just need to structure your data correctly and use the right type of Surv object.
  • The event argument in Surv() must always be a binary indicator, not a categorical stage number.
  • Multi-state models are ideal for tracking transitions between sequential stages like medication switches, as they show how patient distributions shift across stages over time.

内容的提问来源于stack exchange,提问作者Inkling

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 22:27:42