offline taxonomic name resolution
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.
install.packages("taxify")Or the development version from GitHub:
install.packages("pak")
pak::pak("gcol33/taxify") # vectra is installed automaticallylibrary(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: 4taxify 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.
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.
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 usedjoin 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.
result |> add_data("TRY_traits.csv")
result |> add_data("TRY_traits.csv", cols = c("LeafArea", "SLA", "PlantHeight"))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.
taxify(field_names, region = "Belgium")
taxify(field_names, coords = c(4.35, 50.85))- 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.
"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.
MIT (see the LICENSE file)
@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.