`With` usage inside function (wrapper)
Asked Answered
T

3

5

I would like to write a wrapper around a custom function that takes some vectors as input (like: mtcars$hp, mtcars$am etc.) to take input as data frame name (as data parameter, eg.: mtcars) and variable names (like: hp and am), as usual in most standard function.

But I have some problems, my proposed 'demo' function (a wrapper around mean does not work.

Code:

f <- function(x, data=NULL) {
    if (!missing(data)) {
        with(data, mean(x))
    } else {
        mean(x)
    }
}

Running against a vector works of course:

> f(mtcars$hp)
[1] 146.69

But with fails unfortunatelly:

> f(hp, mtcars)
Error in with(d, mean(x)) : object 'hp' not found

While in global environment/without my custom function works right:

> with(mtcars, mean(hp))
[1] 146.69

I have tried to do some experiment with substitute, deparse and others, but without any success. Any hint would be welcomed!

Treadle answered 5/12, 2011 at 15:26 Comment(6)
Have a look at @Hadley Wickham's wiki article here: github.com/hadley/devtools/wiki/EvaluationMatelote
Shouldn't it be f(hp, mtcars)?Respectful
Another option you might want to explore is using a formula, so you'd call it slightly differently - foo(~hp,mtcars) - and then use stuff like model.frame to get the values.Retool
@Retool no need to use model.frame to get the variables.Washedup
What's the other (right?) way to do it then?Retool
I encountered a similar problem and used assign(deparse(substitute(x)), x) inside the function so that the variable could be seen. Is this "wrong"?Doukhobor
N
10

Here's the key piece of the puzzle:

f <- function(x,data=NULL) {
  eval(match.call()$x,data) # this is mtcars$hp, so just take the mean of it or whatever
}

> f(hp,mtcars)
 [1] 110 110  93 110 175 105 245  62  95 123 123 180 180 180 205 215 230  66  52  65  97 150 150 245 175  66
[27]  91 113 264 175 335 109

# it even works without a data.frame specified:
> f(seq(10))
 [1]  1  2  3  4  5  6  7  8  9 10

See @Andrie's link to @Hadley's document for an explanation of why it works. See @Hadley's note for a critical caveat: f() cannot be run from inside another function.

Basically R uses lazy evaluation (e.g. it doesn't evaluate things until they're actually used). So you can get away with passing it hp because it remains an unevaluated symbol until it appears somewhere. Since match.call grabs it as a symbol and waits to evaluate it, all is well.

Then eval evaluates it in the specified environment. According to ?eval, the second argument represents:

The environment in which expr is to be evaluated. May also be NULL, a list, a data frame, a pairlist or an integer as specified to sys.call.

Therefore you're in good shape with either NULL (if you're not passing a data.frame) or a data.frame.

Proof of lazy evaluation is that this doesn't return an error (since x is never used in the function):

> g <- function(x) {
+   0
+ }
> g(hp)
[1] 0
Nimmons answered 5/12, 2011 at 16:0 Comment(5)
What an elegant idiom. Thanks to you (and Hadley) for that.Cristophercristy
Mostly Hadley. I remembered it but not precisely enough to pull it out of my hat without referencing his (awesome) wiki.Nimmons
Thanks for detailed explanation, I really do not mind having asked this question!Treadle
@gsk3 glad you found the article useful! Note the standard warning however: you can only use f interactively, not from another function. Try g <- function(...) f(...), or g(hp,mtcars); h <- function(x, y) f(x, y); h(hp,mtcars)Washedup
@Washedup Thanks for pointing out the caveat. I've added it to the answer. I'm not sure what daroczig wanted, but if he needed that ability, that would push it more towards a formula notation as Spacedman (and I think your document as well) points out. Enjoyed your ggplot2 future preview a few nights ago, by the way!Nimmons
P
3
f <- function(x, data=NULL) {
    if (!missing(data)) { colname=deparse(substitute(x))
         mean(data[[colname]])
    } else {
        mean(x)
    }
}

 f(hp, mtcars)
[1] 146.6875

(Admittedly not as compact as @gsk's and I think I will try to remember his method over mine. And thanks to Josh O'Brien for pointing out an error that's now been fixed.)

Pinnule answered 5/12, 2011 at 16:10 Comment(0)
T
-1

try this:

f <- function(x, data = NULL) {
     if (is.null(data)) {
         mean(x)
     } else { 
         attach(data)
         mean(x)
         detach(data)
     }
 }

Also in your example you enter the data set instead of the column. Your example should be f(hp, mtcars)

Tycoon answered 5/12, 2011 at 15:34 Comment(1)
Nice effort, but missing a parenthesis and I'm not sure I'd prefer attach to with, and it f(hp,mtcars) still fails with "hp not found".Nimmons

© 2022 - 2024 — McMap. All rights reserved.