缺失数据时geom_bar的一致宽度

60
有没有一种方法可以在下面时序示例中出现数据缺失的情况下设置geom_bar()的常量宽度?我尝试了在aes()中设置width,但没有成功。请比较代码示例下面的图表中五月'11和六月'11条形图的宽度。
colours <- c("#FF0000", "#33CC33", "#CCCCCC", "#FFA500", "#000000" )
iris$Month <- rep(seq(from=as.Date("2011-01-01"), to=as.Date("2011-10-01"), by="month"), 15)

colours <- c("#FF0000", "#33CC33", "#CCCCCC", "#FFA500", "#000000" )
iris$Month <- rep(seq(from=as.Date("2011-01-01"), to=as.Date("2011-10-01"), by="month"), 15)
d<-aggregate(iris$Sepal.Length, by=list(iris$Month, iris$Species), sum)
d$quota<-seq(from=2000, to=60000, by=2000)
colnames(d) <- c("Month", "Species", "Sepal.Width", "Quota")
d$Sepal.Width<-d$Sepal.Width * 1000
g1 <- ggplot(data=d, aes(x=Month, y=Quota, color="Quota")) + geom_line(size=1)
g1 + geom_bar(data=d[c(-1:-5),], aes(x=Month, y=Sepal.Width, width=10, group=Species, fill=Species), stat="identity", position="dodge") + scale_fill_manual(values=colours)

plot


1
这里有一个类似的问题链接,但它只涉及到无法处理宽度参数的statsposition='dodge'似乎也有同样的问题。对于更了解ggplot的人来说,可能会有所帮助,但这听起来像是一个潜在的错误。 - Justin
我也遇到了这个问题。很好知道。现在,我将使用下面发布的解决方法,通过将值填入NA来实现。 - tcash21
在回复 https://github.com/tidyverse/ggplot2/issues/1776 的问题时,Hadley 说道:"这就是闪避的工作原理。您可能想尝试使用分面显示。"顺便提一下,这个问题已经多次在 SO 上讨论过了:这里这里 等等。 - Uwe
9
由于在搜索“geom_bar +width +fixed”时,谷歌往往会将我们带到这里,我想指出这个相当鲜为人知的技巧:使用“geom_bar(position = position_dodge(preserve =“single”))”。 - PatrickT
1
ggplot中有一个新的躲避算法。当前版本(2.2.1 Nov-2017)尚未包含它。 - jnas
3个回答

47

ggplot2 3.0.0中引入了新的position_dodge()选项和position_dodge2(),它们可以帮助你。

你可以在position_dodge()中使用preserve = "single",以一个元素为基础确定所有条形图的宽度,这样所有条形图的宽度都将相同。

ggplot(data = d, aes(x = Month, y = Quota, color = "Quota")) + 
     geom_line(size = 1) + 
     geom_col(data = d[c(-1:-5),], aes(y = Sepal.Width, fill = Species), 
              position = position_dodge(preserve = "single") ) + 
     scale_fill_manual(values = colours)

使用 position_dodge2() 可以改变居中的方式,将每组柱状图在每个 x 轴位置上居中。它内置了一些 padding,因此使用 padding = 0 来移除。

ggplot(data = d, aes(x = Month, y = Quota, color = "Quota")) + 
     geom_line(size = 1) + 
     geom_col(data = d[c(-1:-5),], aes(y = Sepal.Width, fill = Species), 
              position = position_dodge2(preserve = "single", padding = 0) ) + 
     scale_fill_manual(values = colours)


2
我尝试了这个选项,但是无法使其与分面图一起工作 - 条形图只是无法对齐。 - mikeck

34

最简单的方法是补充数据集,使得每个组合都存在,即使它的值为NA。以一个更简单的例子说明(因为你的数据集有很多不必要的特征):

dat <- data.frame(a=rep(LETTERS[1:3],3),
                  b=rep(letters[1:3],each=3),
                  v=1:9)[-2,]

ggplot(dat, aes(x=a, y=v, colour=b)) +
  geom_bar(aes(fill=b), stat="identity", position="dodge")

enter image description here

这显示了您试图避免的行为:在组“B”中,没有组“a”,因此条形图更宽。 通过将dat补充为包含所有ab组合的数据框来解决这个问题:

dat.all <- rbind(dat, cbind(expand.grid(a=levels(dat$a), b=levels(dat$b)), v=NA))

ggplot(dat.all, aes(x=a, y=v, colour=b)) +
  geom_bar(aes(fill=b), stat="identity", position="dodge")  

在此输入图片描述


4
当使用箱线图时,我遇到了同样的问题,但是用NA填充的方法并不能解决不等宽箱子的问题。这些NA值会被丢弃。用0填充似乎可以解决问题,但这会导致图表非常难看,并包含不恰当的数据。有什么建议吗? - Etienne Low-Décarie
@EtienneLow-Décarie 暂时没有。提出一个新问题(引用这个问题并说明它不适用于箱形图),也许其他人可以帮忙。 - Brian Diggs
1
未来用户注意:在应用此解决方案时,一定要非常小心数据类型(因子和数字),否则解决方案可能会出现“损坏”的情况(请参见上面@EtienneLow-Décarie的点赞评论)。有关详细信息,请查看此问题 - tonytonov
28
老实说,我不认为更改数据集以使图表看起来漂亮是个好主意。ggplot 应该能够更好地处理缺失观测值。 - user3507584
2
我发现这个解决方案非常好,直到我意识到如果绿色是NA值而不是红色会怎样?在这种情况下,应用NA值后,我的列之间只有一个空白,我的条形图不再“堆叠”。有没有什么解决办法?谢谢! - maycca
记住@JustynaS和@maycca的有效评论,唯一不会干扰数据集或需要大量额外工作(尽管仍然很难看)的解决方案是在“月份”变量上使用facet_wrapfacet_grid - 它将创建三个物种,使得网格/分面中所有条形宽度都相等。 - daRknight

22

我曾经遇到过同样的问题,但是我正在寻找适用于管道符(%>%)的解决方案。使用来自tidyversetidyr::spreadtidyr::gather就可以解决问题了。我使用与@Brian Diggs相同的数据,但是将变量名称大写,以免在转换为宽格式时出现重复的变量名称:

library(tidyverse)

dat <- data.frame(A = rep(LETTERS[1:3], 3),
                  B = rep(letters[1:3], each = 3),
                  V = 1:9)[-2, ]
dat %>% 
  spread(key = B, value = V, fill = NA) %>% # turn data to wide, using fill = NA to generate missing values
  gather(key = B, value = V, -A) %>% # go back to long, with the missings
  ggplot(aes(x = A, y = V, fill = B)) +
  geom_col(position = position_dodge())

编辑:

实际上,结合管道运算符,使用 tidyr::complete 可以更简单地解决这个问题,并且只需要一行代码即可得到相同的结果:

dat %>% 
  complete(A, B) %>% 
  ggplot(aes(x = A, y = V, fill = B)) +
  geom_col(position = position_dodge())

2
非常好的回答,我不知道 tidyr::complete 的存在。 - mikeck
使用tidyr::complete的绝妙想法! - SamuelR

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接