offline taxonomic name resolution
Overview
Hand taxify a column of messy species names. It cleans them, matches them against a Darwin Core backbone on your disk, resolves synonyms to accepted names, and returns one standardized data.frame. Every step runs locally against a versioned snapshot, so there are no API calls, no rate limits, and the same input gives the same output on any machine. A list of thousands resolves in seconds, with the matching engine written in C through the vectra columnar engine.
Installation
install.packages("taxify")Or the development version from GitHub:
install.packages("pak")
pak::pak("gcol33/taxify") # vectra is installed automaticallyUsage
library(taxify)
# the first call installs the default backbone set (COL + GBIF + ITIS, ~4 GB)
taxify(c(
"Quercus robur",
"Pinus abies", # synonym, resolved to Picea abies
"Quercus robus", # typo, fuzzy-corrected to Q. robur
"Taraxacum officinale"
))You get one row per input name on a fixed schema: the matched and accepted names with their IDs and authorship, rank, family, genus, epithet, synonym / hybrid / ambiguity flags, the match type, the fuzzy distance, a coarse kingdom and taxon-group label, and the backbone and version used. summary() prints how the batch resolved.
result <- taxify(c("Quercus robur", "Pinus abies", "Quercus robus", "Taraxacum officinale"))
summary(result)
#> -- taxify results ----------------------------------------------------
#> backbone: COL | 4 names submitted
#>
#> matched 4 (exact: 2, case-insensitive: 0, fuzzy: 2, abbrev: 0)
#> --------------------------------------------------------------
#> taxon groups: vascular plant: 4Backbones
taxify ships 19 backbones as compressed .vtr files, pre-built by the companion taxifydb package and downloaded once. Pass several and they form a fallback chain, where a name unmatched by the first cascades to the next. The chain is staged by match quality: every backbone is asked for an exact match before any backbone is asked for a fuzzy one, so a near neighbour in an early backbone does not settle a name a later backbone holds exactly.
# COL first (all kingdoms), then GBIF for whatever COL leaves open
taxify(c("Quercus robur", "Panthera leo", "Amanita muscaria"), backbone = c("col", "gbif"))Pass no backbone and every installed backbone forms one chain in a fixed priority order: the COL syntheses (COL Extended Release, then COL), then the domain authorities (marine, plants, fungi, algae, fishes, reptiles, mammals, birds, prokaryotes), then the broad aggregators GBIF, ITIS, NCBI, and OTT.
| Backbone | Scope | Names | Download |
|---|---|---|---|
| WFO | Vascular plants | 1.6M | 761 MB |
| COL | All kingdoms | 5.3M | 2.0 GB |
| COL Extended Release | All kingdoms | 7.9M | 1.6 GB |
| GBIF | All kingdoms | 6.4M | 1.6 GB |
| ITIS | US focus, freshwater/marine | 993k | 205 MB |
| NCBI | All life | 2.8M | 531 MB |
| OTT | All life (synthetic) | 3.7M | 763 MB |
| WoRMS | Marine/aquatic | 1.6M | 312 MB |
| Euro+Med | European/Mediterranean plants | 147k | 35 MB |
| Species Fungorum | Fungi | 315k | 71 MB |
| AlgaeBase | Algae | 172k | 36 MB |
| FishBase | Fishes | 103k | 19 MB |
| SeaLifeBase | Non-fish marine/aquatic | 134k | 29 MB |
| Reptile Database | Reptiles | 50k | 10 MB |
| LCVP | Vascular plants | 1.3M | 252 MB |
| WCVP | Vascular plants | 1.4M | 309 MB |
| Mammal Diversity Database | Mammals | 62k | 11 MB |
| AviList | Birds | 41k | 8 MB |
| LPSN | Prokaryotes (Bacteria/Archaea) | 45k | 12 MB |
list_backbones() returns this table live, with the installed and version status of each. taxify_databases() adds the enrichment layers alongside it.
Matching
Input names are normalized first, so the fuzzy pass runs only on names that genuinely differ from the backbone:
"Quercus robur L." -> "Quercus robur" # authorship stripped
"Pinus cf. sylvestris" -> "Pinus sylvestris" # qualifier removed
"Nothofagus x alpina" -> "Nothofagus × alpina" # hybrid sign normalized (x -> ×)
"Betula pendula (Roth) Doll" -> "Betula pendula" # parenthesized author strippedFuzzy matching takes Damerau-Levenshtein, Levenshtein, or Jaro-Winkler with a distance threshold, and runs genus-blocked, so a typo competes against names in its own genus.
taxify and WorldFlora both read the same WFO snapshot, which isolates the two matching implementations on identical data. The corpus is 1,000 accepted binomials drawn from the backbone with a fixed seed; the fuzzy corpus is those names with one substituted character in each epithet, so every one has to resolve by distance.
| taxify | WorldFlora | |
|---|---|---|
| Backbone load | 4.9 s | 20.1 s (CSV into RAM) |
| Exact match, 1,000 names | 2.2 s | 17.1 s |
| Fuzzy match, 1,000 names | 18.8 s | 4,192 s (70 min) |
| Fuzzy match, 5,000 names | 26.6 s | not measured |
| Peak R heap, fuzzy 1,000 | 678 MB | 4.0 GB |
scripts/benchmark-worldflora.R produces these numbers and scripts/benchmark-worldflora-results.json records the run, including package versions and the backbone snapshot. Both packages were measured back to back on one machine (Windows 11, R 4.6.0, taxify 0.3.21, WorldFlora 1.14.5) that was carrying other work at the time, so the ratios are the reliable figures.
Beyond matching
taxify() resolves a name to its accepted name. The same local backbone file answers the related lookups, with nothing else to download:
synonyms("Picea abies") # every synonym of an accepted name
children("Quercus") # accepted species in a genus
downstream("Fagaceae", downto = "genus") # all genera under a family
upstream("Quercus robur", to = "family") # the family a species sits in
class2tree(species) # a lineage as a Newick / ape phylo tree
lowest_common(species) # the deepest shared rank (the MRCA)
parse_name("Quercus robur (L.) H.Karst.") # genus / epithet / author, no lookup
id2name("2878688", backbone = "gbif") # GBIF usage key -> name + classification
comm2sci("pedunculate oak") # common name -> scientific
sci2comm("Quercus robur") # scientific -> common names
reconcile(old_species_list) # how a checklist maps onto the backbone
taxify_lock(result) # freeze the backbone + enrichment versions
cite(result) # citations for every source usedTraits and status
108 enrichment layers join published trait and status data to a result through the backbone-resolved accepted name, so synonyms in either dataset land on the same key.
taxify(plant_names) |>
add_iucn() |> # IUCN Red List
add_griis("AT") |> # GRIIS invasive status
add_zanne() |> # Zanne et al. woodiness
add_eive() # EIVE indicator values
taxify(fish_names) |>
add_fishbase() |> # FishBase morphology and ecology
add_fishmorph() # FISHMORPH functional traits
taxify(plant_names) |>
add_trait("seed_mass") # every source that carries it, harmonized to mgSources span all kingdoms: IUCN, GRIIS, GBIF common names, WCVP, EIVE, Diaz et al., LEDA, GIFT, FungalTraits, FUNGuild, AlgaeTraits, EltonTraits, AVONET, PanTHERIA, AmphiBIO, FISHMORPH, FishBase, AnAge, GloNAF, LepTraits, AnimalTraits, and regional plant-trait sets for France (Baseflor), Britain (Ecoflora), and Germany (FloraWeb), among others. list_enrichments() returns the full set in R, list_traits() browses the cross-source trait vocabulary behind add_trait(), and the enrichments vignette lists every source with its reference and license.
add_data() joins your own table the same way, auto-detecting the species column and matching it through the backbones used in the original call. It reads data.frames, CSV, CSV.GZ, XLSX, SQLite, and .vtr.
Checking a list
inspect() returns only the names that look wrong, each labelled with what stands out and the name to use instead: typos, retired synonyms, made-up genera, near-duplicate spellings, and the lone animal in a list of plants. Each label is ranked by whether it needs a decision, a second look, or optional cleanup.
inspect(field_names) # offline register and list checks
inspect(field_names, backbones = TRUE) # also typos, synonyms, ambiguityFor a regional field list, region steers fuzzy correction toward species that occur where you work, so a misspelling resolves to the plant that grows there. Pass a region name, a TDWG code, or coordinates.
Documentation
- Getting started
- Choosing and combining backbones
- Fuzzy matching
- Constraining matches to a region
- Enrichments
- Custom data
- Inspecting a name list
- Hybrids and aggregates
- Migrating from taxize, WorldFlora, and related tools
- Large-scale workflows
Bug reports and questions go to the issue tracker.
Support
“Software is like sex: it’s better when it’s free.” — Linus Torvalds
I’m a PhD student who builds R packages in my free time because I believe good tools should be free and open. I started these projects for my own work and figured others might find them useful too.
If this package saved you some time, buying me a coffee is a nice way to say thanks. It helps with my coffee addiction.
Citation
@software{taxify,
author = {Colling, Gilles},
title = {taxify: Offline Taxonomic Name Matching Against Darwin Core Backbones},
year = {2026},
url = {https://github.com/gcol33/taxify}
}Cite the backbones and enrichment layers you actually used with cite(result), which pulls each source’s own reference from the manifest.