5

我已经阅读了很多关于使用 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 的最终值中的数据?或者类似的东西?有没有解决的办法?

我希望这样做,因为我想制作很多图表,然后我想混合和匹配图表的图表。

谢谢!

4

2 回答 2

4

我已经清理了您如何生成示例数据框。

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 循环”。您生成一个称为“cols”(cols <- 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() 函数。请参阅Konrad Rudolph在此处提供的答案。以下应该可以工作并保留原始代码的样式。正如康拉德在他的回答中所暗示的那样,使用 lapply 可能更喜欢“R”。请注意,我们已经给出了for 循环本地范围,我们现在在本地重新定义 i。以前,循环中生成的 i 的最后一个值用于生成通过 assign() 函数创建的每个对象。请注意使用 <<- 将 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)

你欠我一杯酒。

于 2016-04-14T17:00:17.417 回答
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使用的话)。在这种情况下,可以定义一个本地环境

请注意,使用类似的构造aes(y=df[,i])可能看起来有效,但会产生非常错误的结果。考虑一个多面图,data.frame 将被分成每个面板的不同组,如果直接传递数值aes()而不是变量名,那么这个子集可能会严重地无法对正确的数据进行分组。

于 2016-04-15T21:50:28.350 回答