Skill Tracer
Skills › Scientific Agent Skills › etetoolkit › taxonomy.md

taxonomy.md

398 lines · skills/etetoolkit/references/taxonomy.md at 5f2bc03

Open on GitHub
1# NCBI and GTDB Taxonomy with ETE 4
2
3ETE 4.4.0 provides local SQLite-backed interfaces for:
4
5- **NCBI Taxonomy** through `NCBITaxa`
6- **Genome Taxonomy Database (GTDB)** through `GTDBTaxa`
7
8Both can translate identifiers, retrieve ranks and lineages, find descendants,
9construct minimal connecting topologies, and annotate `PhyloTree` objects.

NCBI and GTDB Taxonomy with ETE 4

ETE 4.4.0 provides local SQLite-backed interfaces for:

  • NCBI Taxonomy through NCBITaxa
  • Genome Taxonomy Database (GTDB) through GTDBTaxa

Both can translate identifiers, retrieve ranks and lineages, find descendants, construct minimal connecting topologies, and annotate PhyloTree objects.

Raw lines 1 to 10
11## Storage and First Use
13The official tutorial's approximate **600 MB NCBI** and **72 MB GTDB** figures
14should be treated as local first-use footprint estimates, not compressed
15network download sizes. Archives vary by release and can be much smaller.
17Parsed databases are stored under `~/.local/share/ete/` by default. Allow space
18for the downloaded archive, parsed SQLite database, traversal cache, and
19temporary conversion files. Do not create or refresh a database unexpectedly
20in a constrained or offline job.
22No API key or credential is required.

Storage and First Use

The official tutorial's approximate 600 MB NCBI and 72 MB GTDB figures should be treated as local first-use footprint estimates, not compressed network download sizes. Archives vary by release and can be much smaller.

Parsed databases are stored under ~/.local/share/ete/ by default. Allow space for the downloaded archive, parsed SQLite database, traversal cache, and temporary conversion files. Do not create or refresh a database unexpectedly in a constrained or offline job.

No API key or credential is required.

Raw lines 11 to 23
24## Constructors
26```python
27from ete4 import GTDBTaxa, NCBITaxa
29ncbi = NCBITaxa(
30 dbfile=None,
31 taxdump_file=None,
32 memory=False,
33 update=True,
36gtdb = GTDBTaxa(
37 dbfile=None,
38 taxdump_file=None,
39 memory=False,
41```
43Important controls:
45- `dbfile`: explicit parsed SQLite path
46- `taxdump_file`: local taxonomy archive used to create/update a database
47- `memory=True`: load the database into memory for repeated queries
48- `update=False` on `NCBITaxa`: disable the constructor's schema-update path
50When the database is absent, construction creates/downloads it. An existing
51database is not refreshed to newer taxonomy content merely because
52`update=True`; call `update_taxonomy_database()` explicitly when a content
53refresh is intended.
55For a reproducible or offline analysis, provide an explicit `dbfile` and use
56the same file across runs.

Constructors

from ete4 import GTDBTaxa, NCBITaxa

ncbi = NCBITaxa(
    dbfile=None,
    taxdump_file=None,
    memory=False,
    update=True,
)

gtdb = GTDBTaxa(
    dbfile=None,
    taxdump_file=None,
    memory=False,
)

Important controls:

  • dbfile: explicit parsed SQLite path
  • taxdump_file: local taxonomy archive used to create/update a database
  • memory=True: load the database into memory for repeated queries
  • update=False on NCBITaxa: disable the constructor's schema-update path

When the database is absent, construction creates/downloads it. An existing database is not refreshed to newer taxonomy content merely because update=True; call update_taxonomy_database() explicitly when a content refresh is intended.

For a reproducible or offline analysis, provide an explicit dbfile and use the same file across runs.

Raw lines 24 to 57
58## Explicit Updates
60Latest NCBI taxonomy:
62```python
63from ete4 import NCBITaxa
65ncbi = NCBITaxa(update=False)
66ncbi.update_taxonomy_database()
67```
69Latest GTDB taxonomy:
71```python
72from ete4 import GTDBTaxa
74gtdb = GTDBTaxa()
75gtdb.update_taxonomy_database()
76```
78From an already acquired local archive:
80```python
81ncbi.update_taxonomy_database("taxdump.tar.gz")
82gtdb.update_taxonomy_database("gtdb_taxdump.tar.gz")
83```
85For production provenance, record:
87- Source database (NCBI or GTDB)
88- Acquisition date and upstream release when available
89- Archive and parsed-database checksums
90- ETE version
91- Any filtering or rank limit
93Do not replace a shared database in the middle of a multi-step analysis.

Explicit Updates

Latest NCBI taxonomy:

from ete4 import NCBITaxa

ncbi = NCBITaxa(update=False)
ncbi.update_taxonomy_database()

Latest GTDB taxonomy:

from ete4 import GTDBTaxa

gtdb = GTDBTaxa()
gtdb.update_taxonomy_database()

From an already acquired local archive:

ncbi.update_taxonomy_database("taxdump.tar.gz")
gtdb.update_taxonomy_database("gtdb_taxdump.tar.gz")

For production provenance, record:

  • Source database (NCBI or GTDB)
  • Acquisition date and upstream release when available
  • Archive and parsed-database checksums
  • ETE version
  • Any filtering or rank limit

Do not replace a shared database in the middle of a multi-step analysis.

Raw lines 58 to 94
95### ETE 4.4.0 updater caveats
97- NCBI refreshes use `https://ftp.ncbi.nlm.nih.gov/pub/taxonomy/taxdump.tar.gz`.
98 ETE checks the `.md5` sidecar only to decide whether an existing archive is
99 current; it does **not** verify newly downloaded bytes against the checksum.
100 The released downloader also lacks request timeouts and HTTP status checks.
101 Acquire and validate a dated archive separately for reproducible production
102 use, then pass that local archive to the constructor.
103- GTDB refreshes use ETE's converted NCBI-like dump, not a direct GTDB
104 database file.
105- The ETE 4.4.0 GTDB freshness check requests an MD5 sidecar that was absent
106 from the ETE-data location at this review, so a nominal update can redownload data
107 instead of reporting it current.
108- Taxonomy conversion creates temporary files in the process working
109 directory. Run updates in a controlled, writable workspace and remove
110 leftovers if an interrupted update fails.

ETE 4.4.0 updater caveats

  • NCBI refreshes use https://ftp.ncbi.nlm.nih.gov/pub/taxonomy/taxdump.tar.gz. ETE checks the .md5 sidecar only to decide whether an existing archive is current; it does not verify newly downloaded bytes against the checksum. The released downloader also lacks request timeouts and HTTP status checks. Acquire and validate a dated archive separately for reproducible production use, then pass that local archive to the constructor.
  • GTDB refreshes use ETE's converted NCBI-like dump, not a direct GTDB database file.
  • The ETE 4.4.0 GTDB freshness check requests an MD5 sidecar that was absent from the ETE-data location at this review, so a nominal update can redownload data instead of reporting it current.
  • Taxonomy conversion creates temporary files in the process working directory. Run updates in a controlled, writable workspace and remove leftovers if an interrupted update fails.
Raw lines 95 to 111
112## NCBI Translation

NCBI Translation

Raw lines 112 to 113
114### Scientific names to TaxIDs
116```python
117from ete4 import NCBITaxa
119ncbi = NCBITaxa()
120queries = ["Homo sapiens", "Pan troglodytes", "Mus musculus"]
121name_to_taxids = ncbi.get_name_translator(queries)
123for query in queries:
124 candidates = name_to_taxids.get(query, [])
125 if not candidates:
126 print("unresolved:", query)
127 elif len(candidates) > 1:
128 print("ambiguous:", query, candidates)
129 else:
130 print(query, candidates[0])
131```
133The translator returns a list because a name can map to multiple taxonomy
134records. Do not blindly select index zero without checking ambiguity.

Scientific names to TaxIDs

from ete4 import NCBITaxa

ncbi = NCBITaxa()
queries = ["Homo sapiens", "Pan troglodytes", "Mus musculus"]
name_to_taxids = ncbi.get_name_translator(queries)

for query in queries:
    candidates = name_to_taxids.get(query, [])
    if not candidates:
        print("unresolved:", query)
    elif len(candidates) > 1:
        print("ambiguous:", query, candidates)
    else:
        print(query, candidates[0])

The translator returns a list because a name can map to multiple taxonomy records. Do not blindly select index zero without checking ambiguity.

Raw lines 114 to 135
136### TaxIDs to names
138```python
139taxid_to_name = ncbi.get_taxid_translator([9606, 9598, 10090])
140print(taxid_to_name)
141```

TaxIDs to names

taxid_to_name = ncbi.get_taxid_translator([9606, 9598, 10090])
print(taxid_to_name)
Raw lines 136 to 142
143### Ranks and lineage
145```python
146taxid = 9606
147lineage = ncbi.get_lineage(taxid)
148names = ncbi.get_taxid_translator(lineage)
149ranks = ncbi.get_rank(lineage)
151for ancestor in lineage:
152 print(ancestor, names.get(ancestor), ranks.get(ancestor, "no rank"))
153```
155Use `.get()` because not every taxonomy node is guaranteed to have every
156requested annotation.

Ranks and lineage

taxid = 9606
lineage = ncbi.get_lineage(taxid)
names = ncbi.get_taxid_translator(lineage)
ranks = ncbi.get_rank(lineage)

for ancestor in lineage:
    print(ancestor, names.get(ancestor), ranks.get(ancestor, "no rank"))

Use .get() because not every taxonomy node is guaranteed to have every requested annotation.

Raw lines 143 to 157
158## Descendant Taxa
160```python
161descendants = ncbi.get_descendant_taxa("Homo")
162print(ncbi.translate_to_names(descendants))
163```
165Collapse below the species level:
167```python
168species = ncbi.get_descendant_taxa(
169 "Homo",
170 collapse_subspecies=True,
172```
174Return an ETE tree:
176```python
177tree = ncbi.get_descendant_taxa(
178 "Homo",
179 collapse_subspecies=True,
180 return_tree=True,
182print(tree.to_str(props=["sci_name", "taxid", "rank"]))
183```
185Large internal taxa can have many descendants. Estimate scope before
186materializing or printing the complete result.

Descendant Taxa

descendants = ncbi.get_descendant_taxa("Homo")
print(ncbi.translate_to_names(descendants))

Collapse below the species level:

species = ncbi.get_descendant_taxa(
    "Homo",
    collapse_subspecies=True,
)

Return an ETE tree:

tree = ncbi.get_descendant_taxa(
    "Homo",
    collapse_subspecies=True,
    return_tree=True,
)
print(tree.to_str(props=["sci_name", "taxid", "rank"]))

Large internal taxa can have many descendants. Estimate scope before materializing or printing the complete result.

Raw lines 158 to 187
188## NCBI Topology
190```python
191taxids = [9606, 9598, 10090, 7707, 8782]
192tree = ncbi.get_topology(
193 taxids,
194 intermediate_nodes=False,
195 collapse_subspecies=False,
196 annotate=True,
198print(tree.to_str(props=["sci_name", "rank", "taxid"]))
199```
201Retain every intermediate taxonomy node:
203```python
204tree = ncbi.get_topology(
205 [2, 33208],
206 intermediate_nodes=True,
207 annotate=True,
209```
211Taxonomy topology is a classification hierarchy. Do not treat branch lengths
212or omitted intermediate ranks as a molecular phylogeny.

NCBI Topology

taxids = [9606, 9598, 10090, 7707, 8782]
tree = ncbi.get_topology(
    taxids,
    intermediate_nodes=False,
    collapse_subspecies=False,
    annotate=True,
)
print(tree.to_str(props=["sci_name", "rank", "taxid"]))

Retain every intermediate taxonomy node:

tree = ncbi.get_topology(
    [2, 33208],
    intermediate_nodes=True,
    annotate=True,
)

Taxonomy topology is a classification hierarchy. Do not treat branch lengths or omitted intermediate ranks as a molecular phylogeny.

Raw lines 188 to 213
214## GTDB Queries
216GTDB identifiers are strings such as:
218- `d__Bacteria`
219- `p__Firmicutes_B`
220- `f__Korarchaeaceae`
221- `GB_GCA_020833055.1`
222- `RS_GCF_000019605.1`
224Do not pass them to `NCBITaxa`, and do not pass NCBI numeric TaxIDs to
225`GTDBTaxa`.

GTDB Queries

GTDB identifiers are strings such as:

  • d__Bacteria
  • p__Firmicutes_B
  • f__Korarchaeaceae
  • GB_GCA_020833055.1
  • RS_GCF_000019605.1

Do not pass them to NCBITaxa, and do not pass NCBI numeric TaxIDs to GTDBTaxa.

Raw lines 214 to 226
227### Descendants
229```python
230from ete4 import GTDBTaxa
232gtdb = GTDBTaxa()
233descendants = gtdb.get_descendant_taxa("f__Thorarchaeaceae")
234print(descendants)
235```

Descendants

from ete4 import GTDBTaxa

gtdb = GTDBTaxa()
descendants = gtdb.get_descendant_taxa("f__Thorarchaeaceae")
print(descendants)
Raw lines 227 to 236
237### GTDB topology
239```python
240queries = [
241 "p__Huberarchaeota",
242 "o__Peptococcales",
243 "f__Korarchaeaceae",
246tree = gtdb.get_topology(
247 queries,
248 intermediate_nodes=True,
249 collapse_subspecies=True,
250 annotate=True,
252print(tree.to_str(props=["sci_name", "rank"]))
253```
255GTDB and NCBI classifications can disagree because they use different data,
256release cycles, nomenclature, and taxonomic frameworks. State which one was
257used rather than combining labels without a mapping policy.

GTDB topology

queries = [
    "p__Huberarchaeota",
    "o__Peptococcales",
    "f__Korarchaeaceae",
]

tree = gtdb.get_topology(
    queries,
    intermediate_nodes=True,
    collapse_subspecies=True,
    annotate=True,
)
print(tree.to_str(props=["sci_name", "rank"]))

GTDB and NCBI classifications can disagree because they use different data, release cycles, nomenclature, and taxonomic frameworks. State which one was used rather than combining labels without a mapping policy.

Raw lines 237 to 258
259## Annotate a PhyloTree with NCBI

Annotate a PhyloTree with NCBI

Raw lines 259 to 260
261### Leaf names are TaxIDs
263```python
264from ete4 import PhyloTree
266tree = PhyloTree("((9606,9598),10090);")
267taxid_to_name, taxid_to_lineage, taxid_to_rank = tree.annotate_ncbi_taxa(
268 taxid_attr="name",
271print(tree.to_str(props=["name", "sci_name", "taxid", "rank"]))
272```

Leaf names are TaxIDs

from ete4 import PhyloTree

tree = PhyloTree("((9606,9598),10090);")
taxid_to_name, taxid_to_lineage, taxid_to_rank = tree.annotate_ncbi_taxa(
    taxid_attr="name",
)

print(tree.to_str(props=["name", "sci_name", "taxid", "rank"]))
Raw lines 261 to 273
274### Extract TaxIDs from compound names
276```python
277tree = PhyloTree(
278 "((9606|protA,9598|protA),10090|protB);",
279 sp_naming_function=lambda name: name.split("|", 1)[0],
282tree.annotate_ncbi_taxa(taxid_attr="species")
283```

Extract TaxIDs from compound names

tree = PhyloTree(
    "((9606|protA,9598|protA),10090|protB);",
    sp_naming_function=lambda name: name.split("|", 1)[0],
)

tree.annotate_ncbi_taxa(taxid_attr="species")
Raw lines 274 to 284
285### Explicit custom property
287```python
288tree = PhyloTree("((protA,protB),protC);")
290taxids = {
291 "protA": 9606,
292 "protB": 9598,
293 "protC": 10090,
295for leaf in tree.leaves():
296 leaf.add_prop("ncbi_taxid", taxids[leaf.name])
298tree.annotate_ncbi_taxa(taxid_attr="ncbi_taxid")
299```
301Prefer an explicit mapping when names are not stable taxonomy identifiers.

Explicit custom property

tree = PhyloTree("((protA,protB),protC);")

taxids = {
    "protA": 9606,
    "protB": 9598,
    "protC": 10090,
}
for leaf in tree.leaves():
    leaf.add_prop("ncbi_taxid", taxids[leaf.name])

tree.annotate_ncbi_taxa(taxid_attr="ncbi_taxid")

Prefer an explicit mapping when names are not stable taxonomy identifiers.

Raw lines 285 to 302
303## Annotate a PhyloTree with GTDB
305```python
306from ete4 import PhyloTree
308tree = PhyloTree(
309 "((GB_GCA_020833055.1|protA,GB_GCA_003344655.1|protB),"
310 "RS_GCF_000019605.1|protC);",
311 sp_naming_function=lambda name: name.split("|", 1)[0],
314tree.annotate_gtdb_taxa(taxid_attr="species")
315print(tree.to_str(props=["name", "sci_name", "rank"]))
316```
318For genome accessions represented at GTDB subspecies rank, ETE sets
319`sci_name` to the parent species while preserving the accession in the leaf
320name/TaxID. Do not use `sci_name` as a unique genome identifier.
322The annotation methods infer internal-node taxonomy from descendants when
323possible and return the translators they used. Preserve those mappings when
324the analysis needs an auditable record.

Annotate a PhyloTree with GTDB

from ete4 import PhyloTree

tree = PhyloTree(
    "((GB_GCA_020833055.1|protA,GB_GCA_003344655.1|protB),"
    "RS_GCF_000019605.1|protC);",
    sp_naming_function=lambda name: name.split("|", 1)[0],
)

tree.annotate_gtdb_taxa(taxid_attr="species")
print(tree.to_str(props=["name", "sci_name", "rank"]))

For genome accessions represented at GTDB subspecies rank, ETE sets sci_name to the parent species while preserving the accession in the leaf name/TaxID. Do not use sci_name as a unique genome identifier.

The annotation methods infer internal-node taxonomy from descendants when possible and return the translators they used. Preserve those mappings when the analysis needs an auditable record.

Raw lines 303 to 325
326## Cache and Offline Pattern
328Prepare the database in a controlled networked step:
330```python
331import os
332import tempfile
333from pathlib import Path
334from ete4 import NCBITaxa
336# Paths are resolved before entering a disposable, writable conversion directory.
337db_path = Path("taxonomy/ncbi_taxa.sqlite").resolve()
338archive = Path("taxonomy/taxdump.tar.gz").resolve()
339if not archive.is_file():
340 raise FileNotFoundError(archive)
341previous_dir = Path.cwd()
342with tempfile.TemporaryDirectory() as conversion_dir:
343 try:
344 os.chdir(conversion_dir) # run this setup in its own process
345 ncbi = NCBITaxa(
346 dbfile=str(db_path), taxdump_file=str(archive), update=False,
347 )
348 finally:
349 os.chdir(previous_dir)
350```
352Use the pinned database without constructor schema updates in analysis jobs:
354```python
355from pathlib import Path
356from ete4 import NCBITaxa
358db_path = Path("taxonomy/ncbi_taxa.sqlite")
359if not db_path.is_file():
360 raise FileNotFoundError(db_path) # update=False alone does not prevent download
361ncbi = NCBITaxa(dbfile=str(db_path), update=False)
362```
364For a read-only container or cluster job, mount the database at an explicit
365path. Avoid relying on an unwritable home-directory default.
367Preserve the adjacent `<dbfile>.traverse.pkl` as well: descendant queries use
368this traversal cache. It is a Python pickle; use only the cache generated from
369your trusted archive, not an untrusted downloaded pickle. GTDB has no
370`update=False` constructor argument, so also ensure its existing database schema
371matches the installed ETE release before an offline run.
373These queries were exercised with tiny synthetic local NCBI and converted-GTDB
374archives. The real taxonomy-name examples are illustrative and depend on the
375chosen full snapshot; no full database was downloaded during this review.

Cache and Offline Pattern

Prepare the database in a controlled networked step:

import os
import tempfile
from pathlib import Path
from ete4 import NCBITaxa

# Paths are resolved before entering a disposable, writable conversion directory.
db_path = Path("taxonomy/ncbi_taxa.sqlite").resolve()
archive = Path("taxonomy/taxdump.tar.gz").resolve()
if not archive.is_file():
    raise FileNotFoundError(archive)
previous_dir = Path.cwd()
with tempfile.TemporaryDirectory() as conversion_dir:
    try:
        os.chdir(conversion_dir)  # run this setup in its own process
        ncbi = NCBITaxa(
            dbfile=str(db_path), taxdump_file=str(archive), update=False,
        )
    finally:
        os.chdir(previous_dir)

Use the pinned database without constructor schema updates in analysis jobs:

from pathlib import Path
from ete4 import NCBITaxa

db_path = Path("taxonomy/ncbi_taxa.sqlite")
if not db_path.is_file():
    raise FileNotFoundError(db_path)  # update=False alone does not prevent download
ncbi = NCBITaxa(dbfile=str(db_path), update=False)

For a read-only container or cluster job, mount the database at an explicit path. Avoid relying on an unwritable home-directory default.

Preserve the adjacent <dbfile>.traverse.pkl as well: descendant queries use this traversal cache. It is a Python pickle; use only the cache generated from your trusted archive, not an untrusted downloaded pickle. GTDB has no update=False constructor argument, so also ensure its existing database schema matches the installed ETE release before an offline run.

These queries were exercised with tiny synthetic local NCBI and converted-GTDB archives. The real taxonomy-name examples are illustrative and depend on the chosen full snapshot; no full database was downloaded during this review.

Raw lines 326 to 376
377## Validation Checklist
379Before using taxonomy annotations:
3811. Confirm whether identifiers are NCBI or GTDB.
3822. Detect unresolved and multiply resolved names.
3833. Check that accession prefixes and release conventions match the GTDB
384 snapshot.
3854. Record database provenance and checksum.
3865. Distinguish classification topology from inferred sequence phylogeny.
3876. Review rank and scientific-name changes when updating a database.
3887. Export only the annotation properties required downstream.

Validation Checklist

Before using taxonomy annotations:

  1. Confirm whether identifiers are NCBI or GTDB.
  2. Detect unresolved and multiply resolved names.
  3. Check that accession prefixes and release conventions match the GTDB snapshot.
  4. Record database provenance and checksum.
  5. Distinguish classification topology from inferred sequence phylogeny.
  6. Review rank and scientific-name changes when updating a database.
  7. Export only the annotation properties required downstream.
Raw lines 377 to 389
390## Upstream References
392- Taxonomy tutorial:
393 https://etetoolkit.github.io/ete/tutorial/tutorial_taxonomy.html
394- Taxonomy API:
395 https://etetoolkit.github.io/ete/reference/reference_taxonomy.html
396- ETE data repository: https://github.com/etetoolkit/ete-data
397- NCBI Taxonomy: https://www.ncbi.nlm.nih.gov/taxonomy
398- GTDB: https://gtdb.ecogenomic.org/

Upstream References

  • Taxonomy tutorial: https://etetoolkit.github.io/ete/tutorial/tutorial_taxonomy.html
  • Taxonomy API: https://etetoolkit.github.io/ete/reference/reference_taxonomy.html
  • ETE data repository: https://github.com/etetoolkit/ete-data
  • NCBI Taxonomy: https://www.ncbi.nlm.nih.gov/taxonomy
  • GTDB: https://gtdb.ecogenomic.org/
Raw lines 390 to 398