| name | taxonomy |
| description | Rust/Python library for reading, editing, and traversing biological taxonomies. Supports NCBI, JSON, Newick, GTDB, and PhyloXML formats. |
| license | MIT |
| metadata | {"author":"One Codex","source":"https://github.com/onecodex/taxonomy"} |
taxonomy skill
Rust library with Python bindings (pyo3/maturin) for reading, editing, and traversing biological taxonomies. Supports NCBI, JSON, Newick, GTDB, and PhyloXML formats.
Setup
from taxonomy import Taxonomy
Build with maturin develop (activate .venv first: source .venv/bin/activate).
Loading taxonomies
Austin keeps NCBI taxdumps in ~/science. Preferred load path:
tax = Taxonomy.from_ncbi("~/science/taxdump")
tax = Taxonomy.from_json(open("tax.json").read())
tax = Taxonomy.from_newick("(A:0.1,B:0.2)root;")
tax = Taxonomy.from_gtdb(open("bac120_taxonomy.tsv").read())
JSON supports an optional pointer for nested data: Taxonomy.from_json(s, json_pointer="/data/taxonomy").
Node lookup
node = tax["562"]
node = tax.node("562")
"562" in tax
node.id
node.name
node.rank
node.parent
node["readcount"]
Traversal
tax.parent("562")
tax.parent("562", at_rank="genus")
tax.parent_with_distance("562")
tax.children("562")
tax.descendants("562")
tax.lineage("562")
tax.parents("562")
tax.lca("562", "585")
tax.find_all_by_name("Escherichia")
for tax_id in tax:
...
len(tax)
Editing
Edits are in-place. prune() returns a new object.
tax.add_node(parent_id, tax_id, name, rank)
tax.edit_node(tax_id, name=None, rank=None, parent_id=None, parent_distance=None)
tax.remove_node(tax_id)
del tax[tax_id]
Pruning (returns new Taxonomy)
subtree = tax.prune(keep=["562", "585"])
pruned = tax.prune(remove=["1236"])
both = tax.prune(keep=[...], remove=[...])
Exporting
tax.to_json_tree()
tax.to_json_node_links()
tax.to_newick()
tax.to_ncbi("output_dir")
tax.clone()
Taxonomy Time Machine API
https://taxonomy.onecodex.com — tracks NCBI taxonomy history back to 2014. Useful for resolving merged, deleted, or renamed tax IDs.
import httpx
BASE = "https://taxonomy.onecodex.com"
resp = httpx.get(f"{BASE}/api/events", params={"tax_id": "562"})
events = resp.json()
resp = httpx.get(f"{BASE}/api/lineage", params={"tax_id": "562", "version_date": "2020-01-01"})
for e in events:
if e["merged_into_id"]:
print(f"{e['tax_id']} merged into {e['merged_into_id']} on {e['version_date']}")
resp = httpx.get(f"{BASE}/api/search", params={"query": "Escherichia"})
resp = httpx.get(f"{BASE}/api/children", params={"tax_id": "561", "version_date": "2019-06-01"})
Common patterns
def resolve(tax_id, tax):
if tax_id in tax:
return tax_id
events = httpx.get(f"{BASE}/api/events", params={"tax_id": tax_id}).json()
for e in sorted(events, key=lambda e: e["version_date"], reverse=True):
if e["merged_into_id"] and e["merged_into_id"] in tax:
return e["merged_into_id"]
return None
def to_genus(tax_id, tax):
node = tax.parent(tax_id, at_rank="genus")
return node.id if node else None
bacteria = tax.prune(keep=tax.children("2"))
Notes
- All tax IDs are strings internally (NCBI integers →
"562").
TaxonomyError for expected errors; pyo3_runtime.PanicException = Rust bug, file an issue.
- Rank strings match NCBI conventions:
"species", "genus", "no rank", "synthetic", etc.
- Branch distances default to
1.0 for formats that don't encode them (NCBI, JSON).