1 Packages needed

library(plotbiomes)
library(sf)
library(terra)
library(ggplot2)
library(dplyr)

2 Introduction

The plotbiomes package was created to plot Whittaker’s biome types in R via ggplot.

Recently I used a modified version of the biomes polygon dataset to infer the distribution of 48,129 tree species across the different biome types, and uploaded the results in a Zenodo archive. The 48,129 tree species were the same as documented in the Tree Globally Observed Environmental Ranges (TreeGOER) database, with also the same values used for mean annual temperature (MAT, BIO01) and annual precipitation (MAP, BIO12) as extracted from the WorldClim 2.1 database.

Here I show how the Whittaker biome type can be inferred from information on MAN and MAP. For combinations of MAN and MAP that occur outside the polygons, the location with respect to the polygons is also captured in a complementary zonation system.

3 Whittaker biome types polygon

3.1 Read from the shapefile

# whitpol.file <- choose.files() 
# Provide the location where the files for the shapefile 
# (whittaker.shp, whittaker.shx, whittaker.dbf and whittaker.prj) were downloaded to
whitpol <- st_read(whitpol.file, quiet=TRUE)
st_crs(whitpol) <- NA

The polygon shows the distribution of nine biome types.

whitpol
## Simple feature collection with 9 features and 3 fields
## Geometry type: POLYGON
## Dimension:     XY
## Bounding box:  xmin: -15.63134 ymin: 0 xmax: 29.9846 ymax: 44.41541
## CRS:           NA
##   b_ID                            biome b_score                       geometry
## 1    6                           Tundra       1 POLYGON ((-10.62857 0.16969...
## 2    7                    Boreal forest       5 POLYGON ((-3.851808 11.0979...
## 3    8       Temperate grassland/desert       2 POLYGON ((-9.268834 0.33749...
## 4    5               Woodland/shrubland       4 POLYGON ((-9.758988 0.29510...
## 5    9        Temperate seasonal forest       6 POLYGON ((-0.3888281 3.8951...
## 6    3            Temperate rain forest       8 POLYGON ((3.242143 16.19025...
## 7    4             Tropical rain forest       9 POLYGON ((20.76227 34.52938...
## 8    1 Tropical seasonal forest/savanna       7 POLYGON ((28.55986 27.4391,...
## 9    2               Subtropical desert       3 POLYGON ((14.13556 0, 15.00...

3.2 Centroids

In the plot, the b_score will be shown in the centroid of each polygon. This variable represents a score that is higher for biome types that are generally of higher temperature or precipitation in pairwise comparisons between between biome types.

whitcentr <- st_centroid(whitpol)
## Warning: st_centroid assumes attributes are constant over geometries
whitcoord <- st_coordinates(whitcentr)

whitpol <- cbind(whitpol, whitcoord)

3.3 Colour scheme

The plotbiomes package includes a colour scheme. Here it is modified to include the score with the name of the biome type.

whitpol$label <- paste0(whitpol$biome, " (", whitpol$b_score, ")")

Ricklefs.df <- data.frame(Ricklefs_colors)
Ricklefs.df$biome <- rownames(Ricklefs.df)

Ricklefs.df <- left_join(Ricklefs.df,
                         whitpol[, c("biome", "label")],
                         by="biome")
Ricklefs.df
##   Ricklefs_colors                            biome
## 1         #C1E1DD                           Tundra
## 2         #A5C790                    Boreal forest
## 3         #97B669        Temperate seasonal forest
## 4         #75A95E            Temperate rain forest
## 5         #317A22             Tropical rain forest
## 6         #A09700 Tropical seasonal forest/savanna
## 7         #DCBB50               Subtropical desert
## 8         #FCD57A       Temperate grassland/desert
## 9         #D16E3F               Woodland/shrubland
##                                  label                       geometry
## 1                           Tundra (1) POLYGON ((-10.62857 0.16969...
## 2                    Boreal forest (5) POLYGON ((-3.851808 11.0979...
## 3        Temperate seasonal forest (6) POLYGON ((-0.3888281 3.8951...
## 4            Temperate rain forest (8) POLYGON ((3.242143 16.19025...
## 5             Tropical rain forest (9) POLYGON ((20.76227 34.52938...
## 6 Tropical seasonal forest/savanna (7) POLYGON ((28.55986 27.4391,...
## 7               Subtropical desert (3) POLYGON ((14.13556 0, 15.00...
## 8       Temperate grassland/desert (2) POLYGON ((-9.268834 0.33749...
## 9               Woodland/shrubland (4) POLYGON ((-9.758988 0.29510...

3.4 Plot

Now we can plot the figure, including the scores for each biome type. The same script was used to create the figure included in the Zenodo archive.

xlab <- "Average Annual Temperature (°C)"

plot_1 <- ggplot() +
  # add biome polygons
  geom_sf(data = whitpol,
                aes(fill = label),
               colour = "gray98",
               size   = 1) +
  geom_text(data = whitpol,
                aes(x=X,
                    y=Y,
                    label=b_score),
               colour = "black",
               size   = 5) +
  theme_bw() +
  xlab(xlab) +
  ylab("Annual Precipitation (dm)") +
  scale_fill_manual(name   = "Whittaker biomes",
                    breaks = Ricklefs.df$label,
                    labels = Ricklefs.df$label,
                    values = Ricklefs.df$Ricklefs_colors) +
#  scale_y_continuous(limits =c(-1, 45)) +
  coord_sf(clip = "off")

plot_1

4 Obtain the Whittaker biome type from information on mean annual temperature and annual precipitation

The following function extracts the biome type (as its b_score) from the biome types polygons.

whittaker_extract <- function(
    x, whitpol)
{
  names(x) <- c("temp_c", "precp_dm")
  x$ID <- seq_len(nrow(x))
  x$inside <- as.logical(1)
  biometerra <- terra::vect(whitpol)
  biomedata <- terra::extract(biometerra, y=x[, c("temp_c", "precp_dm")])
  
  biomedata <- slice_max(biomedata, order_by=b_score, by=id.y, n=1)
  
  biomeout <- left_join(x,
                        biomedata,
                        by=c("ID"="id.y"))
  
  biomeout[is.na(biomeout$b_score), "inside"] <- as.logical(0)
  
# outside  
  
  biomeout[biomeout$inside == FALSE & biomeout$temp_c < -4.08, "b_score"] <- 1.1 # outside Tundra
  biomeout[is.na(biomeout$b_score) & biomeout$temp_c < 4.35, "b_score"] <- 5.1 # outside Boreal forest
  biomeout[is.na(biomeout$b_score) & biomeout$temp_c < 20.65, "b_score"] <- 8.1 # outside Boreal forest
  biomeout[is.na(biomeout$b_score) & biomeout$precp_dm > 28.131, "b_score"] <- 9.1 # outside Tropical rain forest
  biomeout[is.na(biomeout$b_score) & biomeout$precp_dm > 9.991, "b_score"] <- 7.1 # outside Tropical seasonal forest
  biomeout[is.na(biomeout$b_score), "b_score"] <- 3.1 # outside Subtropical desert
  return(biomeout)

}

As one example, based on the MAT and MAP for Nairobi with data obtained from the CitiesGOER database, its biome type is: Tropical seasonal forest/savanna.

Be careful to specify the precipitation in dm, not mm or cm!

nairobi <- data.frame(MAT=19.61,
                      MAP=1036/100)

whittaker_extract(nairobi, whitpol)
##   temp_c precp_dm ID inside b_ID                            biome b_score
## 1  19.61    10.36  1   TRUE    1 Tropical seasonal forest/savanna       7
##          X        Y                                label
## 1 24.06443 15.78098 Tropical seasonal forest/savanna (7)
plot_1 +
  geom_point(data=nairobi,
             aes(x=MAT,
                 y=MAP),
             colour="red")

5 Treatment of observations on boundaries or outside

Observations on boundaries between polygons are given the biome type of the highest score.

For observations outside the polygons, a different score is given that reflects their proximity to the polygons. For example, a score of 1.1 shows that the observation has the same temperature range as Tundra (with score 1).

To demonstrate how boundary and outside observations are treated, here I am plotting observations on boundaries and outside the polygons.

5.1 Biome types of boundary and outside locations

data.in <- Whittaker_biomes
data.in$precp_dm <- data.in$precp_cm / 10
data.in <- data.in[, c("temp_c", "precp_dm")]
# data.in

data.outside <- data.frame(temp_c = c(-10, -15, 0, 10, 35, 35, 35, 21),
                         precp_dm = c(10, 5, 20, 30, 30, 5, 20, 45))

data.in <- rbind(data.in, data.outside)


data.out <- whittaker_extract(data.in, whitpol)
# data.out

5.2 Scores 1, 2, 3 on boundaries and 1.1 and 3.1 outside polygons

Observations on the boundary between Temperate grassland/desert and Subtropical desert are classified as the latter. Observations on the upper boundaries of these biome types are classified as the biome types occurring above them.

score.focal1 <- c(1, 1.1)
score.focal2 <- c(2) # straight line at precipitation = 0
score.focal3 <- c(3, 3.1) # straight line at precipitation = 0


plot_1 +
  geom_point(data=data.out[data.out$b_score %in% score.focal1, ],
             aes(x=temp_c,
                 y=precp_dm),
             colour="red", shape=1) +

  geom_point(data=data.out[data.out$b_score %in% score.focal2, ],
             aes(x=temp_c,
                 y=precp_dm),
             colour="green", shape=3) +
  
    geom_point(data=data.out[data.out$b_score %in% score.focal3, ],
             aes(x=temp_c,
                 y=precp_dm),
             colour="blue", shape=4)

5.3 Scores 4, 5, 6 on boundaries and 5.1 outside polygons

Observations on the boundary between Temperate grassland/desert and Woodland/shrubland are classified as the latter. Observations on the boundary between Boreal forest and Temperate seasonal forest are classified as the latter.

score.focal4 <- c(4)
score.focal5 <- c(5, 5.1)
score.focal6 <- c(6)

plot_1 +
  geom_point(data=data.out[data.out$b_score %in% score.focal4, ],
             aes(x=temp_c,
                 y=precp_dm),
             colour="red", shape=1) +

  geom_point(data=data.out[data.out$b_score %in% score.focal5, ],
             aes(x=temp_c,
                 y=precp_dm),
             colour="green", shape=3) +
  
    geom_point(data=data.out[data.out$b_score %in% score.focal6, ],
             aes(x=temp_c,
                 y=precp_dm),
             colour="blue", shape=4)

5.4 Scores 7, 8, 9 on boundaries and 7.1, 8.1 and 9.1 outside polygons

Observations on the boundary between Temperate rain forest and Tropical rain forest are classified as the latter. Observations outside the polygons are given a *.1 score that reflects the nearest polygon.

score.focal7 <- c(7, 7.1)
score.focal8 <- c(8, 8.1)
score.focal9 <- c(9, 9.1)

plot_1 +
  geom_point(data=data.out[data.out$b_score %in% score.focal7, ],
             aes(x=temp_c,
                 y=precp_dm),
             colour="red", shape=1) +

  geom_point(data=data.out[data.out$b_score %in% score.focal8, ],
             aes(x=temp_c,
                 y=precp_dm),
             colour="green", shape=3) +
  
    geom_point(data=data.out[data.out$b_score %in% score.focal9, ],
             aes(x=temp_c,
                 y=precp_dm),
             colour="blue", shape=4)

6 Global Biodiversity Standard

This publication was initiated partially from ongoing work in a Darwin Initiative project (DAREX001) that develops a Global Biodiversity Standard for tree planting. The GlobalUsefulNativeTrees database and Tree Globally Observed Environmental Ranges database were realised through co-funding from this project. With scripts such as the ones shown here, when the Global Biodiversity Standard scheme becomes operational, tree planting projects can seek guidance on suitable species for planting.

7 Session Information

sessionInfo()
## R version 4.2.1 (2022-06-23 ucrt)
## Platform: x86_64-w64-mingw32/x64 (64-bit)
## Running under: Windows 10 x64 (build 19045)
## 
## Matrix products: default
## 
## locale:
## [1] LC_COLLATE=English_United Kingdom.utf8 
## [2] LC_CTYPE=English_United Kingdom.utf8   
## [3] LC_MONETARY=English_United Kingdom.utf8
## [4] LC_NUMERIC=C                           
## [5] LC_TIME=English_United Kingdom.utf8    
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
## [1] dplyr_1.1.2           ggplot2_3.5.1         terra_1.7-46         
## [4] sf_1.0-16             plotbiomes_0.0.0.9001
## 
## loaded via a namespace (and not attached):
##  [1] tidyselect_1.2.0   xfun_0.33          bslib_0.4.0        lattice_0.20-45   
##  [5] mapview_2.11.2     colorspace_2.0-3   vctrs_0.6.3        generics_0.1.3    
##  [9] htmltools_0.5.6    stats4_4.2.1       yaml_2.3.5         base64enc_0.1-3   
## [13] utf8_1.2.2         rlang_1.1.1        e1071_1.7-11       jquerylib_0.1.4   
## [17] pillar_1.9.0       withr_2.5.0        glue_1.6.2         DBI_1.1.3         
## [21] sp_1.5-0           lifecycle_1.0.3    stringr_1.4.1      munsell_0.5.0     
## [25] gtable_0.3.1       raster_3.6-23      htmlwidgets_1.5.4  codetools_0.2-18  
## [29] evaluate_0.16      knitr_1.40         fastmap_1.1.1      crosstalk_1.2.0   
## [33] class_7.3-20       fansi_1.0.3        highr_0.9          leafem_0.2.3      
## [37] Rcpp_1.0.12        KernSmooth_2.23-20 scales_1.3.0       classInt_0.4-8    
## [41] satellite_1.0.5    cachem_1.0.6       leaflet_2.2.0      jsonlite_1.8.0    
## [45] farver_2.1.1       png_0.1-7          digest_0.6.29      stringi_1.7.8     
## [49] grid_4.2.1         cli_3.4.1          tools_4.2.1        magrittr_2.0.3    
## [53] sass_0.4.2         proxy_0.4-27       tibble_3.2.1       pkgconfig_2.0.3   
## [57] data.table_1.14.2  rmarkdown_2.16     rstudioapi_0.14    R6_2.5.1          
## [61] units_0.8-0        compiler_4.2.1