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

for循环生成ggplot时geom_segment未对应i值显示正确y坐标

问题:for循环生成ggplot时geom_segment线段y坐标错误统一

我用for loop基于相同代码、不同参数生成多幅ggplot图,每个图本该对应WHO=1/2/3,并显示两条分别对应time=60和time=120的水平geom_segment()线段,但现在所有图里的线段都显示在完全相同的y坐标上。


第一次尝试的代码

for(i in c("1", "2", "3")){

  predplot <- ggplot(filter(df_new, WHO == i), 
                     aes(x = ki67, y = pred, color = time, fill = time)) +

    scale_x_continuous(name = "",
                       breaks = seq(0, 50, 10)) +
    
    geom_segment(aes(x = 0, xend = 40,
                     y = df_margrisk$cuminc[df_margrisk$WHO == i & df_margrisk$time == "60"],
                     yend = df_margrisk$cuminc[df_margrisk$WHO == i & df_margrisk$time == "60"]),
                 color = "red") +
    
    geom_segment(aes(x = 0, xend = 40,
                     y = df_margrisk$cuminc[df_margrisk$WHO == i & df_margrisk$time == "120"],
                     yend = df_margrisk$cuminc[df_margrisk$WHO == i & df_margrisk$time == "120"]),
                 color = "red") +
  
    theme_classic()

  
  assign(paste0("who", i, "pred"), predplot)
  
}

用patchwork拼接:

library(patchwork)
who1pred  | who2pred | who3pred 

第二次尝试的代码

geom_segment(aes(x = 0, xend = 40,
                 y = if (i == "1") df_margrisk$cuminc[df_margrisk$WHO == "1" & df_margrisk$time == "60"] else 
                   if (i == "2") df_margrisk$cuminc[df_margrisk$WHO == "2" & df_margrisk$time == "60"] else
                     df_margrisk$cuminc[df_margrisk$WHO == "3" & df_margrisk$time == "60"],
                 yend = if (i == "1") df_margrisk$cuminc[df_margrisk$WHO == "1" & df_margrisk$time == "60"] else 
                   if (i == "2") df_margrisk$cuminc[df_margrisk$WHO == "2" & df_margrisk$time == "60"] else
                     df_margrisk$cuminc[df_margrisk$WHO == "3" & df_margrisk$time == "60"]), 
             color = "grey70",
             size = .1) +
  
  geom_segment(aes(x = 0, xend = 40,
                   y = if (i == "1") df_margrisk$cuminc[df_margrisk$WHO == "1" & df_margrisk$time == "120"] else 
                     if (i == "2") df_margrisk$cuminc[df_margrisk$WHO == "2" & df_margrisk$time == "120"] else
                       df_margrisk$cuminc[df_margrisk$WHO == "3" & df_margrisk$time == "120"],
                   yend = if (i == "1") df_margrisk$cuminc[df_margrisk$WHO == "1" & df_margrisk$time == "120"] else 
                     if (i == "2") df_margrisk$cuminc[df_margrisk$WHO == "2" & df_margrisk$time == "120"] else
                       df_margrisk$cuminc[df_margrisk$WHO == "3" & df_margrisk$time == "120"]), 
               color = "red",
               size = .1) 

相关数据

df_new

df_new <- structure(list(WHO = c("1", "3", "3", "1", "2", "3", "3", "2", 
"1", "1", "3", "3", "1", "2", "2", "3", "2", "3", "3", "3"), 
    ki67 = c(74, 43, 33, 40, 25, 47, 5, 49, 3, 78, 96, 66, 77, 
    45, 84, 61, 99, 19, 75, 22), pred = c(8.18837741638696e-08, 
    0.656014788470526, 0.467672725799402, 0.0495531139823135, 
    0.676232957612799, 0.555496874842657, 0.452128214447235, 
    0.920513064923983, 0.0592022139774029, 0.507544894144434, 
    0.942705398173106, 0.739445754449513, 0.510905666268942, 
    0.680952044793548, 0.685177333492073, 0.765579700267525, 
    0.35021374381192, 0.61272020356918, 0.854446676200307, 0.442974514059335
    ), time = c("60", "120", "60", "60", "120", "60", "60", "120", 
    "60", "120", "60", "60", "120", "60", "120", "120", "120", 
    "120", "120", "60")), row.names = c(NA, -20L), class = c("tbl_df", 
"tbl", "data.frame"))

df_margrisk

df_margrisk <- structure(list(time = c("60", "120", "60", "120", "60", "120"
), WHO = c("1", "1", "2", "2", "3", "3"), cuminc = c(0.0780868867206532, 
0.142831926593544, 0.25131050422863, 0.357325139768945, 0.550010238368203, 
0.682482624335479)), class = c("tbl_df", "tbl", "data.frame"), row.names = c(NA, 
-6L))

解决方案

问题核心是ggplot的延迟求值机制:当你在aes()里引用循环变量i时,ggplot不会立即计算对应值,而是等到绘图时才读取i的当前状态——循环结束后i的最终值是"3",所以所有图的线段都复用了WHO=3对应的y坐标。

下面是三种可行的解决方法:

方法1:提前过滤线段数据,避免在aes中引用循环变量

在循环内部先提取当前WHO对应的cuminc值,并且不要把y/yend放在aes()里(因为它们是单一常量,不是映射到数据集的变量):

for(i in c("1", "2", "3")){
  # 提前筛选当前WHO对应的边际风险数据
  current_marg <- filter(df_margrisk, WHO == i)
  # 提取对应time的cuminc值
  y60 <- current_marg$cuminc[current_marg$time == "60"]
  y120 <- current_marg$cuminc[current_marg$time == "120"]
  
  predplot <- ggplot(filter(df_new, WHO == i), 
                     aes(x = ki67, y = pred, color = time, fill = time)) +
    scale_x_continuous(name = "", breaks = seq(0, 50, 10)) +
    # 直接传入固定值,无需放在aes内
    geom_segment(x = 0, xend = 40, y = y60, yend = y60, color = "red") +
    geom_segment(x = 0, xend = 40, y = y120, yend = y120, color = "red") +
    theme_classic()
  
  assign(paste0("who", i, "pred"), predplot)
}

方法2:用局部环境固化循环变量

如果一定要在aes()中写逻辑,可以用local()创建局部环境,把当前循环的i值固定下来:

for(i in c("1", "2", "3")){
  predplot <- local({
    current_i <- i
    ggplot(filter(df_new, WHO == current_i), 
           aes(x = ki67, y = pred, color = time, fill = time)) +
      scale_x_continuous(name = "", breaks = seq(0, 50, 10)) +
      geom_segment(aes(x = 0, xend = 40,
                       y = df_margrisk$cuminc[df_margrisk$WHO == current_i & df_margrisk$time == "60"],
                       yend = df_margrisk$cuminc[df_margrisk$WHO == current_i & df_margrisk$time == "60"]),
                   color = "red") +
      geom_segment(aes(x = 0, xend = 40,
                       y = df_margrisk$cuminc[df_margrisk$WHO == current_i & df_margrisk$time == "120"],
                       yend = df_margrisk$cuminc[df_margrisk$WHO == current_i & df_margrisk$time == "120"]),
                   color = "red") +
      theme_classic()
  })
  assign(paste0("who", i, "pred"), predplot)
}

方法3:用列表存图(更优雅的替代方案)

推荐避免用assign()创建零散变量,而是把所有图存入列表,便于后续管理和拼接:

plot_list <- lapply(c("1", "2", "3"), function(i){
  current_marg <- filter(df_margrisk, WHO == i)
  y60 <- current_marg$cuminc[current_marg$time == "60"]
  y120 <- current_marg$cuminc[current_marg$time == "120"]
  
  ggplot(filter(df_new, WHO == i), 
         aes(x = ki67, y = pred, color = time, fill = time)) +
    scale_x_continuous(name = "", breaks = seq(0, 50, 10)) +
    geom_segment(x = 0, xend = 40, y = y60, yend = y60, color = "red") +
    geom_segment(x = 0, xend = 40, y = y120, yend = y120, color = "red") +
    theme_classic()
})

# 用patchwork拼接所有图
library(patchwork)
wrap_plots(plot_list, ncol = 3)

以上三种方法都能让每个图正确显示对应WHO分组的两条水平线段。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 22:07:02