I want to use a simple custom function in a moving window. I've successfully used terra::focal
for this in the past, but am now running into an error:
Error in as.vector(x, "character") : cannot coerce type 'closure' to vector of type 'character'
I'm not clear why this is happening. Other examples of this I can find on SO suggest I'm missing parentheses after the function name, but including them returns a different error instead.
I'm not set on using terra::focal
; an alternative that worked on a matrix that was as fast would also be fine. This isn't a spatial workflow, so I would actually prefer to avoid converting the matrix to spatRaster
to use focal
and then converting back again afterwards.
Example code:
# Custom function: if mean of the window is greater than the threshold
# then return the value unchanged, otherwise return the minimum value in the window.
# (Returning value number 14, as this is the central cell in a 9 x 3 window)
mean.or.min <- function(x, ...){
if(mean(x) > 10) {
return(x[[14]])
} else {
x[[14]] <- min(x)
return(x[[14]])
}
}
# Create example data
set.seed(42)
temp.matrix <- matrix(data = sample(1:30, 600000, replace = TRUE), nrow = 200)
# Convert matrix to a spatRaster
temp.rast <- terra::rast(temp.matrix)
# Moving window
temp.rast.smoothed <- terra::focal(temp.rast, w = c(9,3), fun = mean.or.min, fillvalue = 0, expand = TRUE)
As noted by IFRTM, this is due to a mishandling of warning. The warning should be that "expand is ignored for functions that are not 'built-in'". This has been fixed in the development version (1.7.33).
you can work around it by not using expand=TRUE
. The below works for me.
# simplified function
mean.or.min <- function(x, ...){
if (mean(x) > 10) {
x[14]
} else {
min(x, ...)
}
}
library(terra)
set.seed(42)
temp.matrix <- matrix(data = sample(1:30, 60000, replace = TRUE), nrow = 200)
r <- terra::rast(temp.matrix)
x <- terra::focal(r, w=c(9,3), fun=mean.or.min, fillvalue=0)
You can also do this:
y <- ifel(focal(r, w=c(9,3), mean, fillvalue=0) > 10, r,
focal(r, w=c(9,3), min, fillvalue=0))
Even though this calls focal twice, it is much faster because it uses built-in functions "mean" and "min" instead of an R-based function.
If you cannot build this up from built-in functions, you could consider focalCpp
. In this case that could be:
library(Rcpp)
cppFunction(
'NumericVector focalfun(NumericVector x, size_t ni, size_t nw) {
NumericVector out(ni);
// loop over cells
double mxval = nw * 10;
size_t start = 0;
for (size_t i=0; i<ni; i++) {
size_t end = start + nw;
double vsum = 0;
double vmin = 9999999;
for (size_t j=start; j<end; j++) {
vsum += x[j];
if (x[j] < vmin) {
vmin = x[j];
}
}
if (vsum > mxval) {
out[i] = x[start+13];
} else {
out[i] = vmin;
}
start = end;
}
return out;
}'
)
z <- terra::focalCpp(r, w=c(9,3), fun=focalfun, fillvalue=0)