ggplot: Best way to split x-axis in intervals?

I have some data, which has integer x-value from small to quite large. I want to split the x-axis in intervals and I am currently doing as exemplified below. However:

  1. The group-splitting seems a bit hack'ish, is there a more tidy way to do this?
  2. The last pane gets stretched distorting the overview, how can this be avoided?
# Reproducible example
set.seed(352053)

# Set example data
d <- tibble(x = seq(from = 1, to = 922),
            y = rnorm(n = length(x)))

# First plot, no interval split
d %>% 
  ggplot(aes(x = x, y = y)) +
  geom_col() +
  scale_x_continuous(expand = c(0, 0)) +
  theme_bw()
ggsave(filename = "first_plot_no_split.png",
       width = 10, height = 6, dpi = 72)

# Set groups for interval splitting
d <- d %>% 
  mutate(y_i = rep(x = 1:10, each = 100)[1:length(x)])

# Second plot, with interval splitting
d %>% 
  ggplot(aes(x = x, y = y)) +
  geom_col() +
  scale_x_continuous(expand = c(0, 0)) +
  theme_bw() +
  facet_wrap(vars(y_i), ncol = 1, scales = "free_x") +
  theme(strip.background = element_blank(),
        strip.text.x = element_blank())
ggsave(filename = "second_plot_with_split.png",
       width = 6, height = 10, dpi = 72)

ggplot has a few related functions for discretizing continuous variables: cut_interval, cut_number and cut_width. For the second question, by specifying the number of intervals to cut the data, each interval will have the same width so the last panel will not be distorted.

Using your reprex:

library("tidyverse")

# Reproducible example
set.seed(352053)

# Set example data
d <- tibble(x = seq(from = 1, to = 922),
            y = rnorm(n = length(x)))

# Second plot, with interval splitting
d <- d %>%
  mutate(y_i = cut_interval(x, n = 10)) 

 d %>%
  ggplot(aes(x = x, y = y)) +
  geom_col() +
  scale_x_continuous(expand = c(0, 0)) +
  theme_bw() +
  facet_wrap(vars(y_i), ncol = 1, scales = "free_x") +
  theme(strip.background = element_blank(),
        strip.text.x = element_blank())

ggsave(filename = "second_plot_with_split.png",
       width = 6, height = 10, dpi = 72)

1 Like

Thanks @jrmuirhead, I'm interested in "nice" intervals though, e.g. pane1=1:100, pane2=101:200, pane3=201:300, etc. and then the last pane at same scale, but ending where-ever it ends before the interval is filled?

Still looking for suggestions here :+1:

If we create a separate plot for each group, then we can add a spacer next to the last plot to keep it from stretching to the full width of the previous plots. In the code below, we calculate the relative widths needed for the last plot and the spacer based on how many data rows go into the last plot relative to the 100 rows that go into each of the other plot panels.

library(tidyverse)
library(patchwork)

# Reproducible example
set.seed(352053)

# Set example data
d <- tibble(x = seq(from = 1, to = 922),
            y = rnorm(n = length(x)))

# Create groups with 100 rows each
d = d %>% 
  mutate(group = (x-1) %/% 100) 

# Create a list of plots, one for each group of 100 rows
pl = d %>% 
  # Split into one data frame for each group
  split(d$group) %>% 
  map(
    ~ggplot(.x, aes(x = x, y = y)) +
      geom_col() +
      scale_x_continuous(expand = c(0, 0), breaks=seq(0,nrow(d), 10)) +
      theme_bw() +
      theme(axis.title=element_blank()) 
  )
# Get number of data rows in last plot
n = nrow(d[d$group==max(d$group),])

# Add (to the last plot in the list) an empty plot with width equal to 
#  the amount of empty space we need on the right
# Removing the right plot margin is to ensure (or at least make it more likely) 
#  that the last plot will line up vertically with the previous plots.
pl[[length(pl)]] = pl[[length(pl)]] + theme(plot.margin=margin(r=0)) + plot_spacer() + plot_layout(widths=c(n, 100-n))

# Lay out the plots
wrap_plots(pl, ncol=1)

3 Likes

Quite nice solution there @joels - Cheers! I'm still thinking that it seems rather cumbersome for something that should be simple?

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