Scale raster cell in stack from -1 to 1 R

Scale raster cell in stack from -1 to 1 R



I have a raster stack with 364 layers with a daily rate of change in NDVI values.



I want to scale these values in every cell if positive from 0 to 1 and if negative from -1 to 0. So far I have only found a solution that scale values in single layers (see here: Replace specific value in each band of raster brick in R) and not along cells of multilayer objects. Additionally I have a decent amount of cells with NA for the entire time series and I'm not quite sure how to deal with this fact either.



I took the code from the previously mentioned post and tried to get it working for my problem:


norm <- function(x)-1+(x-min)*((1-(-1))/(max-min))

for(j in 1:ncell(tif))

if(is.na(sum(tif[j])))
NULL
else

cat(paste("Currently processing layer:", j,"/",ncell(tif), "n"))

min <- cellStats(tif[j],'min')
max <- cellStats(tif[j],'max')

#initialize cluster
#number of cores to use for clusterR function (max recommended: ncores - 1)
beginCluster(31)

#normalize
tif[j] <- clusterR(tif[j], calc, args=list(fun=norm), export=c('min',"max"))

#end cluster
endCluster()




I'm not quite certain if this produces the desired output. Any help is very much appreciated!




1 Answer
1



Some example data


library(raster)
r <- raster(ncol=10, nrow=10)
s <- stack(lapply(1:5, function(i) setValues(r, runif(100, -1, 1))))
# adding NAs
s[[2]][sample(100, 25, TRUE)] <- NA



For scaling (or any other operation) by cell (as requested) you can use calc together with a function that works on a vector. For example:


calc


ff <- function(i)
p <- which(i >= 0)
n <- which(i <= 0)
# positive values
if (length(p) > 0)
i[p] <- i[p] - min(i[p], na.rm=TRUE)
i[p] <- i[p] / max(i[p])

# negative values
if (length(n) > 0)
i[n] <- i[n] - max(i[n], na.rm=TRUE)
i[n] <- i[n] / abs(min(i[n]))

i



Test it


ff(c(-.3, -.1, .1, .4, .8))
#[1] -1.0000000 0.0000000 0.0000000 0.4285714 1.0000000
ff(c(-.3, -.1, .1, .4, .8, NA))
#[1] -1.0000000 0.0000000 0.0000000 0.4285714 1.0000000 NA
ff(c(-2,-1))
#[1] -1 0
ff(c(NA, NA))
#[1] NA NA



And use it


z <- calc(s, ff)



See the below to scale by layer, based on the min and max of all cell values (I first thought that this is what was asked for). Note that the functions I used below scale values from -1 to 1, but not the lowest positive value and highest negative value to zero.


minv <- abs(cellStats(s,'min'))
maxv <- cellStats(s,'max')

f1 <- function(i, mn, mx)
j <- i < 0
j[is.na(j)] <- TRUE
i[j] <- i[j] / abs(mn)
i[!j] <- i[!j] / mx
i


ss <- list()
for (i in 1:nlayers(s))
ss[[i]] <- calc(s[[i]], fun=function(x) f1(x, minv[i], maxv[i]))


ss1 <- stack(ss)



Or without a loop


f2 <- function(x, mn, mx)
x <- t(x)
i <- which(x > 0)
i[is.na(i)] <- FALSE
mxx <- x / mx
x <- x / mn
x[i] <- mxx[i]
t(x)


ss2 <- calc(s, fun=function(x) f2(x, minv, maxv))



For reference, to simply scale between 0 and 1


mnv <- cellStats(s,'min')
mxv <- cellStats(s,'max')
x <- (s - mnv) / (mxv - mnv)



To get values between -1 and 1 you can then do


y <- 2 * (x - 1)



But that way previously negative values can become positive and vice versa.



See ?raster::scale for other types of scaling.


?raster::scale






thanks for this great answer! It is almost what I need, but as far as I understood it, this solution works with the min/max values per layer and I would need one that works with the min/max per cell (kind of a local min/max) in order to scale every cell from -1 to 1, don't I?

– Benjamin Sigrist
Sep 16 '18 at 9:16






OK, I missed that because you used cellStats in your code.

– Robert Hijmans
Sep 16 '18 at 19:29



Thanks for contributing an answer to Stack Overflow!



But avoid



To learn more, see our tips on writing great answers.



Required, but never shown



Required, but never shown




By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.