Introduction to taxodist

Overview

taxodist retrieves ordered taxonomic lineages from The Taxonomicon and uses them to compare named nodes in a classification hierarchy.

The package provides functions for:

Inputs may represent species, genera, families, orders, named clades, or other nodes available in the source classification.

What the distance represents

Let \(L_A\) and \(L_B\) be the ordered lineages of two hierarchy nodes. Let \(h(A,B)\) be the depth of their most recent common ancestor, defined from their continuous common prefix.

The distance is

\[ d(A,B) = \begin{cases} 0, & L_A = L_B, \\ 1/h(A,B), & L_A \ne L_B. \end{cases} \]

A deeper shared ancestor produces a smaller distance. Within a connected hierarchy, finite distances lie between zero and one.

If two lineages have no shared root, the package returns an infinite distance. Missing lineages instead produce missing pairwise values in a distance matrix.

The measure is an ultrametric within each connected hierarchy. Its mathematical properties and assumptions are discussed in:

vignette("methodological-notes", package = "taxodist")

Distance and membership are different

A descendant belongs to each clade represented among its ancestors. However, the descendant and ancestor remain distinct hierarchy nodes.

For example, Tyrannosaurus is a member of Dinosauria, but the distance between the two nodes is positive:

\[ d(\textit{Tyrannosaurus},\textit{Dinosauria}) = \frac{1}{\operatorname{depth}(\textit{Dinosauria})}. \]

Use:

The distance represents classification depth. It is not evolutionary time, genetic distance, morphological divergence, or phylogenetic branch length.

Reproducible packaged examples

The package includes taxobase, a reference object generated from The Taxonomicon. It allows documentation and examples to run without contacting an external service.

names(taxobase)
#>  [1] "taxa"                  "found_taxa"            "coverage"             
#>  [4] "matrix"                "pairwise"              "lineage_homo"         
#>  [7] "lineage_tyrannosaurus" "closest"               "filter"               
#> [10] "search"                "statistical_taxa"      "statistical_matrix"   
#> [13] "metadata"
taxobase$metadata
#> $source
#> [1] "The Taxonomicon"
#> 
#> $source_url
#> [1] "http://taxonomicon.taxonomy.nl"
#> 
#> $generated_on
#> [1] "2026-08-06"
#> 
#> $package_version
#> [1] "0.7.0"
#> 
#> $distance_definition
#> [1] "0 for identical nodes; otherwise 1 / depth(MRCA)"

The metadata records the source, generation date, package version, and distance definition used to construct the object.

Stored lineages

tail(taxobase$lineage_tyrannosaurus)
#> [1] "Tyrannoraptora"   "Tyrannosauroidea" "Tyrannosauridae"  "Tyrannosaurinae" 
#> [5] "Tyrannosaurini"   "Tyrannosaurus"
tail(taxobase$lineage_homo)
#> [1] "Hominoidea" "Hominidae"  "Homininae"  "Hominini"   "Hominina"  
#> [6] "Homo"

Lineages are ordered from the root of the source classification to the queried node.

Stored pairwise comparison

taxobase$pairwise
#> 
#> ── Taxonomic Distance ──
#> 
#> • Tyrannosaurus vs Homo
#>   Distance : 0.0263157894736842
#>   MRCA : Amniota (depth 38)
#>   Depth A : 72
#>   Depth B : 75

A taxodist_result records:

class(taxobase$pairwise)
#> [1] "taxodist_result"
names(taxobase$pairwise)
#> [1] "distance"   "mrca"       "mrca_depth" "depth_a"    "depth_b"   
#> [6] "taxon_a"    "taxon_b"

Reference distance matrix

reference_matrix <- taxobase$matrix

inherits(reference_matrix, "dist")
#> [1] TRUE
attr(reference_matrix, "Size")
#> [1] 52
head(attr(reference_matrix, "Labels"))
#> [1] "Tyrannosaurus" "Velociraptor"  "Triceratops"   "Brachiosaurus"
#> [5] "Stegosaurus"   "Ankylosaurus"

round(
  as.matrix(reference_matrix)[1:6, 1:6],
  digits = 4
)
#>               Tyrannosaurus Velociraptor Triceratops Brachiosaurus Stegosaurus
#> Tyrannosaurus        0.0000       0.0149      0.0175        0.0169      0.0175
#> Velociraptor         0.0149       0.0000      0.0175        0.0169      0.0175
#> Triceratops          0.0175       0.0175      0.0000        0.0175      0.0169
#> Brachiosaurus        0.0169       0.0169      0.0175        0.0000      0.0175
#> Stegosaurus          0.0175       0.0175      0.0169        0.0175      0.0000
#> Ankylosaurus         0.0175       0.0175      0.0169        0.0175      0.0161
#>               Ankylosaurus
#> Tyrannosaurus       0.0175
#> Velociraptor        0.0175
#> Triceratops         0.0169
#> Brachiosaurus       0.0175
#> Stegosaurus         0.0161
#> Ankylosaurus        0.0000

A matrix returned by distance_matrix() is a standard R dist object. It is symmetric, has a zero diagonal, and can be supplied to functions accepting pairwise dissimilarities.

Live lineage queries

The following examples contact The Taxonomicon and are not evaluated while the vignette is built.

Retrieving a lineage

get_lineage("Tyrannosaurus")
get_lineage("Drosophila melanogaster")

Numeric Taxonomicon identifiers can also be supplied directly:

get_lineage("67263")

Using an identifier is useful when a name corresponds to more than one valid taxonomic entry.

Computing a pairwise distance

result <- taxo_distance(
  "Tyrannosaurus",
  "Velociraptor"
)

result
result$distance
result$mrca

Finding the MRCA

mrca("Tyrannosaurus", "Velociraptor")
mrca("Tyrannosaurus", "Triceratops")
mrca("Tyrannosaurus", "Homo")

The returned MRCA depends on the classification currently provided by The Taxonomicon and may change following taxonomic revisions.

Inspecting the path between nodes

taxo_path(
  "Tyrannosaurus",
  "Triceratops"
)

taxo_path(
  "Dinosauria",
  "Tyrannosaurus"
)

The path ascends from the first node to the MRCA and descends toward the second node. For an ancestor-descendant pair, the ancestor itself is the MRCA.

Working with multiple taxa

Constructing a distance matrix

taxa <- c(
  "Tyrannosaurus",
  "Velociraptor",
  "Spinosaurus",
  "Allosaurus"
)

mat <- distance_matrix(
  taxa,
  progress = TRUE
)

mat

Lineages are retrieved once and cached for subsequent comparisons. The pairwise computation itself is vectorized, so matrices of several thousand taxa are computed in seconds once their lineages are available.

Distances between two sets of taxa

When only the distances from one set of taxa to another are needed, such as from the members of a community to a set of reference taxa, cross_distances() returns the rectangular matrix directly. The example below uses user-supplied lineages and therefore runs offline:

community <- taxo_from_lineages(list(
  Tyrannosaurus = c("Biota", "Dinosauria", "Theropoda", "Tyrannosaurus"),
  Triceratops = c("Biota", "Dinosauria", "Ornithischia", "Triceratops")
))
reference <- taxo_from_lineages(list(
  Velociraptor = c("Biota", "Dinosauria", "Theropoda", "Velociraptor"),
  Homo = c("Biota", "Mammalia", "Homo")
))

cross_distances(community, reference)
#>               Velociraptor Homo
#> Tyrannosaurus    0.3333333    1
#> Triceratops      0.5000000    1

Finding the closest candidates

A stored example is available in taxobase:

taxobase$closest
#>           taxon   distance
#> 1  Velociraptor 0.01492537
#> 3 Brachiosaurus 0.01694915
#> 2   Triceratops 0.01754386
#> 4      Panthera 0.02631579
#> 6    Drosophila 0.06666667
#> 5       Quercus 0.25000000

For a live query:

closest_relative(
  "Carnotaurus",
  c(
    "Aucasaurus",
    "Velociraptor",
    "Triceratops",
    "Brachiosaurus"
  )
)

Candidates are sorted from smallest to largest distance. A missing candidate is retained with a missing distance.

Focal distances

focal_distances(
  focal = "Tyrannosaurus",
  community = c(
    "Velociraptor",
    "Triceratops",
    "Spinosaurus"
  )
)

This produces a table containing the candidate name, distance, MRCA, and MRCA depth.

Clade operations

Testing membership

is_member("Tyrannosaurus", "Dinosauria")
is_member("Tyrannosaurus", "Theropoda")
is_member("Tyrannosaurus", "Ornithischia")

Matching is case-insensitive, ignores surrounding whitespace, and requires the complete clade name rather than a partial substring.

Filtering taxa

The package data include a stored filtering example:

taxobase$filter
#>  [1] "Tyrannosaurus" "Velociraptor"  "Spinosaurus"   "Gallus"       
#>  [5] "Struthio"      "Falco"         "Corvus"        "Spheniscus"   
#>  [9] "Aquila"        "Ara"           "Passer"

A live operation can be performed with:

taxa <- c(
  "Tyrannosaurus",
  "Carnotaurus",
  "Triceratops",
  "Velociraptor",
  "Homo",
  "Drosophila"
)

filter_clade(taxa, "Dinosauria")
filter_clade(taxa, "Theropoda")

Shared clades and lineage comparison

shared_clades(
  "Tyrannosaurus",
  "Triceratops"
)

compare_lineages(
  "Carnotaurus",
  "Tyrannosaurus"
)

Shared ancestry is determined from the continuous common lineage prefix. A name repeated after the first divergence is not treated as a shared ancestor.

Clustering, ordination, and visualization

The following examples use a subset of the packaged matrix and therefore require no network access.

labels <- attr(reference_matrix, "Labels")[1:8]

example_matrix <- stats::as.dist(
  as.matrix(reference_matrix)[labels, labels]
)

example_matrix
#>               Tyrannosaurus Velociraptor Triceratops Brachiosaurus Stegosaurus
#> Velociraptor     0.01492537                                                   
#> Triceratops      0.01754386   0.01754386                                      
#> Brachiosaurus    0.01694915   0.01694915  0.01754386                          
#> Stegosaurus      0.01754386   0.01754386  0.01694915    0.01754386            
#> Ankylosaurus     0.01754386   0.01754386  0.01694915    0.01754386  0.01612903
#> Spinosaurus      0.01562500   0.01562500  0.01754386    0.01694915  0.01754386
#> Diplodocus       0.01694915   0.01694915  0.01754386    0.01470588  0.01754386
#>               Ankylosaurus Spinosaurus
#> Velociraptor                          
#> Triceratops                           
#> Brachiosaurus                         
#> Stegosaurus                           
#> Ankylosaurus                          
#> Spinosaurus     0.01754386            
#> Diplodocus      0.01754386  0.01694915

Hierarchical clustering

clustering <- taxo_cluster(
  example_matrix,
  method = "average"
)

plot(
  clustering,
  main = "Taxonomic hierarchy distance clustering",
  xlab = "",
  sub = ""
)

The result is a clustering dendrogram, not an independently inferred phylogenetic tree.

Classification trees

taxo_tree() builds a tree directly from lineages rather than from distances. Its topology is the classification itself, its internal nodes are labeled with the named clades at which the taxa diverge, and its branch lengths are chosen so that patristic distances reproduce distance_matrix() exactly. The result is a standard phylo object.

lineages <- taxo_from_lineages(list(
  Tyrannosaurus = c("Biota", "Dinosauria", "Theropoda", "Tyrannosaurus"),
  Velociraptor = c("Biota", "Dinosauria", "Theropoda", "Velociraptor"),
  Triceratops = c("Biota", "Dinosauria", "Ornithischia", "Triceratops"),
  Homo = c("Biota", "Mammalia", "Homo")
))

tree <- taxo_tree(lineages)
tree$node.label
#> [1] "Biota"      "Dinosauria" "Theropoda"

plot(tree, show.node.label = TRUE, main = "Classification tree")


tree_distances <- distance_matrix(lineages, progress = FALSE)
all.equal(
  ape::cophenetic.phylo(tree)[labels(tree_distances), labels(tree_distances)],
  as.matrix(tree_distances)
)
#> [1] TRUE

The tree can be written in Newick format with ape::write.tree(tree).

Principal coordinates analysis

ordination <- taxo_ordinate(
  example_matrix,
  k = 2
)

summary(ordination)
#> 
#> ── Taxonomic Ordination Summary (PCoA) ──
#> 
#> Goodness-of-Fit (GOF): 38.52%
#> 
#>  Axis Eigenvalue Variance_Pct Cumulative_Pct
#>   PC1  0.0002059        20.33          20.33
#>   PC2  0.0001842        18.19          38.52
plot(
  ordination,
  main = "Taxonomic hierarchy distance space"
)

The goodness-of-fit and eigenvalues should be examined before interpreting a low-dimensional representation.

Heatmap

taxo_heatmap(
  example_matrix,
  main = "Taxonomic hierarchy distance matrix",
  col = hcl.colors(12, "YlOrRd", rev = TRUE)
)

Graphical arguments such as main and col are passed to stats::heatmap(), while verbose and progress control lineage retrieval when taxon names are supplied instead of a matrix.

Additional statistical examples and interpretation are provided in:

vignette("statistical-applications", package = "taxodist")

Name resolution and coverage

Taxonomic names may be missing, ambiguous, redirected, or associated with non-biological homonyms.

taxo_search() returns candidate Taxonomicon entries:

taxo_search("Panthera")
taxo_search("Bacteria")

When several valid biological entries exist, the package reports the available identifiers. A numeric identifier should be supplied when the intended taxonomic concept is known.

Names are matched after removing surrounding whitespace and collapsing repeated spaces, and the queried name is located in the lineage without regard to case. Consequently, "tyrannosaurus" and " Tyrannosaurus " resolve to the same node as "Tyrannosaurus".

For batches of names, taxo_resolve() records the status, candidates, selected identifier, and lineage of every input. Homonyms in different parts of the classification can often be resolved automatically by restricting candidates to a clade with within:

resolved <- taxo_resolve(
  c("Tyrannosaurus", "Nereis", "Not_a_real_taxon"),
  within = "Animalia"
)
resolved
distance_matrix(resolved)

A matrix computed from a resolution is identical to one computed from the same names, but it requires no further network requests and keeps an audit trail.

Before calculating a large matrix, coverage can be checked explicitly:

taxa <- c(
  "Tyrannosaurus",
  "Velociraptor",
  "Quercus",
  "Not_a_real_taxon"
)

check_coverage(taxa)

check_coverage() returns TRUE for names found, FALSE for names absent from The Taxonomicon, and NA when the server could not be reached, so a temporary network failure is never mistaken for missing coverage.

Unresolved taxa generate missing pairwise distances. These values should be examined before clustering, ordination, or other methods requiring complete finite matrices.

Cache management

Taxonomicon identifiers and lineages are cached in memory during an R session. This avoids repeated requests for the same taxa.

cache_info()
clear_cache()

The cache can also be saved and restored:

save_cache("taxodist-cache.rds")
clear_cache()
load_cache("taxodist-cache.rds")

Called without a file, save_cache() and load_cache() use a persistent per-user location given by tools::R_user_dir("taxodist", "cache"):

save_cache()
# In a later session:
load_cache()

Cache files preserve retrieved lineages across sessions. They should be regenerated when an analysis is intended to incorporate subsequent revisions of the source classification.

Requests to The Taxonomicon are retried automatically after transient network or server errors. The number of attempts can be changed with options(taxodist.max_tries = 5).

Interpretation and reproducibility

The numerical distance depends on the classification supplied by The Taxonomicon. Changes in lineage resolution or taxonomic concepts can alter MRCA depths and therefore alter distances.

A reproducible analysis should record:

Saved caches and distance matrices can preserve the exact input used in an analysis. taxo_bundle() collects the resolution, lineages, matrix, distance definition, and provenance in a single object that can be written to portable JSON. When an analysis is repeated later, compare_bundles() reports which taxa changed identifier, status, or lineage, and which distances changed as a result:

before <- read_taxodist_bundle("analysis-2026.json")
after <- taxo_bundle(before$resolution$input)
compare_bundles(before, after)

Data source and citation

All retrieved lineage data originate from The Taxonomicon, based on Systema Naturae 2000.

Formatted references for both the package and the data source are available with:

citation("taxodist")

Both the software and the underlying taxonomic source should be cited in published analyses.