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

Rename aesthetics in scales and consistently convert US to British spelling #2750

Merged
merged 6 commits into from Jul 23, 2018

Conversation

clauswilke
Copy link
Member

This PR closes #2649. It has two parts: (i) It reworks and simplifies renaming of aesthetic names. (ii) It uses the reworked code in the scale functions, to address issues such as #2674.

Part II is straightforward. Part I needs a few words of explanation.

Most importantly, I deleted code that supposedly does partial matching of aesthetic names:

ggplot2/R/aes.r

Lines 148 to 150 in 5f49fb4

# Convert prefixes to full names
full <- match(names(x), ggplot_global$all_aesthetics)
names(x)[!is.na(full)] <- ggplot_global$all_aesthetics[full[!is.na(full)]]

As far as I can tell, this code does not do anything, and certainly does not do partial matching. The reason is that match() does not do partial matching:

match(c("col", "color"), "color")
#> [1] NA  1

Indeed, in my testing, the current ggplot2 does not do partial matching. It does convert col to colour, but only because that particular match is listed among the base-to-ggplot conversions:

library(ggplot2)
# col is renamed to colour. this is special-cased
aes(col = x)
#> Aesthetic mapping: 
#> * `colour` -> `x`

# colo is not renamed. there is no partial matching
aes(colo = x)
#> Aesthetic mapping: 
#> * `colo` -> `x`

Second, I've exported a new internal function, standardise_aes_names(). This function is needed by anybody who wants to implement their own scales and take advantage of aes standardisation.

With this patch, the following is possible:

library(ggplot2)
# Create a scale using base-R naming
ggplot(iris, aes(Sepal.Length, Sepal.Width, pch = Species)) +
  geom_point(size = 3) +
  scale_discrete_manual(aesthetics = "pch", values = c('+', "-", '*'))

# The previous example is equivalent to this:
ggplot(iris, aes(Sepal.Length, Sepal.Width, pch = Species)) +
  geom_point(size = 3) +
  scale_shape_manual(values = c('+', "-", '*'))

# The example from #2674 that doesn't work there works now
ggplot(iris, aes(x = Sepal.Length, y = Petal.Length, color = Sepal.Length, fill = Petal.Length)) +
  geom_point(shape = 21, size = 3, stroke = 2) +
  scale_color_viridis_c(name = "Length", option = "B", aesthetics = c("color", "fill"))

Created on 2018-07-11 by the reprex package (v0.2.0).

Copy link
Member

@hadley hadley left a comment

Choose a reason for hiding this comment

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

This is definitely going in the right direction, but I feel like the API needs a bit more thought.

}

# x is a list of aesthetic mappings, as generated by aes()
rename_aes <- function(x) {
Copy link
Member

Choose a reason for hiding this comment

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

Where does this get called from?

Copy link
Member Author

Choose a reason for hiding this comment

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

The main user is the aes() function, but it's actually called from many places:

ggplot2/R/aes.r

Lines 77 to 83 in 17b45f9

aes <- function(x, y, ...) {
exprs <- rlang::enquos(x = x, y = y, ...)
is_missing <- vapply(exprs, rlang::quo_is_missing, logical(1))
aes <- new_aes(exprs[!is_missing], env = parent.frame())
rename_aes(aes)
}

g$default_aes <- defaults(rename_aes(new), old)

args <- rename_aes(args)

mapping <- rename_aes(mapping)

args <- rename_aes(args)

ggplot2/R/layer.r

Lines 99 to 100 in 17b45f9

# Split up params between aesthetics, geom, and stat
params <- rename_aes(params)

override.aes = rename_aes(override.aes),

R/aes.r Outdated
duplicated_names_message <- paste0(
"`", duplicated_names, "`", collapse = ", "
)
warning(
Copy link
Member

Choose a reason for hiding this comment

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

Wasn't this previously the responsibility of plyr::rename()? Shouldn't duplicated names also be checked in standardise_aes_names()? Should we also check for code like aes(colour=1,colour=2,colour=3)?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, this code was taken from plyr::rename(). Because I now have a function that converts names rather than a named list it didn't make sense to call plyr::rename() anymore. Also, this gives us the opportunity to customize the warning message to be more meaningful.

Whether duplicated names should be checked in standardise_aes_names() is not clear to me. I think an argument can be made either way. If the job of standardise_aes_names() is to simply take a vector of strings and standardize each of them then it doesn't necessarily have the job to check for duplicates. Maybe there's a downstream use case where somebody needs aesthetics names standardized without having duplicates checked. On the other hand, moving the check into that function would work just fine with the current code base.

Regarding the case of multiple identical aesthetics, it does get caught by the same check:

> aes(colour=1, colour=2, colour=3)
Aesthetic mapping: 
* `colour` -> 1
* `colour` -> 2
* `colour` -> 3
Warning message:
Standardisation of aesthetics has created duplicates for the following: `colour`, `colour` 

So maybe it's just a matter of making the error message a little clearer. I think printing out the complete list of final aesthetic names, rather than just the ones that are duplicated, might be helpful.

@clauswilke
Copy link
Member Author

I have improved the error message for duplicated aesthetics. Most importantly, now each duplicated aesthetic is at most listed once. I tried outputting the entire list of aesthetics but that was confusing.

> aes(shape = 1, pch = 2, colour = 1, colour = 2, colour = 3)
Aesthetic mapping: 
* `shape`  -> 1
* `shape`  -> 2
* `colour` -> 1
* `colour` -> 2
* `colour` -> 3
Warning message:
Duplicated aesthetics after name standardisation: shape, colour 

if (length(duplicated_names) > 0L) {
duplicated_message <- paste0(unique(duplicated_names), collapse = ", ")
warning(
"Duplicated aesthetics after name standardisation: ", duplicated_message, call. = FALSE
Copy link
Member

Choose a reason for hiding this comment

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

Should we make this an error?

Copy link
Member Author

Choose a reason for hiding this comment

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

I'll leave this to your decision. My own philosophy is it's only an error if the software cannot recover. Here, the duplicated aesthetics are simply ignored and a plot is produced:

library(ggplot2)
df <- data.frame(x = 1:10, y = 1:10)
ggplot(df, aes(x, y, shape = "*", pch = "a")) + geom_point()
#> Warning: Duplicated aesthetics after name standardisation: shape

It's not that different from this case, which doesn't even create a warning:

ggplot(df, aes(x, y, shape = "*")) + geom_point(aes(pch = "a"))

Created on 2018-07-13 by the reprex package (v0.2.0).

@clauswilke clauswilke merged commit 510b4b4 into tidyverse:master Jul 23, 2018
@clauswilke clauswilke deleted the rename-aesthetics branch August 7, 2018 23:25
@lock
Copy link

lock bot commented Feb 3, 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 Feb 3, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Improved support for aesthetic aliases
2 participants