'Common colour and legend for multiple categorical rasters

I want to make a multipanel plot of 3 rasters with categorical values. is there a way where I can automatically draw breaks, and colors for each raster? I have a reproducible example below with three rasters r1,r2, and r3. r1 has values from 0to 5, r2 has values 0,13,4 and r3 has values 0,1,2,4. I want to save myself from specifying the color panel for each raster separately.

Thanks in advance

library(raster)

# Define the rasters
r1 <- raster(nrow=5, ncol=5)
x1<- c(0,1,2,3,4,5)
values(r1) <- sample(x1, ncell(r1), replace=TRUE)

r2 <- raster(nrow=5, ncol=5)
x2<- c(0,1,3,4)
values(r2) <- sample(x2, ncell(r2), replace=TRUE)
as.factor(r2)

r3 <- raster(nrow=5, ncol=5)
x3<- c(0,1,2,4)
values(r3) <- sample(x3, ncell(r3), replace=TRUE)
as.factor(r3)


# creating the breaks and colour vectors

br1    <- c(0,1,2,3,4,5)
cl1    <- c("red","orange","green","blue","gray", "brown")

br2    <- c(0,1,3,4)
cl2    <- c("red","orange","blue","gray")

br3    <- c(0,1,2,4)
cl3    <- c("red","orange","green","brown")


# Create the multi plot
par(mfrow=c(1,3),xpd=T)

plot(r1,col=cl1,legend=F,axes=F,asp=NA)
text(r1,cex=1)
plot(r1,legend.only=T, breaks=br1,col = cl1,bty="n",xpd=T)


plot(r2,col=cl2,legend=F,axes=F,asp=NA)
text(r2,cex=1)
plot(r2,legend.only=T, breaks=br2,col = cl2,bty="n",xpd=T)

plot(r3, col=cl3,legend=F,axes=F,asp=NA)
text(r3, cex=1)
plot(r3, legend.only=T, breaks=br3,col = cl3,bty="n",xpd=T)


Solution 1:[1]

There might be more efficient ways to plot raster panels or facets, e.g. ?plot::raster suggests that the rasterVis package offers more advanced plotting functions for raster objects, including trellis/lattice plots.

However, the following solution aims at avoiding repeated code as indicated in your question and stays close to the original. Here, a custom function and lists, as well as lapply(), are used.

library(raster)

# Define the rasters
r1 <- raster(nrow = 5, ncol = 5)
x1 <- c(0, 1, 2, 3, 4, 5)
values(r1) <- sample(x1, ncell(r1), replace = TRUE)

br1    <- c(0, 1, 2, 3, 4, 5)
cl1    <- c("red", "orange", "green", "blue", "gray", "brown")

r2 <- raster(nrow = 5, ncol = 5)
x2 <- c(0, 1, 3, 4)
values(r2) <- sample(x2, ncell(r2), replace = TRUE)
r2_2 <- r2
as.factor(r2) # as.factor() does not modify the object r2 in place.
identical(r2, r2_2) 

r3 <- raster(nrow = 5, ncol = 5)
x3 <- c(0, 1, 2, 4)
values(r3) <- sample(x3, ncell(r3), replace = TRUE)

# creating the breaks and colour vectors

br1    <- c(0, 1, 2, 3, 4, 5)
cl1    <- c("red", "orange", "green", "blue", "gray", "brown")

raster_list_1 <- list(r1, br1, cl1)

br2    <- c(0, 1, 3, 4)
cl2    <- c("red", "orange", "blue", "gray")

raster_list_2 <- list(r2, br2, cl2)

br3    <- c(0, 1, 2, 4)
cl3    <- c("red", "orange", "green", "brown")

raster_list_3 <- list(r3, br3, cl3)

raster_listoflists <-
  list(raster_list_1, raster_list_2, raster_list_3)

# Create the multi plot
plot_raster_panels <- function(rasterlists) {
  list_size <- length(rasterlists)
  mfrow_before <- par("mfrow")
  par(mfrow = c(1, list_size), xpd = TRUE)
  lapply(rasterlists, function(x) {
    raster_layer <- x[[1]]
    breaks <- x[[2]]
    colours <- x[[3]]
    
    plot(
      raster_layer,
      col = colours,
      legend = FALSE,
      axes = FALSE,
      asp = NA
    )
    text(raster_layer, cex = 1)
    plot(
      raster_layer,
      legend.only = TRUE,
      breaks = breaks,
      col = colours,
      bty = "n",
      xpd = TRUE
    )
  })
  on.exit(par(mfrow = mfrow_before))
  invisible(0)
}

plot_raster_panels(raster_listoflists)

Some remarks and suggestions:

  • I would advise you to aim for a consistent coding style, e.g. don't mix argument = T and argument = TRUE. You should probably avoid using T and F altogether, as they are just aliases to TRUE and FALSE respectively and can be reassigned by users.
  • Use instructive names for variables. No one, not even you in a few months, is going to remember what br1 and cl1 stand for. Use descriptive verbs for functions and nouns that describe content for objects and snake_case for objects, as suggested in Tidyverse Style Guide.
  • You use as.factor(raster_object) in your script but do not assign it. If you want to inspect the result interactively, this is fine. However, be aware that most R functions do not modify objects in place, i.e. the objects are immutable, and you have to assign the result to a new object. A notable exception to this is the R6 package and its functions (Advanced R - R6).
  • I am using lists in my solution and not vectors because lists can, as opposed to vectors, hold different types of data (cf. An Introduction to R - Lists).
  • It is not possible to save the plot of R base plot functions directly into an object since they return NULL or computed values instead (e.g. (plot(1))). To avoid printing NULL to the console, I have used invisible(0) to return an arbitrary number (0 is, however, conventionally used in function returns to signal normal execution).
  • Since we want to keep the plot after the function has run, we can't turn off the graphic device (dev.off()) inside our function. Thus we should reset the mfrow parameter that we changed for the multi-panel plot.

Edit: Edited for clarity, added syntax highlighting and references.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1