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

for loop with ggplots produces graphs with identical values but different headings

我已经阅读了很多关于使用 ggplot 循环生成大量图表的文章,但找不到任何可以解释我的问题的文章...

我有一个数据框,我正在尝试遍历 92 列,为每一列创建一个新图表。我想将每个图保存为单独的 object。当我 运行 我的循环(下面的代码)并打印图表时,所有图表都是正确的。但是,当我用 assign() 更改 print() 命令时,图形不正确。标题正在按应有的方式更改,但是 graph-values 都是相同的(它们都是最终图表的所有值)。我发现这一点是因为当我使用 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的最终值的数据?或者类似的东西?有解决办法吗?

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

谢谢!

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

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 loop' 中进行所有 post 处理,以将您的数据框转换为数字并删除生成的因子 - 请注意 as.data.frame()如果您没有 'stringsAsFactors = FALSE' 并且 cbind() 倾向于默认因子,并且可以通过使用 cbind.data.frame() 而不是 cbind().[=14 来避免数字到字符的转换=]

我还重构了生成图的 'for loop'。您生成一个名为 'cols' (cols <- 2:4 ) 的整数列表,然后您重复该列表以从每一列数据生成您的图表。这是不必要的,我们可以在 for 语句条件中创建一个范围 - 'for (i in 2:ncol(df))' - 这只是重复从 2 到 4(数据框中的列数) - 从 2 开始需要避免第 1 列包含元数据。这是可取的,因为:

i) 当检查你的代码时,使用的条件是立即显而易见的,而不需要搜索你的代码的其余部分

ii) R 有许多 functions/parameters 与您的变量 '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() 函数的计算方式。请参阅提供的答案 by Konrad Rudolph。以下应该可以工作并保留原始代码的样式。正如康拉德在他的回答中建议的那样,"R" 可能更喜欢使用 lapply。请注意,我们已经给出了 for loop 局部作用域,现在我们在局部重新定义了 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)

你欠我一杯酒

有两种标准方法可以处理这个问题:

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)

有时,当在 function/for 循环中包装 ggplot 调用时,会面临局部变量的问题(这里不是这种情况,如果使用 aes_string)。在这种情况下,可以 define a local environment

请注意,使用 aes(y=df[,i]) 这样的结构可能看起来有效,但可能会产生非常错误的结果。 ,data.frame 将针对每个面板分成不同的组,如果将数值直接传递给 aes() 而不是变量名称,则此子集可能无法对正确的数据进行分组。