我已经阅读了很多关于使用ggplot循环生成大量图表的帖子,但找不到任何可以解释我问题的帖子......
我有一个数据框,我试图循环超过92列,为每列创建一个新图。我想将每个绘图保存为单独的对象。当我运行我的循环(下面的代码)并打印图表时,所有图表都是正确的。但是,当我使用assign()更改print()命令时,图形不正确。标题正在按原样改变,但是图形值都是相同的(它们是最终图形的所有值)。我发现了这一点,因为当我使用plot_grid()生成10个图的数字时,图标题和轴标签都是正确的,但值是相同的!
我的数据集很大,所以我提供了一个小数据集,如下图所示。
示例数据名称:
library(ggplot)
library(cowplot)
df <- as.data.frame(cbind(group=c(rep("A", 4), rep("B", 4)), a=sample(1:100, 8), b=sample(100:200, 8), c=sample(300:400, 8))) #make data frame
cols <- 2:4 #define columns for plots
for(i in 1:length(cols)){
df[,cols[i]] <- as.numeric(as.character(df[,cols[i]]))
} #convert columns to numeric
图:
for (i in 1:length(cols)){
g <- ggplot(df, aes(x=group, y=df[,cols[i]])) +
geom_boxplot() +
ggtitle(colnames(df)[cols[i]])
print(g)
assign(colnames(df)[cols[i]], g) #generate an object for each plot
}
plot_grid(a, b, c)
我在想,当ggplots制作情节时,它只会从i的最终值渲染数据?还是那样的?有办法解决这个问题吗?
我希望这样做,因为我想制作很多图表,然后我想混合和匹配数字图。
谢谢!
答案 0 :(得分:3)
我已经清理了您生成示例数据框的方式。
library(ggplot2)
library(cowplot)
df <- data.frame(group=c(rep("A", 4), rep("B", 4)),
a=sample(1:100, 8),
b=sample(100:200, 8),
c=sample(300:400, 8)) #make data frame
只需使用data.frame()即可。这使您的代码更加清晰,并且无需在“for循环”中进行所有后处理,即将数据帧转换为数字并删除生成的因子 - 请注意as.data.frame()和cbind()倾向于默认如果您没有'stringsAsFactors = FALSE',并且可以通过使用cbind.data.frame()而不是cbind()来避免数字到字符的转换。
我还重构了你的'for loop'来生成你的情节。您生成一个名为'cols'(cols&lt; - 2:4)的整数列表,然后重复这些整数以从每列数据生成图。这是不必要的,我们可以在for语句条件中创建一个范围 - 'for(i in 2:ncol(df))' - 这只是重复2到4(数据帧中的列数) - 从2开始需要避免包含元数据的第1列。这是优选的,因为:
i)在查看代码时,所使用的条件立即显而易见,而无需搜索其余代码
ii)R有许多类似于变量'cols'命名的函数/参数,最好避免混淆。
清理代码后,我们现在可以尝试查找错误的原因:
library(ggplot2)
library(cowplot)
df <- data.frame(group=c(rep("A", 4), rep("B", 4)),
a=sample(1:100, 8),
b=sample(100:200, 8),
c=sample(300:400, 8)) #make data frame
for (i in 2:ncol(df)){
g <- ggplot(df, aes(x=group, y=df[,i])) +
geom_boxplot() +
ggtitle(colnames(df)[i])
print(g)
assign(colnames(df)[i], g) #generate an object for each plot
}
为什么你的代码不起作用并不是很明显。 Imo的建议有其价值。将您的绘图保存到列表可以防止您的环境被对象弄得乱七八糟,但是它无法解决这个错误。原因不直观,需要深入了解如何评估assign()函数。请参阅here提供的答案Konrad Rudolph。以下应该可以工作并保留原始代码的样式。正如康拉德在他的回答中所暗示的那样,“R”可能更喜欢使用lapply。请注意,我们已经给出了 for loop 本地范围,现在我们在本地重新定义了i。以前,循环中生成的i的最后一个值用于生成通过assign()函数创建的每个对象。注意使用&lt;&lt; - 将g分配给全局环境。
for (i in 2:ncol(df))
local({
i <- i
g <<- ggplot(df, aes(x=group, y=df[,i])) +
geom_boxplot() +
ggtitle(colnames(df)[i])
print(i)
print(g)
assign(colnames(df)[i], g, pos =1) #generate an object for each plot
})
plot_grid(a, b, c)
你欠我一杯酒。
答案 1 :(得分:2)
有两种标准方法可以解决这个问题:
1-使用长格式data.frame
2-使用aes_string
引用宽格式data.frame
以下是可能策略的说明。
library(ggplot2)
library(gridExtra)
# data from other answer
df <- data.frame(group=c(rep("A", 4), rep("B", 4)),
a=sample(1:100, 8),
b=sample(100:200, 8),
c=sample(300:400, 8))
## first method: long format
m <- reshape2::melt(df, id = "group")
p <- ggplot(m, aes(x=group, y=value)) +
geom_boxplot()
pl <- plyr::dlply(m, "variable", function(.d) p %+% .d + ggtitle(unique(.d$variable)))
grid.arrange(grobs=pl)
## second method: keep wide format
one_plot <- function(col = "a") ggplot(df, aes_string(x="group", y=col)) + geom_boxplot() + ggtitle(col)
pl <- plyr::llply(colnames(df)[-1], one_plot)
grid.arrange(grobs=pl)
## third method: more explicit looping
pl <- vector("list", length = ncol(df)-1)
for(ii in seq_along(pl)){
.col <- colnames(df)[-1][ii]
.p <- ggplot(df, aes_string(x="group", y=.col)) + geom_boxplot() + ggtitle(.col)
pl[[ii]] <- .p
}
grid.arrange(grobs=pl)
有时,当在函数/ for循环中包装ggplot调用时,会遇到局部变量的问题(如果使用aes_string
,则不是这里的情况)。在这种情况下,可以define a local environment。
请注意,使用像aes(y=df[,i])
这样的构造可能会起作用,但会产生非常错误的结果。 Consider a facetted plot,data.frame将被拆分为每个面板的不同组,如果数值直接传递给aes()
而不是变量名,则此子集可能会失败,无法将正确的数据分组。