Tom Wenseleers Tom Wenseleers - 5 days ago 5
R Question

R: sequentially applying an arbitrarty list of functions with arguments to a matrix

I have a list of filtering functions

f1,f2,f3,f4,....
which take a matrix
m
and a number of options as input and return a subset of the rows of matrix as output. Now I would like to be able to define in an orderly way some meta-filtering function settings
metaf1, metaf2, metaf3,...
which would specify the sequential application of a specified nr of filtering functions, e.g. first
f2
and then
f3
, using given options for each. I would like to store these filtering settings in a list of say class
"metafiltering"
, and then have another function apply the filtering steps specified in a given
metafiltering
object. My idea would be able to in this way allow filtering settings to be stored and applied in an orderly way. How would I achieve this in the most elegant way in R? Or is there perhaps other convenient methods to achieve something like this?

EDIT: to give an example, say I have matrix

m=replicate(10, rnorm(20))


and filtering functions (these are just examples, obviously mine are more complicated :-) )

f1=function(m,opt1,opt2) {
return(m[(m[,2]>opt1)&(m[,1]>opt2),])
}
f2=function(m,opt1) {
return(m[(m[,3]>opt1),])
}


And I have defined the following
metafiltering
settings of specific class which would specify two functions which would have to be applied sequentially to matrix m

metafilterfuncs=list(fun1=f1(opt1=0.1,opt2=0.2),fun2=f2(opt1=0.5))
class("metafilterfuncs")="metafiltering"


The question I have then is how I could apply the filtering steps of an arbitrary
metafiltering
function object to given matrix m using the specified functions and settings?

Answer

pryr has a function, compose, like what you need, but it doesn't quite cut it. The compose function requires the functions to be given one by one, not in a list, and it cannot take arguments. It's also oddly placed in that package. A similar function can be found in plyr, namely each. But this function does not apply functions sequentially, but individually and outputs a named vector (list?).

agstudy provided a solution above, but it suffers from a problem: it can only take scalar arguments because it gives the arguments in a named vector. The solution to this is to use a named list instead. So, here's an improved function to replace the one in pryr.

compose2 = function(x, funcs, args, msg_intermediate = F) {
  if (length(funcs) != length(args)) stop("length of functions and arguments must match")
  for (i in seq_along(funcs)) {
    x = do.call(what = funcs[[i]], args = c(x, args[[i]]))
    if ((i != length(funcs)) && msg_intermediate) message(x)
  }
  x
}

msg_intermediate is a nice debugging argument that messages the intermediate results, so one can easier understand what happens.

Test it:

adder = function(x, n) x + n
compose2(0,
         funcs = list(adder, adder, adder),
         args = list(list(n = 1), list(n = 2), list(n = 3)),
         msg_intermediate = T
         )

Outputs:

1
3
[1] 6

This is what you get when you take 0, then add 1 (=1), then add 2 (=3), then add 3 (=6).

The args argument for compose2 takes a list of lists, so that one can supply non-scalar function arguments. Here's an example:

add_div = function(x, n, d) (x + n) / d
compose2(0,
         funcs = list(add_div, add_div, add_div),
         args = list(list(n = 1, d = 1), list(n = 2, d = 2), list(n = 3, d = 3)),
         msg_intermediate = T
)

Output:

1
1.5
[1] 1.5

Which is what you get when you take 0, add 1, divide by 1 (=1), then take 1, add 2 then divide by 2 (=1.5), then take 1.5, add 3 and then divide by 3 (=1.5).