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)結果の確認
xオブジェクトには、APIからのレスポンスがRのリスト形式で格納されています。
print(x)候補が10件返ってきました。 これは、クエリでlimit: 10と指定したためです。 一つ目を見てみます。
x$data$taxonNameSuggestion[[1]]id, fullNameStringHtml, currentPreferredUsageの3つのフィールドが返ってきました。
ここで注目するのは、currentPreferredUsageの中のhasNameフィールドです。 このフィールドには、現在の分類群の学名が含まれています。
x$data$taxonNameSuggestion[[1]]$currentPreferredUsageこの例では、currentPreferredUsageの中のhasNameフィールドに、“Quercus serrata Murray”という学名が表示されています。
x$data$taxonNameSuggestion[[1]]$id
x$data$taxonNameSuggestion[[1]]$currentPreferredUsage$hasName$idx$data$taxonNameSuggestion[[1]]$idとx$data$taxonNameSuggestion[[1]]$currentPreferredUsage$hasName$idを比較すると、両者は一致しています。
このことから、検索した学名”Quercus serrata”は、現在の分類群としても”Quercus serrata”であることがわかります。
このようにすることで、検索した学名からaccepted nameを取得することができます。
Accepted nameを取得する関数の作成
実務的には、処理を関数化しておくと便利です。
今回の一連の処理を関数化したうえで、学名からaccepted nameを取得する関数を作成してみます。 まずは、学名から候補を取得する関数get_wfo_suggestions()を作成します。
この関数では、先ほどのクエリに加え、fullNameStringNoAuthorsPlain、authorsString、rankなどのフィールドも取得するようにしています。 特に、rankは、検索した名前がどのランクの分類群であるかを示すために重要です。 例えば、species(種)か、subspecies(亜種)か、variety(変種)かなどのランクを知ることができます。
Accepted nameを取得するときは、検索した名前と同じランクのaccepted nameを探すことが多いので、ランクの情報も取得するようにしています。
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)get_accepted_name()関数を使って、学名からaccepted nameが含まれる行を取得します。
accepted_name <- get_accepted_name("Quercus serrata")
print(accepted_name)API利用時の注意
WFO Plant List APIは公開APIとして利用できますが、短時間に大量のリクエストを送るとサーバーに負担をかける可能性があります。学習用・確認用として少数の名前を検索する場合は問題ありませんが、多数の学名を一括処理する場合には、以下の点に注意します。
- 同じ名前を何度も問い合わせないように、結果を保存・キャッシュする。
- ループ処理では必要に応じて
Sys.sleep()を入れ、連続アクセスを避ける。 - 大量データの処理では、APIを使って全データを取得しようとせず、公開されているダウンロードデータやローカル環境での利用を検討する。
- APIから返された候補はそのまま採用せず、
rank、WFO ID、currentPreferredUsage、accepted name を確認する。 -
taxonNameSuggestion()は候補検索用なので、厳密な一括照合では候補の確認ルールを明示する。
例えば、ループ処理をする場合は、以下のように Sys.sleep() を入れて、連続アクセスを避けることができます。
for (nm in names) {
result <- get_accepted_name(nm)
Sys.sleep(0.5) # サーバー負荷を下げるため少し待つ
}また、WFO Plant Listでは、APIと同じデータをZenodoからダウンロードでき、DOI付きで引用可能です。そのため、多数の名前を処理する研究用途や、再現性を重視する解析では、APIだけに依存するのではなく、使用したWFO Plant Listのリリース版を明記し、Zenodo上のデータを利用・引用することも検討します。