Search code examples
rggplot2fillgeom-bar

How to double fill a geom_bar with two characteristics


I'm working with house price indices and I have a question on how to add another geom to a ggplot. This is an example data that I made for this question. I have housing data from a census and from online postings. rooms a variable for a housing characteristic (many or few rooms), and value is the percentage of homes for each source that has that characteristic. Then, houses and apts show the percentage of houses and apts that the city has for that data source. So for example, city 1 has 40% houses and 60% apartments in the census data and 45% houses and 55% apartments in the zillow data. I made a geom_bar faceting by rooms and filling by source so I have two plots, one for rooms=1 and another for rooms=2, each one of them with two bars for each city (one for each source). Now, I want to fill those same bars with the percentage of houses and apartments for each city and source.

I'd be very grateful if someone can help me with this.

The code I'm currently using for the plot is the following:


df <- tibble::tribble(
  ~city, ~source, ~rooms, ~value, ~houses, ~apts,
  "city1", "census", "1", 0.8,  0.4,  0.6,
  "city1", "census", "2", 0.2,  0.4,  0.6,
  "city1", "zillow", "1", 0.7,  0.45, 0.55,
  "city1", "zillow", "2", 0.3,  0.45, 0.55,
  "city2", "census", "1", 0.74, 0.66, 0.34,
  "city2", "census", "2", 0.26, 0.66, 0.34,
  "city2", "zillow", "1", 0.37, 0.66, 0.34,
  "city2", "zillow", "2", 0.63, 0.66, 0.34,
  "city3", "census", "1", 0.81, 0.71, 0.29,
  "city3", "census", "2", 0.19, 0.71, 0.29,
  "city3", "zillow", "1", 0.49, 0.71, 0.29,
  "city3", "zillow", "2", 0.51, 0.71, 0.29)

ggplot(df, aes(fill=source, y=city, x=value)) + 
  geom_bar(position = "dodge", stat = "identity") +
  facet_wrap(facets="rooms", nrow=2) 

Solution

  • If I understand correctly, you're looking to kind of separate out and show in one plot the differentiation of:

    • City
    • Rooms
    • Value (the length of the bar here)
    • % houses or % apts (one is the inverse of the other, so basically just showing the same thing)

    If I have that correct, perhaps the simplest way is to just facet across two variables instead of one using facet_grid():

    ggplot(df, aes(fill=houses, y=city, x=value)) +
      geom_col(position='dodge') +
      facet_grid(source ~ rooms)
    

    enter image description here

    Another way may be to use another aesthetic to separate out your source column (instead of making it a facet), and still keeping the fill aesthetic to be mapped to % of houses:

    ggplot(df, aes(fill=houses, y=city, x=value)) +
      geom_col(
        position=position_dodge(width=0.8), width=0.7, size=1, color='black',
        aes(linetype=source)
      ) +
      facet_wrap(~rooms, nrow=2)
    

    enter image description here

    This way kind of works, but doesn't look great as is. If you do this, I'd modify some theme elements and add some alpha value to the bars to make things a bit easier to view. The alpha doesn't translate to the legend, so you actually need to modify that too and... well... here's the final product and code.

    ggplot(df, aes(fill=houses, y=city, x=value)) +
      geom_col(
        position=position_dodge(width=0.8), width=0.7, size=1,
        color='black', alpha=0.4,
        aes(linetype=source, alpha=source)
      ) +
      
      scale_fill_distiller(palette='RdYlBu') +
      guides(
        linetype=guide_legend(override.aes = list(fill=NA))
      ) +
      
      facet_wrap(~rooms, nrow=2) +
      
      theme_classic() +
      theme(
        panel.spacing = unit(0.08, 'npc')
      )
    

    enter image description here

    You probably get the idea. Note a few things though:

    • I'm using geom_col() here, which is recommended instead of geom_bar(stat='identity'). Note the documentation which suggests this usage.

    • It's suggested to use position=position_dodge() in place of position="dodge". This allows you to adjust how far apart you want the dodged columns to be and especially important if you also want to adjust the width of the bars. I wanted to do this in order to make it easier to see the lines of the bars.

    There's a few other tricks hidden in there, but hopefully this helps you out and you can select from something like this to solve your question.