Back to Claude Scientific Skills

ETE 4 API Reference

skills/etetoolkit/references/api_reference.md

2.55.014.0 KB
Original Source

ETE 4 API Reference

This is a task-oriented reference for ETE 4.4.0. It was checked against the official ETE 4 documentation and an installed ete4==4.4.0 package on July 23, 2026. Use the upstream API reference for less common parameters.

Imports and Public Classes

python
from ete4 import (
    EvolTree,
    GTDBTaxa,
    NCBITaxa,
    PhyloTree,
    SeqGroup,
    Tree,
)

Frequently used classes:

  • Tree: general rooted or unrooted tree data structure
  • PhyloTree: Tree subclass with species, alignment, event, and reconciliation methods
  • NCBITaxa: local NCBI taxonomy database interface
  • GTDBTaxa: local Genome Taxonomy Database interface
  • SeqGroup: sequence/alignment container
  • EvolTree: evolutionary-model support, including external PAML workflows

ClusterTree is not exported by ETE 4.4.0. Do not port ETE 3 clustering examples by changing only the import. Use a normal Tree for dendrogram topology and calculate matrix/cluster statistics with a maintained numerical library.

Constructing and Parsing Trees

Empty node or node properties

python
from ete4 import Tree

empty = Tree()
root = Tree({"name": "root", "dist": 0.0, "study": "trial-7"})

ETE 4 nodes do not automatically have a non-null name, distance, or support. node.name, node.dist, and node.support can therefore be None.

Newick string

python
tree = Tree("((A:1,B:2)CladeAB:0.5,C:3)Root;", parser=1)

Use Python strings for Newick text. ETE 4.4.0 retains a path-like string heuristic internally, but the documented and reproducible file form is an open file object; do not depend on the heuristic.

Newick file

python
from pathlib import Path

with Path("tree.nw").open(encoding="utf-8") as handle:
    tree = Tree(handle, parser=1)

Pass an open text file object. This distinction removes the ETE 3 ambiguity between file names and Newick strings.

Common Newick parsers

  • parser="support" or parser=0: flexible branch lengths; internal field is support
  • parser="name" or parser=1: flexible branch lengths; internal field is a name
  • parser=8: all node names, no branch lengths required
  • parser=9: leaf names only
  • parser=100: topology only

Other numeric parsers encode stricter combinations of names and branch lengths. Prefer the parser that matches the actual producer's Newick schema. Inspect a round trip before processing a large collection:

python
tree = Tree("((A:1,B:1)95:0.2,C:1);", parser="support")
assert tree.write(parser="support", props=[]) == "((A:1,B:1)95:0.2,C:1);"

Nexus files

ETE's Nexus parser returns a dictionary of tree names to Tree objects and applies any Nexus translation table:

python
from pathlib import Path

from ete4.parser import nexus

with Path("trees.nex").open(encoding="utf-8") as handle:
    trees = nexus.load(handle, parser=9)

for tree_name, tree in trees.items():
    print(tree_name, list(tree.leaf_names()))

Use the parser expected by the Newick strings inside the Nexus TREES block. The current Nexus module is a reader; serialize processed trees explicitly as Newick unless another library is responsible for writing Nexus.

Node Structure and Properties

Core structural attributes:

python
node.up           # parent or None
node.children     # child list
node.root         # absolute root
node.is_leaf      # bool property
node.is_root      # bool property
node.level        # edges between node and root
node.id           # positional tuple, such as (0, 1, 0)

Biological or user metadata belongs in props:

python
node.add_prop("habitat", "marine")
node.add_props(sample_count=12, qc_pass=True)

habitat = node.get_prop("habitat", "unknown")
same_value = node.props.get("habitat", "unknown")

node.del_prop("qc_pass")

name, dist, and support are special property-backed conveniences:

python
assert node.name == node.props.get("name")

Use add_prop() rather than assigning arbitrary Python attributes if the value must participate in search, serialization, or visualization.

Traversal and Navigation

ETE 4's collection-like methods return iterators.

python
# Includes the current node.
for node in tree.traverse("preorder"):
    ...

# Excludes the current node.
for node in tree.descendants("postorder"):
    ...

for leaf in tree.leaves():
    ...

leaves = list(tree.leaves())
names = list(tree.leaf_names())
ancestors = list(node.ancestors())

Valid traversal strategies are "levelorder" (default), "preorder", and "postorder".

Dynamic leaf criteria

python
def stop_at_named_clade(node):
    return node.name in {"Mammalia", "Aves"}

for visible_node in tree.traverse(is_leaf_fn=stop_at_named_clade):
    ...

This presents selected internal nodes as terminal during that operation without changing the topology.

Search and Lookup

python
first_a = tree["A"]
by_position = tree[0, 1, 0]

all_a = list(tree.search_nodes(name="A"))
long_branches = [n for n in tree.traverse() if (n.dist or 0) > 1]
leaf_a = next(tree.search_leaves_by_name("A"))

mrca = tree.common_ancestor("A", "B")
mrca_from_list = tree.common_ancestor(["A", "B"])

Name lookup returns the first match. Validate uniqueness when names are identifiers:

python
from collections import Counter

leaf_names = list(tree.leaf_names())
duplicates = sorted(name for name, count in Counter(leaf_names).items() if count > 1)
if duplicates:
    raise ValueError(f"Duplicate leaf names: {duplicates}")

Topology Modification

Add and remove nodes

python
child = tree.add_child(name="A", dist=0.5)
sister = child.add_sister(name="B", dist=0.7)

subtree = child.detach()  # remove child plus all descendants
tree.add_child(subtree)   # attach it elsewhere

internal.delete(preserve_branch_length=True)  # remove node, retain children

detach() cuts a complete subtree. delete() eliminates only the selected node and reconnects its children.

Prune

python
tree.prune(
    ["A", "B", "C"],
    preserve_branch_length=True,
)

preserve_branch_length=True transfers deleted branch lengths so distances among retained nodes remain unchanged.

Root and unroot

python
tree.set_outgroup(tree["Outgroup"])
tree.set_midpoint_outgroup()
tree.unroot()

For inspection without immediately modifying the tree:

python
midpoint_node = tree.get_midpoint_outgroup()
tree.set_outgroup(midpoint_node)

Other operations

python
tree.resolve_polytomy(descendants=True)
tree.ladderize()
tree.to_ultrametric(topological=False)
tree.standardize(delete_orphan=True, preserve_branch_length=True)

Polytomy resolution is arbitrary. Never report the generated branching order as biological evidence.

Distances and Cached Content

python
a = tree["A"]
b = tree["B"]

branch_distance = tree.get_distance(a, b)
edge_distance = tree.get_distance(a, b, topological=True)

farthest_leaf, distance = tree.get_farthest_leaf()
closest_leaf, distance = tree.get_closest_leaf()

ETE 4.4 adds distance_matrix() and supersedes the older cophenetic_matrix() for new code:

python
matrix = tree.distance_matrix(squared=True)

For repeated descendant lookups:

python
node_to_leaf_names = tree.get_cached_content(prop="name")
for node in tree.traverse():
    names_below = node_to_leaf_names[node]

Monophyly

python
is_mono, clade_type, extra = tree.check_monophyly(
    values={"A", "B", "C"},
    prop="name",
    unrooted=False,
)

for clade in tree.get_monophyletic(values={"case"}, prop="group"):
    print(clade.id)

Interpret "monophyletic", "paraphyletic", and "polyphyletic" in the context of the tree's rooting.

Newick and Text Output

Newick

python
# No extended NHX properties.
plain_newick = tree.write(parser=1, props=[])

# Selected properties in NHX fields.
annotated_newick = tree.write(parser=1, props=["species", "group"])

# All available properties. Use only when that disclosure is intentional.
all_properties = tree.write(parser=1, props=None)

tree.write(
    outfile="tree.nw",
    parser=1,
    props=["group"],
    format_root_node=True,
)

Important props behavior:

  • props=[] or the default empty tuple: write no extended properties
  • props=["x", "y"]: write selected properties
  • props=None: write all available properties

Use an explicit list in shared or external output so internal metadata is not exported accidentally.

Also use keyword arguments. The first positional argument of write() is outfile in ETE 4, whereas older ETE 3 code may have treated its first positional argument as a feature selection.

Terminal representation

python
print(tree)
print(tree.to_str(props=["name", "dist", "support"], compact=True))

to_str() replaces ETE 3's get_ascii().

Copying

python
exact = tree.copy()                    # cpickle; recommended full copy
topology = tree.copy("newick")         # fast; standard Newick fields
text_props = tree.copy("newick-extended")
deep = tree.copy("deepcopy")           # slowest; complex Python objects

The extended-Newick path converts custom values to text and is not a type-preserving clone.

Tree Comparison

Raw Robinson-Foulds result

python
(
    rf,
    max_rf,
    common_leaves,
    edges_self,
    edges_other,
    discarded_self,
    discarded_other,
) = tree.robinson_foulds(
    other,
    prop_t1="name",
    prop_t2="name",
    unrooted_trees=False,
)

ETE 4 returns seven values. Older examples that unpack five values are wrong.

Summary dictionary

python
result = tree.compare(
    other,
    ref_tree_attr="name",
    source_tree_attr="name",
    unrooted=False,
)

print(result["rf"], result["max_rf"], result["norm_rf"])

Comparison is only meaningful if the selected property has the intended identity semantics. Report filtering by common leaves, support thresholds, rooting, polytomy expansion, and duplication handling.

For unique tip labels, prefer Tree.robinson_foulds() or the normal Tree.compare() path. Do not rely on Tree.compare(has_duplications=True) in ETE 4.4.0: upstream source marks that TreeKO branch as likely broken. The packaged ete4 compare CLI also still passes the removed format= constructor argument and fails at runtime; use the Python methods or the bundled scripts/tree_operations.py compare command.

PhyloTree

Constructor:

python
from ete4 import PhyloTree

tree = PhyloTree(
    "((Hsa|g1,Ptr|g1),Mmu|g1);",
    alignment=None,
    alg_format="fasta",
    sp_naming_function=lambda name: name.split("|", 1)[0],
    parser=None,
)

Always provide sp_naming_function when species-aware methods are needed. The ETE 4.4.0 source default is None; older documentation that implies an automatic first-three-character rule is not reliable.

Alignment:

python
tree.link_to_alignment("alignment.fasta", alg_format="fasta")
for leaf in tree.leaves():
    print(leaf.name, leaf.sequence)

Species handling:

python
tree.set_species_naming_function(lambda name: name.split("|", 1)[0])
species = {leaf.species for leaf in tree.leaves()}

Evolutionary events:

python
events = tree.get_descendant_evol_events(sos_thr=0.0)
for event in events:
    print(event.etype, event.in_seqs, event.out_seqs)

Species-overlap event detection expects a rooted, fully bifurcating gene tree. It annotates node.props["evoltype"]; it does not restore ETE 3's dup feature.

Reconciliation:

python
reconciled_tree, events = gene_tree.reconcile(species_tree)

Gene-family operations:

python
tree_count, duplication_count, speciation_trees = tree.get_speciation_trees(
    autodetect_duplications=True,
    newick_only=False,
    prop="species",
)
for speciation_tree in speciation_trees:
    process(speciation_tree)

subfamilies = tree.split_by_dups(autodetect_duplications=True)
collapsed_copy = tree.collapse_lineage_specific_expansions(return_copy=True)

Species overlap and reconciliation answer different questions. Species overlap uses label overlap between child clades; reconciliation requires a species tree and can infer losses.

TreePattern

ETE 4 can search for repeated subtree shapes:

python
from ete4 import Tree
from ete4.treematcher import TreePattern

tree = Tree("((K,((A,B),C),D),(E,F));")
three_way_split = TreePattern("(,,)", safer=True)

matches = list(three_way_split.search(tree))
print([node.id for node in matches])

Child order is not significant during topology matching. TreePattern also supports Python conditions embedded in pattern nodes, but those conditions must be static, trusted code. Never construct an expression-bearing pattern from user input, file content, model output, or other untrusted text; use topology-only patterns or ordinary Python traversal predicates instead.

Visualization Entry Points

python
tree.explore()                              # SmartView browser
tree.render_sm("tree.png", w=1200, h=800) # SmartView PNG screenshot
tree.render("tree.svg")                    # Qt treeview extra

For custom imports and renderer requirements, see visualization.md.

Error Handling

Catch narrow exceptions at an application boundary and retain context:

python
from pathlib import Path

from ete4 import Tree

path = Path("tree.nw")
try:
    with path.open(encoding="utf-8") as handle:
        tree = Tree(handle, parser=1)
except (OSError, ValueError) as exc:
    raise RuntimeError(f"Could not parse {path} with parser 1") from exc

Do not use a bare except: around parsing or topology edits; it hides schema mistakes and missing-node errors.

Upstream References