Skip to content

Clearer error messages for invalid aesthetics #3091

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 14 commits into from
Apr 29, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

* `coord_map()` now can have axes on the top and right (@karawoo, #3042).

* Clearer error messages for innappropriate aesthetics. (@clairemcwhite, #3060).

* `geom_rug()` gains an "outside" option to allow for moving the rug tassels to outside the plot area. (@njtierney, #3085)
Copy link
Member

Choose a reason for hiding this comment

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

Do we have an idea of where this news item comes from? It should have been present in NEWS.md already, but it isn't, so I'm confused about both why it was lost and how it made its reappearance here.

Copy link
Member

Choose a reason for hiding this comment

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

Ah, it was moved in this commit: 230e8f7 so you can just delete it here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Got it, will do in next commit.


* `geom_rug()` now works with `coord_flip()` (@has2k1, #2987).
Expand Down
20 changes: 20 additions & 0 deletions R/layer.r
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,15 @@ Layer <- ggproto("Layer", NULL,
evaled <- lapply(aesthetics, rlang::eval_tidy, data = data)
evaled <- compact(evaled)

nondata_cols <- check_nondata_cols(evaled)
if(length(nondata_cols) > 0){
msg <- paste0(
"Aesthetics must be valid data columns: ", nondata_cols,
". Did you forget to add stat()?"
Copy link
Member

Choose a reason for hiding this comment

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

Is forgetting stat() really the most likely cause?

Copy link
Member

Choose a reason for hiding this comment

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

There's two ways to get to this point: Forgetting stat() or mistyping a column name in such way that the result is a function, e.g. typing mean when the data column is called Mean. How about: "Did you mistype the name of a data column or forget to add stat()?"

Copy link
Member

Choose a reason for hiding this comment

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

Can we display the thing that they actually typed? Then we could hint to check that blah is a column in the data frame.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't know how to get it to print the actual typed statement, but now it prints the problematic aesthetic (along with the tilde).

Situation 1:
ggplot(mtcars, aes(x = mpg, stat(density))) + geom_tile(stat = "density") + geom_point()

Error: Aesthetics must be valid computed stats: ~stat(density). Did you map your stat in the wrong layer?

Situation 2:

ggplot(mtcars, aes(x = mpg, fill = density)) + geom_tile(stat = "density") + geom_point()

Don't know how to automatically pick scale for object of type function. Defaulting to continuous.
Error: Aesthetics must be valid data columns: ~density. Did you mistype the name of a data column or forget to add stat()?

)
stop(msg, call. = FALSE)
}

n <- nrow(data)
if (n == 0) {
# No data, so look at longest evaluated aesthetic
Expand Down Expand Up @@ -280,6 +289,17 @@ Layer <- ggproto("Layer", NULL,
env$stat <- stat

stat_data <- new_data_frame(lapply(new, rlang::eval_tidy, data, env))

# Check that all columns in aesthetic stats are valid data
nondata_stat_cols <- check_nondata_cols(stat_data)
if(length(nondata_stat_cols) > 0){
msg <- paste0(
"Aesthetics must be valid computed stats: ", nondata_stat_cols,
". Did you map your stat in the wrong layer?"
)
stop(msg, call. = FALSE)
}

names(stat_data) <- names(new)

# Add any new scales, if needed
Expand Down
8 changes: 8 additions & 0 deletions R/utilities.r
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,14 @@ is.discrete <- function(x) {
is.factor(x) || is.character(x) || is.logical(x)
}

# This function checks that all columns of a dataframe `x` are data and
# returns the names of any columns that are not.
# We define "data" as atomic types or lists, not functions or otherwise
check_nondata_cols <- function(x) {
idx <- (vapply(x, function(x) rlang::is_atomic(x) || rlang::is_list(x), logical(1)))
names(x)[which(!idx)]
}

compact <- function(x) {
null <- vapply(x, is.null, logical(1))
x[!null]
Expand Down
16 changes: 16 additions & 0 deletions tests/testthat/test-layer.r
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,22 @@ test_that("missing aesthetics trigger informative error", {
)
})

test_that("function aesthetics are wrapped with stat()", {
df <- data_frame(x = 1:10)
expect_error(
ggplot_build(ggplot(df, aes(density)) + geom_tile(stat = "density")),
"Aesthetics must be valid data columns:"
)
})

test_that("computed stats are in appropriate layer", {
df <- data_frame(x = 1:10)
expect_error(
ggplot_build(ggplot(df, aes(x = x, stat(density))) + geom_tile(stat = "density") + geom_point()),
"Aesthetics must be valid computed stats:"
)
})

test_that("if an aes is mapped to a function that returns NULL, it is removed", {
df <- data_frame(x = 1:10)
null <- function(...) NULL
Expand Down