0

I have a named list of some algorithm, which may look like this

> algorithm
$rBinarize
$rBinarize$x
[1] 40

and may contain an arbitrary number of additional algorithms. Each algorithm carries out an operation on a spatial object (spObj, of class raster) and returns the modified raster. I would then like to use do.call to (s)apply this (and all the other) algorithm(s) within a parent function to the original input. However, what I additionally want to achieve is a sequential application of the defined algorithms, i.e. on the output of the previous algorithm. I came up with the following code, but I am curious about additional suggestions to improve the performance, given it is possible.

if(sequential){
  for(k in seq_along(algorithm)){
    if(length(algorithm[[k]])==0){
      args <- c(spObj = spObj)
    } else{
      args <- c(algorithm[[k]], spObj = spObj)
    }
    spObj <- do.call(what = names(algorithm)[k], args = args)
  }
} else{
  algorithm2 <- lapply(algorithm, function(x) x <- c(x, spObj = spObj))
  modified <- sapply(seq_along(algorithm2), function(j) do.call(what = names(algorithm2)[[j]], args = algorithm2[[j]]))
}

Wouldn't it possible to use some sort of apply() construction instead of the for-loop? I am not sure if I simply don't understand the logic of apply/do.call sufficiently or if this is actually not possible in R.


I modified to for-loop to make it comparable with Davids suggestion and ran a microbenchmark on it:

microbenchmark(a = for(k in seq_along(alg)){
  if(length(alg[[k]][-1])==0){
    args <- c(spObj = spObj)
  } else{
    args <- c(alg[[k]][-1], spObj = spObj)
  }
  spObj <- do.call(what = alg[[k]]$algorithm, args = args)
},
b = Reduce(f = function(x, y) do.call(what = y$algorithm, args = c(list(x), y[-1])),
       x = alg,
       init = spObj))

Which resulted in

Unit: milliseconds
 expr      min       lq     mean   median       uq      max neval
    a 33.36777 35.22067 39.60699 36.79661 40.75072 152.0171   100
    b 33.35236 35.39173 40.32860 37.51993 40.25102 154.0441   100

Is this one of these examples where a for-loop is in fact not slower than any other solution?

1 Answers1

1

You can use the built-in Reduce function.

As a simple example, suppose your chain looked like this:

algorithms <- list(list(func = "sin"),
                   list(func = "cos"),
                   list(func = "log", base = 2))

You'd like this to apply the equivalent of log(cos(sin(x)), 2). Note that I've changed your input structure to have an item in each list called func rather than have that be the names.

You can then apply these with:

Reduce(function(x, y) do.call(y$func, c(list(x), y[-1])),
       algorithms,
       init = spObj)

For example, if spObj starts as 10, this results in -0.2249337, which you'll notice is the same as log(cos(sin(spObj)), 2).

You may need to adapt this slightly to your use case depending on the functions you're using, but Reduce is generally what you're looking for.

David Robinson
  • 77,383
  • 16
  • 167
  • 187
  • awesome, I will try it later, but looks like the solution. Thanks! – Steffen Ehrmann Apr 12 '17 at 13:00
  • This does, however, not result in better performance of the code. – Steffen Ehrmann Apr 12 '17 at 15:07
  • @SteffenEhrmann Why would you expect it to? 1. The reason functional patterns can sometimes speed up code involves how they build and modify data structures, but that doesn't apply to this case. 2. The vast majority of your function time is surely spent within the functions at each step (especially if they're spatial algorithms), not in the iteration, so no change here would help – David Robinson Apr 12 '17 at 15:11
  • 1
    @SteffenEhrmann Here's a good discussion of [when functionals are useful as an alternative to loops](http://adv-r.had.co.nz/Functionals.html)- the main goal is usually expressiveness rather than efficiency – David Robinson Apr 12 '17 at 15:13
  • Ok, thanks for resolving it. I wasn't aware about these things, got my education in another topic ;). – Steffen Ehrmann Apr 12 '17 at 15:16