I have created the following function/example as a generic way to display variable labels in tables and so forth:
#' Function to prettify the output of another function using a `var.labels` attribute
#' This is particularly useful in combination with read.dta et al.
#' @param dat A data.frame with attr `var.labels` giving descriptions of variables
#' @param expr An expression to evaluate with pretty var.labels
#' @return The result of the expression, with variable names replaced with their labels
#' @examples
#' testDF <- data.frame( a=seq(10),b=runif(10),c=rnorm(10) )
#' attr(testDF,"var.labels") <- c("Identifier","Important Data","Lies, Damn Lies, Statistics")
#' prettify( testDF, quote(str(dat)) )
prettify <- function( dat, expr ) {
labels <- attr(dat,"var.labels")
for(i in seq(ncol(dat))) colnames(dat)[i] <- labels[i]
attr(dat,"var.labels") <- NULL
eval( expr )
}
I'd rather the user not have to quote the expression being passed in, however.
replicate
does just that, using this code:
eval.parent(substitute(function(...) expr))
Yet I don't understand how it works, and as is typical for attempts to replicate without comprehension, my attempts to simply copy this code or tinker with it have all failed.
How do I write a function with an unevaluated expression as input, without requiring the user to quote
their expression? I assume the answer will rely heavily on lazy evaluation.
I think to do this in this case you just need eval(substitute(expr))
. expr
is a promise, and we can either get the value of the promise by using expr
directly, or the content of the promise, by using substitute
. See http://cran.r-project.org/doc/manuals/R-lang.html#Promise-objects for details. The content of the promise is a call
, so we just eval
that to get the new result.
prettify <- function( dat, expr ) {
labels <- attr(dat,"var.labels")
for(i in seq(ncol(dat))) colnames(dat)[i] <- labels[i]
attr(dat,"var.labels") <- NULL
eval(substitute(expr))
}
> prettify( testDF, str(dat))
'data.frame': 10 obs. of 3 variables:
$ Identifier : int 1 2 3 4 5 6 7 8 9 10
$ Important Data : num 0.336 0.9479 0.1379 0.94 0.0484 ...
$ Lies, Damn Lies, Statistics: num 1.398 0.654 0.268 -0.397 -0.41 ...
In a suggested edit, @user2103369 suggests that replicate
is different because it uses sapply
to get multiple evaluations, so it needs a function rather than a call.
Interestingly, the promise acts differently depending on if the argument is the default argument or added by the user; see below. I think SoDA addresses this but I don't have it handy. This function prints the value of the promise, evaluates it with eval
, and then evaluates it directly.
foo <- function(a, b=a+1) {
print(substitute(b))
print(eval(substitute(b)))
b
}
Evaluating it directly results in an error when the user supplies the value.
> foo(a=2, b=a+1)
a + 1
[1] 3
Error in foo(a = 2, b = a + 1) : object 'a' not found
But the default value works.
> foo(a=2)
a + 1
[1] 3
[1] 3
In a suggested edit, @user2103369 says that the default argument is evaluated inside the function, while an explicit argument is evaluated in the calling frame. So in this case, the user supplied value fails because a
is not visible in the calling frame.
However, to me (though the OP disagrees; I'm leaving this part for future readers of this answer), this feels like a case where it's more natural to use a function as the second parameter, like this; for one, this means the user doesn't have to know that it's called dat
within the function.
prettify <- function( dat, FUN ) {
f <- match.fun(FUN)
labels <- attr(dat,"var.labels")
for(i in seq(ncol(dat))) colnames(dat)[i] <- labels[i]
attr(dat,"var.labels") <- NULL
f(dat)
}
Then it can be called with an anonymous function, which is exactly what you're looking for, I think, except that the user has to type function(x)
as well.
> prettify( testDF, function(x) str(x) )
'data.frame': 10 obs. of 3 variables:
$ Identifier : int 1 2 3 4 5 6 7 8 9 10
$ Important Data : num 0.296 0.707 0.883 0.821 0.724 ...
$ Lies, Damn Lies, Statistics: num -1.1506 0.4846 -1.824 -0.397 0.0898 ...
Or in simple cases, as in your example, with just the name of the function.
> prettify( testDF, str)
'data.frame': 10 obs. of 3 variables:
$ Identifier : int 1 2 3 4 5 6 7 8 9 10
$ Important Data : num 0.296 0.707 0.883 0.821 0.724 ...
$ Lies, Damn Lies, Statistics: num -1.1506 0.4846 -1.824 -0.397 0.0898 ...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With