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

Posted

技术标签:

【中文标题】"for" 循环只添加最后的 ggplot 层【英文标题】:"for" loop only adds the final ggplot layer 【发布时间】:2014-12-01 20:51:55 【问题描述】:

总结:当我使用“for”循环将层添加到小提琴图(在 ggplot 中)时,唯一添加的层是由最终循环迭代创建的层。然而,在模仿循环产生的代码的显式代码中,所有的层都被添加了。

详细信息:我正在尝试创建具有重叠层的小提琴图,以显示估计分布在多个调查问题响应中重叠或不重叠的程度,按地点分层。我希望能够包含任意数量的地方,所以我在每个地方的数据框中有一列,并尝试使用“for”循环为每个地方生成一个 ggplot 层。但循环只添加循环最后一次迭代中的层。

这段代码说明了问题,以及一些失败的建议方法:

library(ggplot2) 

# Create a dataframe with 500 random normal values for responses to 3 survey questions from two cities
topic <- c("Poverty %","Mean Age","% Smokers")
place <- c("Chicago","Miami")
n <- 500
mean <- c(35,  40,58,  50, 25,20)
var  <- c( 7, 1.5, 3, .25, .5, 1)
df <- data.frame( topic=rep(topic,rep(n,length(topic)))
                 ,c(rnorm(n,mean[1],var[1]),rnorm(n,mean[3],var[3]),rnorm(n,mean[5],var[5]))
                 ,c(rnorm(n,mean[2],var[2]),rnorm(n,mean[4],var[4]),rnorm(n,mean[6],var[6]))
                )
names(df)[2:dim(df)[2]] <- place  # Name those last two columns with the corresponding place name.
head(df) 

# This "for" loop seems to only execute the final loop (i.e., where p=3)
g <- ggplot(df, aes(factor(topic), df[,2]))
for (p in 2:dim(df)[2]) 
  g <- g + geom_violin(aes(y = df[,p], colour = place[p-1]), alpha = 0.3)

g

# But mimicing what the for loop does in explicit code works fine, resulting in both "place"s being displayed in the graph.
g <- ggplot(df, aes(factor(topic), df[,2]))
g <-   g + geom_violin(aes(y = df[,2], colour = place[2-1]), alpha = 0.3)
g <-   g + geom_violin(aes(y = df[,3], colour = place[3-1]), alpha = 0.3)
g

## per http://***.com/questions/18444620/set-layers-in-ggplot2-via-loop , I tried 
g <- ggplot(df, aes(factor(topic), df[,2]))
for (p in 2:dim(df)[2]) 
  df1 <- df[,c(1,p)]
  g <- g + geom_violin(aes(y = df1[,2], colour = place[p-1]), alpha = 0.3)

g
# but got the same undesired result

# per http://***.com/questions/15987367/how-to-add-layers-in-ggplot-using-a-for-loop , I tried
g <- ggplot(df, aes(factor(topic), df[,2]))
for (p in names(df)[-1]) 
  cat(p,"\n")
  g <- g + geom_violin(aes_string(y = p, colour = p), alpha = 0.3)  # produced this error: Error in unit(tic_pos.c, "mm") : 'x' and 'units' must have length > 0
  # g <- g + geom_violin(aes_string(y = p            ), alpha = 0.3)  # produced this error: Error: stat_ydensity requires the following missing aesthetics: y

g
# but that failed to produce any graphic, per the errors noted in the "for" loop above

【问题讨论】:

你为什么不把数据帧melt长格式? 【参考方案1】:

虽然一般来说,重塑数据始终是首选,使用较新版本的 ggplot2 (>3.0.0),您可以使用 !! 将值注入 aes() 例如您可以这样做

g <- ggplot(df, aes(factor(topic), df[,2]))
for (p in 2:dim(df)[2]) 
  g <- g + geom_violin(aes(y = df[,!!p], colour = place[!!p-1]), alpha = 0.3)

g

为了得到想要的结果。 !! 将强制进行评估,而不是像默认设置那样保持惰性。

【讨论】:

【参考方案2】:

您可以使用 aes_() 而不是 aes(),这似乎可以停止惰性求值。在链接到此处 (Update a ggplot using a for loop (R)) 的已关闭问题上找到答案,但认为它应该在这里,因为其他问题已关闭。

【讨论】:

我遇到了类似的问题,这是一个非常简单的解决方案。【参考方案3】:

发生这种情况的原因是ggplot 的“懒惰评估”。这是以这种方式使用ggplot 时的常见问题(在循环中单独制作层,而不是像@hrbrmstr 的解决方案那样为您提供ggplot)。

ggplotaes(...) 的参数存储为表达式,并且仅在绘制绘图时评估它们。所以,在你的循环中,像

aes(y = df[,p], colour = place[p-1])

按原样存储,并在循环完成后渲染绘图时进行评估。此时,p=3,所以所有绘图都以 p=3 呈现。

因此,“正确”的做法是在 reshape2 包中使用 melt(...),以便将数据从宽格式转换为长格式,并让 ggplot 为您管理图层。我将“正确”放在引号中,因为在这种特殊情况下存在微妙之处。当使用融化的数据框计算小提琴的分布时,ggplot 使用总计(芝加哥和迈阿密)作为比例。如果您想要基于频率单独缩放的小提琴,您需要使用循环(可悲)。

解决惰性求值问题的方法是将任何对循环索引的引用放在data=... 定义中。这存储为表达式,实际数据存储在绘图定义中。所以你可以这样做:

g <- ggplot(df,aes(x=topic))
for (p in 2:length(df)) 
  gg.data <- data.frame(topic=df$topic,value=df[,p],city=names(df)[p])
  g <- g + geom_violin(data=gg.data,aes(y=value, color=city))

g

这给出了与你相同的结果。请注意,索引p 不会出现在aes(...) 中。


更新:关于scale="width" 的注释(在评论中提到)。这会导致所有小提琴具有相同的宽度(见下文),这与 OP 的原始代码中的缩放比例不同。 IMO 这不是可视化数据的好方法,因为它表明芝加哥组中的数据要多得多。

ggplot(gg) +geom_violin(aes(x=topic,y=value,color=variable),
                        alpha=0.3,position="identity",scale="width")

【讨论】:

谢谢。我很欣赏循环和ggplot如何发生这种奇怪的解释。现在我明白了。我想它可能是这样的——我试图找到一个命令,将绘图作为每个循环的最后一步(就像“g”),但我没有尝试过。你的循环代码正是我所需要的。 这是一个很好的解释。指出另一种解决方法 - 一个更新的答案(链接到此处的已关闭问题)提到您可以使用 aes_() 而不是 aes() 来覆盖惰性评估(***.com/questions/44317502/…)【参考方案4】:

你可以在没有循环的情况下做到这一点:

df.2 <- melt(df)
gg <- ggplot(df.2, aes(x=topic, y=value))
gg <- gg + geom_violin(position="identity", aes(color=variable), alpha=0.3)
gg

【讨论】:

这不会产生与 OP 的“成功”努力相同的情节,因为当您分别创建两个图层时,小提琴的缩放比例与您按 variable 分组时不同。另外,可能应该提到OP需要为此加载reshape2 非常优雅。使用这种方法,如果我使用 'scale="width"' aes 选项,jlhoward 提到的分组而不是单个缩放的微妙之处将无关紧要。

以上是关于"for" 循环只添加最后的 ggplot 层的主要内容,如果未能解决你的问题,请参考以下文章

新手提问 python for循环问题 print (y) #这里为啥只输出一行?

Swift 3:如何使用 for 循环来循环数组

如何在javascript的for循环中增加相同标签添加onclick事件

为啥事件监听器只在 for 循环中创建的最后一个元素上注册? [复制]

vue for循环的数据 根据条件判断是不是绑定事件

jQuery Ajax $.post 传给php里面有foreach循环,最后拿到的数据循环只执行一次。怎么解决?