Author: Matteo Marcantonio
Date: 2025-09-02

# Multidimension Rao’s Index.

### Both for spatial and spatio-temporal data

#### Matteo Marcantonio

#### 2025-09-02

    library(terra)
    library(gstat)
    library(rasterdiv)
    library(rasterVis)
    library(viridis)

# Overview

In this vignette, we demonstrate how to compute the multidimensional Rao’s Index using rasterdiv for multiple numerical matrices, represented as spatially autocorrelated SpatRaster objects.

## Creating Autocorrelated Spatial Patterns

First, we establish a grid that will be used to create spatial patterns through a semivariogram model.

    gridDim <- 40
    xy <- expand.grid(x=1:gridDim, y=1:gridDim)

Next, we define a semivariogram and use it to simulate autocorrelated spatial data.

    varioMod <- vgm(psill=0.005, range=100, model='Exp')
    zDummy <- gstat(formula=z~1, locations = ~x+y, dummy=TRUE, beta=200, model=varioMod, nmax=1)
    set.seed(123)
    xyz <- predict(zDummy, newdata=xy, nsim=2)

With the simulated data, we then create two SpatRaster objects that could represent environmental conditions like plant functional traits.

    # Define coordinate reference system
    utm32N <- "+proj=utm +zone=32 +ellps=WGS84 +datum=WGS84 +units=m +no_defs"
    # Create SpatRasters
    r <- terra::rast(nrow=40, ncol=40, crs=utm32N, ext=ext(0,10000, 0,10000))
    r1 <- r
    # Populate with simulated values
    values(r) <- xyz$sim1
    values(r1) <- xyz$sim2

## Computing Multidimensional Rao’s Index

We now calculate the multidimensional Rao’s Index using varying window sizes and alpha values.

    mRao <- paRao(x=list(r, r1), window=c(3, 5), alpha=c(1, Inf), na.tolerance=1, method="multidimension", simplify=3)

The output is a nested list of SpatRaster which we can transform in a stack of SpatRaster and plotted together with the input layers, as follows:

### Visualisation of the result

    # Create a list of all the rasters to plot
    rasters_to_plot <- c(r, r1, mRao[[1]]$alpha.1, mRao[[2]]$alpha.Inf)
    names(rasters_to_plot) <- c("Raster1", "Raster2", "Rao_Index_Window_3", "Rao_Index_Window_5")

    # Use lapply to create a list of levelplots
    plots_list <- lapply(rasters_to_plot, function(rst) {
      levelplot(as.matrix(rst, wide=TRUE), margin=FALSE,
      col.regions=magma(100),
      main=list(label=names(rst),
      cex=1.5))
    })

    # Arrange the plots in a grid
    do.call(gridExtra::grid.arrange, c(plots_list, ncol = 2))

## Computing Multidimensional Rao’s Index for Time Series

Now, we demonstrate how to compute the multidimensional Rao’s Index for a time series of rasters accounting for phenology, and compare it with Rao’s Index on the same time series without a distance metrics that account for phenology.

## Creating Autocorrelated Spatio-Temporal Patterns

    # Define variogram model for spatial autocorrelation
    varioMod <- vgm(psill=2, range=20, model='Exp')

    # Generate initial spatially correlated data
    zDummy <- gstat(formula=z~1, locations = ~x+y, dummy=TRUE, beta=1, model=varioMod, nmax=1)
    set.seed(123)
    initial_spatial_data <- predict(zDummy, newdata=xy, nsim=1)
    initial_spatial_matrix <- matrix(as.integer(initial_spatial_data$sim1 * 100), nrow=gridDim, ncol=gridDim)

    # Generate a time series with temporal correlation
    t_steps <- 100
    s_dims <- c(gridDim, gridDim)
    seasonal_amplitude <- 10
    seasonal_amplitudeR <- 5
    seasonal_frequency <- 2 * pi / t_steps
    seasonal_frequencyR <- 2 * pi / t_steps
    non_seasonal_mask <- matrix(FALSE, nrow=gridDim, ncol=gridDim)
    non_seasonal_mask[15:25, 15:25] <- TRUE  # 11 rows centered in the middle

    # Initialize the 3D array to store the time series data
    time_series_data <- array(0, dim=c(s_dims, t_steps))

    # Set the first time step with the generated spatial data
    initial_spatial_matrix[non_seasonal_mask] <- floor(rnorm(length(initial_spatial_matrix[non_seasonal_mask]), mean(initial_spatial_matrix), sd = 50))

    time_series_data[,,1] <- initial_spatial_matrix

The temporally autocorrelated matrices show different seasonality for a square area at the centre. This area may for example represents a building or another land cover type with less pronounced seasonal variation and overall lower index diversity. See the time series plotted below that visualises the different trends in index values for a subset of pixels, inside and outside the square area.

    # Generate the remaining time steps
    for (t in 2:t_steps) {
      for (i in 1:s_dims[1]) {
        for (j in 1:s_dims[2]) {
          if (non_seasonal_mask[i, j]) {
            seasonal_effect <- seasonal_amplitudeR * sin(seasonal_frequencyR * t)
            noise <- rnorm(1, mean = 0, sd = 1)
            time_series_data[i, j, t] <- as.integer(time_series_data[i, j, t - 1] + seasonal_effect + noise)
          } else {
            seasonal_effect <- seasonal_amplitude * sin(seasonal_frequency * t)
            noise <- rnorm(1, mean = 0, sd = 2)  # Adding some noise for variability
            time_series_data[i, j, t] <- as.integer(time_series_data[i, j, t - 1] + seasonal_effect + noise)
          }
        }
      }
    }

    # Convert the 3D array to a SpatRaster for visualization
    raster_ts <- rast(time_series_data, extent = ext(0, 40, 0, 40), crs=utm32N)

    # Plot the time series for a few specific pixels to illustrate the seasonal pattern
    plot(1:t_steps, seq(min(as.matrix(raster_ts)), max(as.matrix(raster_ts)), length.out = t_steps), type = 'o', col = 'white', xlab = 'Time Step', ylab = 'Value')

    for (cl in sample(ncol(raster_ts),10)) {
      for (rw in sample(ncol(raster_ts),10)) {
        pixel_time_series <- time_series_data[rw, cl, ]
        lines(1:t_steps, pixel_time_series, col = rgb(red = 0, green = 0, blue = 1, alpha = 0.3),ylab="Index value")
      }
    }

    # Set dates for the time series
    dates <- seq(as.Date("2023-01-01"), as.Date("2023-12-31"), length.out = nlyr(raster_ts))

## Computing Multidimensional Rao’s Index with and without phenology

    # Calculate Phenological Rao's Index using TWDTW
    RaoPhen <- paRao(x = raster_ts,
                     window = 5,
                     alpha = 2,
                     na.tolerance = 0,
                     time_vector = dates,
                     method = "multidimension",
                     dist_m = "twdtw",
                     np = 7, progBar = FALSE)

    # Calculate Rao's Index using Euclidean distance
    RaoEuc <- paRao(x = raster_ts,
                    window = 5,
                    alpha = 2,
                    na.tolerance = 0,
                    method = "multidimension",
                    dist_m = "euclidean",
                    np = 7, progBar = FALSE)

### Visualisation of the result

The key takeaway is that by accounting for seasonality in our index time series, we can reduce artifact hotspots in Rao’s index. These hotspots often arise from transitions between areas with high spatial variability and those with low spatial variability, which are not due to plant phenology but to buildings or other human-made objects. This effect is illustrated by the square area in the centre of the plot below.

    # Visualization
    raster_ts_plot <- levelplot(mean(raster_ts), margin = FALSE,
                                col.regions = viridis(100),
                                main = list(label = "Average index",
                                cex = 1.5))

    RaoP_plot <- levelplot(RaoPhen[[1]][[1]], margin = FALSE,
                           col.regions = viridis(100),
                           main = list(label = "Phenological Rao",
                           cex = 1.5))

    Rao_plot <- levelplot(RaoEuc[[1]][[1]], margin = FALSE,
                          col.regions = viridis(100),
                          main = list(label = "Rao",
                          cex = 1.5))

    do.call(gridExtra::grid.arrange, c(list(raster_ts_plot, Rao_plot, RaoP_plot), ncol = 3))