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

[Feature Request] different scales by facet #1613

Closed
zeehio opened this Issue Apr 15, 2016 · 16 comments

Comments

Projects
None yet
@zeehio
Copy link

zeehio commented Apr 15, 2016

This feature request first shows a ggplot2 limitation through an example and then it proposes a syntax change to the scale_* functions to overcome the scale limitation. The syntax change is designed so it feels natural to ggplot2 users. No implementation is provided, as I don't know if this syntax has chances to be merged and I would need some pointers to tackle the implementation as cleanly as possible. Discussion/help/comments are welcome.

Current ggplot2 limitation

Faceted plots in ggplot2 apparently require the same scale parameters. This means that it is impossible to plot a percentage (scale_y_continuous(labels=scales::percent_format())) and a scientific number (scale_y_continuous(labels=scales::scientific_format())) on the same axis but different facet.

This is not a new issue (2010) but I have not seen any solution on that thread.

This issue is better described in an example:

library(dplyr)
library(scales)
library(ggplot2)

# Simple tidy data frame, with 4 subjects. We measure three variables on each
# subject (namely: SomeValue, Percent and Scientific).
mydf <- dplyr::data_frame(Subject=rep(c("Alice", "Bob", "Charlie", "Diane"), each=3),
                          Magnitude=rep(c("SomeValue", "Percent", "Scientific"), times=4),
                          Value=c(c(170,0.6,2.7E-4),
                                  c(180, 0.8, 2.5E-4),
                                  c(160, 0.71, 3.2E-4),
                                  c(159, 0.62, 3E-4)),
                          Gender=rep(c("Female", "Male", "Male", "Female"), each=3))

# This is how the df looks like:
head(mydf)
#  Subject  Magnitude   Value Gender
#1   Alice  SomeValue 1.7e+02 Female
#2   Alice    Percent 6.0e-01 Female
#3   Alice Scientific 2.7e-04 Female
#4     Bob  SomeValue 1.8e+02   Male
#5     Bob    Percent 8.0e-01   Male
#6     Bob Scientific 2.5e-04   Male

# Here the percent (top) is well written, the other ones don't make sense
ggplot(mydf) +
  geom_point(aes(x=Subject, y=Value, color=Gender)) +
  scale_y_continuous(labels=scales::percent_format()) +
  #scale_y_continuous(labels=waiver()) +
  #scale_y_continuous(labels=scales::scientific_format()) +
  facet_grid(Magnitude~., scales = "free" )

f1

Proposal

The syntax I would like to suggest is to allow to pass data and mapping parameters to the scale functions (here scale_y_continuous) that would map the scale arguments to each facet.

# Proposed solution syntax. Define a data frame with the facet variable
# and the arguments we want to use:
myscales <- dplyr::data_frame(Magnitude = c("SomeValue", 
                                            "Percent",
                                            "Scientific"),
                              labels = list(waiver(),
                                            scales::percent_format(),
                                            scales::scientific_format()))
head(myscales)
# Source: local data frame [3 x 2]
#
#   Magnitude      labels
#1  SomeValue <S3:waiver>
#2    Percent   <fnct[1]>
#3 Scientific   <fnct[1]>

# Change scale_y_continuous to accept data and aes:
ggplot(mydf) +
  geom_point(aes(x=Subject, y=Value, color=Gender)) +
  scale_y_continuous(data=myscales, mapping=aes(labels=labels)) + # Needs implementation
  facet_grid(Magnitude~., scales = "free" )
  • I find this syntax familiar (like geom_* functions) and tidy.
  • I think it is backwards compatible with the existing arguments.
  • I do not know how hard it may be its implementation given the current ggplot2 design.
@has2k1

This comment has been minimized.

Copy link
Contributor

has2k1 commented Apr 20, 2016

Doing it that way would most likely complicate the design, since it complicates the process by which each layer determines the data and mapping that it should use. A less intrusive change would be to pass the scales into the facet. i.e.

# create scales with different parameters e.g. labels, 
sc1 <- scale_y_continuous(...) 
sc2 <- scale_y_continuous(...)
sc3 <- scale_y_continuous(...)
facet_grid(Magnitude~., scales_y=c(sc1, sc2, sc3) )

Position scales can be shared between facets and that is what scales = "free|free_x|free_y" influences. The change as per the API shown above, would require changes in maybe 3 files; facet-grid.r, facet-wrap.r and panel.r, and should not break backwards compatibility.

If you would like to try implementing it, grep for SCALE_X or SCALE_y -- the results should give you the spots that would need some rethinking.

@zeehio

This comment has been minimized.

Copy link
Author

zeehio commented Apr 22, 2016

Thanks for your feedback and advice, @has2k1.

Progress on your suggestions (and issues I see)

I have tried to work a bit on your approach, I have achieved a point in which I can change the label format for each panel (subplot), but I can't transform the scale (using the trans argument) successfully (all my labels in that panel disappear and the data is not transformed). Once I clean the code I plan to push it in a branch on my ggplot fork, although I am not convinced of the solution to submit a pull request:

From my point of view, scale_* functions should state how do we want the scales (axis, colors, shapes...) and facet_* how do we want to split our data in groups. This is the rationale presented in the grammar of graphics book, on which ggplot2 is based on. To me, the scales parameter inside facet_* does not respect that rationale, although it seems convenient from a ggplot2 implementation point of view.

Further coupling of scale_* and facet_*, as given in your feedback, may be the only solution that I can work on (given my skills), but following my interpretation of the grammar of graphics concept I am not convinced that it is the right one.

Implementation proposal

Current state

In ggplot_build (in plot-build.R). Currently:

  • plot$scales is a ScaleList object that contains all the scales (x, y, color, shape...)
  • The x and y scales are cloned from the plot scale if according to the facet_*(scales='free|fixed') option. This is done inside the train_position function, that also trains the limits for each x, y scale. train_position knows if it has to clone the scales or not using the layout$SCALE_X and layout$SCALE_Y information.
    • With this implementation it seems impossible to me to have some y scales fixed and other y scales free.

Proposal

To me, it would make more sense to:

  • Have a ScaleList object for each panel (a panel is a "subplot"), instead of a ScaleList for each plot.
  • Most scales inside each ScaleList object would usually be identical.
    • Identical (as in the same object) scales in different panels would perform as fixed axis.
    • Not identical scales would perform as free axis.
    • Having different color scales on each panel would allow to use a color gradient in one panel and a discrete palette on another panel. This may be confusing as one subplot could use a gradient and the next one a discrete color scale. Legends could be more complex if people started to use scales in that way. This is good (user has more power), but it may also be bad (hadley is against empowering users to have two y axis on a single panel, he may be against this possibility)
  • As we would have a ScaleList object for each panel, the train_position function would train the scale limits (as its name states). There would be no need to clone scales.

I agree that backwards compatibility should be preserved in any case, as it seems possible.

@has2k1

This comment has been minimized.

Copy link
Contributor

has2k1 commented Apr 24, 2016

The key figuring out the differences between Wilkson's "The Grammar of Graphics" and ggplot2 is Hadley's paper "The Layered Grammar".

Your proposal makes sense and a lot of that is happening under the hood, it is just less obvious. What you wish for as a per panel ScaleList exists as a the panel$layout table. The scales are only cloned as needed.

@hadley

This comment has been minimized.

Copy link
Member

hadley commented Jul 28, 2016

If this was to be implemented, I think the most logical UI would be something like:

facet_grid(Magnitude~., scales = list(x = "fixed", y = list(sc1, sc2, sc3))

But I don't really like matching up scales to rows/columns by positions. You're losing all the semantics of the facetting.

@thomasp85

This comment has been minimized.

Copy link
Member

thomasp85 commented Sep 17, 2016

I propose we close this - it is highly unlikely to be added to facet_grid in the near future, if ever.

Also, I have some planned facet extensions for ggforce that might solve the problem...

@hadley hadley closed this Sep 18, 2016

@pekkakohonen

This comment has been minimized.

Copy link

pekkakohonen commented Aug 3, 2017

I would also like to have this feature ... It is very important for scientific plotting. The problem is that you want to be able to have plots that are comparable, i.e., on the same scale (like all the concentrations that were measured) in different analyses. I suppose for publications you can plot each facet separately and them combine them but this is a lot of work for exploratory analyses.

@tylerhoecker

This comment has been minimized.

Copy link

tylerhoecker commented Nov 11, 2017

I also would appreciate this feature. The geom_blank hack doesn't work to narrow the coord_cartesian parameter. Common in data I work with to have a range of appropriate y scales with in a group to be facetted on same x axis.

@jecogeo

This comment has been minimized.

Copy link

jecogeo commented Feb 18, 2018

Was this feature implemented?

@zeehio

This comment has been minimized.

Copy link
Author

zeehio commented Feb 18, 2018

I hope it had been implemented, but as far as I know it is not possible.

I am using gridExtra::grid.arrange() to "paste" multiple plots.

@s-erhardt

This comment has been minimized.

Copy link

s-erhardt commented Feb 26, 2018

I would also be happy to see this feature implemented in the future.
Just adding a post of mine on stackoverflow to showcase the hassle I have been going through trying to solve this as I had never heard of this kind of 'limitations' before
https://stackoverflow.com/questions/48437700/ggplot-boxplot-independently-specify-facet-scales

@kippjohnson

This comment has been minimized.

Copy link

kippjohnson commented Apr 19, 2018

This can be implemented using the scales argument in facet_grid:

p1 <- ggplot(data=df2, aes(x=condition, y=dose)) + geom_boxplot(outlier.color=NA)
p1 + facet_grid(tissue ~ . , scales='free')

Arguments for scales can be "free", "free_x", "free_y", or the default, "fixed"

@zeehio

This comment has been minimized.

Copy link
Author

zeehio commented Apr 19, 2018

No, it can not. scales= "free" gives the option to have different scale limits per facet, not different scales in general.

Read for instance my first example in the issue where I want one facet with percentages and another with scientific notation. I even took the time to put a screenshot.

@AstreChen

This comment has been minimized.

Copy link

AstreChen commented Jun 26, 2018

I hope developers can add functions that support manipulate scales of each facet, sometimes we only need a sub-range of y to enlarge the visualization of variance, but plot like geom_area doesn't support change the y label of one of the facets .

@mattmar

This comment has been minimized.

Copy link

mattmar commented Jul 9, 2018

I would also highly appreciate this feature in facet_grid!

zeehio added a commit to zeehio/ggplot2 that referenced this issue Jul 10, 2018

`facet_grid()` supports a different scale per each facet.
This can be used in facetting plots that share one axis but have different units in the other axis (@zeehio, tidyverse#1613).

zeehio added a commit to zeehio/ggplot2 that referenced this issue Jul 10, 2018

`facet_grid()` supports a different scale per each facet.
This can be used in facetting plots that share one axis but have different units in the other axis (@zeehio, tidyverse#1613).

zeehio added a commit to zeehio/ggplot2 that referenced this issue Jul 10, 2018

`facet_grid()` supports a different scale per each facet.
This can be used in facetting plots that share one axis but have different units in the other axis (@zeehio, tidyverse#1613).
@zeehio

This comment has been minimized.

Copy link
Author

zeehio commented Jul 11, 2018

For those of you interested in this feature, I wrote a ggplot extension package at https://github.com/zeehio/facetscales

If you can open issues there with reproducible examples showing what you need, I will try to improve the interface to cope with your needs and once I have a stable and well tested functionality, I will submit a pull request to ggplot2.

@lock

This comment has been minimized.

Copy link

lock bot commented Jan 7, 2019

This old issue has been automatically locked. If you believe you have found a related problem, please file a new issue (with reprex) and link to this issue. https://reprex.tidyverse.org/

@lock lock bot locked and limited conversation to collaborators Jan 7, 2019

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.