Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Split y axis in ggplot

Tags:

r

ggplot2

I have data in a structure similar to the following structure:

data.frame(x = c("A", "B", "C", "D", "E"), 
           a = abs(rnorm(5)), 
           b = abs(rnorm(5)))

I want to display this in the following manner (forgive the poor paint job): Example

To do this, I have written the below code:

set.seed(20)
data.frame(x = c("A", "B", "C", "D", "E"), 
           a = abs(rnorm(5)), 
           b = abs(rnorm(5))) %>%
  mutate(b = -b) %>%
  gather("source", "amount", a, b) %>%
  ggplot(aes(x = x,
             y = amount,
             fill = source)) +
  geom_col() +
  scale_y_continuous(labels = abs)

Which gives me the following:

Result

How would I go about adding the gap along y = 0 and filling it with the x axis labels?

like image 305
Daniel V Avatar asked Jan 28 '23 04:01

Daniel V


1 Answers

I can think of a couple of ways to approach this.

You can try to get tricky with facets and free scales, labeling only the top axis using the approach I saw here.

However, you'll see this leaves some awkward space below the plot since it that didn't seem easy to remove (but see here).

library(ggplot2)
library(tidyr)
library(dplyr)

set.seed(20)
dat = data.frame(x = c("A", "B", "C", "D", "E"), 
           a = abs(rnorm(5)), 
           b = abs(rnorm(5))) %>%
    mutate(b = -b) %>%
    gather("source", "amount", a, b) %>%
    mutate(x1 = if_else(source == "a",
                          as.character(x), 
                          paste0(as.character(x), 'no_display')))

# function to suppress labels
delete_no_display = function(v) {
    if_else(stringr::str_detect(v, 'no_display'), '', v)
}

ggplot(dat, aes(x = x1,
               y = amount,
               fill = source)) +
    geom_col() +
    facet_wrap(~source, ncol = 1, scales = "free") +
    scale_x_discrete(name = NULL, label = delete_no_display) + 
    scale_y_continuous(name = NULL,
                       labels = abs,
                       breaks = c(-3, -2, -1, 0, 1, 2, 3), 
                       expand = c(0, 0)) +
    theme(strip.background = element_blank(),
        strip.text.x = element_blank(),
        axis.ticks.x = element_blank())

enter image description here

Another option is to build the plots separately per group and then combine them. You can do this via functions from package cowplot. This package has five helpful vignettes available if you decide to get into the nitty gritty.

This package does have strong opinions on theme, although given the plot you are attempting to make this theme may be what you want so I left it as is.

First I make the two plots. I added a fill legend to the first plot, but that can be removed as needed.

g1 = ggplot(subset(dat, source == "a"), 
            aes(x = x, y = amount, fill = source)) +
    geom_col() +
    scale_x_discrete(name = NULL) + 
    scale_y_continuous(name = NULL, 
                       labels = abs,
                       limits = c(0, 3),
                       expand = expand_scale(mult = c(0, .1) ) ) +
    scale_fill_manual(limits = c("a", "b"), 
                      values = c("#F8766D", "#00BFC4")) +
    theme(plot.margin = margin(0, 0, 0, 0),
          axis.ticks.x = element_blank())

g2 = ggplot(subset(dat, source == "b"), 
            aes(x = x, y = amount, fill = source)) +
    geom_col() +
    scale_x_discrete(name = NULL) + 
    scale_y_continuous(name = NULL, 
                       labels = abs,
                       limits = c(-3, 0),
                       expand = expand_scale(mult = c(.1, 0) ) ) +
    scale_fill_manual(limits = c("a", "b"), 
                      values = c("#F8766D", "#00BFC4"),
                      guide = "none") +
    theme(axis.ticks.x = element_blank(),
          axis.text.x = element_blank(),
          plot.margin = margin(t = 2, unit = "mm"))

Then extract the legend and combine the plots (with no legends) using plot_grid().

library(cowplot)

legend_a = get_legend(g1)

combined = plot_grid(g1 + theme(legend.position = "none"),
          g2,
          ncol = 1, align = "v")

This combined plot then looks like:

enter image description here

You can add the legend back on (see the shared legends vignette) and/or put on an overall y axis label as shown here if you want (although the legend spacing gets funky).

plot_grid(combined, legend_a, 
          rel_widths = c(2, .2),
          scale = c(.93, 1)) +
    draw_label("amount", x = 0, y = .5, angle = 90, vjust = 1)

enter image description here

The downside to the cowplot approach is that the bottom plot ends up slightly larger than the upper one. If I use align = "hv" I'm back to having a little extra space along the bottom. It may be best to remove all axes and then manually insert the labels. I feel like there is info to be gleaned here, but I didn't get that far.

like image 162
aosmith Avatar answered Feb 09 '23 01:02

aosmith