40

I've noticed that quite a few packages allow you to pass symbol names that may not even be valid in the context where the function is called. I'm wondering how this works and how I can use it in my own code?

Here is an example with ggplot2:

a <- data.frame(x=1:10,y=1:10)
library(ggplot2)
qplot(data=a,x=x,y=y)

x and y don't exist in my namespace, but ggplot understands that they are part of the data frame and postpones their evaluation to a context in which they are valid. I've tried doing the same thing:

b <- function(data,name) { within(data,print(name)) }
b(a,x)

However, this fails miserably:

Error in print(name) : object 'x' not found

What am I doing wrong? How does this work?

Note: this is not a duplicate of Pass variable name to a function in r

Community
  • 1
  • 1
static_rtti
  • 53,760
  • 47
  • 136
  • 192

4 Answers4

42

I've recently discovered what I think is a better approach to passing variable names.

a <- data.frame(x = 1:10, y = 1:10)

b <- function(df, name){
    eval(substitute(name), df)
}

b(a, x)
  [1]  1  2  3  4  5  6  7  8  9 10

Update The approach uses non standard evaluation. I began explaining but quickly realized that Hadley Wickham does it much better than I could. Read this http://adv-r.had.co.nz/Computing-on-the-language.html

Jacob H
  • 4,317
  • 2
  • 32
  • 39
  • 1
    Could you detail a bit more (add a few comments) ? This approach sounds interesting. Note that there are no "old threads" on stack overflow, information is preserved for people like you and me who google their questions. – static_rtti Dec 04 '15 at 10:32
  • 2
    This is a gem of an answer, thank you. If it helps, then if `var <- eval(substitute(var), data)` gives you the object, then `var.name <- substitute(var)` gives you the variable name as passed for use within the function. – drstevok Jan 14 '16 at 11:40
18

You can do this using match.call for example:

b <-  function(data,name) {

  ## match.call return a call containing the specified arguments 
  ## and the function name also 
  ## I convert it to a list , from which I remove the first element(-1)
  ## which is the function name

  pars <- as.list(match.call()[-1])
  data[,as.character(pars$name)]

}

 b(mtcars,cyl)
 [1] 6 6 4 6 8 6 8 4 4 6 6 8 8 8 8 8 8 4 4 4 4 8 8 8 8 4 4 4 8 6 8 4

explanation:

match.call returns a call in which all of the specified arguments are specified by their full names.

So here the output of match.call is 2 symbols:

b <-  function(data,name) {
  str(as.list(match.call()[-1]))  ## I am using str to get the type and name
}

b(mtcars,cyl)
List of 2
 $ data: symbol mtcars
 $ name: symbol cyl

So Then I use first symbol mtcars ansd convert the second to a string:

mtcars[,"cyl"]

or equivalent to :

eval(pars$data)[,as.character(pars$name)]
agstudy
  • 119,832
  • 17
  • 199
  • 261
  • Thanks, it's a lot clearer now. I'd still love to understand why my version fails, though. – static_rtti Oct 02 '13 at 10:06
  • 2
    @static_rtti So it is not clear enough :) It fails because you data don't contain a column named "name". You should use something like `within(data,pars$name)` where `pars` is defined using `match.call` as above. – agstudy Oct 02 '13 at 10:14
  • Ok, I think I get it now. "name" is just the local name of the function parameter, so I need to "dereference" it somehow, which is what match.call does. – static_rtti Oct 02 '13 at 10:21
  • Maybe a last question: why do you need `[-1]` after `match.call`? What does it even mean? – static_rtti Oct 02 '13 at 10:26
  • @static_rtti I add some comment in my answer to explain the -1 . It is not the easier part of the R language. Try to read what is a `call`.. – agstudy Oct 02 '13 at 10:33
  • Great, thanks a lot for all of your answers. I am aware that these are dark corners of the language, but I think I have to go through this if I want to move from superficial to real understanding. – static_rtti Oct 02 '13 at 11:50
7

Very old thread but you can also use the get command as well. It seems to work better for me.

a <- data.frame(x = 1:10, y = 11:20)

b <- function(df, name){

   get(name, df)

 }

b(a, "x")
 [1]  1  2  3  4  5  6  7  8  9 10 
Kevin
  • 1,974
  • 1
  • 19
  • 51
4

If you put the variable name between quotes when you call the function, it works:

> b <- function(data,name) { within(data,print(name)) }
> b(a, "x")
[1] "x"
    x  y
1   1  1
2   2  2
3   3  3
4   4  4
5   5  5
6   6  6
7   7  7
8   8  8
9   9  9
10 10 10
Waldir Leoncio
  • 10,853
  • 19
  • 77
  • 107