Function to apply arbitrary functions in R - r

How would one implement in R the function apply.func(func, arg.list), which takes an arbitrary function func and a suitable list arg.list as arguments, and returns the result of calling func with the arguments contained in arg.list. E.g.
apply.func(foo, list(x="A", y=1, z=TRUE))
is equivalent to
foo(x="A", y=1, z=TRUE)
Thanks!
P.S. FWIW, the Python equivalent of apply.func would be something like
def apply_func(func, arg_list):
return func(*arg_list)
or
def apply_func(func, kwarg_dict):
return func(**kwarg_dict)
or some variant thereof.

I think do.call is what you're looking for. You can read about it via ?do.call.
The classic example of how folks use do.call is to rbind data frames or matrices together:
d1 <- data.frame(x = 1:5,y = letters[1:5])
d2 <- data.frame(x = 6:10,y = letters[6:10])
do.call(rbind,list(d1,d2))
Here's another fairly trivial example using sum:
do.call(sum,list(1:5,runif(10)))

R allows functions to be passed as arguments to functions. This means you can define apply.func as follows (where f is a function and ... indicates all other parameters:
apply.func <- function(f, ...)f(...)
You can then use apply.func to and specify any function where the parameters makes sense:
apply.func(paste, 1, 2, 3)
[1] "1 2 3"
apply.func(sum, 1, 2, 3)
[1] 6
However, note that the following may not produce the results you expected, since mean takes a vector as an argument:
apply.func(mean, 1, 2, 3)
[1] 1
Note that there is also a base R function called do.call which effectively does the same thing.

Related

In R, duplicate argument names in function and function within the function. How can I pass an argument directly to dot dot dot?

In this case, how might I pass an argument directly to the dot dot dot in R? Without changing the functions, and preferably not relying on position E.g.
fun_inner <- function(other_arg = NA, a){
print(paste("inner:", a))
a
}
fun_outer <- function(a, ...){
outer <- a
print(paste("outer:", a))
inner <- fun_inner(...)
paste("inner not equal to outer?", inner != outer)
}
e.g. something like this... but this obviously does not work
fun_outer(a = 1, ... = list(a = 2))
Again, constraints being that I'd prefer to only modify the inputted arguments (rather than rewriting these functions)
# e.g. could create wrapper function for `fun_outer()` to call
# that has w/ different names (or other analagous approach),
# but would rather avoid...
fun_inner_wrapper <- function(other_arg, b){
fun_inner(other_arg = other_arg, a = b)
}
and would also prefer not to rely simply on argument position.
# e.g. this inputs different values for `a` argument for `fun_inner()` and
# `fun_outer()`, but is reliant on giving a positional input for `other_arg` which I
# would prefer not to do...
fun_outer(1, 2, 3)
[1] "outer: 1"
[1] "inner: 3"
[1] "inner not equal to outer? TRUE"

Is there an R equivalent of Python's '*'?

In python you can do:
val = [1, 2, 3]
def f(a, b, c):
return(a+b+c)
f(*val)
>>>6
But is there an R equivalent to passing a list/vector to a function and having it unpack the list/vector as the arguments to the function?
val <- c(1, 2, 3)
f <- function(a,
b,
c) {
a+b+c
}
#f(*val)
Base R
do.call In R it is do.call. The first argument is the function or a character string giving the name of the function and the second argument is a list whose components will be passed as individual arguments to the function. No packages are used.
val <- c(1, 2, 3)
f <- function(a, b, c) a+b+c
do.call("f", as.list(val))
## [1] 6
Reduce Another approach is to curry f creating a new function with the first argument fixed, repeatedly doing so using Reduce to handle each successive argument. No packages are used.
Reduce(function(f, x, ...) function(...) f(x, ...), val, init = f)()
## [1] 6
purrr package
invoke The purrr package has invoke which basically just calls do.call but it will also convert the second argument to a list if it is not already a list:
library(purrr)
invoke(f, val)
## [1] 6
lift purrr also has lift which will convert a function that takes individual arguments to a new function that takes a list or vector. It also wraps do.call
lift(f)(val)
## [1] 6
partial purrr also has partial which will curry the function creating a new function with the first argument fixed taking only the remaining arguments so using reduce (also in purrr) to repeatedly invoke such currying:
reduce(val, partial, .init = f)()
## [1] 6
functional package
Curry Curry from the functional package could also be used to fix the first argument. When used together with Reduce from base R to repeatedly apply Curry it gives the same result. Note that Curry uses do.call internally.
library(functional)
Reduce(Curry, init = f, val)()
## [1] 6
Another option is to lift the domain of a function using lift() from the purrr package. Your definition of f takes multiple arguments, which in R terms is known as "dots". You can change its signature to accept a vector instead:
f2 <- purrr::lift_dv(f)
f2(val)
## [1] 6
# Or as a one-liner
purrr::lift_dv(f)(val)

Replacing elements in a list of lists

The apply functions in R are a nice way to simplify for loops to get to an output. Is there an equivalent function that helps one avoid for loops when replacing the values of a vector? This is better understood by example...
# Take this list for example
x = list( list(a=1,b=2), list(a=3,b=4), list(a=5,b=6) )
# To get all of the "a" elements from each list, I can do
vapply(x,"[[",1,"a")
[1] 1 3 5
# If I want to change all of the "a" elements, I cannot do
vapply(x,"[[",1,"a") = 10:12
Error in vapply(x, "[[", 1, "a") = 10:12 :
could not find function "vapply<-"
# (this error was expected)
# Instead I must do something like this...
new.a = 10:12
for(i in seq_along(x)) x[[i]]$a = new.a[i]
Is there a simpler or faster alternative to using a loop?
One option would be to first unlist the list x, then replace the values named "a", and then relist the new list u based on the list structure of x.
u <- unlist(x)
u[names(u) == "a"] <- 10:12
relist(u, x)
vapply is a special case of sapply where you need to pre-specify the return type.
If you a multivariate version of sapply, the function you are looking for is mapply (or Map which is a wrapper with SIMPLIFY=FALSE`)
In general, functions with side-effects are frowned upon in R. The standard approach would be to create a new object when modifying.
You could use modlifyList to perform the modifications
xnew <- Map(modifyList, x, val = lapply(10:12,function(x) list(a = x)))

Positional Matching Trickery

I wrote this nifty function to apply a function for every combination of vectorized arguments:
require(plyr)
require(ggplot2)
###eapply accepts a function and and a call to expand grid
###where columns created by expand.grid must correspond to arguments of fun
##each row created by expand.grid will be called by fun independently
###arguments
##fun either a function or a non-empty character string naming the function to be called.
###... vectors, factors, or a list containing thse
###value
###a data frame
##Details
##at this time, elements of ... must be at least partially named to match args of fun
##positional matching does not work
###from the ddply documentation page:
###The most unambiguous behaviour is achieved when fun returns a data frame - in that case pieces will
###be combined with rbind.fill. If fun returns an atomic vector of fixed length, it will be rbinded
###together and converted to a data frame. Any other values will result in an error.
eapply <- function(fun,...){
if(!is.character(fun)) fun <- as.character(substitute(fun))
adply(
expand.grid(...),
1,
function(x,fun) do.call(fun,x),
fun
)
}
##example use:
m <- function(n,visit.cost){
if(n*visit.cost < 250){
c("total.cost"=n*visit.cost)
}else{
c("total.cost"=250 + (n*visit.cost-250)*.25)
}
}
d <- eapply(m, n=1:30, visit.cost=c(40,60,80,100))
ggplot(d,aes(x=n,y=total.cost,color=as.factor(visit.cost),group=visit.cost)) + geom_line()
How can I rewrite the function such that the arguments passed to expand.grid need not be named:
d <- eapply(m, 1:30, c(40,60,80,100))
Alternatively, are there any existing functions that have similar functionality?
Not the most elegant but this works. Most importantly, it allows you to pass variables to expand.grid without naming them.
eeyore <- function(fun, ...){
if(!is.character(fun)) fun <- as.character(substitute(fun))
f <- match.fun(fun)
args <- as.list(substitute(list(...)))[-1]
foo <- expand.grid(llply(args, eval))
foo$F <- apply(foo, 1, function(x) { f(x[[1]], x[[2]])})
foo
}
d <- eeyore(m, 1:30, c(40,60,80,100))

extracting a function call name from a function call

Does anyone know how to write a function F which takes a function call (say, mean(x = 1:10)) as an argument, and returns just the name of the function being invoked (mean)?
My best attempts so far are summarised below
(function(x1){
return(deparse(substitute(x1)))
})(mean(x = 1:10))
### 'mean(x = 1:10)'
Changing x1 (the function call) to an expression before de-parsing doesn't seem to help much: that returns
(function(x1){
return(deparse(as.expression(substitute(x1))))
})(mean(x = 1:10))
# "expression(mean(x = 1:10))"
If at all possible, I'd like to be able to use anonymous functions as an argument too, so F should return (function(x) print (x)) for (function(x) print (x))(1). If you need any clarification feel free to comment. Thanks.
edit1: just to note, I'd like to avoid checking for the first parenthesis and excising the the code before it (for "mean(x = 1:10)" that would return "mean"), as "bad(Fun_nAme" is actually a legal function name in R.
Question Answered: Josh O'Brien's answer was perfect: the function F that satisfies the above conditions is
F <- function(x) deparse(substitute(x)[[1]])
It works nicely for binary operators, standard functions and anonymous functions.
Here's a simple function that does what you want:
F <- function(x) deparse(substitute(x)[[1]])
F(mean(x=1:10))
# [1] "mean"
F((function(x) print (x))(1))
# [1] "(function(x) print(x))"
F(9+7)
# [1] "+"
I don't know what you're trying to do or if it's a good idea or if this is what you want but here's a whack at it with regex:
FUN <- function(x1){
z <- deparse(substitute(x1))
list(fun=strsplit(z, "\\(")[[c(1, 1)]],
eval=x1)
}
FUN(mean(x = 1:10))

Resources