使用ggplot与assign(paste0)在for循环中绘图时柱状图异常问题
问题:循环批量生成ggplot柱状图时,geom_bar始终显示最后一次迭代结果
问题现象
使用for循环基于AUX_GRAF数据框批量生成ggplot柱状图,生成的图表标题和geom_label显示正确,但所有图表的geom_bar都和最后一次迭代的柱状图一致。循环内打印图表显示正常,但循环外调用保存的图表时出现问题。
用户原代码:
for(i in colnames(AUX_GRAF[2: ncol(AUX_GRAF)])){ AUX01 <- as.numeric(AUX_GRAF[, i]) AUX <- ggplot (AUX_GRAF, aes(x = reorder(SE, as.numeric(SE)), y = AUX01)) + theme(axis.text.x = element_text(face = "bold")) + labs(caption = "Fonte", x = "Semana Epidemiológica", y = "Número de Casos", title = paste0(i, " - Notificados"))+ theme( panel.grid.major = element_line(color = "#C0C0C0"), panel.grid.minor = element_blank(), panel.background = element_rect(fill = "#F5F5F5"), plot.title = element_text(face = "bold", size = 15, colour = "#556B2F")) + geom_bar(stat = "identity", color = "black", fill = "#8FBC8F") + geom_label(aes(label = AUX01), alpha = 0.5, vjust = 0.1) + scale_y_continuous(expand = expansion(mult = c(0, 0.05))) assign(paste0("SE_HIST_NOT_", i), AUX) Sys.sleep(1) print(assign(paste0("SE_HIST_NOT_", i), AUX)) rm(AUX) }
样本数据:
> dput(head(AUX_GRAF)) structure(list(SE = c("21", "22", "23", "24", "25", "26"), ARAPUÃ = c(2, 3, 2, 1, 1, 0), ARIRANHA_DO_IVAÍ = c(0, 0, 0, 0, 0, 1), CÂNDIDO_DE_ABREU = c(1, 0, 0, 1, 0, 0), CRUZMALTINA = c(0, 0, 1, 0, 0, 0), GODOY_MOREIRA = c(0, 1, 0, 0, 0, 0), IVAIPORÃ = c(39, 32, 18, 16, 14, 10), JARDIM_ALEGRE = c(11, 19, 7, 2, 0, 0), LIDIANÓPOLIS = c(22, 18, 9, 9, 4, 11), LUNARDELLI = c(12, 2, 3, 3, 7, 2), MANOEL_RIBAS = c(5, 5, 1, 0, 3, 1), MATO_RICO = c(0, 0, 0, 0, 0, 0), NOVA_TEBAS = c(15, 19, 11, 7, 3, 5), RIO_BRANCO_DO_IVAÍ = c(10, 1, 0, 0, 0, 0), ROSÁRIO_DO_IVAÍ = c(0, 0, 0, 0, 0, 0), SANTA_MARIA_DO_OESTE = c(0, 0, 0, 0, 0, 0), SÃO_JOÃO_DO_IVAÍ = c(3, 2, 0, 0, 0, 1)), row.names = c(NA, 6L), class = "data.frame")
问题原因
ggplot的aes()参数采用延迟求值机制:ggplot对象不会立即计算aes中变量的具体值,而是在实际渲染(如print()或保存图表)时才去读取变量的当前值。
在原代码中,y = AUX01引用的是循环外部的变量,循环结束后AUX01保留的是最后一次迭代的数值,因此所有保存的ggplot对象在渲染时都会读取这个最终值,导致柱状图全部显示为最后一次迭代的结果。
虽然geom_label(aes(label = AUX01))看似正常,但实际上也存在同样的延迟求值风险,只是当前场景下未暴露。
解决方案
有两种可靠的解决方法:
方法1:创建局部数据框(推荐)
每次迭代时生成包含当前列数据的局部数据框,确保ggplot绑定的是当前迭代的数据集:
library(dplyr) for(i in colnames(AUX_GRAF[2: ncol(AUX_GRAF)])){ # 生成当前迭代的局部数据,包含排序后的SE和当前列数值 local_data <- AUX_GRAF %>% mutate( SE_ordered = reorder(SE, as.numeric(SE)), current_cases = as.numeric(.[[i]]) ) # 基于局部数据绘图 AUX <- ggplot(local_data, aes(x = SE_ordered, y = current_cases)) + theme(axis.text.x = element_text(face = "bold")) + labs( caption = "Fonte", x = "Semana Epidemiológica", y = "Número de Casos", title = paste0(i, " - Notificados") ) + theme( panel.grid.major = element_line(color = "#C0C0C0"), panel.grid.minor = element_blank(), panel.background = element_rect(fill = "#F5F5F5"), plot.title = element_text(face = "bold", size = 15, colour = "#556B2F") ) + geom_bar(stat = "identity", color = "black", fill = "#8FBC8F") + geom_label(aes(label = current_cases), alpha = 0.5, vjust = 0.1) + scale_y_continuous(expand = expansion(mult = c(0, 0.05))) # 保存并打印图表 plot_name <- paste0("SE_HIST_NOT_", i) assign(plot_name, AUX) print(AUX) rm(AUX) }
方法2:使用.data代词直接引用列名
利用ggplot的.data代词直接引用当前列名,强制ggplot绑定到数据框的对应列,避免延迟求值问题:
for(i in colnames(AUX_GRAF[2: ncol(AUX_GRAF)])){ AUX <- ggplot(AUX_GRAF, aes( x = reorder(SE, as.numeric(SE)), y = .data[[i]] # 用.data代词直接引用当前列 )) + theme(axis.text.x = element_text(face = "bold")) + labs( caption = "Fonte", x = "Semana Epidemiológica", y = "Número de Casos", title = paste0(i, " - Notificados") ) + theme( panel.grid.major = element_line(color = "#C0C0C0"), panel.grid.minor = element_blank(), panel.background = element_rect(fill = "#F5F5F5"), plot.title = element_text(face = "bold", size = 15, colour = "#556B2F") ) + geom_bar(stat = "identity", color = "black", fill = "#8FBC8F") + geom_label(aes(label = .data[[i]]), alpha = 0.5, vjust = 0.1) + scale_y_continuous(expand = expansion(mult = c(0, 0.05))) # 保存并打印图表 plot_name <- paste0("SE_HIST_NOT_", i) assign(plot_name, AUX) print(AUX) rm(AUX) }
说明
两种方法都能解决延迟求值导致的问题,其中方法1通过局部数据框明确隔离每次迭代的数据,逻辑更清晰;方法2更简洁,适合不需要额外数据处理的场景。
内容的提问来源于stack exchange,提问作者Gustavo
相关产品推荐
相关产品推荐

