循环中存储ggplot对象时所有图均变为最后一张的问题排查
解决ggplot循环生成图表全是最后一张的问题
我太懂这种绞尽脑汁的感觉了——这其实是ggplot延迟求值的经典坑,很多人第一次循环画图都会踩!
问题根源
ggplot不会在你创建plot对象的时候就把数据“固定”下来,它会等到你实际渲染图表(比如print()、保存或者在RStudio里查看)的时候,才去读取你在aes()里引用的变量值。你循环里创建的所有plot对象,其实都共享同一个shorter和longer变量的引用,等你最后查看列表时,这两个变量已经是循环最后一次迭代的值了,所以所有图表都变成了最后那一张的样子。
解决方案1:用local()冻结局部环境
你之前尝试过local(),但没完全用对——需要在local()内部把当前迭代的变量重新赋值,让每个plot对象绑定自己的局部变量:
plotCount <- 5 multiplePlots <- tibble(clockPlots = list(), index = double()) loopCount = 1 # 把数据定义移到循环外,避免重复创建 short <- tibble(val = c(1:12), x = 3 * sin(val * pi / 6), y = 3 * cos(val * pi / 6)) long <- tibble(val = c(1:60), x = 4.5 * sin(val * pi / 30), y = 4.5 * cos(val * pi / 30)) while(plotCount > 0) { # 先把当前迭代的数据存到临时变量 current_short <- short[loopCount,] current_long <- long[loopCount,] # 用local创建独立环境,把临时变量传入并重新赋值 plot <- local({ s <- current_short l <- current_long ggplot() + geom_segment(aes(x = 0, y = 0, xend = s[[2]], yend = s[[3]]), colour = "black", size = 3) + geom_segment(aes(x = 0, y = 0, xend = l[[2]], yend = l[[3]]), colour = "black", size = 2) }) print(plot) # 不要用<<-全局赋值,用常规赋值更安全 multiplePlots <- multiplePlots %>% add_row(clockPlots = list(plot), index = loopCount) plotCount <- plotCount - 1 loopCount <- loopCount + 1 } # 现在查看就会有不同的图表了 multiplePlots$clockPlots[[1]] multiplePlots$clockPlots[[2]]
解决方案2:给geom绑定专属数据(更推荐)
更简洁可靠的方式是,直接把当前迭代的数据传给geom_segment()的data参数,让每个图层绑定自己的数据副本,彻底摆脱外部变量的影响:
plotCount <- 5 plot_list = list() loopCount = 1 short <- tibble(val = c(1:12), x = 3 * sin(val * pi / 6), y = 3 * cos(val * pi / 6)) long <- tibble(val = c(1:60), x = 4.5 * sin(val * pi / 30), y = 4.5 * cos(val * pi / 30)) while(plotCount > 0) { current_short <- short[loopCount,] current_long <- long[loopCount,] plot_list[[loopCount]] <- ggplot() + # 直接把当前数据传给geom,aes里用数据列名即可 geom_segment(data = current_short, aes(x = 0, y = 0, xend = x, yend = y), colour = "black", size = 3) + geom_segment(data = current_long, aes(x = 0, y = 0, xend = x, yend = y), colour = "black", size = 2) plotCount <- plotCount - 1 loopCount <- loopCount + 1 } # 现在每个元素都是独立的图表了 plot_list[[1]] plot_list[[2]] plot_list[[3]]
额外提醒
- 尽量避免使用
<<-全局赋值,它会修改全局环境的变量,很容易导致意外的bug,用常规的<-局部赋值就好。 - 如果循环次数多,建议用
purrr包的迭代函数(比如map())来替代while循环,代码会更简洁,也更容易避免这类环境变量的问题。
内容的提问来源于stack exchange,提问作者Zac Warham
相关产品推荐
相关产品推荐

