First, lets create your example data. Note that in your example the "a" object is not created so, I am assuming that it is your raster stack. I am stacking the conditional raster with the "a" raster stack to simplify the function and the object passed to raster::overlay
.
library(raster)
s = data.frame(class = c("1","1","1","2","2","2","3","3","3"),
value = c(.33,.66,.99,.25,.5,0.75,.1,.4,.9))
cr = raster(matrix(c(1,1,1,2,2,2,3,3,3),nrow=3,ncol=3))
a <- do.call(stack, replicate(3,
raster(matrix(sample(1:9, 9),
nrow=3, ncol=3))))
a <- stack(cr, a)
The first thing to note is that, in the "trans" (now "s") data.frame object, you have replicated values for each conditional "class". As such, in your function trans$value[trans$class==1]
will return three scalars and not one, which is what is currently indexed. If these represent weights for each class this is not a bad thing as long as they are ordered ie., in class 1, c(0.33, 0.66, 0.99) represents the scalars for the associated raster layers 1:3.
In a function like raster::overlay
values are recycled so, if your function writes out 3 values then they will be assigned to three separate rasters in a stack object. We can rely on the fact that in the function x will always have the same number of values as layers in the stack, even if they are NA's.
If this is the case then your function can be written as such. Please note that due to limitations in passing additional function arguments to raster::overlay
, I am calling your scalar data.frame object from the global environment and not as an argument. Your function will need to reflect this explicit object naming condition.
f_trans = function(y){
if(y[[1]] == 1) {
x = y[-1] * s[s$class==1,]$value
} else if(y[[1]] == 2) {
x = y[-1] * s[s$class==2,]$value
} else if(y[[1]] >= 3) {
x = y[-1] * s[s$class==3,]$value
} else {
x = rep(NA,length(y[-1])) }
return(x)
}
Now, lets test the function for each condition. We can see that it returns the expected scalar values. Now we can be confident that the scalars match the vector that they are being applied to.
f_trans(c(1, rep(1,3)))
f_trans(c(2, rep(1,3)))
f_trans(c(3, rep(1,3)))
The function is now appropriate to pass to a function like raster::overlay
.
( r <- overlay(a, fun=f_trans) )
# Check first value
( x <- as.numeric(as.matrix(a)[1,]) )
f_trans(x)
as.matrix(r)[1,]
Note that you can also do this in terra, which is the replacement for the raster package, and it will be much faster in practice. Here is the same analysis in terra. Note that we can now have a function argument for our scalar object that is then specified in terra::app
.
library(terra)
sl = data.frame(class = c("1","1","1","2","2","2","3","3","3"),
value = c(.33,.66,.99,.25,.5,0.75,.1,.4,.9))
a <- c(rast(matrix(c(1,1,1,2,2,2,3,3,3),nrow=3,ncol=3)),
do.call(c, replicate(3, rast(matrix(sample(1:9, 9),
nrow=3, ncol=3)))))
f_trans = function(y, s){
if(y[[1]] == 1) {
x = y[-1] * s[s$class==1,]$value
} else if(y[[1]] == 2) {
x = y[-1] * s[s$class==2,]$value
} else if(y[[1]] >= 3) {
x = y[-1] * s[s$class==3,]$value
} else {
x = rep(NA,length(y[-1])) }
return(x)
}
( r <- app(a, fun=f_trans, s=sl) )