This article shows how taxify matches the names of hybrids and aggregates, and how it attaches trait values to them when the published datasets hold none for the name itself. A hybrid formula with no record of its own resolves through its two parents; an aggregate with no record of its own falls back to its binomial. Installation is covered in Get started. The examples match against World Flora Online (WFO) and Euro+Med PlantBase.
Match the list with taxify(), which
records hybrids in is_hybrid and hybrid_type
and aggregates in qualifier and
aggregate_fallback.
Parse the parents of a hybrid formula with
add_hybrid_info().
Choose whether an aggregate keeps its own
concept with the aggregates argument.
Attach traits with add_trait() or
an add_<source>() function, which fall back to a
hybrid’s parents and an aggregate’s binomial.
Botanical names mark a hybrid with the multiplication sign (×) in one of three places:
before the genus, for a cross between two genera (a nothogenus): ×Cupressocyparis leylandii, the Leyland cypress;
before the epithet, for a cross within one genus (a nothospecies): Mentha ×piperita, peppermint;
between two parent names (a hybrid formula): Salix alba × Salix fragilis.
Most data write a plain x in place of the sign. A hybrid
name is recognised during cleaning, before any backbone is read, and a
formula is first matched as a whole before its parents are resolved one
by one (both described in How
hybrids are detected). The list below has one name of each kind, a
second nothospecies, a nothospecies with an author, and a species that
is no hybrid:
hybrids <- c(
"Quercus robur", "Mentha x piperita", "x Cupressocyparis leylandii",
"Salix alba x Salix fragilis", "Platanus x hispanica",
"Mentha x piperita L."
)
res <- taxify(hybrids, backbone = "wfo", verbose = FALSE)
res[, c("input_name", "accepted_name", "hybrid_type", "match_type")]
#> input_name accepted_name hybrid_type
#> 1 Quercus robur Quercus robur <NA>
#> 2 Mentha x piperita Mentha × piperita nothospecies
#> 3 x Cupressocyparis leylandii × Hesperotropsis leylandii nothogenus
#> 4 Salix alba x Salix fragilis Salix alba × Salix fragilis formula
#> 5 Platanus x hispanica Platanus hispanica nothospecies
#> 6 Mentha x piperita L. Mentha × piperita nothospecies
#> match_type
#> 1 exact
#> 2 exact
#> 3 exact
#> 4 hybrid_formula
#> 5 exact
#> 6 exact
#> Sources: WFO 2026.06 | cite() for full citationsis_hybrid (not shown) is TRUE on the five
hybrid rows, and hybrid_type names the kind. Both describe
the input and are set during cleaning, so they are filled whether or not
the name matched.
WFO places the Leyland cypress in a different nothogenus. WFO holds
no record for the Salix cross, so the row resolves through its
two parents, with match_type = "hybrid_formula":
accepted_name holds the cross of the accepted parent names,
and matched_name the cross of the parents as written. The
two differ only when a parent is a synonym. The ID, rank and
classification columns of a formula row stay NA, and when a
parent does not match, accepted_name stays NA
as well.
A formula row has no backbone record of its own, so its parents are
what can be looked up. add_hybrid_info() parses the two
parents of each hybrid formula and resolves them against the backbones
of the result. It adds the parents as written, their accepted names and
their backbone IDs:
info <- add_hybrid_info(res)
info[info$hybrid_type %in% "formula",
c("hybrid_parent_1", "hybrid_parent_1_accepted",
"hybrid_parent_2", "hybrid_parent_2_accepted")]
#> hybrid_parent_1 hybrid_parent_1_accepted hybrid_parent_2
#> 4 Salix alba Salix alba Salix fragilis
#> hybrid_parent_2_accepted
#> 4 Salix fragilis
#> Sources: WFO 2026.06 | cite() for full citationsThe parent columns are NA for a nothospecies or a
nothogenus, whose name does not say which species were crossed: the
parents of Mentha ×piperita, M. aquatica and
M. spicata, appear nowhere in it.
Formulas are written in several ways. An abbreviated genus in the second parent is expanded from the first parent, and a bare epithet is read as a species of the first parent’s genus:
formulas <- c(
"Quercus pyrenaica x Q. petraea", "Populus nigra x Populus deltoides",
"Rosa canina x R. gallica", "Salix alba x fragilis"
)
fo <- add_hybrid_info(taxify(formulas, backbone = "wfo", verbose = FALSE))
fo[, c("input_name", "hybrid_parent_1", "hybrid_parent_2")]
#> input_name hybrid_parent_1 hybrid_parent_2
#> 1 Quercus pyrenaica x Q. petraea Quercus pyrenaica Quercus petraea
#> 2 Populus nigra x Populus deltoides Populus nigra Populus deltoides
#> 3 Rosa canina x R. gallica Rosa canina Rosa gallica
#> 4 Salix alba x fragilis Salix alba Salix fragilis
#> Sources: WFO | cite() for full citationsDetection runs during cleaning, before any backbone is read. The sign
× is replaced with a space-padded x, and an
x counts as a hybrid marker only when it stands alone
between spaces, so the x inside Saxifraga is never
one. A sign misread as × or Ã\u0097, which
happens when text in UTF-8 is read as Latin-1, is repaired first.
A nothospecies or nothogenus is matched with the sign removed and
with the sign kept in each of its spellings
(Mentha × piperita, ×Cupressocyparis), because
backbones differ in how they store it. A hybrid formula is first matched
as a whole, since some backbones store a cross as a synonym of the
nothospecies it produces; only when that fails are the parents resolved
one by one.
The parser expects a binomial on each side of the sign. A formula with a subspecies or variety on one side is still detected, and the rank and infraspecific epithet stay part of that parent’s name.
An aggregate groups closely related species under one name, as in
apomictic groups such as Rubus fruticosus and Taraxacum
officinale, where field data often record the group and leave the
species open. It is written with agg. or s.l.
(sensu lato, in the broad sense). taxify reads the variants a
source may use (aggr., agg, -agg,
coll. sp., sensu lato) as agg. or
s.l. and records the marker in qualifier. The
opposite marker s.str. (sensu stricto, in the
narrow sense) is recorded in qualifier and the name is
matched as written.
Some backbones hold an aggregate as a taxon of its own, others hold
only the binomial. By default (aggregates = "preserve") an
aggregate is matched as a taxon where the backbone has one, and falls
back to its binomial otherwise. WFO holds no aggregates:
aggs <- c(
"Rubus fruticosus agg.", "Taraxacum officinale agg.",
"Galium mollugo s.l.", "Quercus robur"
)
wfo <- taxify(aggs, backbone = "wfo", verbose = FALSE)
wfo[, c("input_name", "accepted_name", "qualifier", "aggregate_fallback")]
#> input_name accepted_name qualifier aggregate_fallback
#> 1 Rubus fruticosus agg. Rubus fruticosus agg. TRUE
#> 2 Taraxacum officinale agg. Taraxacum officinale agg. TRUE
#> 3 Galium mollugo s.l. Galium mollugo s.l. TRUE
#> 4 Quercus robur Quercus robur <NA> NA
#> Sources: WFO 2026.06 | cite() for full citationsThe three aggregates resolve to their binomials, and
aggregate_fallback = TRUE records that the group itself was
not found.
Euro+Med PlantBase holds aggregates as taxa:
em <- taxify(aggs, backbone = "euromed", verbose = FALSE)
em[, c("input_name", "accepted_name", "aggregate_fallback")]
#> input_name accepted_name aggregate_fallback
#> 1 Rubus fruticosus agg. Rubus plicatus TRUE
#> 2 Taraxacum officinale agg. Taraxacum sect. Taraxacum FALSE
#> 3 Galium mollugo s.l. Galium mollugo TRUE
#> 4 Quercus robur Quercus robur NA
#> Sources: EUROMED 2026.08 | cite() for full citationsTaraxacum officinale agg. matches the Euro+Med aggregate,
which Euro+Med treats as a synonym of Taraxacum sect.
Taraxacum, so aggregate_fallback is
FALSE. Euro+Med treats the binomial Rubus
fruticosus as a synonym of Rubus plicatus, and the row
follows it.
aggregates = "collapse" removes the marker before
matching, so the binomial is matched like any other name and
aggregate_fallback stays NA:
col <- taxify(aggs, backbone = "euromed", aggregates = "collapse",
verbose = FALSE)
col[, c("input_name", "accepted_name", "aggregate_fallback")]
#> input_name accepted_name aggregate_fallback
#> 1 Rubus fruticosus agg. Rubus plicatus NA
#> 2 Taraxacum officinale agg. Taraxacum officinale NA
#> 3 Galium mollugo s.l. Galium mollugo NA
#> 4 Quercus robur Quercus robur NA
#> Sources: EUROMED 2026.08 | cite() for full citationsPublished datasets rarely hold a value for a hybrid formula or an
aggregate. add_trait() and every
add_<source>() function fill those rows from related
names.
For a hybrid formula, a value recorded for the hybrid itself is used
first. Without one, the values of the two parents are combined: numbers
are averaged, and a category is kept when the parents agree and written
as "A x B" when they differ, with a warning. When only one
parent has a value, that value is used. After
add_hybrid_info(), an add_<source>()
function also adds each parent’s value as
<trait>_parent1 and
<trait>_parent2.
For an aggregate, the value recorded for the aggregate is used first, and the binomial’s value otherwise. A species without a value of its own takes its aggregate’s value where the dataset records the trait only for the group.
The next chunk attaches plant height to two willows and their cross.
mode = "wide" keeps one column per dataset, so the cross
can be compared with its parents row by row:
salix <- c("Salix alba", "Salix fragilis", "Salix alba x Salix fragilis")
tr <- taxify(salix, backbone = "wfo", verbose = FALSE) |>
add_trait("plant_height", mode = "wide")
#> add_trait('plant_height'): gift 3, diaz 3, austraits 2, bien 3, brot 0 (of 3 resolved names).
tr[, c("input_name", "plant_height_gift", "plant_height_diaz",
"plant_height_austraits")]
#> input_name plant_height_gift plant_height_diaz
#> 1 Salix alba 30.0 17.36306
#> 2 Salix fragilis 25.0 14.94508
#> 3 Salix alba x Salix fragilis 27.5 16.15407
#> plant_height_austraits
#> 1 20
#> 2 NA
#> 3 20
#> Sources: WFO 2026.06, GIFT (Weigelt et al. 2020) 2026.08, Diaz et al. 2022 2026.08, AusTraits (Falster et al. 2021) 2026.08, BIEN (Maitner et al. 2018) 2026.08 | cite() for full citationsThe cross has no value of its own in any of these datasets. In GIFT and in the data of Díaz et al. its height is the mean of the two parents; AusTraits has a height for Salix alba only, and the cross takes it.
aggregate_trait_fallback = FALSE, or
options(taxify.aggregate_trait_fallback = FALSE) for a
session, leaves such rows NA. With
options(taxify.trait_provenance = TRUE), an
add_<source>() function adds a
<source>_basis column that records whether each value
is the name’s own, an aggregate’s, or a binomial’s.
How names are matched: the other match stages.
Attaching published data:
add_trait() and the add_<source>()
functions in full.