6

There are many discussions about scoping, environments and functions already. See e.g. here or here. However, I am not sure I have found a good solution to the following problem:

 df <- data.frame(id=rep(LETTERS[1:2],each=2), x=1:4)
 d <- -1
 myfun <- function(df, d){
          require(plyr)
          new.dat <- ddply(df, .(id), transform, x=x*d) 
          return(new.dat)}
 myfun(df, 1)

You can easily verify that the globally defined d=-1 was used, instead of the d=1 as provided in the argument. (If no globally defined d exists, then a object not found message is returned) The big question is now: how do I make the d argument to the function used instead of the globally defined d?

I was under the impression that the following should work:

      myfun2 <- function(df, d){
          here <- environment()
          new.dat <- ddply(df, .(id), transform, x=x*with(here,d)) 
          return(new.dat)}
      myfun2(df, 1)

It is my understanding that with(here, d) retrieves the object d from the environment here. So, the result should be 1. An error is returned, though, saying

  Error in eval(substitute(expr), data, enclos = parent.frame()) : 
   invalid 'envir' argument of type 'closure' 

I am not sure I understand why this does not work, and I would be happy if anyone could shed some light on this, or if you could provide alternative solutions. Note that wrapping the entire ddply-statement into with(...) does not seem to help either.

A solution that does work is to attach the current environment inside the function:

 myfun3 <- function(df, d){
   here <- environment()
   attach(here)
   new.dat <- ddply(df, .(id), transform, x=x*d) 
   detach(here)
   return(new.dat)
 }

but I don't like this solution since it works by masking the globally defined d with the local d, which I think is not very elegant.

Any comments / pointers are appreciated.

Community
  • 1
  • 1
coffeinjunky
  • 11,254
  • 39
  • 57
  • `new.dat <- ddply(df, .(id), function(x,d){x<-x*d}, d)` sort of works, but not quite – nico Jul 05 '14 at 14:33

1 Answers1

4

To wake up the lazy evaluation and be sure that you are using the local d argument, use force. Add this line:

d <- force(d)

to the start of myfun.


OK, it seems that I misunderstood the problem. In this case, the problem is that ddply has non-standard evaluation and only looks inside df for variables when applying transformations, so it doesn't see the local d even if you force it. As Hadley pointed out, a you need to wrap transform insdie a call to here.

myfun <- function(df, d){
      require(plyr)
      new.dat <- ddply(df, .(id), here(transform), x=x*d) 
      return(new.dat)}

Minor unrelated code improvements:
Since you aren't doing anything with the case when require returns FALSE, you should swap it with library.
mutate is an improved drop-in replacement for alternative to transform.
You don't need the explicit return.

myfun <- function(df, d){
      library(plyr)
      ddply(df, .(id), here(mutate), x=x*d)}
Richie Cotton
  • 118,240
  • 47
  • 247
  • 360
  • 1
    `mutate` is **not** a drop-in replacement for `transform`; compare `transform(d, x=x*d, y=x)` and `mutate(d, x=x*d, y=x)` – baptiste Jul 06 '14 at 08:01
  • Thanks! That was instructive. I have still not understood why the `with(here, d)`-solution does not work though. Providing a number instead of `d` works (obviously). Why is writing `with(here, d)` not the same thing? My understanding is that both should be equivalent from `ddply`'s perspective. – coffeinjunky Jul 06 '14 at 09:26
  • 1
    @coffeinjunky within `ddply`, the environment `here` isn't accessible; the error message is confusing because "here" is also the name of a plyr function. – baptiste Jul 06 '14 at 09:45
  • Good point! Changing it to `here2` leads to an `object here2 not found` message. So, `ddply` evaluates everything in the global environment, i.e. `with(here2, d)` is evaluated in the global environment, but there, the local environment `here2` is not visible despite the explicit address? Is this what causes the issue? – coffeinjunky Jul 06 '14 at 09:58
  • 1
    yes – it's like addressing a postcard to someone on Mars or Tatooine; make it as specific as you want, the post office won't reach it. – baptiste Jul 06 '14 at 10:46