Search code examples
rggplot2plotpositionbar-chart

How to get the same width for every bar in this geom_col() with position_dodge() in ggplot2?


I'm trying to reproduce the following plot:

enter image description here

So far, I have produced the following code in R:

library(tidyverse)

tribble(~attribute, ~level, ~importance_score,
        "Price", "$70 per month", -0.18,
        "Price", "$50 per month", 0,
        "Price", "$30 per month", 0.18,
        "Data included", "500MB", -0.25,
        "Data included", "1GB", -0.10,
        "Data included", "10GB", 0.11,
        "Data included", "Unlimited", 0.23,
        "International minutes included", "0 min", -0.01,
        "International minutes included", "90 min", -0.01,
        "International minutes included", "300 min", 0.02,
        "SMS included", "300 messages", -0.06,
        "SMS included", "Unlimited text", 0.06) %>% 
  mutate(id = 1:nrow(.)) %>% 
  arrange(desc(id)) %>% 
  mutate(attribute = as_factor(attribute),
         level = as_factor(level)) %>%
  ggplot(aes(attribute, importance_score, fill = level)) +
  geom_col(width = 0.5, position = position_dodge(0.75)) +
  coord_flip() +
  scale_y_continuous(breaks = seq(-0.4, 0.4, by = 0.1), limits = c(-0.35, 0.35)) +
  scale_fill_manual(values = c("$70 per month" = "#721817",
                               "$50 per month" = "#721817",
                               "$30 per month" = "#721817",
                               "500 MB" = "#fa9f42",
                               "1GB" = "#fa9f42",
                               "10GB" = "#fa9f42",
                               "Unlimited" = "#fa9f42",
                               "0 min" = "#2b4162",
                               "90 min" = "#2b4162",
                               "300 min" = "#2b4162",
                               "300 messages" = "#0b6e4f",
                               "Unlimited text" = "#0b6e4f")) +
  labs(y = "Relative value", x = "Levels by attribute") +
  theme(panel.grid.minor = element_blank(),
        panel.grid.major.y = element_blank(),
        panel.grid.major.x = element_line(color = "gray95", linetype = 1, size = 1),
        panel.grid.major = element_blank(),
        panel.background = element_blank(),
        legend.position = "none",
        text = element_text(size = 15))

which outputs the following plot:

enter image description here

My question is, how would you go about controlling the width argument in geom_col() without messing up the position = position_dodge() argument, which allows the plot to group by the "attribute" variable? What I want is to have the same width for every bar in this plot and also, to have the same distance between each grouping variable.

When I use position = position_dodge(0.75, preserve = "single"), I get:

enter image description here

In this case, it is not as clear to see in this plot, but: the length is not the same among the two green brackets I drew on top of the plot. How do I solve this?


Solution

  • One option to achieve your desired result would be to make use of facet_grid like so:

    1. Map factor(id) on y instead of atrribute
    2. Facet by attribute. Add scales="free_y" and space=free_y
    3. Style the strip texts and get rid of axis labels and ticks via themeoptions.
    4. To add some space between groups of bars you could adjust the expansion of the y scale

    Note: As far as I get it you could map attribute on fill which would simplify your scale_fill_manual as you only need to set four colors.

    library(tidyverse)
    
    dd <- dd %>% 
      mutate(id = 1:nrow(.)) %>% 
      arrange(desc(id)) %>% 
      mutate(attribute = forcats::fct_rev(as_factor(attribute)),
              level = as_factor(level))
    
    ggplot(dd, aes(importance_score, factor(id), fill = attribute)) +
      geom_col() +
      scale_x_continuous(breaks = seq(-0.4, 0.4, by = 0.1), limits = c(-0.35, 0.35), expand = c(0, 0)) +
      scale_y_discrete(expand = expansion(add = c(1, 1))) +
      scale_fill_manual(values = c("Price" = "#721817", 
                                   "Data included" = "#fa9f42",
                                   "International minutes included" = "#2b4162",
                                   "SMS included" = "#0b6e4f")) +
      labs(y = "Relative value", x = "Levels by attribute") +
      facet_grid(attribute ~ ., scales = "free_y", space = "free_y", switch = "y") +
      theme(panel.grid.minor = element_blank(),
            panel.grid.major.y = element_blank(),
            panel.grid.major.x = element_line(color = "gray95", linetype = 1, size = 1),
            panel.grid.major = element_blank(),
            panel.background = element_blank(),
            legend.position = "none",
            text = element_text(size = 15),
            strip.text.y.left = element_text(angle = 360, hjust = 1),
            strip.background.y = element_blank(),
            axis.text.y = element_blank(),
            axis.ticks.y = element_blank(),
            axis.ticks.length.y = unit(0, "pt"),
            panel.spacing.y = unit(0, "pt")
            )
    

    DATA

    dd <- tribble(~attribute, ~level, ~importance_score,
            "Price", "$70 per month", -0.18,
            "Price", "$50 per month", 0,
            "Price", "$30 per month", 0.18,
            "Data included", "500MB", -0.25,
            "Data included", "1GB", -0.10,
            "Data included", "10GB", 0.11,
            "Data included", "Unlimited", 0.23,
            "International minutes included", "0 min", -0.01,
            "International minutes included", "90 min", -0.01,
            "International minutes included", "300 min", 0.02,
            "SMS included", "300 messages", -0.06,
            "SMS included", "Unlimited text", 0.06)