如何在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
Survobject. Theeventargument inSurv(time, event)expects a binary indicator (0= censored,1= event occurred), but you passedepisode(the stage number). This confused the survival package, which marked those non-binary event values as uncertain (hence the?in yourS_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
Survobject. - The
eventargument inSurv()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

