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

api_reference.md

560 lines · skills/etetoolkit/references/api_reference.md at 5f2bc03

Open on GitHub

Most marks come from matching words in the file, so treat them as pointers.

1# ETE 4 API Reference
2
3This is a task-oriented reference for **ETE 4.4.0**. It was checked against the
4official ETE 4 documentation and an installed `ete4==4.4.0` package on
5September 30, 2026. Use the upstream API reference for less common parameters.
6

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 September 30, 2026. Use the upstream API reference for less common parameters.

Raw lines 1 to 6
7## Imports and Public Classes
8
9```python
10from ete4 import (
11 EvolTree,
12 GTDBTaxa,
13 NCBITaxa,
14 PhyloTree,
15 SeqGroup,
16 Tree,
18```
20Frequently used classes:
22- `Tree`: general rooted or unrooted tree data structure
23- `PhyloTree`: `Tree` subclass with species, alignment, event, and
24 reconciliation methods
25- `NCBITaxa`: local NCBI taxonomy database interface
26- `GTDBTaxa`: local Genome Taxonomy Database interface
27- `SeqGroup`: sequence/alignment container
28- `EvolTree`: evolutionary-model support, including external PAML workflows
30`ClusterTree` is not exported by ETE 4.4.0. Do not port ETE 3 clustering
31examples by changing only the import. Use a normal `Tree` for dendrogram
32topology and calculate matrix/cluster statistics with a maintained numerical
33library.

Imports and Public Classes

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.

Raw lines 7 to 34
35## Constructing and Parsing Trees

Constructing and Parsing Trees

Raw lines 35 to 36
37### Empty node or node properties
39```python
40from ete4 import Tree
42empty = Tree()
43root = Tree({"name": "root", "dist": 0.0, "study": "trial-7"})
44```
46ETE 4 nodes do not automatically have a non-null name, distance, or support.
47`node.name`, `node.dist`, and `node.support` can therefore be `None`.

Empty node or node properties

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.

Raw lines 37 to 48
49### Newick string
51```python
52tree = Tree("((A:1,B:2)CladeAB:0.5,C:3)Root;", parser=1)
53```
55Use Python strings for Newick text. ETE 4.4.0 retains a path-like string
56heuristic internally, but the documented and reproducible file form is an open
57file object; do not depend on the heuristic.

Newick string

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.

Raw lines 49 to 58
59### Newick file
61```python
62from pathlib import Path
64with Path("tree.nw").open(encoding="utf-8") as handle:
65 tree = Tree(handle, parser=1)
66```
68Pass an open text file object. This distinction removes the ETE 3 ambiguity
69between file names and Newick strings.

Newick file

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.

Raw lines 59 to 70
71### Common Newick parsers
73- `parser="support"` or `parser=0`: flexible branch lengths; internal field is
74 support
75- `parser="name"` or `parser=1`: flexible branch lengths; internal field is a
76 name
77- `parser=8`: all node names, no branch lengths required
78- `parser=9`: leaf names only
79- `parser=100`: topology only
81Other numeric parsers encode stricter combinations of names and branch
82lengths. Prefer the parser that matches the actual producer's Newick schema.
83Inspect a round trip before processing a large collection:
85```python
86tree = Tree("((A:1,B:1)95:0.2,C:1);", parser="support")
87assert tree.write(parser="support", props=[]) == "((A:1,B:1)95:0.2,C:1);"
88```

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:

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);"
Raw lines 71 to 89
90### Nexus files
92ETE's Nexus parser returns a dictionary of tree names to `Tree` objects and
93applies any Nexus translation table:
95```python
96from pathlib import Path
98from ete4.parser import nexus
100with Path("trees.nex").open(encoding="utf-8") as handle:
101 trees = nexus.load(handle, parser=9)
103for tree_name, tree in trees.items():
104 print(tree_name, list(tree.leaf_names()))
105```
107Use the parser expected by the Newick strings inside the Nexus `TREES` block.
108The current Nexus module is a reader; serialize processed trees explicitly as
109Newick unless another library is responsible for writing Nexus.

Nexus files

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

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.

Raw lines 90 to 110
111## Node Structure and Properties
113Core structural attributes:
115```python
116node.up # parent or None
117node.children # child list
118node.root # absolute root
119node.is_leaf # bool property
120node.is_root # bool property
121node.level # edges between node and root
122node.id # positional tuple, such as (0, 1, 0)
123```
125Biological or user metadata belongs in `props`:
127```python
128node.add_prop("habitat", "marine")
129node.add_props(sample_count=12, qc_pass=True)
131habitat = node.get_prop("habitat", "unknown")
132same_value = node.props.get("habitat", "unknown")
134node.del_prop("qc_pass")
135```
137`name`, `dist`, and `support` are special property-backed conveniences:
139```python
140assert node.name == node.props.get("name")
141```
143Use `add_prop()` rather than assigning arbitrary Python attributes if the value
144must participate in search, serialization, or visualization.

Node Structure and Properties

Core structural attributes:

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:

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:

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.

Raw lines 111 to 145
146## Traversal and Navigation
148ETE 4's collection-like methods return iterators.
150```python
151# Includes the current node.
152for node in tree.traverse("preorder"):
153 ...
155# Excludes the current node.
156for node in tree.descendants("postorder"):
157 ...
159for leaf in tree.leaves():
160 ...
162leaves = list(tree.leaves())
163names = list(tree.leaf_names())
164ancestors = list(node.ancestors())
165```
167Valid traversal strategies are `"levelorder"` (default), `"preorder"`, and
168`"postorder"`.

Traversal and Navigation

ETE 4's collection-like methods return iterators.

# 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".

Raw lines 146 to 169
170### Dynamic leaf criteria
172```python
173def stop_at_named_clade(node):
174 return node.name in {"Mammalia", "Aves"}
176for visible_node in tree.traverse(is_leaf_fn=stop_at_named_clade):
177 ...
178```
180This presents selected internal nodes as terminal during that operation
181without changing the topology.

Dynamic leaf criteria

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.

Raw lines 170 to 182
183## Search and Lookup
185```python
186first_a = tree["A"]
187by_position = tree[0, 1, 0]
189all_a = list(tree.search_nodes(name="A"))
190long_branches = [n for n in tree.traverse() if (n.dist or 0) > 1]
191leaf_a = next(tree.search_leaves_by_name("A"))
193mrca = tree.common_ancestor("A", "B")
194mrca_from_list = tree.common_ancestor(["A", "B"])
195```
197Name lookup returns the first match. Validate uniqueness when names are
198identifiers:
200```python
201from collections import Counter
203leaf_names = list(tree.leaf_names())
204duplicates = sorted(name for name, count in Counter(leaf_names).items() if count > 1)
205if duplicates:
206 raise ValueError(f"Duplicate leaf names: {duplicates}")
207```

Search and Lookup

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:

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}")
Raw lines 183 to 208
209## Topology Modification

Topology Modification

Raw lines 209 to 210
211### Add and remove nodes
213```python
214child = tree.add_child(name="A", dist=0.5)
215sister = child.add_sister(name="B", dist=0.7)
217subtree = child.detach() # remove child plus all descendants
218tree.add_child(subtree) # attach it elsewhere
220internal.delete(preserve_branch_length=True) # remove node, retain children
221```
223`detach()` cuts a complete subtree. `delete()` eliminates only the selected
224node and reconnects its children.

Add and remove nodes

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.

Raw lines 211 to 225
226### Prune
228```python
229tree.prune(
230 ["A", "B", "C"],
231 preserve_branch_length=True,
233```
235`preserve_branch_length=True` transfers deleted branch lengths so distances
236among retained nodes remain unchanged.

Prune

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

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

Raw lines 226 to 237
238### Root and unroot
240```python
241tree.set_outgroup(tree["Outgroup"])
242tree.set_midpoint_outgroup()
243tree.unroot()
244```
246For inspection without immediately modifying the tree:
248```python
249midpoint_node = tree.get_midpoint_outgroup()
250print(midpoint_node.id)
251tree.set_midpoint_outgroup() # exact midpoint placement
252```
254`set_outgroup(get_midpoint_outgroup())` cuts the selected edge at its default
255position, which need not be the diameter midpoint. Prefer the direct method.

Root and unroot

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

For inspection without immediately modifying the tree:

midpoint_node = tree.get_midpoint_outgroup()
print(midpoint_node.id)
tree.set_midpoint_outgroup()  # exact midpoint placement

set_outgroup(get_midpoint_outgroup()) cuts the selected edge at its default position, which need not be the diameter midpoint. Prefer the direct method.

Raw lines 238 to 256
257### Other operations
259```python
260tree.resolve_polytomy(descendants=True)
261tree.ladderize()
262tree.to_ultrametric(topological=False)
263tree.standardize(delete_orphan=True, preserve_branch_length=True)
264```
266Polytomy resolution is arbitrary. Never report the generated branching order
267as biological evidence.

Other operations

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.

Raw lines 257 to 268
269## Distances and Cached Content
271```python
272a = tree["A"]
273b = tree["B"]
275branch_distance = tree.get_distance(a, b)
276edge_distance = tree.get_distance(a, b, topological=True)
278farthest_leaf, distance = tree.get_farthest_leaf()
279closest_leaf, distance = tree.get_closest_leaf()
280```
282ETE 4.4 adds `distance_matrix()` and supersedes the older
283`cophenetic_matrix()` for new code:
285```python
286names = list(tree.leaf_names()) # same order as the matrix rows/columns
287matrix = tree.distance_matrix(squared=True)
288distance_a_b = matrix[names.index("A")][names.index("B")]
289```
291`distance_matrix()` returns a list of lists, without labels. `squared=True`
292means a square symmetric matrix, not squared distances. The default returns
293only the ragged upper triangle. Preserve leaf order alongside the matrix.
295For repeated descendant lookups:
297```python
298node_to_leaf_names = tree.get_cached_content(prop="name")
299for node in tree.traverse():
300 names_below = node_to_leaf_names[node]
301```

Distances and Cached Content

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:

names = list(tree.leaf_names())  # same order as the matrix rows/columns
matrix = tree.distance_matrix(squared=True)
distance_a_b = matrix[names.index("A")][names.index("B")]

distance_matrix() returns a list of lists, without labels. squared=True means a square symmetric matrix, not squared distances. The default returns only the ragged upper triangle. Preserve leaf order alongside the matrix.

For repeated descendant lookups:

node_to_leaf_names = tree.get_cached_content(prop="name")
for node in tree.traverse():
    names_below = node_to_leaf_names[node]
Raw lines 269 to 302
303## Monophyly
305```python
306is_mono, clade_type, extra = tree.check_monophyly(
307 values={"A", "B", "C"},
308 prop="name",
309 unrooted=False,
312for clade in tree.get_monophyletic(values={"case"}, prop="group"):
313 print(clade.id)
314```
316Interpret `"monophyletic"`, `"paraphyletic"`, and `"polyphyletic"` in the
317context of the tree's rooting.

Monophyly

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.

Raw lines 303 to 318
319## Newick and Text Output

Newick and Text Output

Raw lines 319 to 320
321### Newick
323```python
324# No extended NHX properties.
325plain_newick = tree.write(parser=1, props=[])
327# Selected properties in NHX fields.
328annotated_newick = tree.write(parser=1, props=["species", "group"])
330# All available properties. Use only when that disclosure is intentional.
331all_properties = tree.write(parser=1, props=None)
333tree.write(
334 outfile="tree.nw",
335 parser=1,
336 props=["group"],
337 format_root_node=True,
339```
341Important `props` behavior:
343- `props=[]` or the default empty tuple: write no extended properties
344- `props=["x", "y"]`: write selected properties
345- `props=None`: write all available properties
347Use an explicit list in shared or external output so internal metadata is not
348exported accidentally.
350Also use keyword arguments. The first positional argument of `write()` is
351`outfile` in ETE 4, whereas older ETE 3 code may have treated its first
352positional argument as a feature selection.

Newick

# 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.

Raw lines 321 to 353
354### Terminal representation
356```python
357print(tree)
358print(tree.to_str(props=["name", "dist", "support"], compact=True))
359```
361`to_str()` replaces ETE 3's `get_ascii()`.

Terminal representation

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

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

Raw lines 354 to 362
363## Copying
365```python
366exact = tree.copy() # cpickle; recommended full copy
367topology = tree.copy("newick") # fast; standard Newick fields
368text_props = tree.copy("newick-extended")
369deep = tree.copy("deepcopy") # slowest; complex Python objects
370```
372The extended-Newick path converts custom values to text and is not a
373type-preserving clone.

Copying

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.

Raw lines 363 to 374
375## Tree Comparison

Tree Comparison

Raw lines 375 to 376
377### Raw Robinson-Foulds result
379```python
381 rf,
382 max_rf,
383 common_leaves,
384 edges_self,
385 edges_other,
386 discarded_self,
387 discarded_other,
388) = tree.robinson_foulds(
389 other,
390 prop_t1="name",
391 prop_t2="name",
392 unrooted_trees=False,
394```
396ETE 4 returns seven values. Older examples that unpack five values are wrong.

Raw Robinson-Foulds result

(
    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.

Raw lines 377 to 397
398### Summary dictionary
400```python
401result = tree.compare(
402 other,
403 ref_tree_attr="name",
404 source_tree_attr="name",
405 unrooted=False,
408print(result["rf"], result["max_rf"], result["norm_rf"])
409```
411Comparison is only meaningful if the selected property has the intended
412identity semantics. Report filtering by common leaves, support thresholds,
413rooting, polytomy expansion, and duplication handling.
415For unique tip labels, prefer `Tree.robinson_foulds()` or the normal
416`Tree.compare()` path. Do not rely on `Tree.compare(has_duplications=True)` in
417ETE 4.4.0: upstream source marks that TreeKO branch as likely broken. The
418packaged `ete4 compare` CLI also still passes the removed `format=` constructor
419argument and fails at runtime; use the Python methods or the bundled
420`scripts/tree_operations.py compare` command.

Summary dictionary

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.

Raw lines 398 to 421
Runs code scripts/tree_operations.py :420
422## PhyloTree
424Constructor:
426```python
427from ete4 import PhyloTree
429tree = PhyloTree(
430 "((Hsa|g1,Ptr|g1),Mmu|g1);",
431 alignment=None,
432 alg_format="fasta",
433 sp_naming_function=lambda name: name.split("|", 1)[0],
434 parser=None,
436```
438Always provide `sp_naming_function` when species-aware methods are needed. The
439ETE 4.4.0 source default is `None`; older documentation that implies an
440automatic first-three-character rule is not reliable.
442Alignment:
444```python
445tree.link_to_alignment("alignment.fasta", alg_format="fasta")
446for leaf in tree.leaves():
447 print(leaf.name, leaf.props.get("sequence"))
448```
450Alignment sequences are stored in `props["sequence"]`, not a `leaf.sequence`
451attribute. Missing alignment names leave that property unset; verify coverage
452and equal sequence lengths before downstream alignment analysis.
454Species handling:
456```python
457tree.set_species_naming_function(lambda name: name.split("|", 1)[0])
458species = {leaf.species for leaf in tree.leaves()}
459```
461Evolutionary events:
463```python
464events = tree.get_descendant_evol_events(sos_thr=0.0)
465for event in events:
466 print(event.etype, event.in_seqs, event.out_seqs)
467```
469Species-overlap event detection expects a rooted, fully bifurcating gene tree.
470It annotates `node.props["evoltype"]`; it does not restore ETE 3's `dup`
471feature.
473Reconciliation:
475```python
476reconciled_tree, events = gene_tree.reconcile(species_tree)
477```
479Gene-family operations:
481```python
482tree_count, duplication_count, speciation_trees = tree.get_speciation_trees(
483 autodetect_duplications=True,
484 newick_only=False,
485 prop="species",
487for speciation_tree in speciation_trees:
488 process(speciation_tree)
490subfamilies = tree.split_by_dups(autodetect_duplications=True)
491collapsed_copy = tree.collapse_lineage_specific_expansions(return_copy=True)
492```
494Species overlap and reconciliation answer different questions. Species overlap
495uses label overlap between child clades; reconciliation requires a species
496tree and can infer losses.

PhyloTree

Constructor:

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:

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

Alignment sequences are stored in props["sequence"], not a leaf.sequence attribute. Missing alignment names leave that property unset; verify coverage and equal sequence lengths before downstream alignment analysis.

Species handling:

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

Evolutionary events:

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:

reconciled_tree, events = gene_tree.reconcile(species_tree)

Gene-family operations:

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.

Raw lines 422 to 497
498## TreePattern
500ETE 4 can search for repeated subtree shapes:
502```python
503from ete4 import Tree
504from ete4.treematcher import TreePattern
506tree = Tree("((K,((A,B),C),D),(E,F));")
507three_way_split = TreePattern("(,,)", safer=True)
509matches = list(three_way_split.search(tree))
510print([node.id for node in matches])
511```
513Child order is not significant during topology matching. `TreePattern` also
514supports Python conditions embedded in pattern nodes, but those conditions
515must be static, trusted code. Never construct an expression-bearing pattern
516from user input, file content, model output, or other untrusted text; use
517topology-only patterns or ordinary Python traversal predicates instead.

TreePattern

ETE 4 can search for repeated subtree shapes:

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.

Raw lines 498 to 518
519## Visualization Entry Points
521```python
522tree.explore() # SmartView browser
523tree.render_sm("tree.png", w=1200, h=800) # SmartView PNG screenshot
524tree.render("tree.svg") # Qt treeview extra
525```
527For custom imports and renderer requirements, see `visualization.md`.

Visualization Entry Points

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.

Raw lines 519 to 528
529## Error Handling
531Catch narrow exceptions at an application boundary and retain context:
533```python
534from pathlib import Path
536from ete4 import Tree
538path = Path("tree.nw")
539try:
540 with path.open(encoding="utf-8") as handle:
541 tree = Tree(handle, parser=1)
542except (OSError, ValueError) as exc:
543 raise RuntimeError(f"Could not parse {path} with parser 1") from exc
544```
546Do not use a bare `except:` around parsing or topology edits; it hides schema
547mistakes and missing-node errors.

Error Handling

Catch narrow exceptions at an application boundary and retain context:

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.

Raw lines 529 to 548
549## Upstream References
551- Tree tutorial: https://etetoolkit.github.io/ete/tutorial/tutorial_trees.html
552- Tree API: https://etetoolkit.github.io/ete/reference/reference_tree.html
553- PhyloTree tutorial: https://etetoolkit.github.io/ete/tutorial/tutorial_phylogeny.html
554- PhyloTree API: https://etetoolkit.github.io/ete/reference/reference_phylo.html
555- Parsers: https://etetoolkit.github.io/ete/reference/reference_parsers.html
556- Tree matcher tutorial:
557 https://etetoolkit.github.io/ete/tutorial/tutorial_treematcher.html
558- Tree matcher API:
559 https://etetoolkit.github.io/ete/reference/reference_treematcher.html
560- Migration: https://etetoolkit.github.io/ete/3to4.html

Upstream References

  • Tree tutorial: https://etetoolkit.github.io/ete/tutorial/tutorial_trees.html
  • Tree API: https://etetoolkit.github.io/ete/reference/reference_tree.html
  • PhyloTree tutorial: https://etetoolkit.github.io/ete/tutorial/tutorial_phylogeny.html
  • PhyloTree API: https://etetoolkit.github.io/ete/reference/reference_phylo.html
  • Parsers: https://etetoolkit.github.io/ete/reference/reference_parsers.html
  • Tree matcher tutorial: https://etetoolkit.github.io/ete/tutorial/tutorial_treematcher.html
  • Tree matcher API: https://etetoolkit.github.io/ete/reference/reference_treematcher.html
  • Migration: https://etetoolkit.github.io/ete/3to4.html
Raw lines 549 to 560