This article explains the stages
taxify() runs to bring an input name to a backbone record:
cleaning, exact matching, fuzzy matching, the choice among several
records with the same name, and the fallbacks for names no backbone
holds. It shows what each stage writes into the result and which
arguments change it. Installation is covered in Get started. Most examples match against World
Flora Online (WFO), so that every row comes from the same backbone.
Each name passes through these stages in order and leaves at the
first one that resolves it. match_type records which one
did:
exact: the cleaned name equals a backbone
name.
exact_ci: the names are equal after capitals and
spelling variants of Latin are made equal.
abbrev: an abbreviated genus and the epithet
identify one species.
fuzzy: the closest backbone name within the fuzzy
threshold.
rank_fallback: an infraspecific name no backbone
holds, resolved to its species.
basionym: an unplaced record resolved through the
name it was based on.
none and out_of_scope: no match,
described in Names no backbone
matches.
Names of hybrids and aggregates go through two further stages, described in Hybrids and aggregates.
Names in data carry authors, qualifiers, extra spaces and capitals that backbone names do not. Cleaning removes them before any comparison:
raw <- c(
"Quercus robur L.", "Quercus robur (L.) Sm.", " Quercus robur ",
"cf. Betula pendula", "Betula pendula s.str.", "QUERCUS ROBUR",
"Q. petraea"
)
res <- taxify(raw, backbone = "wfo", verbose = FALSE)
res[, c("input_name", "matched_name", "match_type", "qualifier")]
#> input_name matched_name match_type qualifier
#> 1 Quercus robur L. Quercus robur exact <NA>
#> 2 Quercus robur (L.) Sm. Quercus robur exact <NA>
#> 3 Quercus robur Quercus robur exact <NA>
#> 4 cf. Betula pendula Betula pendula exact cf.
#> 5 Betula pendula s.str. Betula pendula exact s.str.
#> 6 QUERCUS ROBUR Quercus robur exact_ci <NA>
#> 7 Q. petraea Quercus petraea abbrev <NA>
#> Sources: WFO 2026.06 | cite() for full citationsThe first three names match exactly once the author and the spaces
are gone. The qualifiers cf. and s.str. are
removed from the name and kept in qualifier.
QUERCUS ROBUR matches with capitals ignored, as
exact_ci. Q. petraea has an abbreviated genus;
Quercus is the only genus starting with Q that has the epithet
petraea, so the row has match_type = "abbrev".
The exact_ci stage also makes spelling variants of Latin
equal: ae and oe with i, y with
i, ph with f, rh with r,
th with t, and a final ii with i. An
epithet written sylvestris in one source and
silvestris in another therefore matches without fuzzy
matching.
An infraspecific rank stays part of the name. ssp. and
Var. are written as subsp. and
var., and the cleaned name keeps the rank and its
epithet.
A name with no exact match is compared with the backbone names of the
same genus, and the closest one within fuzzy_threshold is
taken; a name whose genus is misspelt is compared with the names that
share its first two letters, as described in Misspelt genera. fuzzy_dist is
the number of edits between the two names divided by the length of the
longer one. The list below has typing errors of several kinds:
messy <- c(
"Qurecus robur", "Taraxacum officianle", "Plantago lanceoalata",
"Trifolium repnes", "Lolium perrene", "Achillea millefolum",
"Betual pendula", "Ranunculus acris"
)
res <- taxify(messy, backbone = "wfo", verbose = FALSE)
res[, c("input_name", "matched_name", "match_type", "fuzzy_dist")]
#> input_name matched_name match_type fuzzy_dist
#> 1 Qurecus robur Quercus robur fuzzy 0.07692308
#> 2 Taraxacum officianle Taraxacum officinale fuzzy 0.05000000
#> 3 Plantago lanceoalata Plantago lanceolata fuzzy 0.05000000
#> 4 Trifolium repnes Trifolium repens fuzzy 0.06250000
#> 5 Lolium perrene Lolium perenne fuzzy 0.14285714
#> 6 Achillea millefolum Achillea millefolium fuzzy 0.05000000
#> 7 Betual pendula Betula pendula fuzzy 0.07142857
#> 8 Ranunculus acris Ranunculus acris exact NA
#> Sources: WFO 2026.06 | cite() for full citationsTaraxacum officianle has two letters swapped, one edit on 20
characters, so its distance is 0.05. Lolium perrene needs two
edits to become Lolium perenne, 2 / 14 = 0.143. A name matched
by an exact stage has no distance, so fuzzy_dist is
NA.
A fuzzy match runs only for names that every exact stage missed, so it never replaces an exact match.
fuzzy_method chooses how edits are counted:
"dl" (Damerau-Levenshtein, the default) counts an
insertion, a deletion, a substitution and a swap of two neighbouring
letters as one edit each.
"levenshtein" counts a swap as two edits, a deletion
and an insertion.
"jw" (Jaro-Winkler) gives a similarity between 0 and
1 that weights agreement at the start of the name; the distance is one
minus that similarity.
The same names under each method:
dist_of <- function(method) {
r <- taxify(messy, backbone = "wfo", fuzzy_method = method,
verbose = FALSE)
round(r$fuzzy_dist, 3)
}
data.frame(
input = messy,
dl = dist_of("dl"),
levenshtein = dist_of("levenshtein"),
jw = dist_of("jw")
)
#> input dl levenshtein jw
#> 1 Qurecus robur 0.077 0.154 0.021
#> 2 Taraxacum officianle 0.050 0.100 0.010
#> 3 Plantago lanceoalata 0.050 0.050 0.031
#> 4 Trifolium repnes 0.062 0.125 0.013
#> 5 Lolium perrene 0.143 0.143 0.029
#> 6 Achillea millefolum 0.050 0.050 0.010
#> 7 Betual pendula 0.071 0.143 0.014
#> 8 Ranunculus acris NA NA NAThe Levenshtein distance is twice the Damerau-Levenshtein distance
for the names with swapped letters (Qurecus,
officianle, repnes, Betual) and the same for
the others. The Jaro-Winkler distances are smaller throughout, because
the shared genus at the start of each name raises the similarity, so a
threshold set for "dl" is much looser under
"jw".
fuzzy_threshold = 0.2, the default, accepts a distance
up to 0.2: one edit on a 5-letter name, two on a 12-letter name, four on
a 20-letter name. A lower value is stricter:
poa <- c("Poa anua", "Poa pratenss", "Poa trialis")
strict <- taxify(poa, backbone = "wfo", fuzzy_threshold = 0.15,
verbose = FALSE)
strict[, c("input_name", "matched_name", "match_type", "fuzzy_dist")]
#> input_name matched_name match_type fuzzy_dist
#> 1 Poa anua Poa annua fuzzy 0.11111111
#> 2 Poa pratenss Poa pratensis fuzzy 0.07692308
#> 3 Poa trialis <NA> none NA
#> Sources: WFO 2026.06 | cite() for full citationsPoa trialis needs two edits on 13 characters to become Poa trivialis, a distance of 0.154, which the default threshold accepts and 0.15 rejects.
A whole number caps the edits themselves, whatever the length of the
name. fuzzy_threshold = 1 accepts one edit, which fits data
whose errors are known to be single letters, such as an optical
character recognition (OCR) process that drops or doubles a
character:
one_edit <- taxify(poa, backbone = "wfo", fuzzy_threshold = 1,
verbose = FALSE)
one_edit[, c("input_name", "matched_name", "match_type")]
#> input_name matched_name match_type
#> 1 Poa anua Poa annua fuzzy
#> 2 Poa pratenss Poa pratensis fuzzy
#> 3 Poa trialis <NA> none
#> Sources: WFO 2026.06 | cite() for full citationsJaro-Winkler counts no edits, so it takes only a fractional threshold.
The comparison is made within the genus, which keeps it fast on backbones with millions of names and keeps a misspelt epithet from matching a species of another genus. A misspelt genus has no names to be compared with, so for WFO, the Catalogue of Life (COL), COL’s Extended Release and the GBIF Backbone Taxonomy a second pass compares the remaining names with all names that start with the same two letters. Betual pendula above was matched in that pass. A typing error in the first two letters is not found:
A fuzzy match finds the closest name in spelling, which is not always the intended taxon. Two cases from the backbones:
check <- c("Quercus fantasia", "Quercus robur var. robur")
res <- taxify(check, backbone = "wfo", verbose = FALSE)
res[, c("input_name", "matched_name", "accepted_name", "fuzzy_dist")]
#> input_name matched_name accepted_name fuzzy_dist
#> 1 Quercus fantasia Quercus matasii Quercus glauca 0.1875000
#> 2 Quercus robur var. robur Quercus robur var. robusta Quercus robur 0.1153846
#> Sources: WFO 2026.06 | cite() for full citationsQuercus fantasia is an invented name within the threshold of
Quercus matasii. Quercus robur var. robur is
not in WFO, and the fuzzy stage reaches var. robusta, three
edits away, before the fallback to the species can run.
inspect() labels them as likely misspellings, as shown in
Checking a name list.
fuzzy = FALSE turns the stage off, for a list that has
already been checked:
Some spellings belong to more than one taxon. When a spelling leads to several records, one is picked: the record with the input’s author when the input carries one, otherwise an accepted record before an unplaced one before a synonym, then the species rank, then the lowest ID. Pinus abies was published by Linnaeus for the Norway spruce and again by Thunberg for a Japanese spruce:
pa <- c("Pinus abies", "Pinus abies L.", "Pinus abies Thunb.")
res <- taxify(pa, backbone = "col", verbose = FALSE)
#> Warning: 3 of 3 names resolve to more than one accepted ID (Pinus abies, Pinus
#> abies L., Pinus abies Thunb.). `accepted_id` holds one and `accepted_ids` all
#> of them; list them with taxify_ids() before requesting data by ID.
res[, c("input_name", "accepted_name", "n_ids", "accepted_ids")]
#> input_name accepted_name n_ids accepted_ids
#> 1 Pinus abies Picea abies 4 4HPZF|8K9Y|6BRMR|4HQ4F
#> 2 Pinus abies L. Picea abies 4 4HPZF|8K9Y|6BRMR|4HQ4F
#> 3 Pinus abies Thunb. Picea polita 4 4HQ4F|4HPZF|8K9Y|6BRMR
#> Sources: COL 2026.09 | cite() for full citationsn_ids counts the accepted taxa the spelling leads to,
and accepted_ids lists their IDs, the pick first. The
warning names every row with more than one. The author picks the record
in the third row: Pinus abies Thunb. resolves to Picea
polita. taxify_ids() lists every candidate of a row
with its name, author and status, as shown in Requesting GBIF data for a species
list.
A genus name can also be used in two kingdoms. Prunella is a
bird genus (the accentors) and a plant genus (self-heal), and COL
accepts both. Without further information the pick follows the lowest
ID, here the bird. kingdom keeps only the records in the
named kingdom:
cols <- c("input_name", "accepted_name", "family", "n_ids", "pick_basis")
default <- taxify("Prunella", backbone = "col", verbose = FALSE)
plant <- taxify("Prunella", backbone = "col", kingdom = "plants",
verbose = FALSE)
rbind(default[, cols], plant[, cols])
#> input_name accepted_name family n_ids pick_basis
#> 1 Prunella Prunella Prunellidae 2 id
#> 2 Prunella Prunella Lamiaceae 2 kingdom
#> Sources: COL 2026.09 | cite() for full citationsWith kingdom = "plants" the row moves to the plant genus
in Lamiaceae, and pick_basis records that the kingdom
decided it. A name with no record in the named kingdom is unmatched, and
in a fallback chain it goes on to the next backbone.
A name list often contains names a backbone treats as synonyms, and
such a row carries the accepted name its record points to, with
is_synonym = TRUE. When a name has both a synonym record
and an unplaced record, the synonym is picked if it shares its type with
its accepted name: the accepted name keeps the synonym’s final epithet
and names its author in parentheses.
syn <- taxify(c("Lycopsis orientalis", "Pinus sylvestris"), backbone = "wfo",
verbose = FALSE)
syn[, c("input_name", "accepted_name", "is_synonym", "taxonomic_status")]
#> input_name accepted_name is_synonym
#> 1 Lycopsis orientalis Anchusa arvensis subsp. orientalis TRUE
#> 2 Pinus sylvestris Pinus sylvestris FALSE
#> taxonomic_status
#> 1 SYNONYM
#> 2 ACCEPTED
#> Sources: WFO 2026.06 | cite() for full citationsLycopsis orientalis L. is such a synonym of Anchusa arvensis subsp. orientalis (L.) Nordh., and it is picked over the unplaced Lycopsis orientalis of another author.
The last stage handles a name that matched only an unplaced record.
When that record names the name it was based on (its basionym), and the
basionym is placed in the backbone, the row takes the basionym’s
accepted taxon, with match_type = "basionym". The other
columns still describe the record that was matched.
A subspecies or variety that a backbone holds matches as written. One
that no backbone holds resolves to its species in the
rank_fallback stage:
infra <- c("Quercus robur ssp. robur", "Quercus robur subsp. imaginaria")
res <- taxify(infra, backbone = "wfo", verbose = FALSE)
res[, c("input_name", "matched_name", "match_type", "rank")]
#> input_name matched_name match_type
#> 1 Quercus robur ssp. robur Quercus robur subsp. robur exact
#> 2 Quercus robur subsp. imaginaria Quercus robur rank_fallback
#> rank
#> 1 subspecies
#> 2 species
#> Sources: WFO 2026.06 | cite() for full citationsssp. is read as subsp., so the first name
matches exactly. rank is the rank of the matched record, so
the fallback row shows species although the input named a
subspecies.
A name that reaches no record has match_type = "none" or
"out_of_scope". The genus register decides between the two:
an index of the genera in all
19
backbones, each with its kingdom, family and life form, built over a
fixed set of backbones and downloaded like a backbone, so every machine
reports the same labels.
un <- taxify(c("Blorbus maximus", "Carcinus maenas"), backbone = "wfo",
verbose = FALSE)
un[, c("input_name", "match_type", "life_form")]
#> input_name match_type life_form
#> 1 Blorbus maximus none <NA>
#> 2 Carcinus maenas out_of_scope animal
#> Sources: WFO | cite() for full citationsBlorbus is in no backbone, so the row is none:
the name may be misspelt or invented. Carcinus is a crab genus
held by other backbones but not by WFO, so the row is
out_of_scope and is not compared by fuzzy matching. The
register also fills kingdom_group, taxon_group
and life_form on every matched row, whichever backbone
matched it. Choosing and
combining backbones shows how to find a backbone that holds a
genus.
Hybrids and aggregates: names with a hybrid sign or an aggregate marker.
Matching within a region: restricting fuzzy matches to species present in a region.
Checking a name list: every
label inspect() gives a matched list.