krlmlr
krlmlr

Reputation: 25444

Function that returns an aesthetic mapping

I would like to create a function that "works just like" ggplot2's aes() function. My humble attempts fail with an "Object not found" error:

library(ggplot2)

data <- data.frame(a=1:5, b=1:5)

# Works
ggplot(data) + geom_point() + aes(x=a, y=b)

my.aes <- function(x, y) { aes(x=x, y=y) }
# Fails with "Error in eval(expr, envir, enclos) : object 'x' not found"
ggplot(data) + geom_point() + my.aes(x=a, y=b)

What is the correct way to implement my.aes()? This is for encapsulation and code reuse.

Perhaps this is related, I just don't see yet how: How to write an R function that evaluates an expression within a data-frame.

Upvotes: 1

Views: 192

Answers (1)

user1935457
user1935457

Reputation: 721

Type aes without any parentheses or arguments to see what it's doing:

function (x, y, ...) 
{
    aes <- structure(as.list(match.call()[-1]), class = "uneval")
    rename_aes(aes)
}

It takes the name of its arguments without evaluating them. It's basically saving the names for later so it can evaluate them in the context of the data frame you're trying to plot (that's why your error message is complaining about eval). So when you include my.aes(x=a, y=b) in your ggplot construction, it's looking for x in data--because x was not evaluated in aes(x=x, y=y).

An alternate way of thinking about what's going on in aes is something like

my.aes <- function(x, y) {
  ans <- list(x = substitute(x), y = substitute(y))
  class(ans) <- "uneval"
  ans
}

which should work in the example above, but see the note in plyr::. (which uses the same match.call()[-1] paradigm as aes):

Similar tricks can be performed with substitute, but when functions can be called in multiple ways it becomes increasingly tricky to ensure that the values are extracted from the correct frame. Substitute tricks also make it difficult to program against the functions that use them, while the quoted class provides as.quoted.character to convert strings to the appropriate data structure.

If you want my.aes to call aes itself, perhaps something like:

my.aes <- function(x,y) {
    do.call(aes, as.list(match.call()[-1]))
} 

Example with the aes_string function pointed out by Roman Luštrik:

my.aes <- function(x,y) {
    aes_string(x = x, y = y)
}

but you would need to change your call to my.aes("a", "b") in this case.

Upvotes: 3

Related Questions