How to vary x-axis scales by column in facets

37 views Asked by At

I am creating a 2x2 ggplot using facet_wrap where the columns are two different groups. This means that the x-axis on the columns is the same between the top row and the bottom row, but different between the columns. I want to remove the repeated x-axis tick labels, when they are repeated.

Is it possible to have the x-axis vary between columns in facet_wrap?

This is a toy example of the data, and what I have tried so far. What you will see is that the x-axis is repeated in the top and bottom row, when I use the argument "scales = free_x". If I don't use free_x then the scale of the x-axis is too wide for each plot.

Is there an argument I could use, or should I be looking at something like patchwork to link the plots together?

library(ggplot2)

n = 100

df = data.frame(x = c(rnorm(n/2), rnorm(n/2, mean = 100)),
                y = rnorm(n),
                group = rep(1:2, each = n/2),
                facet = rep(c("A", "C", "B", "D"), each = n/4))

ggplot(data = df, aes(x = x, y = y)) + 
  geom_point() + 
  facet_wrap(~facet + group, scales = "free_x")
2

There are 2 answers

0
stefan On BEST ANSWER

Depending on your desired result patchwork will give you the most flexibility. A second option which at least gets you close to your desired result would be to use ggh4x::facetted_pos_scales which allows to set the scales individual per panel. However, I only found an option to remove the axis labels for the top row but not the tick marks.

library(ggplot2)

set.seed(123)

n <- 100

df <- data.frame(
  x = c(rnorm(n / 2), rnorm(n / 2, mean = 100)),
  y = rnorm(n),
  group = rep(1:2, each = n / 2),
  facet = rep(c("A", "C", "B", "D"), each = n / 4)
)

scale_y <- df |>
  split(~ group + facet, drop = TRUE) |>
  lapply(
    \(x) {
      labels <- if (!unique(x$facet) %in% c("A", "B")) waiver()
      limits <- range(df[df$group == unique(x$group), "x"])
      scale_x_continuous(
        labels = labels,
        limits = limits
      )
    }
  )

ggplot(data = df, aes(x = x, y = y)) +
  geom_point() +
  facet_wrap(~ facet + group,
    scales = "free_x"
  ) +
  ggh4x::facetted_pos_scales(
    x = scale_y
  )

0
M-- On

Here's another approach which relies on adding dummy points for each facet + group with the desired range of x and a y value greater than the maximum of the whole data. Those points will be taken out (not shown on the plots) by defining the desired ylim.

n = 100
set.seed(123)
df = data.frame(x = c(rnorm(n/2), rnorm(n/2, mean = 100)),
                y = rnorm(n),
                group = rep(1:2, each = n/2),
                facet = rep(c("A", "C", "B", "D"), each = n/4))

library(ggplot2)
library(dplyr)

df %>% 
  bind_rows(
    reframe(df, x = c(min(x), max(x)), y = max(df$y) + 10, 
                    facet = unique(facet),
                    .by = group),
    reframe(df, x = c(min(x), max(x)), y = max(df$y) + 10, 
                    facet = rev(unique(facet)),
                    .by = group)) %>% 
ggplot(aes(x = x, y = y)) + 
  geom_point() + 
  facet_wrap(~ facet + group, scales = "free_x") +
  coord_cartesian(ylim = range(df$y))

Created on 2024-03-22 with reprex v2.0.2