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

使用assign()与计数器在for循环中出现绘图异常的问题

问题描述

我编写了自定义简化函数lc用于绘制数据的LC曲线,该函数会为数据中选中的每个month生成对应图表。我使用assign函数动态将每个绘图分配给新对象以便后续使用,通过计数器与paste0函数在for循环中更改对象名称。相关代码如下:

library(dplyr)
library(ggplot2)
library(ecotox)
df <- ecotox::lamprey_tox
lc<- function(data, dose='dose', months=c(), month='month', total='total', response='response'){

  data <- data%>% filter(month %in% c(months))
  counter=0
  for (i in months){
    counter <- counter + 1
    a <- data[data$month %in% i,]
    print(a)
    lc <- LC_probit((response/ total) ~ log10(as.numeric(dose)), p = c(50, 90),
                    weights = total,
                    data =a)
    LC50 <- lc$dose[1] #提取LC50
    LC90 <- lc$dose[2] #提取LC90
    assign(paste0('p', counter), ggplot(data=a,aes(x=log10(as.numeric(dose)),y=(response/total)))+ 
             geom_point(size=4)+ 
             geom_smooth(method="glm", 
                         method.args=list(family=binomial(link="probit")), 
                         aes(weight=total),colour="blue",se=TRUE, level=0.95) +
             geom_segment(aes(x = log10(LC50), xend = log10(LC50), y=-Inf, yend=0.5), linetype='dashed', size=1) +
             geom_segment(aes(x = -Inf, xend = log10(LC50), y=0.5, yend=0.5), linetype='dashed', size=1) +
             geom_segment(aes(x = log10(LC90), xend = log10(LC90), y=-Inf, yend=0.9), linetype='dashed', size=1) +
             geom_segment(aes(x = -Inf, xend = log10(LC90), y=0.9, yend=0.9), linetype='dashed', size=1)) 
          
    print(p1) 
  }
}

调用lc(df, months=c('May', 'June', 'August'))后,发现绘图未正确更新:geom_segment未随绘图内容更新,导致其与geom_smooth无法对齐;但将assign中的计数器改为固定值(如1)时,绘图显示正常。请问这一现象的原因是什么?


原因分析与解决方法

核心原因:ggplot延迟求值 + assign使用不当

  1. ggplot的延迟求值特性:ggplot对象不会在创建时立即计算所有参数,而是在执行print()时才会解析引用的变量。你在geom_segment的aes()中直接引用了循环变量LC50/LC90,当循环结束后,所有ggplot对象都会指向最后一次循环的LC50/LC90值,导致所有图的线段都对应最后一个月份的数值,和当前图的拟合线错位。

  2. assign与固定打印p1的错误搭配:每次循环用assign生成p1、p2、p3,但始终只打印p1,后续循环生成的p2/p3完全没被输出;同时p1中的LC50/LC90会因延迟求值,最终被替换为循环最后一次的数值。当把计数器改成固定值1时,每次循环都覆盖p1,且立即打印,此时变量还没被下一次循环修改,所以看起来正常,但本质没解决延迟求值的隐患。

正确解决思路

  • 将静态值移出aes():LC50/LC90是单个常量,不需要用aes()映射数据列,直接作为参数传给geom_segment,创建ggplot时就会固化当前循环的数值。
  • 用列表存储绘图对象:替代assign动态创建对象的方式,更符合R的编程习惯,避免作用域混乱和全局环境污染。
  • 打印对应循环的绘图对象:不要固定打印p1,而是打印当前循环生成的图。

修改后的示例代码

library(dplyr)
library(ggplot2)
library(ecotox)
df <- ecotox::lamprey_tox

lc <- function(data, dose='dose', months=c(), month_col='month', total='total', response='response'){
  # 过滤目标月份数据
  data_filtered <- data %>% filter(.data[[month_col]] %in% months)
  # 用列表统一存储绘图对象
  plot_list <- list()
  
  for (i in seq_along(months)){
    current_month <- months[i]
    a <- data_filtered %>% filter(.data[[month_col]] == current_month)
    
    # 计算LC值
    lc_result <- LC_probit((.data[[response]] / .data[[total]]) ~ log10(as.numeric(.data[[dose]])), 
                          p = c(50, 90),
                          weights = .data[[total]],
                          data = a)
    LC50 <- lc_result$dose[1]
    LC90 <- lc_result$dose[2]
    
    # 创建绘图,geom_segment使用静态参数而非aes映射
    current_plot <- ggplot(data=a, aes(x=log10(as.numeric(.data[[dose]])), y=(.data[[response]]/.data[[total]])))+ 
      geom_point(size=4)+ 
      geom_smooth(method="glm", 
                  method.args=list(family=binomial(link="probit")), 
                  aes(weight=.data[[total]]), colour="blue", se=TRUE, level=0.95) +
      # 静态常量直接传参,无需放入aes()
      geom_segment(x = log10(LC50), xend = log10(LC50), y=-Inf, yend=0.5, linetype='dashed', size=1) +
      geom_segment(x = -Inf, xend = log10(LC50), y=0.5, yend=0.5, linetype='dashed', size=1) +
      geom_segment(x = log10(LC90), xend = log10(LC90), y=-Inf, yend=0.9, linetype='dashed', size=1) +
      geom_segment(x = -Inf, xend = log10(LC90), y=0.9, yend=0.9, linetype='dashed', size=1) +
      ggtitle(paste("LC Curve for", current_month)) # 添加月份标题
    
    # 存储到列表
    plot_list[[current_month]] <- current_plot
    # 打印当前循环的图
    print(current_plot)
  }
  
  # 返回绘图列表,方便后续调用
  return(plot_list)
}

# 调用函数
plots <- lc(df, months=c('May', 'June', 'August'))

关键修改说明

  1. 静态参数移出aes():geom_segment的坐标是固定LC值,直接作为参数传入,避免延迟求值导致的变量覆盖。
  2. 列表存储绘图:替代assign动态创建对象,更易管理且避免全局环境污染。
  3. .data[[colname]]引用列名:避免非标准求值的潜在问题,提升函数健壮性。
  4. 打印当前绘图:确保每次输出对应月份的图表,而非固定的p1。

内容的提问来源于stack exchange,提问作者scott.pilgrim.vs.r

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 10:55:23