-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathget_gbif_taxonomy.R
More file actions
269 lines (208 loc) · 10.7 KB
/
Copy pathget_gbif_taxonomy.R
File metadata and controls
269 lines (208 loc) · 10.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
#' Get accepted canonical names and taxonomy for a given species name
#'
#' The function maps user provided names to accepted species names.
#'
#' @param x a character string or vector of species names.
#' @param subspecies logical. If TRUE (default), the given name is resolved to
#' subspecies epithet, otherwise it will be mapped to species level.
#' @param higherrank logical. If FALSE (default), it will not allow remapping of
#' unknown species names to higher taxon ranks (e.g. genus).
#' @param verbose logical. If FALSE (default), warnings and messages are
#' suppressed.
#' @param fuzzy logical. Defaults to TRUE to deal with misspelled names. May
#' produce wrong assignments in case of very similar taxon names. If FALSE
#' (default), names are only resolved to exactly matching taxa on GBIF
#' taxonomy service.
#' @param conf_threshold numerical, ranging from 0 to 100 (default value = 90).
#' Defines the confidence level of the request to be accepted. To cover for
#' misspellings and errors, could go as low as 50.
#' @param resolve_synonyms logical. If TRUE (default), user provided synonyms
#' are mapped to the accepted names on GBIF taxonomy service.
#'
#' @return a data.frame mapping the user supplied names to the accepted taxon
#' names and higher taxonomic information (kingdom, phylum, class, order,
#' family, genus).
#'
#' @details The function relies on package 'taxize' by Scott Chamberlain. It
#' uses the spell-checking and fuzzy matching algorithms provided by Global
#' Names Resolver (`taxize::gnr_resolve()`) and forwards synonyms to the
#' accepted names as provided by GBIF Backbone Taxonomy
#' (`taxize::get_gbif_id_()`).
#'
#' If 'synonym' is returned as TRUE, the user provided name has been
#' identified as a synonym and was mapped to an accepted name.
#'
#' The field confidence reports the confidence of the matching procedure
#' performed by the function `get_gbifid_()` of the package 'taxize'. The
#' taxonID is a globally valid URI that links to the taxon description of the
#' GBIF backbone taxonomy.
#'
#' @import taxize
#' @import curl
#' @importFrom data.table rbindlist
#' @export
#'
#' @examples
#'
#' get_gbif_taxonomy(c("Chorthippus albomarginatus", "Chorthippus apricarius",
#' "Chorthippus biguttulus", "Chorthippus dorsatus", "Chorthippus montanus",
#' "Chorthippus parallelus", "Chrysochraon dispar", "Conocephalus dorsalis",
#' "Conocephalus fuscus", "Decticus verrucivorus", "Euthystira brachyptera",
#' "Gomphocerippus rufus", "Gryllus campestris", "Metrioptera roeselii",
#' "Omocestus viridulus", "Phaneroptera falcata", "Platycleis albopunctata",
#' "Spec", "Stenobothrus lineatus", "Stenobothrus stigmaticus",
#' "Stethophyma grossum", "Tetrix kraussi", "Tetrix subulata",
#' "Tetrix tenuicornis", "Tetrix undulata", "Tettigonia cantans",
#' "Tettigonia viridissima")
#' )
#'
#' get_gbif_taxonomy("Vicia")
get_gbif_taxonomy <- function(x,
subspecies = TRUE,
higherrank = FALSE,
verbose = FALSE,
fuzzy = TRUE,
conf_threshold = 90,
resolve_synonyms = TRUE
) {
matchtype = status = confidence = NULL
# test for internet connectivity
if( !curl::has_internet() ) {
message("Connection to Gbif Taxonomy API failed. Please check internet connectivity!")
temp <- lapply(x, function(i) {data.frame()} )
names(temp) <- x
} else {
# get gbif mappings
temp <- taxize::get_gbifid_(x, messages = verbose)
}
# loop over all species returns
for(i in 1:length(temp)) {
warning_i = ""
synonym_i = FALSE
# add warning in offline mode
if( !curl::has_internet() ) {
warning_i = "Gbif Taxonomy Service unavailable! Internet connection required."
temp[[i]] <- data.frame(verbatimScientificName = x[i], matchtype = "NONE", status = "NA", rank = "species", stringsAsFactors = FALSE)
} else {
# buildup empty returns
if(nrow(temp[[i]]) == 0) {
warning_i <- paste("No matching species concept!")
temp[[i]] <- data.frame(verbatimScientificName = x[i], matchtype = "NONE", status = "NA", rank = "species", stringsAsFactors = FALSE)
}
# clean out fuzzy matches, if not allowed
if(!fuzzy & nrow(temp[[i]]) > 0) {
temp[[i]] <- subset(temp[[i]], matchtype != "FUZZY")
if(nrow(temp[[i]]) == 0) {
warning_i <- paste(warning_i, "Fuzzy matching might yield results.")
}
}
# check for confidence threshold
if(!is.null(conf_threshold) & nrow(temp[[i]]) > 0) {
temp[[i]] <- subset(temp[[i]], confidence >= conf_threshold)
if(nrow(temp[[i]]) == 0) {
temp[[i]] <- data.frame(verbatimScientificName = x[i], matchtype = "NONE", status = "NA", rank = "species", stringsAsFactors = FALSE)
warning_i <- paste(warning_i, "Check spelling or lower confidence threshold!")
}
}
# remove all synonyms, if accepted exact match is found
if(any(temp[[i]]$status == "ACCEPTED")) {
temp[[i]] <- subset(temp[[i]], status == "ACCEPTED")
temp[[i]] <- subset(temp[[i]], temp[[i]]$confidence == max(temp[[i]]$confidence))
#warning_i <- paste(warning_i, "Automatically mapped to accepted species name!", sep = " ")
if(nrow(temp[[i]]) > 1) {
temp[[i]] <- temp[[i]][1,]
warning_i <- paste(warning_i, "Selected first of multiple equally ranked concepts!")
}
}
# resolve all synonyms, if allowed
if(!any(temp[[i]]$status == "ACCEPTED") & any(temp[[i]]$status == "SYNONYM") & any(temp[[i]]$rank == "species")) {
if(resolve_synonyms) {
keep <- temp[i]
temp[i] <- taxize::get_gbifid_(temp[[i]]$species[which.max(temp[[i]]$confidence)], messages = verbose)
if(temp[[i]][1,]$status == "ACCEPTED") {
temp[[i]] <- subset(temp[[i]], matchtype == "EXACT" & status == "ACCEPTED")
temp[[i]] <- subset(temp[[i]], temp[[i]]$confidence == max(temp[[i]]$confidence))
if(nrow(temp[[i]]) > 1) {
temp[[i]] <- temp[[i]][1,]
warning_i <- paste(warning_i, "Selected first of multiple equally ranked concepts!")
}
warning_i <- paste(warning_i, "A synonym was mapped to the accepted species concept!", sep = " ")
synonym_i = TRUE
} else {
status <- temp[[i]][1,]$status
temp[i] <- keep
if(nrow(temp[[i]]) > 1) {
temp[[i]] <- temp[[i]][1,]
warning_i <- paste(warning_i, "Selected first of multiple equally ranked concepts!")
}
warning_i <- paste0(warning_i, " Resolved synonym '", temp[[i]]$species,"' is labelled '", status, "'. Clarification required!" )
}
} else {
temp[[i]] <- subset(temp[[i]], status == "SYNONYM")
temp[[i]] <- subset(temp[[i]], temp[[i]]$confidence == max(temp[[i]]$confidence))
warning_i <- paste(warning_i, "The provided taxon seems to be a synonym of '", temp[[i]]$species,"'!", sep = "")
}
}
# check for doubtful status
if(all(temp[[i]]$status == "DOUBTFUL")) {
temp[[i]] <- subset(temp[[i]], status == "DOUBTFUL")
warning_i <- paste(warning_i, "Mapped concept is labelled 'DOUBTFUL'!")
temp[[i]] <- subset(temp[[i]], temp[[i]]$confidence == max(temp[[i]]$confidence))
#warning_i <- paste(warning_i, "Automatically mapped to accepted species name!", sep = " ")
if(nrow(temp[[i]]) > 1) {
temp[[i]] <- temp[[i]][1,]
warning_i <- paste(warning_i, "Selected first of multiple equally ranked concepts!")
}
}
# 3. check rankorder of result
rankorder <- c("kingdom", "phylum", "class", "order", "family", "genus", "species", "subspecies")
#print(temp)
#print(temp[[i]]$rank[1])
#print(rankorder)
if(match(temp[[i]]$rank[1], rankorder) > 7 & !subspecies) {
if(length(strsplit(as.character(temp[[i]]$canonicalname), " ")[[1]]) > 2) {
temp[i] <- taxize::get_gbifid_(paste(strsplit(names(temp[i]), " ")[[1]][1:2], collapse = " "), messages = verbose)
temp[[i]] <- subset(temp[[i]], temp[[i]]$confidence == max(temp[[i]]$confidence))
warning_i<- paste(warning_i, "Subspecies has been remapped to species concept!", sep = " ")
} else {
temp[[i]] <- data.frame(verbatimScientificName = x[i], matchtype = "NONE", rank = "subspecies", stringsAsFactors = FALSE)
warning_i <- paste(warning_i, "No mapping of subspecies name to species was possible!", sep = " ")
}
}
if(temp[[i]]$matchtype[1] == "HIGHERRANK") {
if(higherrank) {
temp[[i]] <- subset(temp[[i]], temp[[i]]$confidence == max(temp[[i]]$confidence))
warning_i <- paste(warning_i, "No matching species concept! Entry has been mapped to higher taxonomic level.")
} else {
temp[[i]] <- data.frame(verbatimScientificName = x[i], matchtype = "NONE", rank = "highertaxon", stringsAsFactors = FALSE)
warning_i <- paste("No matching species concept!", warning_i)
}
}
}
# 4. create structured output
temp[[i]] <- data.frame(
verbatimScientificName = x[i],
synonym = synonym_i,
scientificName = if(is.null(temp[[i]]$canonicalname)) NA else temp[[i]]$canonicalname,
author = if(is.null(temp[[i]]$canonicalname)) NA else sub(paste0(temp[[i]]$canonicalname," "), "", temp[[i]]$scientificname),
taxonRank = if(is.null(temp[[i]]$rank)) NA else temp[[i]]$rank,
confidence = if(is.null(temp[[i]]$confidence)) NA else temp[[i]]$confidence,
kingdom = if(is.null(temp[[i]]$kingdom)) NA else temp[[i]]$kingdom,
phylum = if(is.null(temp[[i]]$phylum)) NA else temp[[i]]$phylum,
class = if(is.null(temp[[i]]$class)) NA else temp[[i]]$class,
order = if(is.null(temp[[i]]$order)) NA else temp[[i]]$order,
family = if(is.null(temp[[i]]$family)) NA else temp[[i]]$family,
genus = if(is.null(temp[[i]]$genus)) NA else temp[[i]]$genus,
taxonomy = "GBIF Backbone Taxonomy",
taxonID = if(is.null(temp[[i]]$usagekey)) NA else paste0("http://www.gbif.org/species/", temp[[i]]$usagekey, ""),
warnings = NA,
stringsAsFactors = FALSE
)
temp[[i]]$warnings <- warning_i
if(verbose & nchar(warning_i) >= 1) warning(warning_i)
}
#compile output data.frame
out <- data.table::rbindlist(temp, fill = TRUE)
class(out) <- c("data.frame", "taxonomy")
return(out)
}