Bias surface


Summary


Description

Real-world biodiversity data is rarely collected systematically. Biologists often sample near roads, rivers, or research stations, creating spatial clustering that does not necessarily reflect optimal habitat.

The nicheR package allows you to simulate these spatial sampling biases. This workflow involves two steps:

  1. prepare_bias(): Standardizing raw environmental or anthropocentric covariates into probability-scaling surfaces.

  2. apply_bias(): Mathematically fusing those bias surfaces with your habitat suitability predictions.


Getting ready

First, we load the core packages required for our spatial and niche operations. For this vignette, we assume you have already defined a nicheR_ellipsoid object.

library(nicheR)
library(terra)

# Saving original plotting parameters
original_par <- par(no.readonly = TRUE)

# 1. Load reference niche (nicheR_ellipsoid object)
data("ref_ellipse", package = "nicheR")

# 2. Load pre-calculated prediction surfaces (from previous vignettes)
# These SpatRasters contain "suitability", "Mahalanobis", "suitability_trunc", etc.
pred <- terra::rast(system.file("extdata", "predictions_rast.tif", package = "nicheR"))
pred_3d <- terra::rast(system.file("extdata", "predictions_3d_rast.tif", package = "nicheR"))


Preparing the Bias Layer

Raw bias proxies (e.g., species richness, nighttime lights, distance to roads) come in varying scales and units. prepare_bias() resamples, aligns, and min-max standardizes these layers to a strict [0, 1] scale. If multiple layers are provided, it allows you to assign unique directional effects to each before multiplying them into a single composite surface.

# Load a sample bias layer containing 'sp_richness' and 'nighttime'
biases_file <- system.file("extdata", "ma_biases.tif", package = "nicheR")
raw_bias <- terra::rast(biases_file)

# --- Plotting the Output ---
par(mfrow = c(1, 2), mar = c(4, 4, 3, 2))

# Plot the raw inputs
terra::plot(raw_bias[["sp_richness"]], main = "Species Richness")
terra::plot(raw_bias[["nighttime"]], main = "Nighttime Lights")


Function Arguments


Example: Mixed-Direction Composite Bias

In this example, our raw_bias raster contains two layers: sp_richness and nighttime (nighttime lights/urbanization).

Ecologically, sampling effort is likely highest in areas with known high species richness, but lowest in highly urbanized areas. Therefore, we will assign a "direct" effect to richness and an "inverse" effect to nighttime lights to create a realistic composite bias surface.

# Prepare a composite bias surface mapping unique directions to each layer
prep_composite <- prepare_bias(bias_surface = raw_bias, 
                               effect_direction = c("direct", "inverse"), 
                               verbose = FALSE)
# Plot the resulting unified bias probability surface
terra::plot(prep_composite$composite_surface, main = "Composite Bias Surface")

Notice how the final composite surface creates “hotspots” for sampling in areas that feature both high species richness AND low urbanization, accurately reflecting the combined effect_direction parameter.


Applying Bias to Predictions

Once your bias surface is scaled between 0 and 1, you must integrate it with your actual ecological prediction (e.g., habitat suitability). apply_bias() aligns the grids and multiplies the suitability by the bias. The resulting output is a weighted surface, not a true biological probability.


Function Arguments


Example: Comparing Applied Biases

Below, we take a standard suitability prediction and restrict it using the composite bias layer we just prepared. We will apply it as a "direct" multiplier, meaning areas with high composite bias scores will retain their suitability, while areas with low bias scores will be penalized.

# Apply the composite bias to our suitability layer
applied_bias <- apply_bias(prepared_bias = prep_composite, 
                           prediction = pred, 
                           prediction_layer = "suitability",
                           effect_direction = "direct")
#> Starting: apply_bias()
#> Step: applying bias with 'direct' effect to to "suitability" layer...
#> Done: apply_bias(). Note: values are no longer probabilities

# --- Plotting the Output ---
par(mfrow = c(1, 2), mar = c(4, 4, 3, 2))

# Original Biological Suitability
terra::plot(pred[["suitability"]], main = "Habitat Suitability")

# Suitability mathematically restricted by our composite sampling bias
terra::plot(applied_bias[[1]], main = "Suitability + Composite Bias")

In the final comparison, observe how the spatial footprint of the species shrinks based on the bias layer. When drawing points from the applied_bias raster using sample_biased_data(), the algorithm is forced to ignore large swaths of highly suitable habitat simply because the simulated sampling effort (driven by nighttime lights and richness) in those areas is too low.

Three-Dimensional Example

Because collection bias is fundamentally geographic, the process of applying it works identically regardless of how many environmental dimensions were used to define the fundamental niche. Here, we apply the exact same composite bias surface to our 3-dimensional species prediction.

# Apply the composite bias to our 3D suitability layer
applied_bias_3d <- apply_bias(prepared_bias = prep_composite, 
                              prediction = pred_3d, 
                              prediction_layer = "suitability",
                              effect_direction = "direct")
#> Starting: apply_bias()
#> Step: applying bias with 'direct' effect to to "suitability" layer...
#> Done: apply_bias(). Note: values are no longer probabilities

# --- Plotting the Output ---
par(mfrow = c(1, 2), mar = c(4, 4, 3, 2))

terra::plot(pred_3d[["suitability"]], main = "3D Habitat Suitability")
terra::plot(applied_bias_3d[[1]], main = "3D Suitability + Composite Bias")

# Reset plotting parameters
par(original_par)

Save and export

# Save the final biased prediction layers to a temporary directory
temp_rast <- file.path(tempdir(), "applied_bias_rast.tif")
temp_rast_3d <- file.path(tempdir(), "applied_bias_3d_rast.tif")

terra::writeRaster(applied_bias[[1]], filename = temp_rast, overwrite = TRUE)
terra::writeRaster(applied_bias_3d[[1]], filename = temp_rast_3d, overwrite = TRUE)