ggh4x::facet_grid2() - Is there a way to have free & independent scales on the x axis, but for the columns to display the same scales?

143 views Asked by At

I'm using the ggh4x package for the facet_grid2() function, facetting on 2 variables (one for the rows, one for the cols). The values in the y axis are different, and the range of the x axis is different, so I am using the scales="free" argument. I want each of my facets to display a different x axis, so I use the argument independent="x". Is there a way to keep those x axis values independent for each row, but for the range displayed to be consistent between my two columns?

Below is a reproducible example to exemplify the issue:

iris <- iris %>% 
  mutate(group1 = rep(c("w","x","y","z"), each=20, length.out=nrow(iris)),
         group2 = rep(c("A","B"), length.out=nrow(iris)))

ggplot(iris,
       aes(x=Sepal.Length,
           y=group1)) +
  geom_point() +
  facet_grid2(Species~group2, 
              scales = "free",
              independent = "x")

My ideal result would have the x axis of the first row show a range of 4.4-5.6 for both columns, 5-7 for row 2 (both columns) and 5-8 for row 3 (both columns).

Thanks!

2

There are 2 answers

0
eipi10 On

One way to control the x-axis range by row would be to create separate facetted plots for each row and then combine them. Below is an example. We split the data by Species (the row variable). Then we use map to create a facetted plot for each level of Species. Within map we get the x-range of Sepal.Width for the given level of Species and use that to set the x-axis limits for each plot. Then we use patchwork::wrap_plots() to combine the plots.

library(tidyverse)
library(patchwork)

iris <- iris %>% 
  mutate(group1 = rep(c("w","x","y","z"), each=20, length.out=nrow(iris)),
         group2 = rep(c("A","B"), length.out=nrow(iris)))

# Create a list of plots, one for each level of Species
p = iris %>% 
  group_by(Species) %>% 
  group_split() %>% 
  map(~{

    # Get x-axis range for the current plot
    rng = range(.x[["Sepal.Length"]])

    ggplot(.x, aes(x=Sepal.Length, y=group1)) +
      geom_point() +
      facet_grid(Species~group2) +
      scale_x_continuous(limits=rng) 
  })

p[1:2] = p[1:2] %>% map(~.x + labs(x=NULL))
p[c(1,3)] = p[c(1,3)] %>% map(~.x + labs(y=NULL))

wrap_plots(p, ncol=1) 

Created on 2023-12-08 with reprex v2.0.2

2
stefan On

Another option would be to use ggh4x::scales_x_facet or ggh4x::facetted_pos_scales which both allow to individually set the scale for each or a group of panels:

library(ggplot2)
library(ggh4x)

ggplot(
  iris,
  aes(
    x = Sepal.Length,
    y = group1
  )
) +
  geom_point() +
  facet_grid2(Species ~ group2,
    scales = "free_x",
    independent = "x"
  ) +
  scale_x_facet(
    Species %in% "setosa", limits = c(4.4, 5.6)
  ) +
  scale_x_facet(
    Species %in% "versicolor", limits = c(5, 7)
  ) +
  scale_x_facet(
    Species %in% "virginica", limits = c(5, 8)
  )

enter image description here

EDIT Or instead of replicating the code one might use e.g. the purrr::map family of functions to create a list of scales as suggested by @eipi10 in his comment:

library(ggplot2)
library(ggh4x)
library(purrr)

scale_x <- iris |>
  split(~Species) |>
  map(~range(.x$Sepal.Length)) |> 
  imap(
    ~ scale_x_facet(
      Species == .y,
      limits = .x
    )
  )

p <- ggplot(
  iris,
  aes(
    x = Sepal.Length,
    y = group1
  )
) +
  geom_point() +
  facet_grid2(Species ~ group2,
              scales = "free_x",
              independent = "x"
  ) + 
  scale_x