Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

scale option that preserves scale but not location #5377

Closed
RoyalTS opened this issue Aug 2, 2023 · 2 comments
Closed

scale option that preserves scale but not location #5377

RoyalTS opened this issue Aug 2, 2023 · 2 comments

Comments

@RoyalTS
Copy link

RoyalTS commented Aug 2, 2023

When facetting over things with different range, I currently have the option to give all facets the same axis limits, like so:

iris |> 
  group_by(Sepal.Width) |> 
  summarize(across(ends_with('Length'), mean)) |> 
  tidyr::pivot_longer(ends_with('Length')) |> 
  ggplot(aes(Sepal.Width, value, color=name)) +
  geom_line() +
  facet_wrap(~name)
Screenshot 2023-08-02 at 10 12 16

or to free axes completely, like so:

iris |> 
  group_by(Sepal.Width) |> 
  summarize(across(ends_with('Length'), mean)) |> 
  tidyr::pivot_longer(ends_with('Length')) |> 
  ggplot(aes(Sepal.Width, value, color=name)) +
  geom_line() +
  facet_wrap(~name, scales='free_y')
Screenshot 2023-08-02 at 10 13 02

free_* really does two things there though: It frees the scale of the axis – the pixel distance between the breaks – and it frees the absolute location of the scale – whether the break at 5 happens at the same horizontal point across facets.

I would love to have an option to scales that allows me to free location while maintaining scale. That is, vertical distances within each facet would correspond to the same absolute differences in the variable being plotted, but those differences might occur from another baseline.

An admittedly very hacky demonstration:

iris |> 
  group_by(Sepal.Width) |> 
  summarize(across(ends_with('Length'), mean)) |> 
  tidyr::pivot_longer(ends_with('Length')) -> plot_data

plot_data |>
  summarize(across(value, list(min=min, max=max)), .by=name) |> 
  mutate(
    range=value_max-value_min,
    midpoint = value_min + range/2
  ) |> 
  mutate(
    # center on the midpoint of the largest range among the facets
    max_range = max(range),
    axis_min = midpoint - max_range / 2,
    axis_max = midpoint + max_range / 2
  ) -> facet_spec

make_facet <- function(df, x_var, axis_min, axis_max) {
  df |>
    filter(name == x_var) |> 
    ggplot(aes(Sepal.Width, value)) +
    geom_line() +
    facet_wrap(~name) +
    coord_cartesian(ylim=c(axis_min, axis_max))
}

facet_spec |> 
  rowwise() |> 
  mutate(plots = list(make_facet(plot_data, name, axis_min,	axis_max))) |> 
  pull(plots) |> 
  patchwork::wrap_plots()
Screenshot 2023-08-02 at 10 33 07
@teunbrand
Copy link
Collaborator

If you pre-calculate the maximum range per facet, you can just use a function as the limits argument that will set a consistent range. I'm unsure whether this is widely applied enough to merit an additional option in facets.

library(tidyverse)

df <- iris |> 
  group_by(Sepal.Width) |> 
  summarize(across(ends_with('Length'), mean)) |> 
  pivot_longer(ends_with('Length'))

max_range <- df |>
  group_by(name) |>
  summarise(range = diff(range(value))) |>
  summarise(range = max(range)) |>
  pull()

ggplot(df, aes(Sepal.Width, value, color=name)) +
  geom_line() +
  facet_wrap(~name, scales = "free_y") +
  scale_y_continuous(
    limits = \(x) mean(x) + c(-0.5, 0.5) * max_range
  )

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

@RoyalTS
Copy link
Author

RoyalTS commented Aug 5, 2023

Oh! I had no idea limits could take a function as an argument! That'll do!

@RoyalTS RoyalTS closed this as completed Aug 5, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants