1

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.

Community
  • 1
  • 1
krlmlr
  • 25,056
  • 14
  • 120
  • 217

1 Answers1

3

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.

user1935457
  • 721
  • 4
  • 7
  • Thank you for the explanation. Your suggestion basically means reimplementing `aes`, which I would rather avoid. Is there a way to implement `my.aes` using `aes` only? (Later, there will be also a `my.geom_text`, and perhaps others...) – krlmlr Jan 16 '13 at 09:43
  • It's hard for me to give a more specific answer without knowing more about how the behavior of `my.aes` will differ from `aes`--in your example they do the same thing. Roman's `aes_string` suggestion would work if you're willing to write my.aes("a", "b") instead of my.aes(a,b), but I'm not going to put that in my answer as it was his idea. – user1935457 Jan 16 '13 at 10:38
  • Feel free to include that in your answer, I hold no claim to the idea. Just like the aliens, it's out there! :) – Roman Luštrik Jan 16 '13 at 10:49
  • 1
    See my edit for a a version that passes down the call from `my.aes` to `aes`. Not sure how that will fit in with what you're planning next, however. – user1935457 Jan 16 '13 at 10:50
  • I think you need to quote the passed arguments for this to work. – Roman Luštrik Jan 16 '13 at 11:01