Faceted bar plot with observation name adjacent to bar for each group and space=free

Viewed 50

Using ggplot I'm trying to make something like a faceted barplot where

  • bars representing the same value are the same size (sort of like space = "free")
  • names are adjacent to bars (sort of like scales = "free_y")
  • graphs are generated with code - no trial and error adjustment of size or scale or stuff

I'm open to a multi-plot solution with something like cowplot::plot_grid

Here's a sample dataset.

df <- data.frame(name = c('A very long name','A short name','A really truly long big name that is very long','One shorter name'),
                 value =c(100,50,10,10),
                 group = c(2022,2022,2022,2021))

What I'm aiming for would look something like this:

enter image description here

Two things I've tried and rejected:

ggplot(df,
       aes(x = name, y = value)) +
  geom_col(aes(fill = -value)) +
  coord_flip() +
  facet_grid(~group, space = "free", scales = "free_x") +
  theme(legend.position = "none")

enter image description here

ggplot(df,
       aes(x = name, y = value)) +
  geom_col(aes(fill = -value)) +
  coord_flip() +
  facet_wrap(~group, scales = "free_y") +
  theme(legend.position = "none")

enter image description here

3 Answers

Here is a solution using vanilla ggplot2, taken from a related ggplot2 issue. You can use the fact that breaks and limits arguments accept functions. Below, we use that to pad limits with dummy names, and then use the breaks function to censor the dummy names. It requires you to know the maximum number of categories on a facet beforehand though.

library(ggplot2)

df <- data.frame(name = c('A very long name','A short name','A really truly long big name that is very long','One shorter name'),
                 value =c(100,50,10,10),
                 group = c(2022,2022,2022,2021))

max_categories <- 3

ggplot(df,
       aes(y = name, x = value)) +
  geom_col(aes(fill = -value)) +
  scale_y_discrete(
    limits = function(x) {
      y <- paste0("dummy", seq_len(max_categories))
      c(y[seq_len(max_categories - length(x))], x)
    },
    breaks = function(x) {
      x[!startsWith(x, "dummy")]
    }
  ) +
  facet_wrap(~group, scales = "free_y") +
  theme(legend.position = "none")

Created on 2021-05-09 by the reprex package (v0.3.0)

A few sidenotes; I switched the x and y aes to make the coord_flip() unnecessary. Also, you can set scales = "free"+ space = "free_x", if you want the panels to adjust their width in response to the data.

With patchwork you could try:

library(ggplot2)
library(dplyr)
library(patchwork)

df <- data.frame(name = c('A very long name','A short name','A really truly long big name that is very long','One shorter name'),
                 value =c(100,50,10,10),
                 group = c(2022,2022,2022,2021))

# plots could be simplified with a function and appearance edited to suit your needs

p2022 <- 
  ggplot(data = filter(df, group == 2022), aes(x = name, y = value)) +
  geom_col(aes(fill = -value)) +
  coord_flip() +
  labs(x = NULL) +
  facet_grid(~group) +
  theme(legend.position = "none")

p2021 <-
  ggplot(data = filter(df, group == 2021), aes(x = name, y = value)) +
  geom_col(aes(fill = -value)) +
  coord_flip() +
  scale_y_continuous(limits = c(0, max(df$value)))+
  labs(x = NULL) +
  facet_grid(~group) +
  theme(legend.position = "none")

# define the plotting layout

design <- "
12
#2
#2"

# plot

p2021 + p2022  + plot_layout(design = design)

Created on 2021-05-09 by the reprex package (v2.0.0)

Another approach could be:

ggplot(df,
       aes(x = name, y = value)) +
  geom_col(aes(fill = -value)) +
  coord_flip() +
  facet_wrap(~group)+
  theme(legend.position = "none")

enter image description here

This could be an alternative approach:

p  <- ggplot(df,
       aes(x = name, y = value)) +
  geom_col(aes(fill = -value)) +
  coord_flip() +
  facet_grid(group~., space = "free", scales = "free") +
  theme(legend.position = "none")

enter image description here

Related