rggplot2rasterterra

How to set factor levels in a rast stack, using terra, when different levels exist in each layer


I have a terra::rast() stack of continuous data, which I classify to n classes, convert to factors and plot on a discrete scale.

But when the first raster does not have all the factors present that some of the other rasters do (e.g. only 3 classification values instead of the whole set of 10, converted to factors), the plot goes a bit haywire.

I am looking for a way to inform the rast stack (or plot) that the levels of the whole stack are min - max of the the stack, but some layers may have less.

Interestingly, if the 1st layer has all classes/factors possible then the subsequent ones are ok and the plot is fine.

# dummy data
library(terra)
r1 <- rast(nrows = 10, ncols = 10, xmin = 0, xmax = 10, ymin = 0, ymax = 10)
r1[] <- runif(ncell(r1), min = 1, max = 5)  # Random values between 1 and 5

r2 <- rast(nrows = 10, ncols = 10, xmin = 0, xmax = 10, ymin = 0, ymax = 10)
r2[] <- runif(ncell(r2), min = 1, max = 5)

# Combine rasters into a stack
s <- c(r1/r1, r1/r2, r2/r1, r2/r2)
names(s) <- c("r1/r1", "r1/r2", "r2/r1", "r2/r2")

# Reclassify the raster stack
# Define reclassification matrix
m_rc <- matrix(c(0, 0.5, 1, 
                           0.5, 0.9, 2, 
                           0.9, 1.1, 3,
                           1.1, 2, 4,
                           2, max(global(s, max, na.rm=T)$max), 5), 
                         ncol = 3, byrow = TRUE)

# Apply reclassification
s_r <- classify(s, m_rc)

# Convert reclassified raster to factor for categorical plotting
s_r_f <- as.factor(s_r)

# Step 3: Plot using ggplot2 and tidyterra with custom legend labels
ggplot() +
  geom_spatraster(data = s_r_f) +
  facet_wrap(~lyr, nrow = 2) +  # Separate plots for each layer
  scale_fill_manual(
    values = c("blue","lightblue" , "white", "yellow", "red"),  # Assign custom colors
    na.value = "transparent",    # Transparent for NA values
    ) +
  labs(
    title = "Reclassified Raster Stack",
    labels = c("0 - 0.5","0.5 - 0.9","0.9 - 1.1","1.1 - 2","> 2"),
    fill = "Class"
  ) +
  theme_minimal()

Solution

  • That should be improved a bit in the package, but right now you can do this:

    # set the same levels to all layers
    x <- categories(x, 0, levels(s_r_f)[[2]])
    
    panel(x, all_levels=TRUE, col = c("blue","lightblue", "white", "yellow", "red"), main=1:4)
    

    enter image description here


    With the development version of terra (version 1.8-9) you can now do

    panel(s_r_f, col = c("blue","lightblue", "white", "yellow", "red"))
    

    And for use with tidyterra and ggplot you can first use combineLevels

    x <- combineLevels(s_r_f)
    
    library(tidyterra)
    library(ggplot2)
    
    ggplot() +
      geom_spatraster(data = x) +
      facet_wrap(~lyr, nrow = 2) +  # Separate plots for each layer
      scale_fill_manual(
        values = c("blue","lightblue" , "white", "yellow", "red"),  # Assign custom colors
        na.value = "transparent",    # Transparent for NA values
        ) +
      labs(
        title = "Reclassified Raster Stack",
        labels = c("0 - 0.5","0.5 - 0.9","0.9 - 1.1","1.1 - 2","> 2"),
        fill = "Class"
      ) +
      theme_minimal()
    

    enter image description here