WFO Plant List APIをRから利用する方法
The World Flora Onlineという、世界中の植物のデータベースがあります。 WFO Plant List APIという形でAPIも提供されているので、Rからアクセスし、学名からaccepted nameを取得する方法を紹介してみます。
パッケージのインストール
APIを利用しやすくするために、httr2パッケージをインストールします。 また、APIからのレスポンスはJSON形式なので、jsonliteパッケージもインストールします。
インストール後、library()関数を使ってパッケージを読み込みます。
エンドポイントの指定
APIのエンドポイントは、以下のURLになります。
endpoint <- "https://list.worldfloraonline.org/gql.php"学名から検索するクエリの作成
どのように検索するかを、GraphQLのクエリとして記述します。 ここでは、“Quercus serrata”(和名はコナラ)という学名を検索するクエリを作成します。
query <- '
query {
taxonNameSuggestion(
termsString: "Quercus serrata"
limit: 10
) {
id
fullNameStringHtml
currentPreferredUsage {
hasName {
id
fullNameStringHtml
}
}
}
}
'このクエリの意味は、以下のようになります。
-
taxonNameSuggestionというフィールドを呼び出す -
termsString引数に検索したい学名を指定する -
limit引数に検索結果の最大数を指定する - クエリの結果として、
id、fullNameStringHtml、およびcurrentPreferredUsageフィールドを取得する
fullNameStringHtmlは検索した名前の学名をHTML形式で表現したものです。 currentPreferredUsageはその名前が現在どの分類群として扱われているかを示します。 currentPreferredUsageの中のhasNameフィールドには、現在の分類群の学名が含まれています。 つまり、Accepted Nameがある場合は、currentPreferredUsageの中のhasNameフィールドにその学名が表示されます。
リクエストを作成してAPIに送信する
httr2::request()関数を使って、APIに送るリクエストの土台を作ります。
req <- request(endpoint)GraphQLのqueryをJSONとして入れる。
req <- req_body_json(
req,
list(query = query),
auto_unbox = TRUE
)実際にWFOへ送信します。
resp <- req_perform(req)返ってきたJSONをRのlistに変換します。 これで、APIからのレスポンスをRで扱えるようになります。
x <- resp_body_json(resp, simplifyVector = FALSE)Checking the Result
The x object stores the API response as an R list.
print(x)Ten candidates were returned because the query specified limit: 10. Let’s inspect the first result.
x$data$taxonNameSuggestion[[1]]The three fields id, fullNameStringHtml, and currentPreferredUsage were returned.
The field to focus on is hasName inside currentPreferredUsage. This field contains the scientific name of the current taxon.
x$data$taxonNameSuggestion[[1]]$currentPreferredUsageIn this example, the hasName field inside currentPreferredUsage displays the scientific name “Quercus serrata Murray”.
x$data$taxonNameSuggestion[[1]]$id
x$data$taxonNameSuggestion[[1]]$currentPreferredUsage$hasName$idComparing x$data$taxonNameSuggestion[[1]]$id with x$data$taxonNameSuggestion[[1]]$currentPreferredUsage$hasName$id, the two values are identical.
This shows that the searched scientific name "Quercus serrata" is currently treated as "Quercus serrata".
In this way, you can retrieve an accepted name from a searched scientific name.
Creating a Function to Retrieve Accepted Names
In practical work, it is useful to wrap the process in functions.
Here, I turn the steps above into functions and create a function that retrieves the accepted name from a scientific name. First, create get_wfo_suggestions(), which retrieves candidates from a scientific name.
In addition to the query above, this function also retrieves fields such as fullNameStringNoAuthorsPlain, authorsString, and rank. The rank field is especially important because it shows the taxonomic rank of the searched name. For example, it lets you distinguish whether the name is at the rank of species, subspecies, variety, and so on.
When retrieving an accepted name, it is often useful to look for an accepted name at the same rank as the searched name, so this function also retrieves rank information.
get_wfo_suggestions <- function(
name,
limit = 10,
endpoint = "https://list.worldfloraonline.org/gql.php"
) {
query <- '
query NameSearch($terms: String!, $limit: Int) {
taxonNameSuggestion(
termsString: $terms
limit: $limit
) {
id
fullNameStringPlain
fullNameStringNoAuthorsPlain
authorsString
rank
currentPreferredUsage {
hasName {
id
fullNameStringPlain
fullNameStringNoAuthorsPlain
authorsString
rank
}
}
}
}
'
req <- request(endpoint)
req <- req_body_json(
req,
list(
query = query,
variables = list(
terms = name,
limit = limit
)
),
auto_unbox = TRUE
)
resp <- req_perform(req)
x <- resp_body_json(resp, simplifyVector = FALSE)
res <- x$data$taxonNameSuggestion
if (is.null(res) || length(res) == 0) {
return(data.frame())
}
get_value <- function(z, field) {
if (is.null(z[[field]])) {
NA_character_
} else {
z[[field]]
}
}
get_accepted <- function(z, field) {
if (is.null(z$currentPreferredUsage)) {
NA_character_
} else if (is.null(z$currentPreferredUsage$hasName[[field]])) {
NA_character_
} else {
z$currentPreferredUsage$hasName[[field]]
}
}
out <- data.frame(
input = name,
id = sapply(res, get_value, field = "id"),
name = sapply(res, get_value, field = "fullNameStringPlain"),
name_no_author = sapply(
res,
get_value,
field = "fullNameStringNoAuthorsPlain"
),
authors = sapply(res, get_value, field = "authorsString"),
rank = sapply(res, get_value, field = "rank"),
accepted_id = sapply(res, get_accepted, field = "id"),
accepted_name = sapply(res, get_accepted, field = "fullNameStringPlain"),
accepted_name_no_author = sapply(
res,
get_accepted,
field = "fullNameStringNoAuthorsPlain"
),
accepted_authors = sapply(res, get_accepted, field = "authorsString"),
accepted_rank = sapply(res, get_accepted, field = "rank"),
stringsAsFactors = FALSE
)
out$is_accepted <- !is.na(out$accepted_id) & out$id == out$accepted_id
out
}つぎに、学名からaccepted nameを取得する関数get_accepted_name()を作成します。
get_accepted_name()関数は、学名とランクを引数に取り、get_wfo_suggestions()関数を呼び出して候補を取得します。
result <- get_wfo_suggestions("Quercus serrata")
print(result)Use get_accepted_name() to retrieve the row that contains the accepted name for a scientific name.
accepted_name <- get_accepted_name("Quercus serrata")
print(accepted_name)Notes on Using the API
The WFO Plant List API is publicly available, but sending a large number of requests in a short time may place load on the server. Searching a small number of names for learning or checking is fine, but when processing many scientific names at once, keep the following points in mind.
- Save or cache results so that the same name is not queried repeatedly.
- Add
Sys.sleep()in loops when needed to avoid continuous access. - For large datasets, consider using the published download data or a local workflow instead of trying to retrieve all data through the API.
- Do not accept candidates returned by the API without review; check
rank, WFO ID,currentPreferredUsage, and accepted name. - Because
taxonNameSuggestion()is for candidate search, make the candidate-review rules explicit when doing strict bulk matching.
For example, when running a loop, you can add Sys.sleep() as follows to avoid continuous access.
for (nm in names) {
result <- get_accepted_name(nm)
Sys.sleep(0.5) # サーバー負荷を下げるため少し待つ
}また、WFO Plant Listでは、APIと同じデータをZenodoからダウンロードでき、DOI付きで引用可能です。そのため、多数の名前を処理する研究用途や、再現性を重視する解析では、APIだけに依存するのではなく、使用したWFO Plant Listのリリース版を明記し、Zenodo上のデータを利用・引用することも検討します。