Dave Gruenewald
Dave Gruenewald

Reputation: 5689

Custom axis breaks in ggplot2 facets based on other columns

I have data to plot, where the x-axis is found in one column, and the x-axis major breaks are found in other columns.

For my sample data, I will modify the iris dataset from ggplot2. Note: the low and high are arbitrarily calculated here - I've only chosen min & max for ease of reproducibility.

library(dplyr)
library(ggplot2)


df <- iris %>% 
  group_by(Species) %>% 
  mutate(low = min(Sepal.Length),
         high = max(Sepal.Length)) %>% 
  ungroup()
> df
# A tibble: 150 x 7
   Sepal.Length Sepal.Width Petal.Length Petal.Width Species   low  high
          <dbl>       <dbl>        <dbl>       <dbl> <fct>   <dbl> <dbl>
 1          5.1         3.5          1.4         0.2 setosa    4.3   5.8
 2          4.9         3            1.4         0.2 setosa    4.3   5.8
 3          4.7         3.2          1.3         0.2 setosa    4.3   5.8
 4          4.6         3.1          1.5         0.2 setosa    4.3   5.8
 5          5           3.6          1.4         0.2 setosa    4.3   5.8
 6          5.4         3.9          1.7         0.4 setosa    4.3   5.8
 7          4.6         3.4          1.4         0.3 setosa    4.3   5.8
 8          5           3.4          1.5         0.2 setosa    4.3   5.8
 9          4.4         2.9          1.4         0.2 setosa    4.3   5.8
10          4.9         3.1          1.5         0.1 setosa    4.3   5.8
# ... with 140 more rows

I am hoping to plot x = Sepal.Length faceting for Species, but with the only two major breaks being df$min and df$max.

I'm having trouble getting the breaks in the correct facet.

df %>% 
  ggplot(aes(x = Sepal.Length,
             y = Petal.Length)) + 
  geom_point() + 
  facet_wrap(. ~ Species) + 
  scale_x_continuous(breaks = c(df$low, df$high))

enter image description here

As you can see, the values from df$low and df$high applied to all facets. I was hoping that facet setosa would only have major breaks at 4.3 and 5.8 only, versicolor at 4.9 and 7.0 only, and virginica at 4.9 and 7.9 only.

Is there a way to pass the facet variable to breaks in scale_x_continuous? Or should I abandon this approach and create three separate ggplots and merge them together with gridExtra?

Any help would be appreciated!

Upvotes: 3

Views: 1240

Answers (2)

teunbrand
teunbrand

Reputation: 38053

The break arguments to scales don't support tidy evaluation in the context of the data.frame passed to the main ggplot2 call. If your breaks can be calculated from the facet limits (typically the data limits + expansion, when scales = "free"), you can pass a function to the breaks argument that calculates the breaks from the limits.

If you are really set on having seperate scales per facet, there are a few packages on github that support providing custom scales to facets of a plot. Both require you to specify a scale for each facet manually. Disclaimer: I've contributed to the first package and am author of the second package.

library(dplyr)
#> 
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:stats':
#> 
#>     filter, lag
#> The following objects are masked from 'package:base':
#> 
#>     intersect, setdiff, setequal, union
library(ggplot2)


df <- iris %>% 
  group_by(Species) %>% 
  mutate(low = min(Sepal.Length),
         high = max(Sepal.Length)) %>% 
  ungroup()

# Forgive the clunky tidyverse syntax
scale_list <- df$Species %>% levels() %>% setNames(.,.) %>% lapply(., function(i) {
  scale_x_continuous(breaks = unique(unlist(df[df$Species == i, c("low", "high")])))
})

#devtools::install_github("zeehio/facetscales")
library(facetscales)

df %>% 
  ggplot(aes(x = Sepal.Length,
             y = Petal.Length)) + 
  geom_point() + 
  facet_grid_sc(cols = vars(Species), scales = list(x = scale_list))


#devtools::install_github("teunbrand/ggnomics")
library(ggnomics)

df %>% 
  ggplot(aes(x = Sepal.Length,
             y = Petal.Length)) + 
  geom_point() + 
  facet_wrap(. ~ Species, scales = "free_x") +
  facetted_pos_scales(x = scale_list)

Created on 2020-02-11 by the reprex package (v0.3.0)

Upvotes: 1

dave-edison
dave-edison

Reputation: 3736

Here is a possible solution using patchwork:

library(ggplot2)
library(purrr)
library(patchwork)

df %>% 
  split(.$Species) %>% 
  map(~{
    .x %>% 
      ggplot(aes(x = Sepal.Length,
                 y = Petal.Length)) + 
        geom_point() + 
        facet_wrap(~ Species) + 
        scale_x_continuous(breaks = c(max(.x$low), max(.x$high))) +
        # assuming you want to use same y axis for each plot
        scale_y_continuous(limits = c(min(df$Petal.Length), max(df$Petal.Length)))
  }) %>% 
  reduce(`+`)

enter image description here

I think this is the easiest way that doesn't involve messing with ggproto

Upvotes: 2

Related Questions