nameparser (R)
September 4, 2026 · View on GitHub
An R binding — via extendr/rextendr —
to the Rust port of the GBIF name parser. Parses
scientific names into their structured atoms (genus, epithets, authorship, rank, ...) with
byte-for-byte behavioural parity to the Java org.gbif:name-parser, at native speed and with
no JVM dependency.
Install
Requires a Rust toolchain (cargo/rustc) on PATH at build time — this package compiles a
small Rust crate (src/rust/) and links it into the R package's shared library. There is no
CRAN release yet (see "Deferrals" below — the tarball is ready, the submission is pending), so
install from a local checkout or GitHub:
# from a local checkout of this repo:
install.packages("rextendr") # if not already installed; needed to build from source
devtools::install("bindings/r")
# or, without devtools:
# R CMD INSTALL bindings/r
# from GitHub:
# remotes::install_github("gbif/name-parser-rust", subdir = "bindings/r")
Usage
parse_names() — vectorized tibble
The primary entry point. Takes a character vector of scientific names and returns a
tibble, one row per input name, never throwing — an
unparsable name gets a row with parsed = FALSE and NA atoms rather than an R condition.
library(nameparser)
out <- parse_names(c(
"Abies alba Mill.",
"Vulpes vulpes silaceus Miller, 1907",
"Tobacco mosaic virus"
))
out[, c("scientificName", "parsed", "rank", "genus", "specificEpithet",
"infraspecificEpithet", "combinationAuthors", "combinationYear", "error")]
#> # A tibble: 3 x 9
#> scientificName parsed rank genus specificEpithet infraspecificEpithet
#> <chr> <lgl> <chr> <chr> <chr> <chr>
#> 1 Abies alba Mill. TRUE SPEC… Abies alba <NA>
#> 2 Vulpes vulpes silaceu… TRUE SUBS… Vulp… vulpes silaceus
#> 3 Tobacco mosaic virus FALSE <NA> <NA> <NA> <NA>
#> # i 3 more variables: combinationAuthors <chr>, combinationYear <chr>, error <chr>
parse_names() accepts optional scalar hints applied to every name in the vector:
authorship, rank, code (the parser's SCREAMING_SNAKE_CASE enum names, e.g.
"SPECIES", "ZOOLOGICAL").
All 43 columns, in order: scientificName, result, parsed, error, type, taxon,
taxonRank, rank, code, uninomial, genus, infragenericEpithet, specificEpithet,
infraspecificEpithet, cultivarEpithet, phrase, candidatus, notho, originalSpelling,
epithetQualifier, extinct, taxonomicNote, nomenclaturalNote, publishedIn,
publishedInYear, publishedInPage, unparsed, doubtful, manuscript, state,
combinationAuthors, combinationExAuthors, combinationYear, basionymAuthors,
basionymExAuthors, basionymYear, sanctioningAuthor, warnings, canonical,
canonicalWithoutAuthorship, canonicalMinimal, canonicalComplete, authorshipComplete.
result is the 5.0.0 three-way outcome ("parsed" / "informal" / "unparsable"); taxon
and taxonRank carry an informal name's supraspecific anchor; the last five are NameFormatter
renderings (canonical is populated for informal rows too, the other four are NA there).
parse_name_json() — lossless escape hatch
For the rare case a flattened tibble column can't represent (see "Known limitations" below),
parse_name_json() returns the parser's complete, nested JSON for a single name — the exact
same wire shape the Java CLI and this project's Python binding emit — which you can parse
with jsonlite:
js <- parse_name_json("Abies alba Mill.")
jsonlite::fromJSON(js, simplifyVector = FALSE)$combinationAuthorship
#> $authors
#> $authors[[1]]
#> [1] "Mill."
#>
#> $exAuthors
#> list()
On an unparsable name, it returns {"error":{"type":...,"code":...,"message":...}} (code
omitted, not null, when the parser has none) — the same shape
crates/nameparser-ffi's unparsable_json and crates/nameparser-cli's render_row
produce, so it is directly diffable against those surfaces' output.
Design: fitting the rgbif ecosystem
The API is shaped to feel at home next to rgbif's own
name_parse(): a vectorized function taking a scientificname argument and returning one
row per name, rather than a per-name object a caller must loop over. That is where the
resemblance is intentional stops, though — the columns themselves are the new, faithful
ParsedName model (the same schema the Java FFM, Python, and CLI bindings all expose),
not rgbif::name_parse()'s legacy GBIF Name Parser v1 web-service schema. Concretely:
camelCase column names matching the JSON/Java/Python wire format (specificEpithet, not
specificepithet or species); parsed = FALSE + NA atoms for unparsable rows rather than
a separate parsedPartially flag; enum columns (type, rank, code, state) as plain
SCREAMING_SNAKE_CASE strings, not integers or factors.
Known limitations / deferrals
errorCodeis not a tibble column. On an unparsable row,parse_names()surfacestype(theNameType) anderror(the message) but notParseError.code(theNomCode, e.g."VIRUS"for"Tobacco mosaic virus") — adding a 36th column just for this oneErr-only field was judged not worth re-touching the five parallel column listsparse_names_implmaintains.parse_name_json()does carry it losslessly (see above) — use it when you needcodeon an error row.genericAuthorship/specificAuthorshipare JSON-only. These niche botanicalCombinedAuthorshipbundles (infrageneric-rank names with two independent author strings) are not flattened intoparse_names()columns; get them viaparse_name_json().- Not on CRAN yet. The submission tarball is built and passing:
scripts/build-r-tarball.sh --checkbundles the core crate, vendors the whole dependency graph intosrc/rust/vendor.tar.xzfor CRAN's offline build, generatesLICENSE.note, and runsR CMD check --as-cran. What remains is the submission itself, which is a manual, human-reviewed web form (https://cran.r-project.org/submit.html) — seeRELEASE.md§2 "R → CRAN". Until it lands, install from a local checkout orremotes::install_github().
Testing
Rscript -e 'devtools::load_all("bindings/r", quiet=TRUE); testthat::test_dir("bindings/r/tests/testthat")'
Includes a corpus parity gate (tests/testthat/test-parity.R) that diffs
parse_name_json() against the frozen golden snapshot testdata/golden/expected-parse.jsonl
over the ~8,000-name benchmark corpus — the same snapshot the native CLI and Python binding
validate against. It skips cleanly (does not fail) if that git-ignored file isn't present
locally; see crates/nameparser/tests/parse_golden.rs's module doc to regenerate it.
License
Apache-2.0, matching the rest of this repository and org.gbif:name-parser.