带有 ggplots 的 for 循环生成具有相同值但标题不同的图形

Posted

技术标签:

【中文标题】带有 ggplots 的 for 循环生成具有相同值但标题不同的图形【英文标题】:for loop with ggplots produces graphs with identical values but different headings 【发布时间】:2016-08-06 05:49:01 【问题描述】:

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

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

谢谢!

【问题讨论】:

【参考方案1】:

有两种标准的方法来处理这个问题:

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() 而不是变量名,则此子集可能会严重无法对正确的数据进行分组。

【讨论】:

【参考方案2】:

我已经清理了您生成示例数据框的方式。

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

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

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)

你欠我一杯。

【讨论】:

非常感谢您详细回答 Graeme。我必须考虑一下当地人在做什么并重新定义“i”。 aes() 应该引用变量名,而不是数字向量,例如 df[,i]。这样做可能会导致意想不到的结果,例如在没有注意到的情况下绘制错误的数据。更不用说使用 &lt;&lt;-assign 几乎总是一个明显的迹象,表明在 R 中解决问题的方法是错误的。

以上是关于带有 ggplots 的 for 循环生成具有相同值但标题不同的图形的主要内容,如果未能解决你的问题,请参考以下文章

R ggplot2绘制具有不等长度矢量的循环

ggplot2用于带有两个相同刻度标签的条形图

使用带有geom_point的for循环将点添加到现有ggplot对象中

使用 for 循环保存多个 ggplots

"for" 循环只添加最后的 ggplot 层

使用循环在 ggplot 中创建具有不同 Y 轴值的多个图