4
votes

Using the diamonds data set in the ggplot2 package, I can generate the following chart.

library(ggplot2)
library(dplyr)

diamond.summary <- 
  diamonds %>%
  mutate(carat = ifelse(runif(nrow(.)) < 0.05, NA_real_, carat)) %>%
  group_by(carat_quintile = ntile(carat, 5)) %>%
  summarise(avg_price = mean(price))

diamond.summary %>%
  filter(!is.na(carat_quintile)) %>%
  ggplot(aes(carat_quintile, avg_price)) + 
  geom_bar(stat = "identity", 
           color = "black",
           width = 1) + 
  scale_x_continuous("Carat percentile",
                     breaks = 1:6 - 0.5,
                     labels = seq(0,100, by = 20)) + 
  scale_y_continuous(expand = c(0,0),
                     limits = c(0, 1.1* max(diamond.summary$avg_price)))

enter image description here

So far, so easy. However, I would also like to display the average price of the missing entries alongside the chart. Similar to the following: enter image description here

diamond.summary %>%
  mutate(Facet = is.na(carat_quintile),
         carat_quintile_noNA = ifelse(Facet, "Unknown", carat_quintile)) %>%
  ggplot(aes(x = carat_quintile_noNA, y = avg_price, fill = Facet)) + 
  geom_bar(stat = "identity") + 
  facet_grid(~Facet, scales = "free_x", space = "free_x") + 
  scale_x_discrete(breaks = (0:6) - 0.5)

However, when I try to perform the same trick using scale_x_continuous, I get the error Discrete value supplied to continuous scale. When I try to use scale_x_discrete(breaks = c(0:6 + 0.5)) for example, the axis ticks and labels disappear.

My question is, how can I get the same faceted chart above with the tick marks in the first panel placed as in the first chart in this post? Advice about chart design could be an acceptable solution, but I don't think all problems like this are solvable with a redesign.

2
I am not sure if I understand your question, but maybe you use something like that + scale_x_discrete("carat_quintile_noNA", labels = c("1" = "-0.5","2" = "0.5", "3" = "1.5","4" = "2.5","5" = "3.5", "Unknown" = "4.5")) - MLavoie
Nope that places the tick marks in the middle of the bars. I'd like them between the bars. - Hugh

2 Answers

7
votes

The trick is to convert your factor to a numeric, assigning a magic number to the unknown quantity. (ggplot2 will not plot bars with true NA values.) Then use scale_x_continuous

diamond.summary %>%
  mutate(Facet = is.na(carat_quintile),
         carat_quintile_noNA = ifelse(Facet, "Unknown", carat_quintile),
         ## 
         ## 99 is a magic number.  For our plot, it just has
         ## to be larger than 5. The value 6 would be a natural
         ## choice, but this means that the x tick marks would 
         ## overflow ino the 'unknown' facet.  You could choose
         ## choose 7 to avoid this, but any large number works.  
         ## I used 99 to make it clear that it's magic.
         numeric = ifelse(Facet, 99, carat_quintile)) %>%

  ggplot(aes(x = numeric, y = avg_price, fill = Facet)) + 
  geom_bar(stat = "identity", width = 1) + 
  facet_grid(~Facet, scales = "free_x", space = "free_x") + 
  scale_x_continuous(breaks = c(0:5 + 0.5, 99),
                     labels = c(paste0(c(0:5) * 20, "%"), "Unknown"))

enter image description here

1
votes

One solution is to approach a bit differently, and reposition the bars instead of the ticks, using position_nudge.

library(ggplot2)
library(dplyr)

diamond.summary <- 
  diamonds %>%
  mutate(carat = ifelse(runif(nrow(.)) < 0.05, NA_real_, carat)) %>%
  group_by(carat_quintile = ntile(carat, 5)) %>%
  summarise(avg_price = mean(price))

# nudge bars to the left
diamond.summary %>%
  filter(!is.na(carat_quintile)) %>%
  ggplot(aes(carat_quintile, avg_price)) + 
  geom_bar(stat = "identity", 
           color = "black",
           width = 1,
           position=position_nudge((x=-1))) + 
  scale_x_continuous("Carat percentile",
                     breaks = 1:6 - 0.5,
                     labels = seq(0,100, by = 20)) + 
  scale_y_continuous(expand = c(0,0),
                     limits = c(0, 1.1* max(diamond.summary$avg_price)))

nudge bars left

# nudge bars to the right
diamond.summary %>%
  filter(!is.na(carat_quintile)) %>%
  ggplot(aes(carat_quintile, avg_price)) + 
  geom_bar(stat = "identity", 
           color = "black",
           width = 1,
           position=position_nudge((x=1))) + 
  scale_x_continuous("Carat percentile",
                     breaks = 1:6 - 0.5,
                     labels = seq(0,100, by = 20)) + 
  scale_y_continuous(expand = c(0,0),
                     limits = c(0, 1.1* max(diamond.summary$avg_price)))

nudge bars right