Programming-safe version of subset - to evaluate its condition while called from another function
r
Solution
Just because it's such mind-bending fun (??), here is a slightly different solution that addresses a problem Hadley pointed to in comments to my accepted solution.
Hadley posted a gist demonstrating a situation in which my accepted function goes awry. The twist in that example (copied below) is that a symbol passed to `SUBSET()` is defined in the body (rather than the arguments) of one of the calling functions; it thus gets captured by `substitute()` instead of the intended global variable. Confusing stuff, I know.
f <- function() {
cyl <- 4
g()
}
g <- function() {
SUBSET(mtcars, cyl == 4)$cyl
}
f()
Here is a better function that will only substitute the values of symbols found in calling functions' argument lists. It works in all of the situations that Hadley or I have so far proposed.
SUBSET <- function(`_dat`, expr) {
ff <- sys.frames()
n <- length(ff)
ex <- substitute(expr)
ii <- seq_len(n)
for(i in ii) {
## 'which' is the frame number, and 'n' is # of frames to go back.
margs <- as.list(match.call(definition = sys.function(n - i),
call = sys.call(sys.parent(i))))[-1]
ex <- eval(substitute(substitute(x, env = ll),
env = list(x = ex, ll = margs)))
}
`_dat`[eval(ex, envir = `_dat`),]
}
## Works in Hadley's counterexample ...
f()
# [1] 4 4 4 4 4 4 4 4 4 4 4
## ... and in my original test cases.
sub <- function(x, condition) SUBSET(x, condition)
sub2 <- function(AA, BB) sub(AA, BB)
a <- SUBSET(mtcars, cyl == 4) ## Direct call to SUBSET()
b <- sub(mtcars, cyl == 4) ## SUBSET() called one level down
c <- sub2(mtcars, cyl == 4)
all(identical(a, b), identical(b, c))
# [1] TRUE
IMPORTANT: Please note that this still is not (nor can it be made into) a generally useful function. There's simply no way for the function to know which symbols you want it to use in all of the substitutions it performs as it works up the call stack. There are many situations in which users would want it to use the values of symbols assigned to within function bodies, but this function will always ignore those.
Problem
As `subset()` manual states: Warning: This is a convenience function intended for use interactively I learned from this great article not only the secret behind this warning, but a good understanding of `substitute()`, `match.call()`, `eval()`, `quote()`, `call`, `promise` and other related R subjects, that are a little bit complicated. Now I understand what's the warning above for. A super-simple implementation of `subset()` could be as follows: ``` subset = function(x, condition) x[eval(substitute(condition), envir=x),] ``` While `subset(mtcars, cyl==4)` returns the table of rows in `mtcars` that satisfy `cyl==4`, enveloping `subset()` in another function fails: ``` sub = function(x, condition) subset(x, condition) sub(mtcars, cyl == 4) # Error in eval(expr, envir, enclos) : object 'cyl' not found ``` Using the original version of `subset()` also produces exactly the same error condition. This is due to the limitation of `substitute()-eval()` pair: It works fine while `condition` is `cyl==4`, but when the `condition` is passed through the enveloping function `sub()`, the `condition` argument of `subset()` will be no longer `cyl==4`, but the nested `condition` in the `sub()` body, and the `eval()` fails - it's a bit complicated. But does it exist any other implementation of `subset()` with exactly the same arguments that would be programming-safe - i.e. able to evaluate its condition while it's called by another function?