Skip to contents

Overview

The terra package includes terra::bioclim() which computes bioclimatic variables from a SpatRaster stack. xbioclim fills a complementary niche: it works directly with plain R numeric vectors (and, by extension, matrices and data frames), without requiring any spatial infrastructure.

This vignette walks through the key similarities and differences between the two packages so you can choose the right tool—or combine both—for your workflow.

Shared vocabulary

Both packages follow the same WorldClim variable definitions (BIO01–BIO19) and the same underlying formulas. If you already know how to interpret the variables from one package, the numbers produced by the other should be directly comparable.

Input format

Feature xbioclim terra
Input type Plain numeric vector (length 12) SpatRaster with 12 monthly layers
Spatial awareness None – pure numeric Full CRS + extent handling
Multi-pixel Row-wise apply() over a matrix Operates natively on rasters
Installation Lightweight (no system libs needed) Requires GDAL/PROJ

xbioclim: vector inputs

# Monthly climate normals for a single location
tas    <- c(12.0, 13.3, 15.8, 17.0, 17.5, 16.5,
            15.6, 15.6, 15.2, 14.5, 12.9, 12.0)
tasmax <- c(21.2, 23.0, 25.7, 26.6, 26.3, 24.4,
            22.8, 22.9, 22.3, 21.7, 20.5, 20.4)
tasmin <- c( 3.5,  4.5,  6.9,  9.4, 10.8, 11.4,
            10.8, 10.7,  9.9,  8.2,  5.9,  4.1)
pr     <- c(10,  7, 11, 21, 52, 132,
           163, 152, 116, 62, 16,   8)

# All 19 variables in one call
result_rx <- bioclim(tas, tasmax, tasmin, pr)
result_rx
#>     bio01     bio02     bio03     bio04     bio05     bio06     bio07     bio08 
#>  14.82500  15.14167  65.54834 180.46814  26.60000   3.50000  23.10000  15.90000 
#>     bio09     bio10     bio11     bio12     bio13     bio14     bio15     bio16 
#>  12.43333  17.00000  12.30000 750.00000 163.00000   7.00000  93.81627 447.00000 
#>     bio17     bio18     bio19 
#>  25.00000 205.00000  34.00000

terra: raster inputs (conceptual)

The equivalent terra workflow requires a SpatRaster where each layer holds one month of data. Below is a minimal reproducible sketch—no real raster files are read from disk:

library(terra)

# Assume `r_tas`, `r_tasmax`, `r_tasmin`, `r_pr` are each SpatRasters
# with 12 layers (one per month), all sharing the same CRS and extent.

# terra::bioclim() accepts the four stacks and returns a 19-layer SpatRaster
bio_raster <- terra::bioclim(r_tas, r_tasmax, r_tasmin, r_pr)

# Extract values at a single cell (equivalent to xbioclim on one pixel)
cell_id <- cellFromXY(bio_raster, cbind(-99.1, 19.4))
values(bio_raster)[cell_id, ]

Function-level comparison

Computing a single variable

xbioclim exposes every variable as its own named function, making intent explicit and reducing overhead when only one variable is needed:

bio04(tas)   # Temperature Seasonality – xbioclim
#> [1] 180.4681
bio15(pr)    # Precipitation Seasonality – xbioclim
#> [1] 93.81627

In terra you would compute all 19 layers and then subset the desired layer from the output SpatRaster:

# terra approach – always computes all 19
bio_raster <- terra::bioclim(r_tas, r_tasmax, r_tasmin, r_pr)
bio_raster[["bio04"]]  # Temperature Seasonality layer

Computing all 19 variables

result <- bioclim(tas, tasmax, tasmin, pr)
print(result)
#>     bio01     bio02     bio03     bio04     bio05     bio06     bio07     bio08 
#>  14.82500  15.14167  65.54834 180.46814  26.60000   3.50000  23.10000  15.90000 
#>     bio09     bio10     bio11     bio12     bio13     bio14     bio15     bio16 
#>  12.43333  17.00000  12.30000 750.00000 163.00000   7.00000  93.81627 447.00000 
#>     bio17     bio18     bio19 
#>  25.00000 205.00000  34.00000

In terra the output is a SpatRaster with the same variable names as layer names, so downstream raster algebra works naturally:

bio_raster <- terra::bioclim(r_tas, r_tasmax, r_tasmin, r_pr)
names(bio_raster)  # "bio01" ... "bio19"

Applying xbioclim to tabular climate data

A common use-case outside of raster workflows is working with station data or point samples stored in a data frame. xbioclim is ideal here:

# Simulated station records: 3 stations × 12 months
set.seed(1)
n_stations <- 3
months <- 1:12

station_df <- data.frame(
  station = rep(paste0("S", 1:n_stations), each = 12),
  month   = rep(months, times = n_stations),
  tas     = c(
    c(5, 6, 9, 13, 17, 21, 23, 22, 18, 13, 8, 5),     # temperate
    c(24, 25, 26, 27, 28, 27, 26, 26, 26, 25, 24, 23), # tropical
    c(-10, -8, -2, 5, 12, 17, 20, 18, 12, 4, -3, -8)   # continental
  ),
  tasmax  = c(
    c(8, 10, 14, 18, 23, 27, 30, 29, 25, 19, 13, 9),
    c(30, 31, 32, 33, 34, 33, 32, 32, 32, 31, 30, 29),
    c(-5, -3,  4, 11, 18, 24, 27, 25, 18,  9,  2, -3)
  ),
  tasmin  = c(
    c(1, 2, 4, 8, 12, 16, 18, 17, 13, 8, 4, 1),
    c(20, 21, 22, 23, 24, 23, 22, 22, 22, 21, 20, 19),
    c(-16, -14, -9, -1, 6, 11, 14, 12, 6, -1, -8, -14)
  ),
  pr      = c(
    c(60, 50, 45, 40, 35, 20, 10, 15, 35, 55, 65, 65),
    c(5, 10, 20, 40, 80, 150, 180, 160, 120, 60, 20, 8),
    c(25, 20, 22, 30, 45, 60, 70, 65, 45, 30, 28, 25)
  )
)

# Apply bioclim() per station using split + lapply
# Convert station to character to ensure robust splitting
bio_list <- lapply(
  split(station_df, as.character(station_df$station)),
  function(d) bioclim(d$tas, d$tasmax, d$tasmin, d$pr)
)

# Combine into a data frame, keeping numeric columns numeric
bio_df <- do.call(rbind, lapply(names(bio_list), function(nm) {
  as.data.frame(as.list(c(station = nm, bio_list[[nm]])))
}))

print(bio_df[, c("station", "bio01", "bio04", "bio12", "bio15")])
#>   station            bio01            bio04 bio12            bio15
#> 1      S1 13.3333333333333 647.216261298253   495 44.0176940797999
#> 2      S2 25.5833333333333 138.192699598142   853 87.9061331911443
#> 3      S3             4.75 1045.72542604006   465 43.9104818887438

When to use each package

Scenario Recommended package
Processing global rasters (NetCDF, GeoTIFF) terra
Computing variables for point/station data xbioclim
Lightweight scripts with no spatial dependency xbioclim
Integration into existing terra workflows terra (or both)
Custom block processing with non-standard data xbioclim
SDM workflows that use terra::extract() output xbioclim

Bridging both packages

If you extract raster values with terra and want to compute bioclim variables with xbioclim (e.g., for custom weighting or filtering), the workflow is:

library(terra)
library(xbioclim)

# 1. Extract monthly values at species occurrence points
pts <- vect(occurrences, geom = c("lon", "lat"), crs = "EPSG:4326")
tas_vals    <- extract(r_tas,    pts)[, -1]  # drop ID column
tasmax_vals <- extract(r_tasmax, pts)[, -1]
tasmin_vals <- extract(r_tasmin, pts)[, -1]
pr_vals     <- extract(r_pr,     pts)[, -1]

# 2. Compute bioclim variables for each occurrence point
bio_matrix <- t(mapply(function(i) {
  bioclim(
    as.numeric(tas_vals[i, ]),
    as.numeric(tasmax_vals[i, ]),
    as.numeric(tasmin_vals[i, ]),
    as.numeric(pr_vals[i, ])
  )
}, seq_len(nrow(pts))))

This hybrid approach is particularly useful for occurrence-based niche modelling, where you need bioclimatic values at irregular point locations rather than over a complete raster grid.