Reputation: 685
I have two rasters and I would like them to have the same levels and in the same order. However, one of the rasters does not have all the levels that the other does but I have managed to add these levels in using levels(rast) <- all_labels
.
My issue is that now the two RATs have all the same levels (even though some of the levels are not seen in one of the actual rasters), but the levels are in different orders, and therefore, the same label is associated with a different integer:
E.g.
#Showing the first 5 of each to illustrate the issue
#original raster
[[1]]
ID label
1 0 Ocean
2 1 Urban Ecosystem
3 2 Bare Ground
4 3 Coastal Ecosystem
5 4 Alpine Ecosystem
...
#New raster
ID label
1 0 Bare Ground
2 1 Urban Ecosystem
3 2 Coastal Ecosystem
4 3 Ocean
5 4 Alpine Ecosystem
...
So, what I would like to do is have the labels correspond to the same integer. If this were a base R problem I would use factor(..., levels = c(...))
to simply change the ordering of the labels, which would update the underlying integers in the dataframe accordingly.
e.g.
#Creating a factor which has the levels in alphabetical order
incorrect_factor <- factor(c(one, two, three, four))
incorrect_factor
#Changing ordering
factor_reordered <- factor(my_factor, levels = c("one", "two", "three", "four"))
factor_reordered
Is there a way to do the same kind of thing with terra
? I think I can see a way to do it with terra::classify
to change the actual integers; essentially manually re-classifying the underlying integers in the problem raster to match my target raster but I am not certain it would work and it would make for a very error prone bit of code...
Below is some code that I pulled from examples to create a categorical raster to create a small reprex of the problem:
#Creating a raster (using the terra::cats example code)
set.seed(0)
r <- rast(nrows=10, ncols=10)
values(r) <- sample(3, ncell(r), replace=TRUE)
cls <- data.frame(id=1:3, cover=c("forest", "water", "urban"))
levels(r) <- cls
#Original
plot(r)
levels(r)
#Second raster with variables in a different order - this would be the one to change to match the above raster with the labels in the same order and the integers updated to reflect this.
r_2 <- rast(nrows=10, ncols=10)
values(r_2) <- sample(3, ncell(r), replace=TRUE)
cls <- data.frame(id=1:3, cover=c("water", "urban","forest"))
levels(r_2) <- cls
plot(r_2)
levels(r_2)
I have googled this quite a bit and I haven't found any thing that addresses the issue I am having but if I missed anything please let me know!
Thank you in advance!
Upvotes: 3
Views: 1240
Reputation: 47036
There could be a method for this, but it is not that hard to do this "manually". Note that I am using terra version 1.5.44, which is currently the development version. You can install it with install.packages('terra', repos='https://rspatial.r-universe.dev')
Example data
library(terra)
#terra 1.5.44
set.seed(0)
r <- rast(nrows=10, ncols=10)
values(r) <- sample(3, ncell(r), replace=TRUE)
levels(r) <- data.frame(id=1:3, cover=c("forest", "water", "urban"))
r_2 <- setValues(r, sample(3, ncell(r), replace=TRUE))
levels(r_2) <- data.frame(id=1:3, cover=c("water", "urban","forest"))
levels(r)[[1]]
# id cover
#1 1 forest
#2 2 water
#3 3 urban
levels(r_2)[[1]]
# id cover
#1 1 water
#2 2 urban
#3 3 forest
Solution
First merge the labels and their codes
m <- merge(levels(r)[[1]], levels(r_2)[[1]], by=2, all=TRUE)
m
# cover id.x id.y
#1 forest 1 3
#2 urban 3 2
#3 water 2 1
# for labels that do not occur in the first raster
# not relevant in this example
m <- m[!is.na(m[,2]), ]
Use subst
and set the new levels
s <- subst(r_2, m[,3], m[,2])
levels(s) <- levels(r)
levels(s)[[1]]
# id cover
#1 1 forest
#2 2 water
#3 3 urban
Upvotes: 4
Reputation: 1281
The principles in Robert Hijman's example worked for me, but the code of the example didn't. Here's an example that I got to work.
#sample data from the subst help file
z <- rast(ncols=5, nrows=5, xmin=0, xmax=1, ymin=0, ymax=1, crs="")
z <- init(z, 1:6)
x <- subst(z, 3, 7)
x <- subst(z, 2:3, NA)
x <- subst(x, NA, 10)
levels(x) <- data.frame(value = c(1,4,5,6,10),
lc_class = c("a", "b", "c", "d", "e"))
#idenfity what t0 want to change the values to
new_levels <- data.frame(new_value = c(1,2,3,4,5,6,7,8,9,10),
old_value = c(1,4,5,6,10, NA, NA, NA, NA, NA),
lc_class = c("a", "b", "c", "d", "e", "f", "g", "h", "i", "j"))
#substitute new values for old values
y <- subst(x, new_levels$old_value, new_levels$new_value)
#rename levels according to new values
levels(y) <- new_levels[,c(1,3)]
#add a cell from one of the new levels to test that it worked and plots properly
y[1,1] <- 10
plot(y)
Upvotes: 1