45
votes

I am making a dodged barplot in ggplot2 and one grouping has a zero count that I want to display. I remembered seeing this on HERE a while back and figured the scale_x_discrete(drop=F) would work. It does not appear to work with dodged bars. How can I make the zero counts show?

For instance, (code below) in the plot below, type8~group4 has no examples. I would still like the plot to display the empty space for the zero count instead of eliminating the bar. How can I do this?

enter image description here

mtcars2 <- data.frame(type=factor(mtcars$cyl), 
    group=factor(mtcars$gear))

m2 <- ggplot(mtcars2, aes(x=type , fill=group))
p2 <- m2 + geom_bar(colour="black", position="dodge") +
        scale_x_discrete(drop=F)
p2
6

6 Answers

30
votes

Here's how you can do it without making summary tables first.
It did not work in my CRAN versioin (2.2.1) but in the latest development version of ggplot (2.2.1.900) I had no issues.

ggplot(mtcars, aes(factor(cyl), fill = factor(vs))) +
  geom_bar(position = position_dodge(preserve = "single"))

http://ggplot2.tidyverse.org/reference/position_dodge.html

17
votes

Updated geom_bar() needs stat = "identity"

For what it's worth: The table of counts, dat, above contains NA. Sometimes, it is useful to have an explicit 0 instead; for instance, if the next step is to put counts above the bars. The following code does just that, although it's probably no simpler than Joran's. It involves two steps: get a crosstabulation of counts using dcast, then melt the table using melt, followed by ggplot() as usual.

library(ggplot2)
library(reshape2)
mtcars2 = data.frame(type=factor(mtcars$cyl), group=factor(mtcars$gear))

dat = dcast(mtcars2, type ~ group, fun.aggregate = length)
dat.melt = melt(dat, id.vars = "type", measure.vars = c("3", "4", "5"))
dat.melt

ggplot(dat.melt, aes(x = type,y = value, fill = variable)) + 
  geom_bar(stat = "identity", colour = "black", position = position_dodge(width = .8), width = 0.7) +
  ylim(0, 14) +
  geom_text(aes(label = value), position = position_dodge(width = .8), vjust = -0.5)

enter image description here

13
votes

The only way I know of is to pre-compute the counts and add a dummy row:

dat <- rbind(ddply(mtcars2,.(type,group),summarise,count = length(group)),c(8,4,NA))

ggplot(dat,aes(x = type,y = count,fill = group)) + 
    geom_bar(colour = "black",position = "dodge",stat = "identity")

enter image description here

I thought that using stat_bin(drop = FALSE,geom = "bar",...) instead would work, but apparently it does not.

8
votes

I asked this same question, but I only wanted to use data.table, as it's a faster solution for much larger data sets. I included notes on the data so that those that are less experienced and want to understand why I did what I did can do so easily. Here is how I manipulated the mtcars data set:

library(data.table)
library(scales)
library(ggplot2)

mtcars <- data.table(mtcars)
mtcars$Cylinders <- as.factor(mtcars$cyl) # Creates new column with data from cyl called Cylinders as a factor. This allows ggplot2 to automatically use the name "Cylinders" and recognize that it's a factor
mtcars$Gears <- as.factor(mtcars$gear) # Just like above, but with gears to Gears
setkey(mtcars, Cylinders, Gears) # Set key for 2 different columns
mtcars <- mtcars[CJ(unique(Cylinders), unique(Gears)), .N, allow.cartesian = TRUE] # Uses CJ to create a completed list of all unique combinations of Cylinders and Gears. Then counts how many of each combination there are and reports it in a column called "N"

And here is the call that produced the graph

ggplot(mtcars, aes(x=Cylinders, y = N, fill = Gears)) + 
               geom_bar(position="dodge", stat="identity") + 
               ylab("Count") + theme(legend.position="top") + 
               scale_x_discrete(drop = FALSE)

And it produces this graph:

Cylinder Graph

Furthermore, if there is continuous data, like that in the diamonds data set (thanks to mnel):

library(data.table)
library(scales)
library(ggplot2)

diamonds <- data.table(diamonds) # I modified the diamonds data set in order to create gaps for illustrative purposes
setkey(diamonds, color, cut) 
diamonds[J("E",c("Fair","Good")), carat := 0]
diamonds[J("G",c("Premium","Good","Fair")), carat := 0]
diamonds[J("J",c("Very Good","Fair")), carat := 0]
diamonds <- diamonds[carat != 0]

Then using CJ would work as well.

data <- data.table(diamonds)[,list(mean_carat = mean(carat)), keyby = c('cut', 'color')] # This step defines our data set as the combinations of cut and color that exist and their means. However, the problem with this is that it doesn't have all combinations possible
data <- data[CJ(unique(cut),unique(color))] # This functions exactly the same way as it did in the discrete example. It creates a complete list of all possible unique combinations of cut and color
ggplot(data, aes(color, mean_carat, fill=cut)) +
             geom_bar(stat = "identity", position = "dodge") + 
             ylab("Mean Carat") + xlab("Color")

Giving us this graph:

Diamonds Fixed

4
votes

Use count and complete from dplyr to do this.

library(tidyverse)

mtcars %>% 
    mutate(
        type = as.factor(cyl),
        group = as.factor(gear)
    ) %>%
    count(type, group) %>% 
    complete(type, group, fill = list(n = 0)) %>%
    ggplot(aes(x = type, y = n, fill = group)) +
        geom_bar(colour = "black", position = "dodge", stat = "identity")
0
votes

You can exploit the feature of the table() function, which computes the number of occurrences of a factor for all its levels

# load plyr package to use ddply
library(plyr) 

# compute the counts using ddply, including zero occurrences for some factor levels
df <- ddply(mtcars2, .(group), summarise, 
 types = as.numeric(names(table(type))), 
 counts = as.numeric(table(type)))

# plot the results
ggplot(df, aes(x = types, y = counts, fill = group)) +
 geom_bar(stat='identity',colour="black", position="dodge")