1 Packages needed

library(data.table)
library(readxl)
library(dplyr)

2 Introduction

The Tree Globally Observed Environmental Ranges (TreeGOER) database provides information for most known tree species of their environmental ranges for 38 bioclimatic, eight soil and three topographic variables.

In this document, I show how outputs generated by the GlobalUsefulNativeTrees (GlobUNT) online database can be filtered to match bioclimatic conditions of the planting site. Bioclimatic conditions in historical (baseline) and future climates can be sourced from the CitiesGOER database for 52,602 cities (their distribution is shown here).

A similar algorithm and R script is used internally in GlobUNT to filter species for 10 preselected variables (nine of these correspond to the variables used in case studies of the Tree Globally Observed Environmental Ranges (TreeGOER), so you could check the justification there).

The algorithm calculates a Climate Score for each species whereby:

  • Score = 3 means that in ‘environmental space’ the planting site occurs within the 25% - 75% species’s range (as documented in the TreeGOER) for all variables.
  • Score = 2 corresponds to the 5% - 95% species’s range for all variables. This implies that the planting site was outside the 25% - 75% range for some variables.
  • Score = 1 corresponds to the 0% - 100% species’s range for all variables.
  • Score = 0 means that the planting site occurs outside the 0% - 100% species’s range for some of the variables.
  • Score = -1 means that the species is not documented by TreeGOER.

This algorithm is essentially the same as the BIOCLIM model (see Booth et al. 2014 and Booth 2018).

3 Data needed

3.1 TreeGOER database

# treegoer.file <- choose.files() # Provide the location where the TreeGOER file (https://zenodo.org/records/10008994: TreeGOER_2023.txt) was downloaded to
treegoer <- fread(treegoer.file, sep="|", encoding="UTF-8")
nrow(treegoer)
## [1] 2450209
length(unique(treegoer$species))
## [1] 48129

3.2 Bioclimatic conditions at the planting site

We will calculate climate scores for the baseline climate and for median climates projected for the 2050s for Shared Socio-economic Pathway 3-7.0. Planting conditions will be inferred from the CitiesGOER database for Nairobi.

# Provide the locations where the CitiesGOER files (https://zenodo.org/records/10004594: CitiesGOER_baseline.xlsx and CitiesGOER_2050s_ssp370.xlsx) were downloaded to

site.base <- data.frame(read_excel(baseline.file,
                                   sheet="Cities data",
                                   skip=6))
nrow(site.base)
## [1] 52602
head(site.base)
##   SEQ2   SEQ1 Geoname.ID              Name        ASCII.Name Country.Code
## 1    1 135560    1127768             Aībak             Aibak           AF
## 2    2 135566    1149052 ‘Alāqahdārī Dīshū 'Alaqahdari Dishu           AF
## 3    3   1843    1148709        Anār Darah        Anar Darah           AF
## 4    4  45163    1148658           Andkhōy           Andkhoy           AF
## 5    5  65295    1148106       Ārt Khwājah       Art Khwajah           AF
## 6    6  80501    1148311          Asadābād          Asadabad           AF
##   Country.name.EN Population Modification.date        Coordinates      LON
## 1     Afghanistan      47823        2020-06-09 36.26468, 68.01551 68.01551
## 2     Afghanistan       9196        2018-02-17 30.43206, 63.29802 63.29802
## 3     Afghanistan      10023        2020-06-09  32.7587, 61.65397 61.65397
## 4     Afghanistan      29208        2020-06-09 36.95293, 65.12376 65.12376
## 5     Afghanistan      18623        2018-02-17 37.08571, 69.47958 69.47958
## 6     Afghanistan      48400        2020-06-09 34.87311, 71.14697 71.14697
##        LAT LON.ZONE LAT.ZONE bio01 bio02 bio03   bio04 bio05 bio06 bio07 bio08
## 1 36.26468     67.5     35.0 15.03 12.98 31.98 1010.06  36.6  -4.0  40.6  9.08
## 2 30.43206     62.5     30.0 22.21 16.31 39.58  965.08  42.9   1.7  41.2 12.47
## 3 32.75870     60.0     32.5 18.81 13.53 34.44  966.17  39.1  -0.2  39.3  8.78
## 4 36.95293     65.0     35.0 16.83 14.20 33.81 1028.89  39.6  -2.4  42.0  5.87
## 5 37.08571     67.5     35.0 17.35 14.02 33.08 1037.17  40.3  -2.1  42.4 11.52
## 6 34.87311     70.0     32.5 19.55 13.39 36.69  893.14  38.1   1.6  36.5 13.67
##   bio09 bio10 bio11 bio12 bio13 bio14  bio15 bio16 bio17 bio18 bio19 annualPET
## 1 27.62 27.62  2.45   257    59     0  92.85   151     2     2    98   1329.70
## 2 32.83 33.78 10.02    56    15     0  90.71    37     2     2    31   1828.55
## 3 30.62 30.62  6.60   107    27     0 105.45    74     0     0    63   1523.48
## 4 29.72 29.72  4.20   235    57     0  94.56   137     1     1   111   1463.59
## 5 28.50 30.13  4.23   403    93     1  90.88   230     4     5   149   1476.91
## 6 25.62 30.37  8.33   474   104     9  82.43   265    44    46   131   1493.55
##   aridityIndexThornthwaite climaticMoistureIndex continentality embergerQ
## 1                    85.63                 -0.81           28.1     21.87
## 2                    96.94                 -0.97           26.5      4.60
## 3                    92.98                 -0.93           27.0      9.30
## 4                    86.40                 -0.84           28.8     19.17
## 5                    87.57                 -0.73           29.0     32.52
## 6                    76.93                 -0.68           23.7     44.31
##   growingDegDays0 growingDegDays5 maxTempColdest meanTempColdest
## 1          5511.1          3915.7            5.7             0.8
## 2          8132.7          6307.7           15.2             8.5
## 3          6893.1          5068.1           10.3             5.0
## 4          6165.0          4426.2            7.3             2.4
## 5          6356.9          4606.3            7.3             2.6
## 6          7150.0          5325.0           12.8             7.2
##   meanTempWarmest minTempWarmest monthCountByTemp10 PETColdestQuarter
## 1            28.9           21.3                  7             33.67
## 2            35.0           27.1                 10             65.21
## 3            32.0           24.8                  9             47.26
## 4            31.2           22.8                  8             35.85
## 5            31.6           22.8                  9             35.26
## 6            30.9           24.2                  9             48.37
##   PETDriestQuarter PETseasonality PETWarmestQuarter PETWettestQuarter
## 1           199.60        6834.21            199.60             76.51
## 2           236.56        7189.45            236.39             85.05
## 3           209.12        6725.91            209.12             64.39
## 4           220.91        7647.82            220.91             49.55
## 5           202.92        7762.62            224.90             81.83
## 6           149.02        6449.21            202.83             93.23
##   thermicityIndex     MCWD  bdod   cec  clay nitrogen phh2o  sand  silt   soc
## 1          289.83 -1078.88 146.7 177.3 274.4    123.1  78.7 285.4 440.3  73.6
## 2          488.75 -1772.54 145.3 154.6 290.7     42.3  82.4 371.9 337.3  22.6
## 3          394.25 -1416.50 146.2 120.7 259.9     78.9  78.8 512.5 227.4  30.8
## 4          357.25 -1238.91    NA    NA    NA       NA    NA    NA    NA    NA
## 5          370.50 -1134.02    NA    NA    NA       NA    NA    NA    NA    NA
## 6          394.83 -1036.85 145.3 100.8 167.1    124.1  79.0 445.4 387.9 117.2
##   topoWet   tri
## 1   10.33 18.75
## 2   13.37  0.62
## 3    9.88 61.25
## 4   14.43  1.62
## 5   11.66 13.38
## 6    8.72 63.88
site.fut <- data.frame(read_excel(future.file,
                                  sheet="Cities data",
                                  skip=6))
nrow(site.fut)
## [1] 52602

3.3 Species list from GlobUNT

GlobUNT was filtered for Kenya and trees used as human food. The search resulted in 461 species.

# globunt.file <- choose.files()
globunt.file <- "C:\\Data\\Kenya_GlobUNT_HF_2023-11-15.txt"
globunt <- fread(globunt.file, sep="|", encoding="UTF-8")
nrow(globunt)
## [1] 461

4 Create a new data.frame with a wide format from the TreeGOER

Similar to the case studies of the TreeGOER manuscript (also see the Supplementary Script), a new data.frame is created that documents the ranges for a selected subset of environmental variables.

4.1 TreeGOER ranges for the GlobUNT species

globunt.treegoer <- dplyr::left_join(globunt[, c("Species", "Switchboard")],
                                     treegoer,
                                     by=c("Switchboard"="species")) # standardized species names in the databases
length(unique(globunt.treegoer$Switchboard))
## [1] 461
length(unique(globunt.treegoer$Species)) # Each species was standardized to a different taxon
## [1] 461

4.2 Variables of interest

Any environmental variable from TreeGOER can be included. Here only variables included in the climate filter of GlobUNT will be included.

focal.vars <- c("bio01", "bio12",
                "climaticMoistureIndex", "monthCountByTemp10", "growingDegDays5",
                "bio05", "bio06", "bio16", "bio17",
                "MCWD")

4.3 Create the new data.frame

focal.treegoer <- focal.vars[1]

focal.ranges <- globunt.treegoer[globunt.treegoer$var == focal.treegoer, c("Species", "n", "MIN", "Q05", "QRT1", "QRT3", "Q95", "MAX")]
names(focal.ranges)[3:8] <- paste0(focal.treegoer, "_", names(focal.ranges)[3:8])
ranges.lookup <- focal.ranges
for (i in 2:length(focal.vars)) {
  
  focal.treegoer <- focal.vars[i]


  focal.ranges <- globunt.treegoer[globunt.treegoer$var == focal.treegoer, c("Species", "n", "MIN", "Q05", "QRT1", "QRT3", "Q95", "MAX")]
  names(focal.ranges)[3:8] <- paste0(focal.treegoer, "_", names(focal.ranges)[3:8])
  
  # check - note that data was missing for some explanatory variables especially soil
  cat(paste(focal.treegoer, ":", all.equal(focal.ranges$Taxon, ranges.lookup$Taxon), "\n"))
  ranges.lookup <- cbind(ranges.lookup, focal.ranges[, c(3:8)])
}
## bio12 : TRUE 
## climaticMoistureIndex : TRUE 
## monthCountByTemp10 : TRUE 
## growingDegDays5 : TRUE 
## bio05 : TRUE 
## bio06 : TRUE 
## bio16 : TRUE 
## bio17 : TRUE 
## MCWD : TRUE

The new data.frame has separate columns for the minimum, maximum, 5%, 95%, 25% (QRT1) and 75% (QRT3) ranges for each of the 10 focal environmental variables.

names(ranges.lookup)
##  [1] "Species"                    "n"                         
##  [3] "bio01_MIN"                  "bio01_Q05"                 
##  [5] "bio01_QRT1"                 "bio01_QRT3"                
##  [7] "bio01_Q95"                  "bio01_MAX"                 
##  [9] "bio12_MIN"                  "bio12_Q05"                 
## [11] "bio12_QRT1"                 "bio12_QRT3"                
## [13] "bio12_Q95"                  "bio12_MAX"                 
## [15] "climaticMoistureIndex_MIN"  "climaticMoistureIndex_Q05" 
## [17] "climaticMoistureIndex_QRT1" "climaticMoistureIndex_QRT3"
## [19] "climaticMoistureIndex_Q95"  "climaticMoistureIndex_MAX" 
## [21] "monthCountByTemp10_MIN"     "monthCountByTemp10_Q05"    
## [23] "monthCountByTemp10_QRT1"    "monthCountByTemp10_QRT3"   
## [25] "monthCountByTemp10_Q95"     "monthCountByTemp10_MAX"    
## [27] "growingDegDays5_MIN"        "growingDegDays5_Q05"       
## [29] "growingDegDays5_QRT1"       "growingDegDays5_QRT3"      
## [31] "growingDegDays5_Q95"        "growingDegDays5_MAX"       
## [33] "bio05_MIN"                  "bio05_Q05"                 
## [35] "bio05_QRT1"                 "bio05_QRT3"                
## [37] "bio05_Q95"                  "bio05_MAX"                 
## [39] "bio06_MIN"                  "bio06_Q05"                 
## [41] "bio06_QRT1"                 "bio06_QRT3"                
## [43] "bio06_Q95"                  "bio06_MAX"                 
## [45] "bio16_MIN"                  "bio16_Q05"                 
## [47] "bio16_QRT1"                 "bio16_QRT3"                
## [49] "bio16_Q95"                  "bio16_MAX"                 
## [51] "bio17_MIN"                  "bio17_Q05"                 
## [53] "bio17_QRT1"                 "bio17_QRT3"                
## [55] "bio17_Q95"                  "bio17_MAX"                 
## [57] "MCWD_MIN"                   "MCWD_Q05"                  
## [59] "MCWD_QRT1"                  "MCWD_QRT3"                 
## [61] "MCWD_Q95"                   "MCWD_MAX"

5 Filter function

A custom function is used to filter species for the conditions at the planting site. The same function is used internally in the GlobUNT.

# filter function modified from the filter function in the TreeGOER manuscript
filter.function <- function(focal.loc, filter.data, focal.vars, limit.vars=c("Q05", "Q95")) {
  filtered.data <- filter.data
  for (f in 1:length(focal.vars)) {
    focal.var <- focal.vars[f]
    LL <- paste0(focal.var, "_", limit.vars[1])
    filtered.data <- filtered.data[filtered.data[, LL] <= as.numeric(focal.loc[, focal.var]), ]
    UL <- paste0(focal.var, "_", limit.vars[2])
    filtered.data <- filtered.data[filtered.data[, UL] >= as.numeric(focal.loc[, focal.var]), ]
  }
  return(filtered.data) # modify the function to return the list of the suitable species
  #  return(nrow(filtered.data)) # return number of species as in TreeGOER manuscript
}

6 Filtering for mean annual temperature (BIO01)

For the first filtering exericse, we will only use the mean annual temperature (BIO01). This is the same bioclimatic variable that is used in the BGCI Climate Assessment Tool.

Similar to internal calculations in GlobUNT, the list of species is filtered sequentially for the minimum - maximum range (score = 1), 5% - 95% range (score = 2) and 25% - 75% range (score = 3).

6.1 Variable(s) selected for filtering

PL.vars <- "bio01"

6.2 Filtering for the baseline climate

First we filter for the baseline climate, selecting Nairobi …

PL.data <- site.base[site.base$Name == "Nairobi", ] # PL: Planting Location
PL.data
##        SEQ2  SEQ1 Geoname.ID    Name ASCII.Name Country.Code Country.name.EN
## 25839 25839 21095     184745 Nairobi    Nairobi           KE           Kenya
##       Population Modification.date        Coordinates      LON      LAT
## 25839    2750547        2021-07-29 -1.28333, 36.81667 36.81667 -1.28333
##       LON.ZONE LAT.ZONE bio01 bio02 bio03  bio04 bio05 bio06 bio07 bio08 bio09
## 25839       35     -2.5 19.61 11.52 73.88 119.98  28.2  12.6  15.6 20.57 18.12
##       bio10 bio11 bio12 bio13 bio14 bio15 bio16 bio17 bio18 bio19 annualPET
## 25839 20.98 17.95  1036   240    21 80.38   527    70   384    85   1549.78
##       aridityIndexThornthwaite climaticMoistureIndex continentality embergerQ
## 25839                    60.43                 -0.33            3.7    226.19
##       growingDegDays0 growingDegDays5 maxTempColdest meanTempColdest
## 25839          7156.5          5331.5           22.6            17.6
##       meanTempWarmest minTempWarmest monthCountByTemp10 PETColdestQuarter
## 25839            21.3           14.6                 12            108.29
##       PETDriestQuarter PETseasonality PETWarmestQuarter PETWettestQuarter
## 25839           117.31        1719.36            146.18            131.45
##       thermicityIndex    MCWD  bdod   cec  clay nitrogen phh2o  sand  silt
## 25839          495.17 -424.48 127.9 192.8 494.4    180.8  60.3 243.3 262.4
##         soc topoWet tri
## 25839 216.7   11.53  10

… and then calculating the climate score

country.data <- globunt[, c("Species", "climate_description")] # include the main biome documented in WCVP/POWO
country.data <- data.frame(country.data)

filteri <- data.frame(ranges.lookup)

country.data$Climate.score <- rep(-1, nrow(country.data))
country.data[country.data$Species %in% filteri$Species, "Climate.score"] <- 0

country.data <- left_join(country.data,
                          filteri[ , c("Species", "n")],
                          by="Species")

names(country.data)[which(names(country.data)=="n")] <- "n.TreeGOER"

filteri <- filter.function(focal.loc=PL.data,
                           filter.data=filteri,
                           focal.vars=PL.vars,
                           limit.vars=c("MIN", "MAX"))

country.data[country.data$Species %in% filteri$Species, "Climate.score"] <- 1

filteri <- filter.function(focal.loc=PL.data,
                           filter.data=filteri,
                           focal.vars=PL.vars,
                           limit.vars=c("Q05", "Q95"))

country.data[country.data$Species %in% filteri$Species, "Climate.score"] <- 2

filteri <- filter.function(focal.loc=PL.data,
                           filter.data=filteri,
                           focal.vars=PL.vars,
                           limit.vars=c("QRT1", "QRT3"))

country.data[country.data$Species %in% filteri$Species, "Climate.score"] <- 3

6.3 Checking the results

The following species got the highest score.

nrow(country.data[country.data$Climate.score == 3, ])
## [1] 102
head(country.data[country.data$Climate.score == 3, ])
##                      Species     climate_description Climate.score n.TreeGOER
## 3  Acokanthera oppositifolia seasonally dry tropical             3        117
## 4      Acokanthera schimperi seasonally dry tropical             3         56
## 7    Afrocanthium lactescens seasonally dry tropical             3         52
## 9          Alangium chinense seasonally dry tropical             3        443
## 16         Albizia gummifera seasonally dry tropical             3        331
## 22            Aloe volkensii seasonally dry tropical             3         30
tail(country.data[country.data$Climate.score == 3, ])
##                    Species     climate_description Climate.score n.TreeGOER
## 433         Vepris nobilis seasonally dry tropical             3        148
## 445      Volkameria glabra seasonally dry tropical             3        215
## 447   Warburgia ugandensis            wet tropical             3         47
## 450         Ximenia caffra seasonally dry tropical             3        339
## 460     Ziziphus mucronata seasonally dry tropical             3       1129
## 461 Ziziphus spina-christi desert or dry shrubland             3        819

We can cross-check that the planting location is within the 25% - 75% range of the first species:

PL.data[, PL.vars] # Conditions in the selected planting location
## [1] 19.61
check.species <- country.data[country.data$Climate.score == 3, "Species"][1]
ranges.lookup[ranges.lookup$Species == check.species, 
              c("Species", "bio01_MIN", "bio01_Q05", "bio01_QRT1", "bio01_QRT3", "bio01_Q95", "bio01_MAX")]
##                      Species bio01_MIN bio01_Q05 bio01_QRT1 bio01_QRT3
## 1: Acokanthera oppositifolia     15.06     15.45      17.03       19.7
##    bio01_Q95 bio01_MAX
## 1:     21.61      22.7

These are the species with score = 2.

nrow(country.data[country.data$Climate.score == 2, ])
## [1] 159
head(country.data[country.data$Climate.score == 2, ])
##                  Species     climate_description Climate.score n.TreeGOER
## 1     Acalypha fruticosa seasonally dry tropical             2        123
## 2        Acalypha ornata seasonally dry tropical             2        240
## 6         Adenium obesum desert or dry shrubland             2        594
## 11 Albizia adianthifolia seasonally dry tropical             2        971
## 12         Albizia amara seasonally dry tropical             2        222
## 13 Albizia anthelmintica seasonally dry tropical             2        218
tail(country.data[country.data$Climate.score == 2, ])
##                   Species     climate_description Climate.score n.TreeGOER
## 448   Woodfordia uniflora seasonally dry tropical             2         24
## 449     Ximenia americana seasonally dry tropical             2       2527
## 454     Xymalos monospora            wet tropical             2        321
## 455        Zanha africana seasonally dry tropical             2         47
## 456 Zanthoxylum chalybeum seasonally dry tropical             2         42
## 457  Zanthoxylum gilletii            wet tropical             2        235

We can cross-check that the planting location is within the 5% - 95% range, but outside the 25% - 75% range of the first species:

PL.data[, PL.vars] # Conditions in the selected planting location
## [1] 19.61
check.species <- country.data[country.data$Climate.score == 2, "Species"][1]
ranges.lookup[ranges.lookup$Species == check.species, 
              c("Species", "bio01_MIN", "bio01_Q05", "bio01_QRT1", "bio01_QRT3", "bio01_Q95", "bio01_MAX")]
##               Species bio01_MIN bio01_Q05 bio01_QRT1 bio01_QRT3 bio01_Q95
## 1: Acalypha fruticosa     14.14     18.18      20.81      24.62     27.04
##    bio01_MAX
## 1:      28.9

These are the species with Climate Score = 1.

nrow(country.data[country.data$Climate.score == 1, ])
## [1] 92
head(country.data[country.data$Climate.score == 1, ])
##                   Species     climate_description Climate.score n.TreeGOER
## 5      Adansonia digitata seasonally dry tropical             1       2218
## 8      Afzelia quanzensis seasonally dry tropical             1        252
## 14       Albizia coriaria seasonally dry tropical             1         80
## 20   Alchornea cordifolia            wet tropical             1        683
## 23    Annona senegalensis seasonally dry tropical             1       2590
## 35 Balanites rotundifolia desert or dry shrubland             1         32
tail(country.data[country.data$Climate.score == 1, ])
##                 Species     climate_description Climate.score n.TreeGOER
## 427  Vachellia tortilis desert or dry shrubland             1       3009
## 435       Vitex doniana seasonally dry tropical             1       1842
## 436    Vitex ferruginea seasonally dry tropical             1        153
## 443   Voacanga africana seasonally dry tropical             1        531
## 452  Xylopia aethiopica            wet tropical             1        645
## 458 Ziziphus abyssinica seasonally dry tropical             1        213

We can cross-check that the planting location is within the 0% - 100% range, but outside the 5% - 95% range of the first species:

PL.data[, PL.vars] # Conditions in the selected planting location
## [1] 19.61
check.species <- country.data[country.data$Climate.score == 1, "Species"][1]
ranges.lookup[ranges.lookup$Species == check.species, 
              c("Species", "bio01_MIN", "bio01_Q05", "bio01_QRT1", "bio01_QRT3", "bio01_Q95", "bio01_MAX")]
##               Species bio01_MIN bio01_Q05 bio01_QRT1 bio01_QRT3 bio01_Q95
## 1: Adansonia digitata     13.68     21.64      24.47      27.96     28.85
##    bio01_MAX
## 1:     30.53

These are the species with Climate Score = 0.

nrow(country.data[country.data$Climate.score == 0, ])
## [1] 92
head(country.data[country.data$Climate.score == 0, ])
##                  Species     climate_description Climate.score n.TreeGOER
## 10 Alangium salviifolium            wet tropical             0         48
## 15    Albizia glaberrima seasonally dry tropical             0        365
## 19         Albizia zygia seasonally dry tropical             0       1198
## 25    Antiaris toxicaria            wet tropical             0       1150
## 28   Asteranthe asterias seasonally dry tropical             0         73
## 32  Balanites aegyptiaca desert or dry shrubland             0       3499
tail(country.data[country.data$Climate.score == 0, ])
##                   Species     climate_description Climate.score n.TreeGOER
## 410   Thilachium thomasii desert or dry shrubland             0         16
## 420     Vachellia elatior seasonally dry tropical             0         26
## 426       Vachellia seyal desert or dry shrubland             0       1684
## 434   Vepris samburuensis seasonally dry tropical             0          2
## 446 Warburgia stuhlmannii            wet tropical             0         12
## 451   Xylocarpus granatum            wet tropical             0        256

We can cross-check that the planting location is outside the 0% - 100% range of the first species:

PL.data[, PL.vars] # Conditions in the selected planting location
## [1] 19.61
check.species <- country.data[country.data$Climate.score == 0, "Species"][1]
ranges.lookup[ranges.lookup$Species == check.species, 
              c("Species", "bio01_MIN", "bio01_Q05", "bio01_QRT1", "bio01_QRT3", "bio01_Q95", "bio01_MAX")]
##                  Species bio01_MIN bio01_Q05 bio01_QRT1 bio01_QRT3 bio01_Q95
## 1: Alangium salviifolium     22.58     22.85      24.24      26.27     28.06
##    bio01_MAX
## 1:     28.42

6.4 Filtering for the future climate

Almost the same script can be used for filter species suitable for the future climate. The only modification is that we now obtain the bioclimatic conditions from the future climate data set.

We select Nairobi again…

PL.data <- site.fut[site.fut$Name == "Nairobi", ] # PL: Planting Location
PL.data
##        SEQ2  SEQ1 Geoname.ID    Name ASCII.Name Country.Code Country.name.EN
## 25839 25839 21095     184745 Nairobi    Nairobi           KE           Kenya
##       Population Modification.date        Coordinates      LON      LAT bio01
## 25839    2750547        2021-07-29 -1.28333, 36.81667 36.81667 -1.28333  21.2
##       bio02 bio03 bio04 bio05 bio06 bio07 bio08 bio09 bio10 bio11  bio12 bio13
## 25839 11.15 74.25 117.1  29.3  14.3  15.1  22.3  19.6  22.5  19.6 1066.5   239
##       bio14 bio15 bio16 bio17 bio18 bio19 annualPET aridityIndexThornthwaite
## 25839    20  78.8 518.5    68 393.5  80.5   1585.04                     57.4
##       climaticMoistureIndex continentality embergerQ growingDegDays0
## 25839                 -0.33           3.68    236.41         7731.25
##       growingDegDays5 maxTempColdest meanTempColdest meanTempWarmest
## 25839         5906.25           24.2           19.27           22.78
##       minTempWarmest monthCountByTemp10 PETColdestQuarter PETDriestQuarter
## 25839           16.4                 12            112.81           120.94
##       PETseasonality PETWarmestQuarter PETWettestQuarter thermicityIndex   MCWD
## 25839         1703.4            148.13            134.08          542.29 -475.2

… and then proceed with the same script to calculate the climate score

country.data <- globunt[, c("Species", "climate_description")] # include the main biome documented in WCVP/POWO
country.data <- data.frame(country.data)

filteri <- data.frame(ranges.lookup)

country.data$Climate.score <- rep(-1, nrow(country.data))
country.data[country.data$Species %in% filteri$Species, "Climate.score"] <- 0

country.data <- left_join(country.data,
                          filteri[ , c("Species", "n")],
                          by="Species")

names(country.data)[which(names(country.data)=="n")] <- "n.TreeGOER"

filteri <- filter.function(focal.loc=PL.data,
                           filter.data=filteri,
                           focal.vars=PL.vars,
                           limit.vars=c("MIN", "MAX"))

country.data[country.data$Species %in% filteri$Species, "Climate.score"] <- 1

filteri <- filter.function(focal.loc=PL.data,
                           filter.data=filteri,
                           focal.vars=PL.vars,
                           limit.vars=c("Q05", "Q95"))

country.data[country.data$Species %in% filteri$Species, "Climate.score"] <- 2

filteri <- filter.function(focal.loc=PL.data,
                           filter.data=filteri,
                           focal.vars=PL.vars,
                           limit.vars=c("QRT1", "QRT3"))

country.data[country.data$Species %in% filteri$Species, "Climate.score"] <- 3

6.5 Checking the results

For the future climate, the breakdown is:

nrow(country.data[country.data$Climate.score == 3, ])
## [1] 138
nrow(country.data[country.data$Climate.score == 2, ])
## [1] 186
nrow(country.data[country.data$Climate.score == 1, ])
## [1] 67
nrow(country.data[country.data$Climate.score == 0, ])
## [1] 54

Doing some cross-checking

PL.data[, PL.vars] # Conditions in the selected planting location
## [1] 21.2
check.species <- country.data[country.data$Climate.score == 3, "Species"][1]
ranges.lookup[ranges.lookup$Species == check.species, 
              c("Species", "bio01_MIN", "bio01_Q05", "bio01_QRT1", "bio01_QRT3", "bio01_Q95", "bio01_MAX")]
##               Species bio01_MIN bio01_Q05 bio01_QRT1 bio01_QRT3 bio01_Q95
## 1: Acalypha fruticosa     14.14     18.18      20.81      24.62     27.04
##    bio01_MAX
## 1:      28.9
check.species <- country.data[country.data$Climate.score == 2, "Species"][1]
ranges.lookup[ranges.lookup$Species == check.species, 
              c("Species", "bio01_MIN", "bio01_Q05", "bio01_QRT1", "bio01_QRT3", "bio01_Q95", "bio01_MAX")]
##                      Species bio01_MIN bio01_Q05 bio01_QRT1 bio01_QRT3
## 1: Acokanthera oppositifolia     15.06     15.45      17.03       19.7
##    bio01_Q95 bio01_MAX
## 1:     21.61      22.7
check.species <- country.data[country.data$Climate.score == 1, "Species"][1]
ranges.lookup[ranges.lookup$Species == check.species, 
              c("Species", "bio01_MIN", "bio01_Q05", "bio01_QRT1", "bio01_QRT3", "bio01_Q95", "bio01_MAX")]
##               Species bio01_MIN bio01_Q05 bio01_QRT1 bio01_QRT3 bio01_Q95
## 1: Adansonia digitata     13.68     21.64      24.47      27.96     28.85
##    bio01_MAX
## 1:     30.53
check.species <- country.data[country.data$Climate.score == 0, "Species"][1]
ranges.lookup[ranges.lookup$Species == check.species, 
              c("Species", "bio01_MIN", "bio01_Q05", "bio01_QRT1", "bio01_QRT3", "bio01_Q95", "bio01_MAX")]
##                  Species bio01_MIN bio01_Q05 bio01_QRT1 bio01_QRT3 bio01_Q95
## 1: Alangium salviifolium     22.58     22.85      24.24      26.27     28.06
##    bio01_MAX
## 1:     28.42

7 Filtering for three bioclimatic variables

In the second filtering exercise, we filter the species for three environmental variables, a growing degree days variable (GDD5), the minimum temperature of the coldest month (BIO06) and a moisture index (CMI). Similar environmental variables have been used previously in research on the adaption of tree species to future climates (see Booth 2016).

The same scripts can be used after selecting the variables.

7.1 Variable(s) selected for filtering

PL.vars <- c("growingDegDays5", "bio06", "climaticMoistureIndex")
PL.vars %in% focal.vars # checking if we had selected these variables earlier
## [1] TRUE TRUE TRUE

7.2 Filtering for the baseline climate

First we filter for the baseline climate, selecting Nairobi …

PL.data <- site.base[site.base$Name == "Nairobi", ] # PL: Planting Location
PL.data[, PL.vars]
##       growingDegDays5 bio06 climaticMoistureIndex
## 25839          5331.5  12.6                 -0.33

… and then calculating the climate score

country.data <- globunt[, c("Species", "climate_description")] # include the main biome documented in WCVP/POWO
country.data <- data.frame(country.data)

filteri <- data.frame(ranges.lookup)

country.data$Climate.score <- rep(-1, nrow(country.data))
country.data[country.data$Species %in% filteri$Species, "Climate.score"] <- 0

country.data <- left_join(country.data,
                          filteri[ , c("Species", "n")],
                          by="Species")

names(country.data)[which(names(country.data)=="n")] <- "n.TreeGOER"

filteri <- filter.function(focal.loc=PL.data,
                           filter.data=filteri,
                           focal.vars=PL.vars,
                           limit.vars=c("MIN", "MAX"))

country.data[country.data$Species %in% filteri$Species, "Climate.score"] <- 1

filteri <- filter.function(focal.loc=PL.data,
                           filter.data=filteri,
                           focal.vars=PL.vars,
                           limit.vars=c("Q05", "Q95"))

country.data[country.data$Species %in% filteri$Species, "Climate.score"] <- 2

filteri <- filter.function(focal.loc=PL.data,
                           filter.data=filteri,
                           focal.vars=PL.vars,
                           limit.vars=c("QRT1", "QRT3"))

country.data[country.data$Species %in% filteri$Species, "Climate.score"] <- 3

7.3 Checking the results

PL.data[, PL.vars]
##       growingDegDays5 bio06 climaticMoistureIndex
## 25839          5331.5  12.6                 -0.33
nrow(country.data[country.data$Climate.score == 3, ])
## [1] 40
nrow(country.data[country.data$Climate.score == 2, ])
## [1] 191
nrow(country.data[country.data$Climate.score == 1, ])
## [1] 113

Doing some cross-checking

PL.data[, PL.vars] # Conditions in the selected planting location
##       growingDegDays5 bio06 climaticMoistureIndex
## 25839          5331.5  12.6                 -0.33
check.species <- country.data[country.data$Climate.score == 3, "Species"][1]
ranges.lookup[ranges.lookup$Species == check.species, 
              c("Species", "growingDegDays5_Q05", "growingDegDays5_QRT1", "growingDegDays5_QRT3", "growingDegDays5_Q95",
                "bio06_Q05", "bio06_QRT1", "bio06_QRT3", "bio06_Q95",
                "climaticMoistureIndex_Q05", "climaticMoistureIndex_QRT1", "climaticMoistureIndex_QRT3", "climaticMoistureIndex_Q95")]
##           Species growingDegDays5_Q05 growingDegDays5_QRT1 growingDegDays5_QRT3
## 1: Aloe volkensii              4469.2              4982.85               6626.1
##    growingDegDays5_Q95 bio06_Q05 bio06_QRT1 bio06_QRT3 bio06_Q95
## 1:              7615.4       9.2      11.33       16.3      19.6
##    climaticMoistureIndex_Q05 climaticMoistureIndex_QRT1
## 1:                     -0.64                      -0.48
##    climaticMoistureIndex_QRT3 climaticMoistureIndex_Q95
## 1:                       -0.2                      0.17
check.species <- country.data[country.data$Climate.score == 2, "Species"][1]
ranges.lookup[ranges.lookup$Species == check.species, 
              c("Species", "growingDegDays5_Q05", "growingDegDays5_QRT1", "growingDegDays5_QRT3", "growingDegDays5_Q95",
                "bio06_Q05", "bio06_QRT1", "bio06_QRT3", "bio06_Q95",
                "climaticMoistureIndex_Q05", "climaticMoistureIndex_QRT1", "climaticMoistureIndex_QRT3", "climaticMoistureIndex_Q95")]
##               Species growingDegDays5_Q05 growingDegDays5_QRT1
## 1: Acalypha fruticosa             4808.96              5763.85
##    growingDegDays5_QRT3 growingDegDays5_Q95 bio06_Q05 bio06_QRT1 bio06_QRT3
## 1:              7161.05             8038.47      7.66      11.65       16.2
##    bio06_Q95 climaticMoistureIndex_Q05 climaticMoistureIndex_QRT1
## 1:     20.16                     -0.86                      -0.68
##    climaticMoistureIndex_QRT3 climaticMoistureIndex_Q95
## 1:                      -0.38                     -0.13
check.species <- country.data[country.data$Climate.score == 1, "Species"][1]
ranges.lookup[ranges.lookup$Species == check.species, 
              c("Species", "growingDegDays5_Q05", "growingDegDays5_QRT1", "growingDegDays5_QRT3", "growingDegDays5_Q95",
                "bio06_Q05", "bio06_QRT1", "bio06_QRT3", "bio06_Q95",
                "climaticMoistureIndex_Q05", "climaticMoistureIndex_QRT1", "climaticMoistureIndex_QRT3", "climaticMoistureIndex_Q95")]
##                      Species growingDegDays5_Q05 growingDegDays5_QRT1
## 1: Acokanthera oppositifolia             3809.01               4384.3
##    growingDegDays5_QRT3 growingDegDays5_Q95 bio06_Q05 bio06_QRT1 bio06_QRT3
## 1:               5362.1             6060.28      1.67        3.4        9.5
##    bio06_Q95 climaticMoistureIndex_Q05 climaticMoistureIndex_QRT1
## 1:     11.51                     -0.66                      -0.55
##    climaticMoistureIndex_QRT3 climaticMoistureIndex_Q95
## 1:                      -0.37                     -0.13

7.4 Filtering for the future climate

Again modifying the script is easy, again also selecting Nairobi:

PL.data <- site.fut[site.fut$Name == "Nairobi", ] # PL: Planting Location
PL.data[, PL.vars]
##       growingDegDays5 bio06 climaticMoistureIndex
## 25839         5906.25  14.3                 -0.33

… and then calculating the climate score

country.data <- globunt[, c("Species", "climate_description")] # include the main biome documented in WCVP/POWO
country.data <- data.frame(country.data)

filteri <- data.frame(ranges.lookup)

country.data$Climate.score <- rep(-1, nrow(country.data))
country.data[country.data$Species %in% filteri$Species, "Climate.score"] <- 0

country.data <- left_join(country.data,
                          filteri[ , c("Species", "n")],
                          by="Species")

names(country.data)[which(names(country.data)=="n")] <- "n.TreeGOER"

filteri <- filter.function(focal.loc=PL.data,
                           filter.data=filteri,
                           focal.vars=PL.vars,
                           limit.vars=c("MIN", "MAX"))

country.data[country.data$Species %in% filteri$Species, "Climate.score"] <- 1

filteri <- filter.function(focal.loc=PL.data,
                           filter.data=filteri,
                           focal.vars=PL.vars,
                           limit.vars=c("Q05", "Q95"))

country.data[country.data$Species %in% filteri$Species, "Climate.score"] <- 2

filteri <- filter.function(focal.loc=PL.data,
                           filter.data=filteri,
                           focal.vars=PL.vars,
                           limit.vars=c("QRT1", "QRT3"))

country.data[country.data$Species %in% filteri$Species, "Climate.score"] <- 3

7.5 Checking the results

PL.data[, PL.vars]
##       growingDegDays5 bio06 climaticMoistureIndex
## 25839         5906.25  14.3                 -0.33
nrow(country.data[country.data$Climate.score == 3, ])
## [1] 44
nrow(country.data[country.data$Climate.score == 2, ])
## [1] 229
nrow(country.data[country.data$Climate.score == 1, ])
## [1] 100

8 Filtering for ten bioclimatic variables

As you will have expected by now, it is easy to modify the script to include more variables.

8.1 Variable(s) selected for filtering

PL.vars <- focal.vars

8.2 Filtering for the baseline climate

First we filter for the baseline climate, selecting Nairobi …

PL.data <- site.base[site.base$Name == "Nairobi", ] # PL: Planting Location
PL.data[, PL.vars]
##       bio01 bio12 climaticMoistureIndex monthCountByTemp10 growingDegDays5
## 25839 19.61  1036                 -0.33                 12          5331.5
##       bio05 bio06 bio16 bio17    MCWD
## 25839  28.2  12.6   527    70 -424.48

… and then calculating the climate score

country.data <- globunt[, c("Species", "climate_description")] # include the main biome documented in WCVP/POWO
country.data <- data.frame(country.data)

filteri <- data.frame(ranges.lookup)

country.data$Climate.score <- rep(-1, nrow(country.data))
country.data[country.data$Species %in% filteri$Species, "Climate.score"] <- 0

country.data <- left_join(country.data,
                          filteri[ , c("Species", "n")],
                          by="Species")

names(country.data)[which(names(country.data)=="n")] <- "n.TreeGOER"

filteri <- filter.function(focal.loc=PL.data,
                           filter.data=filteri,
                           focal.vars=PL.vars,
                           limit.vars=c("MIN", "MAX"))

country.data[country.data$Species %in% filteri$Species, "Climate.score"] <- 1

filteri <- filter.function(focal.loc=PL.data,
                           filter.data=filteri,
                           focal.vars=PL.vars,
                           limit.vars=c("Q05", "Q95"))

country.data[country.data$Species %in% filteri$Species, "Climate.score"] <- 2

filteri <- filter.function(focal.loc=PL.data,
                           filter.data=filteri,
                           focal.vars=PL.vars,
                           limit.vars=c("QRT1", "QRT3"))

country.data[country.data$Species %in% filteri$Species, "Climate.score"] <- 3

8.3 Checking the results

PL.data[, PL.vars]
##       bio01 bio12 climaticMoistureIndex monthCountByTemp10 growingDegDays5
## 25839 19.61  1036                 -0.33                 12          5331.5
##       bio05 bio06 bio16 bio17    MCWD
## 25839  28.2  12.6   527    70 -424.48
nrow(country.data[country.data$Climate.score == 3, ])
## [1] 17
nrow(country.data[country.data$Climate.score == 2, ])
## [1] 158
nrow(country.data[country.data$Climate.score == 1, ])
## [1] 148

8.4 Filtering for the future climate

Once again modifying the script is easy, selecting Nairobi:

PL.data <- site.fut[site.fut$Name == "Nairobi", ] # PL: Planting Location
PL.data[, PL.vars]
##       bio01  bio12 climaticMoistureIndex monthCountByTemp10 growingDegDays5
## 25839  21.2 1066.5                 -0.33                 12         5906.25
##       bio05 bio06 bio16 bio17   MCWD
## 25839  29.3  14.3 518.5    68 -475.2

… and then calculating the climate score

country.data <- globunt[, c("Species", "climate_description")] # include the main biome documented in WCVP/POWO
country.data <- data.frame(country.data)

filteri <- data.frame(ranges.lookup)

country.data$Climate.score <- rep(-1, nrow(country.data))
country.data[country.data$Species %in% filteri$Species, "Climate.score"] <- 0

country.data <- left_join(country.data,
                          filteri[ , c("Species", "n")],
                          by="Species")

names(country.data)[which(names(country.data)=="n")] <- "n.TreeGOER"

filteri <- filter.function(focal.loc=PL.data,
                           filter.data=filteri,
                           focal.vars=PL.vars,
                           limit.vars=c("MIN", "MAX"))

country.data[country.data$Species %in% filteri$Species, "Climate.score"] <- 1

filteri <- filter.function(focal.loc=PL.data,
                           filter.data=filteri,
                           focal.vars=PL.vars,
                           limit.vars=c("Q05", "Q95"))

country.data[country.data$Species %in% filteri$Species, "Climate.score"] <- 2

filteri <- filter.function(focal.loc=PL.data,
                           filter.data=filteri,
                           focal.vars=PL.vars,
                           limit.vars=c("QRT1", "QRT3"))

country.data[country.data$Species %in% filteri$Species, "Climate.score"] <- 3

8.5 Checking the results

PL.data[, PL.vars]
##       bio01  bio12 climaticMoistureIndex monthCountByTemp10 growingDegDays5
## 25839  21.2 1066.5                 -0.33                 12         5906.25
##       bio05 bio06 bio16 bio17   MCWD
## 25839  29.3  14.3 518.5    68 -475.2
country.data[country.data$Climate.score == 3, ]
##                        Species     climate_description Climate.score n.TreeGOER
## 22              Aloe volkensii seasonally dry tropical             3         30
## 51          Bridelia micrantha            wet tropical             3        705
## 74            Clausena anisata seasonally dry tropical             3        848
## 97             Cordia africana seasonally dry tropical             3        181
## 129       Diospyros natalensis seasonally dry tropical             3         86
## 148         Ekebergia capensis seasonally dry tropical             3        699
## 157   Erythroxylum emarginatum            wet tropical             3        267
## 165        Euphorbia umbellata seasonally dry tropical             3         60
## 191        Garcinia buchananii seasonally dry tropical             3         50
## 297          Phoenix reclinata seasonally dry tropical             3        774
## 322           Rauvolfia caffra seasonally dry tropical             3        342
## 382       Strychnos henningsii seasonally dry tropical             3        138
## 412         Trichilia dregeana            wet tropical             3        128
## 431 Vangueria madagascariensis seasonally dry tropical             3        220
## 433             Vepris nobilis seasonally dry tropical             3        148
## 437             Vitex fischeri seasonally dry tropical             3         26
nrow(country.data[country.data$Climate.score == 3, ])
## [1] 16
nrow(country.data[country.data$Climate.score == 2, ])
## [1] 195
nrow(country.data[country.data$Climate.score == 1, ])
## [1] 151

9 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.

10 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       readxl_1.4.1      data.table_1.14.2
## 
## loaded via a namespace (and not attached):
##  [1] rstudioapi_0.14  knitr_1.40       magrittr_2.0.3   tidyselect_1.2.0
##  [5] R6_2.5.1         rlang_1.1.1      fastmap_1.1.1    fansi_1.0.3     
##  [9] stringr_1.4.1    tools_4.2.1      xfun_0.33        utf8_1.2.2      
## [13] cli_3.4.1        jquerylib_0.1.4  htmltools_0.5.6  yaml_2.3.5      
## [17] digest_0.6.29    tibble_3.2.1     lifecycle_1.0.3  sass_0.4.2      
## [21] vctrs_0.6.3      glue_1.6.2       cachem_1.0.6     evaluate_0.16   
## [25] rmarkdown_2.16   stringi_1.7.8    pillar_1.9.0     compiler_4.2.1  
## [29] bslib_0.4.0      cellranger_1.1.0 generics_0.1.3   jsonlite_1.8.0  
## [33] pkgconfig_2.0.3