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.7M | 775 MB |
| COL | All kingdoms | 5.4M | 2.1 GB |
| COL Extended Release | All kingdoms | 8.1M | 1.6 GB |
| GBIF Backbone Taxonomy (legacy) | All kingdoms | 6.4M | 1.7 GB |
| ITIS | US focus, freshwater/marine | 1.0M | 206 MB |
| NCBI | All life | 3.0M | 549 MB |
| OTT | All life (synthetic) | 3.7M | 763 MB |
| WoRMS | Marine/aquatic | 1.6M | 304 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.
Speed and memory were compared with WorldFlora, an R package that matches names against WFO. Both packages read the same WFO snapshot, so the differences below come from the matching code. The exact-match 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 |
Windows 11, R 4.6.0, taxify 0.3.21, WorldFlora 1.14.5 (scripts/benchmark-worldflora.R).
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.