Skip to content

Aesthetics for position adjustments #6100

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

Merged
merged 18 commits into from
Jan 27, 2025

Conversation

teunbrand
Copy link
Collaborator

@teunbrand teunbrand commented Sep 12, 2024

This PR aims to fix #3026, fix #5445 and fix #3022.

Briefly, it adds the ability for position adjustments to declare their own aesthetics. It is currently only implemented for position_nudge().

On any layer where position = "nudge", one can use nudge_x and nudge_y either as a mapped aesthetic inside aes(), or static aesthetic passed through layer(params).

A few examples:

devtools::load_all("~/packages/ggplot2")
#> ℹ Loading ggplot2

df <- data.frame(x = 1:2, y = 1:2, z = c(0.5, 1))

ggplot(df, aes(x, y)) +
  geom_point(position = "nudge", nudge_x = 0.5)

ggplot(df, aes(x, y, nudge_y = z)) +
  geom_point(position = "nudge")

Created on 2024-09-12 with reprex v2.1.1

@clauswilke
Copy link
Member

clauswilke commented Sep 12, 2024

@teunbrand As you're working on this, I'd suggest you also look at the long history of issues with misplaced boxplots or violins when categories are missing. I realized long ago that the only way to truly fix this is to somehow have position adjustments be aware of aesthetics. Not sure whether your current work can address this, but conceptually it seems related.

See for example the long discussion here: #3022, and various other issues quoted therein.

Also, here is a reprex that highlights the issue. There is no way I am aware of that can properly format and place boxplots when categories are missing.

library(ggplot2)

# boxes for water polo, netball, and gymnastics have the wrong size
ggplot(ggridges::Aus_athletes, aes(height, sport, fill = sex)) +
  geom_boxplot()

# box for water polo is in the wrong location
ggplot(ggridges::Aus_athletes, aes(height, sport, fill = sex)) +
  geom_boxplot(position = position_dodge(preserve = "single"))

# boxes for water polo, netball, and gymnastics are in the wrong location
ggplot(ggridges::Aus_athletes, aes(height, sport, fill = sex)) +
  geom_boxplot(position = position_dodge2(preserve = "single"))

Created on 2024-09-12 with reprex v2.0.2

Let me know if you think I should open a new issue for this. I don't think we have one currently, but am not entirely certain.

@teunbrand
Copy link
Collaborator Author

Hi @clauswilke, I think that the issue you're highlight is fundamentally the same as #3345. I have pondered them, but haven't been able to come up with a good solution for them (yet). In any case, that issue will not be fixed by this PR.

@clauswilke
Copy link
Member

Yes, agreed, it's the same as #3345.

@teunbrand
Copy link
Collaborator Author

teunbrand commented Sep 13, 2024

On second thought, this PR might be able to fix it if we have something like an order aesthetic for position_dodge().
I'll play around with this for a bit.

@teunbrand
Copy link
Collaborator Author

I've added an order aesthetic to position_dodge() to fix #3022 and #3345.

As an example without order, consider the following case where boxes have partially arbitrary order within their x position:

devtools::load_all("~/packages/ggplot2")
#> ℹ Loading ggplot2

set.seed(0)
df <- data.frame(
  x = rep(rep(LETTERS[1:7], c(3, 2, 2, 2, 1, 1, 1)), each = 10),
  y = rnorm(120),
  group = rep(c("X", "Y", "Z")[c(1:3, 1:2, 1, 3, 2:3, 1:3)], each = 10)
)

ggplot(df, aes(x, y, fill = group)) +
  geom_boxplot(position = position_dodge(preserve = "single"))

Now with the order aesthetic, the boxes are always in the same order regardless of their x position.

last_plot() + aes(order = group)

Created on 2024-09-18 with reprex v2.1.1

@teunbrand teunbrand changed the title WIP: Aesthetics for positions Aesthetics for position adjustments Dec 5, 2024
@teunbrand teunbrand marked this pull request as ready for review December 5, 2024 09:44
@teunbrand
Copy link
Collaborator Author

There isn't anything I'd like to add at this moment, so I'm marking this as ready for review

Comment on lines +81 to +100
use_defaults = function(self, data, params = list()) {

aes <- self$aesthetics()
defaults <- self$default_aes

params <- params[intersect(names(params), aes)]
params <- params[setdiff(names(params), names(data))]
defaults <- defaults[setdiff(names(defaults), c(names(params), names(data)))]

if ((length(params) + length(defaults)) < 1) {
return(data)
}

new <- compact(lapply(defaults, eval_tidy, data = data))
new[names(params)] <- params
check_aesthetics(new, nrow(data))

data[names(new)] <- new
data

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are some restrictions here that don't apply to the classic aesthetics, right? You can't use after_scale() for instance. Is that somehow taken care of?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any surviving modifiers would be ignored. after_stat() is already evaluated, so that isn't really a concern. after_scale() doesn't really apply as position adjustments are computed before scales are applied. It will be interpreted as there is no such aesthetic, therefore the default value is substituted.

I'm not sure whether this was clear, but this does not replace Geom$use_defaults(), it just evaluates the Position$default_aes really.

@@ -104,7 +106,10 @@ PositionDodge <- ggproto("PositionDodge", Position,
preserve = "total",
orientation = "x",
reverse = NULL,
default_aes = aes(order = NULL),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can be a bit worried that order is so general that it conflicts with aesthetics from extension packages. Have you looked into that?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is hard to search for, but I can confidently tell you that no package on CRAN has a scale_order_*() function. I've personally never seen an extension package use an order aesthetic.

Copy link
Member

@thomasp85 thomasp85 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Merge branch 'main' into position_aesthetics

# Conflicts:
#	R/utilities-help.R
@teunbrand teunbrand merged commit ed1b80d into tidyverse:main Jan 27, 2025
13 checks passed
@teunbrand teunbrand deleted the position_aesthetics branch January 27, 2025 13:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants