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
相关产品推荐
相关产品推荐

