Preserve constant bar widths for uneven faceted plots?

My goal is to have the same bar widths on every facets. I have tried multiple ways but couldn't get it done. The bar widths in the 1st and 2nd facets are bigger than those in the 3rd facet in the example below. Anyone ran into this problem before? Suggestions are welcome!

library(ggplot2)

dat <- read.table(
  text = "ID Value Day Order
          AA 0.443 Day1 1
          BB 0.110 Day1 2
          DD 0.129 Day2 3
          BB 0.076 Day2 4
          DD 0.500 Day3 5
          CC 0.143 Day3 6
          AA 0.034 Day3 7", 
  header = TRUE)
dat
#>   ID Value  Day Order
#> 1 AA 0.443 Day1     1
#> 2 BB 0.110 Day1     2
#> 3 DD 0.129 Day2     3
#> 4 BB 0.076 Day2     4
#> 5 DD 0.500 Day3     5
#> 6 CC 0.143 Day3     6
#> 7 AA 0.034 Day3     7

ggplot(dat, 
       aes(Order, Value)) +
  geom_col(aes(fill = ID),
           position = position_dodge2(width = 0.5, preserve = "single")) +
  facet_grid(. ~ Day) +
  scale_x_continuous(
    breaks = dat$Order,
    labels = dat$ID)



ggplot(dat, 
       aes(Order, Value)) +
  geom_col(aes(fill = ID),
           position = position_dodge(width = 0.8, preserve = "single")) +
  facet_grid(. ~ Day,
             scales = 'free') +
  scale_x_continuous(
    breaks = dat$Order,
    labels = dat$ID)


ggplot(dat, 
       aes(Order, Value)) +
  geom_col(aes(fill = ID),
           width = 0.8,
           position = position_dodge2(width = 0.8, preserve = "single")) +
  facet_grid(. ~ Day,
             scales = 'free') +
  scale_x_continuous(
    breaks = dat$Order,
    labels = dat$ID)

You just have to add space = "free" to facet_grid()

library(ggplot2)

dat <- read.table(
    text = "ID Value Day Order
      AA 0.443 Day1 1
      BB 0.110 Day1 2
      DD 0.129 Day2 3
      BB 0.076 Day2 4
      DD 0.500 Day3 5
      CC 0.143 Day3 6
      AA 0.034 Day3 7", 
    header = TRUE)

ggplot(dat, 
       aes(Order, Value)) +
    geom_col(aes(fill = ID),
             width = 0.8,
             position = position_dodge2(width = 0.8, preserve = "single")) +
    facet_grid(. ~ Day,
               , scales="free",
               space = "free") +
    scale_x_continuous(
        breaks = dat$Order,
        labels = dat$ID)

4 Likes

That's nice. Thank you @andresrcs!

Can we take it a step further for cases having more than one row of facets? Ideally, I would love to get all facets of equal size. If facets only have 1 or 2 bars, insert blank (Value = 0) bars to keep their size the same. I know it may be too much to ask for :slight_smile:


library(dplyr)
library(ggplot2)

dat <- read.table(
  text = "Exp ID Value Day Order
          Ex1 AA 0.443 Day1 1
          Ex1 BB 0.110 Day1 2
          Ex1 DD 0.129 Day2 3
          Ex1 BB 0.076 Day2 4
          Ex1 DD 0.500 Day3 5
          Ex1 CC 0.143 Day3 6
          Ex1 AA 0.034 Day3 7
          Ex2 BB 0.543 Day1 8
          Ex2 AA 0.310 Day1 9
          Ex2 CC 0.150 Day1 10
          Ex2 DD 0.329 Day2 11
          Ex2 BB 0.196 Day2 12
          Ex2 AA 0.056 Day2 13
          Ex2 BB 0.300 Day3 14
          Ex2 CC 0.243 Day3 15
          Ex2 DD 0.134 Day3 16", 
  header = TRUE)

## Try facet for 2 variables
ggplot(dat, 
       aes(Order, Value)) +
  geom_col(aes(fill = ID),
           width = 0.8,
           position = position_dodge2(width = 0.8, preserve = "single")) +
  facet_grid(Exp ~ Day,
             scales= "free",
             space = "free") +
  scale_x_continuous(
    breaks = dat$Order,
    labels = dat$ID)



## Try cowplot
gg1 <- ggplot(dat %>% filter(Exp == "Ex1"), 
              aes(Order, Value)) +
  geom_col(aes(fill = ID),
           width = 0.8,
           position = position_dodge2(width = 0.8, preserve = "single")) +
  facet_grid(. ~ Day,
             scales= "free",
             space = "free") +
  scale_x_continuous(
    breaks = dat$Order,
    labels = dat$ID)

gg2 <- ggplot(dat %>% filter(Exp == "Ex2"), 
              aes(Order, Value)) +
  geom_col(aes(fill = ID),
           width = 0.8,
           position = position_dodge2(width = 0.8, preserve = "single")) +
  facet_grid(. ~ Day,
             scales= "free",
             space = "free") +
  scale_x_continuous(
    breaks = dat$Order,
    labels = dat$ID)

library(cowplot)
plot_grid(gg1, gg2,
          nrow = 2)

There is an easy way if you are willing to sacrifice the x-axis labels

dat <- dat %>% 
    group_by(Exp, Day) %>% 
    mutate(Order = row_number())

ggplot(dat, 
       aes(Order, Value)) +
    geom_col(aes(fill = ID),
             width = 0.8,
             position = position_dodge2(width = 0.8)) +
    facet_grid(Exp ~ Day)

1 Like

Thank you! Unfortunately the labels are kind of important here

For the cowplot approach you could add empty rows to the groups that don't have three rows to force equal sized bars and facets.

For example, here I make a new variable order that is 1 to the max size of each Exp/Day group. Then I can use complete() from tidyr to add a row for any group with less than three observations. I hard-coded the max group size to 3 in complete() by using order = 1:3 (i.e., you need to know what the max group size is).

I set Value to 0 for any new rows and put in a blank for ID (I had to turn ID into a character vector for this). I finish by making a new Order variable with a unique value for every row based on the order of the dataset.

library(tidyr)

dat_complete = dat %>%
    group_by(Exp, Day) %>%
    mutate(order = 1:n(), ID = as.character(ID) ) %>%
    complete(order = 1:3, fill = list(Value = 0, ID = "")) %>%
    ungroup() %>%
    mutate(Order = 1:n())
dat_complete

# A tibble: 18 x 6
   Exp   Day   order ID    Value Order
   <fct> <fct> <int> <chr> <dbl> <int>
 1 Ex1   Day1      1 AA    0.443     1
 2 Ex1   Day1      2 BB    0.11      2
 3 Ex1   Day1      3 ""    0         3
 4 Ex1   Day2      1 DD    0.129     4
 5 Ex1   Day2      2 BB    0.076     5
 6 Ex1   Day2      3 ""    0         6
 7 Ex1   Day3      1 DD    0.5       7
 8 Ex1   Day3      2 CC    0.143     8
 9 Ex1   Day3      3 AA    0.034     9
10 Ex2   Day1      1 BB    0.543    10
11 Ex2   Day1      2 AA    0.31     11
12 Ex2   Day1      3 CC    0.15     12
13 Ex2   Day2      1 DD    0.329    13
14 Ex2   Day2      2 BB    0.196    14
15 Ex2   Day2      3 AA    0.056    15
16 Ex2   Day3      1 BB    0.3      16
17 Ex2   Day3      2 CC    0.243    17
18 Ex2   Day3      3 DD    0.134    18

You'd have more work to do to control your colors to get rid of the empty ID and maybe do something about the extra tick marks, but your panels would all be the same width. :woman_shrugging:

Note even if making a plot for one Exp at a time for use in cowplot you can use facet_grid() to add the facet label.

ggplot(dat_complete %>% filter(Exp == "Ex1"), 
              aes(Order, Value)) +
    geom_col(aes(fill = ID),
             width = 0.8,
             position = position_dodge2(width = 0.8, preserve = "single")) +
    facet_grid(Exp ~ Day,
               scales= "free") +
    scale_x_continuous(
        breaks = dat_complete$Order,
        labels = dat_complete$ID)

3 Likes

How about this little trick?

dat <- dat %>% 
    group_by(Exp, Day) %>% 
    mutate(Order = row_number())

dat %>% ggplot(aes(Order, Value)) +
    geom_col(aes(fill = ID),
             width = 0.8,
             position = position_dodge2(width = 0.8)) +
    geom_text(aes(label = ID), y = 0, vjust = 1.2) +
    facet_grid(Exp ~ Day) +
    scale_y_continuous(expand = c(0.09,0)) +
    theme_light() +
    theme(axis.text.x=element_blank())

3 Likes

Nice! I thought about this too but forgot about complete. Thank you!

That's clever. I like both your and @aosmith's solutions. Thanks again!

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.

If you have a query related to it or one of the replies, start a new topic and refer back with a link.