Claude Skill

alterlab-etetoolkit

Manipulate, annotate, and render phylogenetic trees programmatically with the ETE Toolkit (ete3) — parse and edit Newick/NHX, detect duplication/speciation events, infer orthology and paralogy, query NCBI taxonomy, and export PDF/SVG figures. Use when traversing or reformatting t

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download alterlab-ieu-alterlab-academic-skills-skills_bioinformatics_alterlab-etetoolkit-e4836c0.zip · 28 KB
Part of alterlab-ieu/alterlab-academic-skills — 94 skills

Install

skills CLI npx skills add https://github.com/AlterLab-IEU/AlterLab-Academic-Skills/tree/main/skills/bioinformatics/alterlab-etetoolkit
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install alterlab-ieu-alterlab-academic-skills@llmmart
Git git clone https://github.com/AlterLab-IEU/AlterLab-Academic-Skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole alterlab-ieu/alterlab-academic-skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

ETE Toolkit Skill

Overview

ETE (Environment for Tree Exploration) is a toolkit for phylogenetic and hierarchical tree analysis. Manipulate trees, analyze evolutionary events, visualize results, and integrate with biological databases for phylogenomic research and clustering analysis.

ete3 or ete4?

The examples in this skill and its references are written for ete3 3.1.3, which is still what most published pipelines import — but ete3 has had no release since May 2023, and ete4 (4.4.0, Sept 2025) is the maintained line. Port deliberately: the ete4 API is not drop-in.

ete3 ete4
from ete3 import Tree from ete4 import Tree
Tree("t.nw", format=1) Tree(open("t.nw"), parser=1) — the keyword is parser
tree.iter_leaves() / get_leaves() tree.leaves()
custom features via node.add_feature() node.props[...] dictionary
Qt-based show() / render() (needs PyQt5) browser-based explore() with adaptive zoom, plus GTDB taxonomy support

Check which one is installed before writing code (python -c "import ete4"), and say which you targeted in the methods.

When to Use

  • Parsing, traversing, or reformatting tree files (Newick / NHX / PhyloXML / NeXML)
  • Pruning, rooting, collapsing, or resolving polytomies in a tree
  • Detecting duplication/speciation events and inferring orthologs/paralogs from gene trees
  • Querying NCBI Taxonomy (taxid/name translation, lineages, taxonomy trees)
  • Producing publication-quality PDF/SVG/PNG tree figures
  • Comparing trees (Robinson-Foulds) or analyzing clustering dendrograms

Core Capabilities

ETE exposes six capability areas. Each has detailed, copy-ready code in the references (see Index below).

  1. Tree manipulation — I/O across formats, traversal (pre/post/levelorder), topology edits (prune, root, collapse), distances, RF tree comparison.
  2. Phylogenetic analysis — alignment linkage, species naming, duplication/speciation detection (Species Overlap or reconciliation), orthology/paralogy.
  3. NCBI Taxonomy — local cached DB, taxid↔name translation, lineage retrieval, taxonomy-tree building, tree annotation.
  4. Visualization — PNG/PDF/SVG export, rectangular/circular layouts, NodeStyle, Face objects, layout functions, interactive GUI.
  5. Clustering analysisClusterTree, data-matrix linking, silhouette/Dunn metrics, heatmap views.
  6. Tree comparison — Robinson-Foulds (raw + normalized), partition/bipartition analysis, batch pairwise distance matrices.

Core Workflow

The canonical minimal pattern — load, edit, save:

from ete3 import Tree

# Load tree from file (format 1 = with internal node names)
tree = Tree("tree.nw", format=1)

# Prune to taxa of interest, preserving branch lengths
tree.prune(["species1", "species2", "species3"], preserve_branch_length=True)

# Midpoint root
tree.set_outgroup(tree.get_midpoint_outgroup())

# Save
tree.write(outfile="rooted_tree.nw")

For class selection: use Tree/TreeNode for generic topology work, PhyloTree for gene trees and evolutionary analysis, ClusterTree for dendrograms with data matrices, and NCBITaxa for taxonomy queries.

Does NOT Trigger

Scenario Use Instead
Build a tree from unaligned sequences (align + model selection + ML inference) alterlab-phylogenetics
Distance matrices, diversity metrics, or ordination from alignments alterlab-scikit-bio
Tree I/O inside a broader Biopython pipeline (Bio.Phylo) alterlab-biopython
Generic graph algorithms on a non-tree network alterlab-networkx
Taxonomic classification of amplicon reads alterlab-qiime2-amplicon

Command-Line Scripts

  • scripts/tree_operations.py — stats, format conversion, rerooting, pruning, ASCII view. Example: python scripts/tree_operations.py reroot tree.nw rooted.nw --midpoint
  • scripts/quick_visualize.py — rapid PDF/PNG rendering with circular layout, support coloring, and DPI control. Example: python scripts/quick_visualize.py tree.nw out.pdf --mode c --color-by-support

Reference Index

Load the relevant file when detailed information is needed:

  • references/api_reference.md — Complete API for all ETE classes/methods (Tree, PhyloTree, ClusterTree, NCBITaxa): parameters, return types, code examples.
  • references/workflows.md — Per-task workflow patterns (tree operations, phylogenetic analysis, comparison, taxonomy integration, clustering).
  • references/visualization.md — Full visualization guide: TreeStyle, NodeStyle, Faces, layout functions, advanced rendering.
  • references/use_cases.md — End-to-end worked use cases (phylogenomic pipeline, batch preprocessing, publication figures, automated multi-tree analysis).
  • references/setup_and_troubleshooting.md — Installation, NCBI Taxonomy first-run setup, and troubleshooting (imports, Qt rendering, memory, DB corruption).
  • references/newick_and_best_practices.md — Newick/NHX format specifications (0-100) and best-practice checklist.

Part of the AlterLab Academic Skills suite.

Files (alterlab-academic-skills)
  • evals
    • evals.json 4.7 KB
      {
        "skill": "alterlab-etetoolkit",
        "evals": [
          {
            "id": "detect-duplication-speciation-events",
            "prompt": "I have a gene tree in Newick (gene_tree.nw) plus its alignment, and gene names are formatted like 'species_geneid'. I want to detect which internal nodes are duplications versus speciations and then pull out the orthologs of species1_gene1.",
            "expected_output": "Invokes alterlab-etetoolkit: loads a PhyloTree with the alignment, sets a species naming function splitting on '_', calls get_descendant_evol_events(), inspects node.evoltype 'D'/'S' (Species Overlap), and extracts orthologs/paralogs from the events for the query gene.",
            "assertions": [
              { "type": "should_trigger", "value": true },
              { "type": "output_contains", "value": "PhyloTree" },
              { "type": "behavior", "value": "Uses get_descendant_evol_events and evoltype D/S to distinguish duplication from speciation and derive orthologs." }
            ]
          },
          {
            "id": "robinson-foulds-comparison",
            "prompt": "I built two trees of the same taxa with different methods and I want to quantify how different their topologies are, including a normalized score and which bipartitions are unique to each.",
            "expected_output": "Invokes alterlab-etetoolkit: loads both trees and calls tree1.robinson_foulds(tree2), reports rf/max_rf and the normalized RF, and uses the returned parts_t1/parts_t2 sets to find partitions unique to each tree.",
            "assertions": [
              { "type": "should_trigger", "value": true },
              { "type": "output_contains", "value": "robinson_foulds" },
              { "type": "behavior", "value": "Computes Robinson-Foulds distance, reports a normalized RF, and diffs the bipartition sets." }
            ]
          },
          {
            "id": "prune-reroot-publication-figure",
            "prompt": "Take my tree.nw, midpoint-root it, prune it down to about 12 focal species keeping the branch lengths, color the leaves by clade, show bootstrap support, and render a vector PDF I can edit for a figure.",
            "expected_output": "Invokes alterlab-etetoolkit: uses get_midpoint_outgroup/set_outgroup to root, prune(taxa, preserve_branch_length=True), builds a TreeStyle with show_branch_support and a layout/NodeStyle for clade colors, then tree.render('figure.pdf') for an editable vector figure.",
            "assertions": [
              { "type": "should_trigger", "value": true },
              { "type": "output_contains", "value": "render" },
              { "type": "behavior", "value": "Prunes with preserve_branch_length=True, midpoint-roots, and renders a vector PDF/SVG via TreeStyle for publication." }
            ]
          },
          {
            "id": "ncbi-taxonomy-topology",
            "prompt": "Given a list of species names like Homo sapiens, Pan troglodytes, and Mus musculus, build the minimal NCBI taxonomy tree connecting them and annotate each node with its rank and taxid.",
            "expected_output": "Invokes alterlab-etetoolkit: uses NCBITaxa, get_name_translator to map names to taxids, get_topology(taxids) to build the connecting tree, and reads node.sci_name/rank/taxid for annotation (noting the one-time ~300MB taxa.sqlite download).",
            "assertions": [
              { "type": "should_trigger", "value": true },
              { "type": "output_contains", "value": "NCBITaxa" },
              { "type": "behavior", "value": "Uses NCBITaxa.get_topology with translated taxids and annotates nodes with rank/taxid." }
            ]
          },
          {
            "id": "near-miss-phylogenetics",
            "prompt": "I have a FASTA of 80 unaligned 16S rRNA sequences. Align them, pick the best substitution model, build a maximum-likelihood tree with bootstrap support, and give me the Newick file.",
            "expected_output": "Should NOT trigger alterlab-etetoolkit. Building a tree from raw sequences (MAFFT alignment, IQ-TREE 2 model selection and ML inference with ultrafast bootstraps) is alterlab-phylogenetics territory. ete3/etetoolkit operates on existing trees and defers de-novo tree reconstruction to the phylogenetics pipeline.",
            "assertions": [
              { "type": "should_not_trigger", "value": true },
              { "type": "output_contains", "value": "phylogenetics" }
            ]
          },
          {
            "id": "near-miss-scikit-bio",
            "prompt": "I have a multiple sequence alignment and I want to compute a pairwise distance matrix between the sequences using a substitution model, as a plain numerical matrix.",
            "expected_output": "Should NOT trigger alterlab-etetoolkit. Computing a sequence distance matrix from an alignment is core sequence/diversity work for alterlab-scikit-bio, not tree manipulation. etetoolkit works on tree objects, not raw alignment distance computation.",
            "assertions": [
              { "type": "should_not_trigger", "value": true },
              { "type": "output_contains", "value": "scikit-bio" }
            ]
          }
        ]
      }
      
  • references
    • api_reference.md 12.3 KB
      # ETE Toolkit API Reference
      
      ## Overview
      
      ETE (Environment for Tree Exploration) is a Python toolkit for phylogenetic tree manipulation, analysis, and visualization. This reference covers the main classes and methods.
      
      ## Core Classes
      
      ### TreeNode (alias: Tree)
      
      The fundamental class representing tree structures with hierarchical node organization.
      
      **Constructor:**
      ```python
      from ete3 import Tree
      t = Tree(newick=None, format=0, dist=None, support=None, name=None)
      ```
      
      **Parameters:**
      - `newick`: Newick string or file path
      - `format`: Newick format (0-100). Common formats:
        - `0`: Flexible format with branch lengths and names
        - `1`: With internal node names
        - `2`: With bootstrap/support values
        - `5`: Internal node names and branch lengths
        - `8`: All features (names, distances, support)
        - `9`: Leaf names only
        - `100`: Topology only
      - `dist`: Branch length to parent (default: 1.0)
      - `support`: Bootstrap/confidence value (default: 1.0)
      - `name`: Node identifier
      
      ### PhyloTree
      
      Specialized class for phylogenetic analysis, extending TreeNode.
      
      **Constructor:**
      ```python
      from ete3 import PhyloTree
      t = PhyloTree(newick=None, alignment=None, alg_format='fasta',
                    sp_naming_function=None, format=0)
      ```
      
      **Additional Parameters:**
      - `alignment`: Path to alignment file or alignment string
      - `alg_format`: 'fasta' or 'phylip'
      - `sp_naming_function`: Custom function to extract species from node names
      
      ### ClusterTree
      
      Class for hierarchical clustering analysis.
      
      **Constructor:**
      ```python
      from ete3 import ClusterTree
      t = ClusterTree(newick, text_array=None)
      ```
      
      **Parameters:**
      - `text_array`: Tab-delimited matrix with column headers and row names
      
      ### NCBITaxa
      
      Class for NCBI taxonomy database operations.
      
      **Constructor:**
      ```python
      from ete3 import NCBITaxa
      ncbi = NCBITaxa(dbfile=None)
      ```
      
      First instantiation downloads ~300MB NCBI taxonomy database to `~/.etetoolkit/taxa.sqlite`.
      
      ## Node Properties
      
      ### Basic Attributes
      
      | Property | Type | Description | Default |
      |----------|------|-------------|---------|
      | `name` | str | Node identifier | "NoName" |
      | `dist` | float | Branch length to parent | 1.0 |
      | `support` | float | Bootstrap/confidence value | 1.0 |
      | `up` | TreeNode | Parent node reference | None |
      | `children` | list | Child nodes | [] |
      
      ### Custom Features
      
      Add any custom data to nodes:
      ```python
      node.add_feature("custom_name", value)
      node.add_features(feature1=value1, feature2=value2)
      ```
      
      Access features:
      ```python
      value = node.custom_name
      # or
      value = getattr(node, "custom_name", default_value)
      ```
      
      ## Navigation & Traversal
      
      ### Basic Navigation
      
      ```python
      # Check node type
      node.is_leaf()          # Returns True if terminal node
      node.is_root()          # Returns True if root node
      len(node)               # Number of leaves under node
      
      # Get relatives
      parent = node.up
      children = node.children
      root = node.get_tree_root()
      ```
      
      ### Traversal Strategies
      
      ```python
      # Three traversal strategies
      for node in tree.traverse("preorder"):    # Root → Left → Right
          print(node.name)
      
      for node in tree.traverse("postorder"):   # Left → Right → Root
          print(node.name)
      
      for node in tree.traverse("levelorder"):  # Level by level
          print(node.name)
      
      # Exclude root
      for node in tree.iter_descendants("postorder"):
          print(node.name)
      ```
      
      ### Getting Nodes
      
      ```python
      # Get all leaves
      leaves = tree.get_leaves()
      for leaf in tree:  # Shortcut iteration
          print(leaf.name)
      
      # Get all descendants
      descendants = tree.get_descendants()
      
      # Get ancestors
      ancestors = node.get_ancestors()
      
      # Get specific nodes by attribute
      nodes = tree.search_nodes(name="NodeA")
      node = tree & "NodeA"  # Shortcut syntax
      
      # Get leaves by name
      leaves = tree.get_leaves_by_name("LeafA")
      
      # Get common ancestor
      ancestor = tree.get_common_ancestor("LeafA", "LeafB", "LeafC")
      
      # Custom filtering
      filtered = [n for n in tree.traverse() if n.dist > 0.5 and n.is_leaf()]
      ```
      
      ### Iterator Methods (Memory Efficient)
      
      ```python
      # For large trees, use iterators
      for match in tree.iter_search_nodes(name="X"):
          if some_condition:
              break  # Stop early
      
      for leaf in tree.iter_leaves():
          process(leaf)
      
      for descendant in node.iter_descendants():
          process(descendant)
      ```
      
      ## Tree Construction & Modification
      
      ### Creating Trees from Scratch
      
      ```python
      # Empty tree
      t = Tree()
      
      # Add children
      child1 = t.add_child(name="A", dist=1.0)
      child2 = t.add_child(name="B", dist=2.0)
      
      # Add siblings
      sister = child1.add_sister(name="C", dist=1.5)
      
      # Populate with random topology
      t.populate(10)  # Creates 10 random leaves
      t.populate(5, names_library=["A", "B", "C", "D", "E"])
      ```
      
      ### Removing & Deleting Nodes
      
      ```python
      # Detach: removes node and its entire subtree from the parent
      node.detach()
      # or
      parent.remove_child(node)  # same effect: child + subtree gone
      
      # Delete: removes only this node, reconnecting its children to its parent
      node.delete()
      # delete(preserve_branch_length=True) adds the deleted node's dist to its children
      node.delete(preserve_branch_length=True)
      ```
      
      ### Pruning
      
      Keep only specified leaves:
      ```python
      # Keep only these leaves, remove all others
      tree.prune(["A", "B", "C"])
      
      # Preserve original branch lengths
      tree.prune(["A", "B", "C"], preserve_branch_length=True)
      ```
      
      ### Tree Concatenation
      
      ```python
      # Attach one tree as child of another
      t1 = Tree("(A,(B,C));")
      t2 = Tree("((D,E),(F,G));")
      A = t1 & "A"
      A.add_child(t2)
      ```
      
      ### Tree Copying
      
      ```python
      # Four copy methods
      copy1 = tree.copy()  # Default: cpickle (preserves types)
      copy2 = tree.copy("newick")  # Fastest: basic topology
      copy3 = tree.copy("newick-extended")  # Includes custom features as text
      copy4 = tree.copy("deepcopy")  # Slowest: handles complex objects
      ```
      
      ## Tree Operations
      
      ### Rooting
      
      ```python
      # Set outgroup (reroot tree)
      outgroup_node = tree & "OutgroupLeaf"
      tree.set_outgroup(outgroup_node)
      
      # Midpoint rooting
      midpoint = tree.get_midpoint_outgroup()
      tree.set_outgroup(midpoint)
      
      # Unroot tree
      tree.unroot()
      ```
      
      ### Resolving Polytomies
      
      ```python
      # Resolve multifurcations to bifurcations
      tree.resolve_polytomy(recursive=False)  # Single node only
      tree.resolve_polytomy(recursive=True)   # Entire tree
      ```
      
      ### Ladderize
      
      ```python
      # Sort branches by size
      tree.ladderize()
      tree.ladderize(direction=1)  # Ascending order
      ```
      
      ### Convert to Ultrametric
      
      ```python
      # Make all leaves equidistant from root
      tree.convert_to_ultrametric()
      tree.convert_to_ultrametric(tree_length=100)  # Specific total length
      ```
      
      ## Distance & Comparison
      
      ### Distance Calculations
      
      ```python
      # Branch length distance between nodes
      dist = tree.get_distance("A", "B")
      dist = nodeA.get_distance(nodeB)
      
      # Topology-only distance (count nodes)
      dist = tree.get_distance("A", "B", topology_only=True)
      
      # Farthest node
      farthest, distance = node.get_farthest_node()
      farthest_leaf, distance = node.get_farthest_leaf()
      ```
      
      ### Monophyly Testing
      
      ```python
      # Check if values form monophyletic group
      is_mono, clade_type, base_node = tree.check_monophyly(
          values=["A", "B", "C"],
          target_attr="name"
      )
      # Returns: (bool, "monophyletic"|"paraphyletic"|"polyphyletic", node)
      
      # Get all monophyletic clades
      monophyletic_nodes = tree.get_monophyletic(
          values=["A", "B", "C"],
          target_attr="name"
      )
      ```
      
      ### Tree Comparison
      
      ```python
      # Robinson-Foulds distance.
      # robinson_foulds() returns MORE than 5 values in current ete3
      # (rf, max_rf, common_leaves, parts_t1, parts_t2, discarded_t1, discarded_t2),
      # so unpack defensively rather than to a fixed 5-tuple.
      result = t1.robinson_foulds(t2)
      rf, max_rf, common_leaves, parts_t1, parts_t2 = result[:5]
      print(f"RF distance: {rf}/{max_rf}")
      
      # Normalized RF distance (compare() returns a dict; key is "norm_rf", 0.0–1.0)
      norm_rf = t1.compare(t2)["norm_rf"]
      ```
      
      ## Input/Output
      
      ### Reading Trees
      
      ```python
      # From string
      t = Tree("(A:1,(B:1,(C:1,D:1):0.5):0.5);")
      
      # From file
      t = Tree("tree.nw")
      
      # With format
      t = Tree("tree.nw", format=1)
      ```
      
      ### Writing Trees
      
      ```python
      # To string
      newick = tree.write()
      newick = tree.write(format=1)
      newick = tree.write(format=1, features=["support", "custom_feature"])
      
      # To file
      tree.write(outfile="output.nw")
      tree.write(format=5, outfile="output.nw", features=["name", "dist"])
      
      # Custom leaf function (for collapsing)
      def is_leaf(node):
          return len(node) <= 3  # Treat small clades as leaves
      
      newick = tree.write(is_leaf_fn=is_leaf)
      ```
      
      ### Tree Rendering
      
      ```python
      # Show interactive GUI
      tree.show()
      
      # Render to file (PNG, PDF, SVG)
      tree.render("tree.png")
      tree.render("tree.pdf", w=200, units="mm")
      tree.render("tree.svg", dpi=300)
      
      # ASCII representation
      print(tree)
      print(tree.get_ascii(show_internal=True, compact=False))
      ```
      
      ## Performance Optimization
      
      ### Caching Content
      
      For frequent access to node contents:
      ```python
      # Cache all node contents
      node2content = tree.get_cached_content()
      
      # Fast lookup
      for node in tree.traverse():
          leaves = node2content[node]
          print(f"Node has {len(leaves)} leaves")
      ```
      
      ### Precomputing Distances
      
      ```python
      # For multiple distance queries
      node2dist = {}
      for node in tree.traverse():
          node2dist[node] = node.get_distance(tree)
      ```
      
      ## PhyloTree-Specific Methods
      
      ### Sequence Alignment
      
      ```python
      # Link alignment
      tree.link_to_alignment("alignment.fasta", alg_format="fasta")
      
      # Access sequences
      for leaf in tree:
          print(f"{leaf.name}: {leaf.sequence}")
      ```
      
      ### Species Naming
      
      ```python
      # Default: first 3 letters
      # Custom function
      def get_species(node_name):
          return node_name.split("_")[0]
      
      tree.set_species_naming_function(get_species)
      
      # Manual setting
      for leaf in tree:
          leaf.species = extract_species(leaf.name)
      ```
      
      ### Evolutionary Events
      
      ```python
      # Detect duplication/speciation events
      events = tree.get_descendant_evol_events()
      
      for node in tree.traverse():
          if hasattr(node, "evoltype"):
              print(f"{node.name}: {node.evoltype}")  # "D" or "S"
      
      # With species tree
      species_tree = Tree("(human, (chimp, gorilla));")
      events = tree.get_descendant_evol_events(species_tree=species_tree)
      ```
      
      ### Gene Tree Operations
      
      ```python
      # Get species trees from duplicated gene families
      species_trees = tree.get_speciation_trees()
      
      # Split by duplication events
      subtrees = tree.split_by_dups()
      
      # Collapse lineage-specific expansions
      tree.collapse_lineage_specific_expansions()
      ```
      
      ## NCBITaxa Methods
      
      ### Database Operations
      
      ```python
      from ete3 import NCBITaxa
      ncbi = NCBITaxa()
      
      # Update database
      ncbi.update_taxonomy_database()
      ```
      
      ### Querying Taxonomy
      
      ```python
      # Get taxid from name
      taxid = ncbi.get_name_translator(["Homo sapiens"])
      # Returns: {'Homo sapiens': [9606]}
      
      # Get name from taxid
      names = ncbi.get_taxid_translator([9606, 9598])
      # Returns: {9606: 'Homo sapiens', 9598: 'Pan troglodytes'}
      
      # Get rank
      rank = ncbi.get_rank([9606])
      # Returns: {9606: 'species'}
      
      # Get lineage
      lineage = ncbi.get_lineage(9606)
      # Returns: [1, 131567, 2759, ..., 9606]
      
      # Get descendants
      descendants = ncbi.get_descendant_taxa("Primates")
      descendants = ncbi.get_descendant_taxa("Primates", collapse_subspecies=True)
      ```
      
      ### Building Taxonomy Trees
      
      ```python
      # Get minimal tree connecting taxa
      tree = ncbi.get_topology([9606, 9598, 9593])  # Human, chimp, gorilla
      
      # Annotate tree with taxonomy
      tree.annotate_ncbi_taxa()
      
      # Access taxonomy info
      for node in tree.traverse():
          print(f"{node.sci_name} ({node.taxid}) - Rank: {node.rank}")
      ```
      
      ## ClusterTree Methods
      
      ### Linking to Data
      
      ```python
      # Link matrix to tree
      tree.link_to_arraytable(matrix_string)
      
      # Access profiles
      for leaf in tree:
          print(leaf.profile)  # Numerical array
      ```
      
      ### Cluster Metrics
      
      ```python
      # Get silhouette coefficient
      silhouette = tree.get_silhouette()
      
      # Get Dunn index
      dunn = tree.get_dunn()
      
      # Inter/intra cluster distances
      inter = node.intercluster_dist
      intra = node.intracluster_dist
      
      # Standard deviation
      dev = node.deviation
      ```
      
      ### Distance Metrics
      
      Supported metrics:
      - `"euclidean"`: Euclidean distance
      - `"pearson"`: Pearson correlation
      - `"spearman"`: Spearman rank correlation
      
      ```python
      tree.dist_to(node2, metric="pearson")
      ```
      
      ## Common Error Handling
      
      ```python
      # Check if tree is empty
      if tree.children:
          print("Tree has children")
      
      # Check if node exists
      nodes = tree.search_nodes(name="X")
      if nodes:
          node = nodes[0]
      
      # Safe feature access
      value = getattr(node, "feature_name", default_value)
      
      # Check format compatibility
      try:
          tree.write(format=1)
      except:
          print("Tree lacks internal node names")
      ```
      
      > For traversal/caching/pruning/copy-method best practices, see
      > `newick_and_best_practices.md` (single source of truth — not duplicated here).
      
    • newick_and_best_practices.md 1.7 KB
      # ETE Toolkit — Newick Format Reference & Best Practices
      
      ## Newick Format Reference
      
      ETE supports multiple Newick format specifications (0-100):
      
      - **Format 0**: Flexible with branch lengths (default)
      - **Format 1**: With internal node names
      - **Format 2**: With bootstrap/support values
      - **Format 5**: Internal node names + branch lengths
      - **Format 8**: All features (names, distances, support)
      - **Format 9**: Leaf names only
      - **Format 100**: Topology only
      
      Specify format when reading/writing:
      
      ```python
      tree = Tree("tree.nw", format=1)
      tree.write(outfile="output.nw", format=5)
      ```
      
      NHX (New Hampshire eXtended) format preserves custom features:
      
      ```python
      tree.write(outfile="tree.nhx", features=["habitat", "temperature", "depth"])
      ```
      
      ## Best Practices
      
      1. **Preserve branch lengths**: Use `preserve_branch_length=True` when pruning for phylogenetic analysis
      2. **Cache content**: Use `get_cached_content()` for repeated access to node contents on large trees
      3. **Use iterators**: Employ `iter_*` methods for memory-efficient processing of large trees
      4. **Choose appropriate traversal**: Postorder for bottom-up analysis, preorder for top-down
      5. **Validate monophyly**: Always check returned clade type (monophyletic/paraphyletic/polyphyletic)
      6. **Vector formats for publication**: Use PDF or SVG for publication figures (scalable, editable)
      7. **Interactive testing**: Use `tree.show()` to test visualizations before rendering to file
      8. **PhyloTree for phylogenetics**: Use PhyloTree class for gene trees and evolutionary analysis
      9. **Copy method selection**: "newick" for speed, "cpickle" for full fidelity, "deepcopy" for complex objects
      10. **NCBI query caching**: Store NCBI taxonomy query results to avoid repeated database access
      
    • setup_and_troubleshooting.md 1.9 KB
      # ETE Toolkit — Installation, Setup & Troubleshooting
      
      ## Installation
      
      ```bash
      # Basic installation — 3.1.3 is the last ete3 release (May 2023)
      uv pip install ete3
      
      # The maintained line is ete4 (4.4.0 as of 2026-09). Its API differs:
      # `from ete4 import Tree`, `parser=` instead of `format=`, `tree.leaves()`,
      # `node.props`, and a browser-based `explore()` in place of the Qt viewer.
      uv pip install ete4
      
      # With external dependencies for rendering (optional but recommended)
      # On macOS:
      brew install qt@5
      
      # On Ubuntu/Debian:
      sudo apt-get install python3-pyqt5 python3-pyqt5.qtsvg
      
      # For full features including the Qt GUI
      uv pip install "ete3[gui]"
      ```
      
      ## First-time NCBI Taxonomy setup
      
      The first time `NCBITaxa` is instantiated, it automatically downloads the NCBI taxonomy
      database (~300MB) to `~/.etetoolkit/taxa.sqlite`. This happens only once:
      
      ```python
      from ete3 import NCBITaxa
      ncbi = NCBITaxa()  # Downloads database on first run
      ```
      
      Update taxonomy database:
      
      ```python
      ncbi.update_taxonomy_database()  # Download latest NCBI data
      ```
      
      ## Troubleshooting
      
      ### Import errors
      
      ```bash
      # If "ModuleNotFoundError: No module named 'ete3'"
      uv pip install ete3
      
      # For GUI and rendering issues
      uv pip install "ete3[gui]"
      ```
      
      ### Rendering issues
      
      If `tree.render()` or `tree.show()` fails with Qt-related errors, install system dependencies:
      
      ```bash
      # macOS
      brew install qt@5
      
      # Ubuntu/Debian
      sudo apt-get install python3-pyqt5 python3-pyqt5.qtsvg
      ```
      
      ### NCBI Taxonomy database
      
      If database download fails or becomes corrupted:
      
      ```python
      from ete3 import NCBITaxa
      ncbi = NCBITaxa()
      ncbi.update_taxonomy_database()  # Redownload database
      ```
      
      ### Memory issues with large trees
      
      For very large trees (>10,000 leaves), use iterators instead of list comprehensions:
      
      ```python
      # Memory-efficient iteration
      for leaf in tree.iter_leaves():
          process(leaf)
      
      # Instead of
      for leaf in tree.get_leaves():  # Loads all into memory
          process(leaf)
      ```
      
    • use_cases.md 3.3 KB
      # ETE Toolkit — Worked Use Cases
      
      End-to-end recipes combining tree manipulation, phylogenetic analysis, taxonomy, and
      visualization. For per-task patterns see `workflows.md`; for full method signatures see
      `api_reference.md`; for styling see `visualization.md`.
      
      ## Use Case 1: Phylogenomic Pipeline
      
      Complete workflow from gene tree to ortholog identification:
      
      ```python
      from ete3 import PhyloTree, NCBITaxa
      
      # 1. Load gene tree with alignment
      tree = PhyloTree("gene_tree.nw", alignment="alignment.fasta")
      
      # 2. Configure species naming
      tree.set_species_naming_function(lambda x: x.split("_")[0])
      
      # 3. Detect evolutionary events
      tree.get_descendant_evol_events()
      
      # 4. Annotate with taxonomy
      ncbi = NCBITaxa()
      for leaf in tree:
          if leaf.species in species_to_taxid:
              taxid = species_to_taxid[leaf.species]
              lineage = ncbi.get_lineage(taxid)
              leaf.add_feature("lineage", lineage)
      
      # 5. Extract ortholog groups
      ortho_groups = tree.get_speciation_trees()
      
      # 6. Save and visualize
      for i, ortho in enumerate(ortho_groups):
          ortho.write(outfile=f"ortho_{i}.nw")
      ```
      
      ## Use Case 2: Tree Preprocessing and Formatting
      
      Batch process trees for analysis:
      
      ```bash
      # Convert format
      python scripts/tree_operations.py convert input.nw output.nw --in-format 0 --out-format 1
      
      # Root at midpoint
      python scripts/tree_operations.py reroot input.nw rooted.nw --midpoint
      
      # Prune to focal taxa
      python scripts/tree_operations.py prune rooted.nw pruned.nw --keep-taxa taxa_list.txt
      
      # Get statistics
      python scripts/tree_operations.py stats pruned.nw
      ```
      
      ## Use Case 3: Publication-Quality Figures
      
      Create styled visualizations:
      
      ```python
      from ete3 import Tree, TreeStyle, NodeStyle, TextFace
      
      tree = Tree("tree.nw")
      
      # Define clade colors
      clade_colors = {
          "Mammals": "red",
          "Birds": "blue",
          "Fish": "green"
      }
      
      def layout(node):
          # Highlight clades
          if node.is_leaf():
              for clade, color in clade_colors.items():
                  if clade in node.name:
                      nstyle = NodeStyle()
                      nstyle["fgcolor"] = color
                      nstyle["size"] = 8
                      node.set_style(nstyle)
          else:
              # Add support values
              if node.support > 0.95:
                  support = TextFace(f"{node.support:.2f}", fsize=8)
                  node.add_face(support, column=0, position="branch-top")
      
      ts = TreeStyle()
      ts.layout_fn = layout
      ts.show_scale = True
      
      # Render for publication
      tree.render("figure.pdf", w=200, units="mm", tree_style=ts)
      tree.render("figure.svg", tree_style=ts)  # Editable vector
      ```
      
      ## Use Case 4: Automated Tree Analysis
      
      Process multiple trees systematically:
      
      ```python
      from ete3 import Tree
      import os
      
      input_dir = "trees"
      output_dir = "processed"
      
      for filename in os.listdir(input_dir):
          if filename.endswith(".nw"):
              tree = Tree(os.path.join(input_dir, filename))
      
              # Standardize: midpoint root, resolve polytomies
              midpoint = tree.get_midpoint_outgroup()
              tree.set_outgroup(midpoint)
              tree.resolve_polytomy(recursive=True)
      
              # Filter low support branches
              for node in tree.traverse():
                  if hasattr(node, 'support') and node.support < 0.5:
                      if not node.is_leaf() and not node.is_root():
                          node.delete()
      
              # Save processed tree
              output_file = os.path.join(output_dir, f"processed_{filename}")
              tree.write(outfile=output_file)
      ```
      
    • visualization.md 19.1 KB
      # ETE Toolkit Visualization Guide
      
      Complete guide to tree visualization with ETE Toolkit.
      
      ## Table of Contents
      1. [Rendering Basics](#rendering-basics)
      2. [TreeStyle Configuration](#treestyle-configuration)
      3. [Node Styling](#node-styling)
      4. [Faces](#faces)
      5. [Layout Functions](#layout-functions)
      6. [Advanced Visualization](#advanced-visualization)
      
      ---
      
      ## Rendering Basics
      
      ### Output Formats
      
      ETE supports three main output formats:
      
      ```python
      from ete3 import Tree
      
      tree = Tree("tree.nw")
      
      # PNG (raster, good for presentations)
      tree.render("output.png", w=800, h=600, units="px", dpi=300)
      
      # PDF (vector, good for publications)
      tree.render("output.pdf", w=200, units="mm")
      
      # SVG (vector, editable)
      tree.render("output.svg")
      ```
      
      ### Units and Dimensions
      
      ```python
      # Pixels
      tree.render("tree.png", w=1200, h=800, units="px")
      
      # Millimeters
      tree.render("tree.pdf", w=210, h=297, units="mm")  # A4 size
      
      # Inches
      tree.render("tree.pdf", w=8.5, h=11, units="in")  # US Letter
      
      # Auto-size (aspect ratio preserved)
      tree.render("tree.pdf", w=200, units="mm")  # Height auto-calculated
      ```
      
      ### Interactive Visualization
      
      ```python
      from ete3 import Tree
      
      tree = Tree("tree.nw")
      
      # Launch GUI
      # - Zoom with mouse wheel
      # - Pan by dragging
      # - Search with Ctrl+F
      # - Export from menu
      # - Edit node properties
      tree.show()
      ```
      
      ---
      
      ## TreeStyle Configuration
      
      ### Basic TreeStyle Options
      
      ```python
      from ete3 import Tree, TreeStyle
      
      tree = Tree("tree.nw")
      ts = TreeStyle()
      
      # Display options
      ts.show_leaf_name = True          # Show leaf names
      ts.show_branch_length = True      # Show branch lengths
      ts.show_branch_support = True     # Show support values
      ts.show_scale = True              # Show scale bar
      
      # Branch length scaling
      ts.scale = 50                     # Pixels per branch length unit
      ts.min_leaf_separation = 10       # Minimum space between leaves (pixels)
      
      # Layout orientation
      ts.rotation = 0                   # 0=left-to-right, 90=top-to-bottom
      ts.branch_vertical_margin = 10    # Vertical spacing between branches
      
      # Tree shape
      ts.mode = "r"                     # "r"=rectangular (default), "c"=circular
      
      tree.render("tree.pdf", tree_style=ts)
      ```
      
      ### Circular Trees
      
      ```python
      from ete3 import Tree, TreeStyle
      
      tree = Tree("tree.nw")
      ts = TreeStyle()
      
      # Circular mode
      ts.mode = "c"
      ts.arc_start = 0      # Starting angle (degrees)
      ts.arc_span = 360     # Angular span (degrees, 360=full circle)
      
      # For semicircle
      ts.arc_start = -180
      ts.arc_span = 180
      
      tree.render("circular_tree.pdf", tree_style=ts)
      ```
      
      ### Title and Legend
      
      ```python
      from ete3 import Tree, TreeStyle, TextFace
      
      tree = Tree("tree.nw")
      ts = TreeStyle()
      
      # Add title
      title = TextFace("Phylogenetic Tree of Species", fsize=20, bold=True)
      ts.title.add_face(title, column=0)
      
      # Add legend
      ts.legend.add_face(TextFace("Red nodes: High support", fsize=10), column=0)
      ts.legend.add_face(TextFace("Blue nodes: Low support", fsize=10), column=0)
      
      # Legend position
      ts.legend_position = 1  # 1=top-right, 2=top-left, 3=bottom-left, 4=bottom-right
      
      tree.render("tree_with_legend.pdf", tree_style=ts)
      ```
      
      ### Custom Background
      
      ```python
      from ete3 import Tree, TreeStyle
      
      tree = Tree("tree.nw")
      ts = TreeStyle()
      
      # Background color
      ts.bgcolor = "#f0f0f0"  # Light gray background
      
      # Tree border
      ts.show_border = True
      
      tree.render("tree_background.pdf", tree_style=ts)
      ```
      
      ---
      
      ## Node Styling
      
      ### NodeStyle Properties
      
      ```python
      from ete3 import Tree, NodeStyle
      
      tree = Tree("tree.nw")
      
      for node in tree.traverse():
          nstyle = NodeStyle()
      
          # Node size and shape
          nstyle["size"] = 10                # Node size in pixels
          nstyle["shape"] = "circle"         # "circle", "square", "sphere"
      
          # Colors
          nstyle["fgcolor"] = "blue"         # Foreground color (node itself)
          nstyle["bgcolor"] = "lightblue"    # Background color (only for sphere)
      
          # Line style for branches
          nstyle["hz_line_type"] = 0         # 0=solid, 1=dashed, 2=dotted
          nstyle["vt_line_type"] = 0         # Vertical line type
          nstyle["hz_line_color"] = "black"  # Horizontal line color
          nstyle["vt_line_color"] = "black"  # Vertical line color
          nstyle["hz_line_width"] = 2        # Line width in pixels
          nstyle["vt_line_width"] = 2
      
          node.set_style(nstyle)
      
      tree.render("styled_tree.pdf")
      ```
      
      ### Conditional Styling
      
      ```python
      from ete3 import Tree, NodeStyle
      
      tree = Tree("tree.nw")
      
      # Style based on node properties
      for node in tree.traverse():
          nstyle = NodeStyle()
      
          if node.is_leaf():
              # Leaf node style
              nstyle["size"] = 8
              nstyle["fgcolor"] = "darkgreen"
              nstyle["shape"] = "circle"
          else:
              # Internal node style based on support
              if node.support > 0.9:
                  nstyle["size"] = 6
                  nstyle["fgcolor"] = "red"
                  nstyle["shape"] = "sphere"
              else:
                  nstyle["size"] = 4
                  nstyle["fgcolor"] = "gray"
                  nstyle["shape"] = "circle"
      
          # Style branches by length
          if node.dist > 1.0:
              nstyle["hz_line_width"] = 3
              nstyle["hz_line_color"] = "blue"
          else:
              nstyle["hz_line_width"] = 1
              nstyle["hz_line_color"] = "black"
      
          node.set_style(nstyle)
      
      tree.render("conditional_styled_tree.pdf")
      ```
      
      ### Hiding Nodes
      
      ```python
      from ete3 import Tree, NodeStyle
      
      tree = Tree("tree.nw")
      
      # Hide specific nodes
      for node in tree.traverse():
          if node.support < 0.5:  # Hide low support nodes
              nstyle = NodeStyle()
              nstyle["draw_descendants"] = False  # Don't draw this node's subtree
              nstyle["size"] = 0                   # Make node invisible
              node.set_style(nstyle)
      
      tree.render("filtered_tree.pdf")
      ```
      
      ---
      
      ## Faces
      
      Faces are graphical elements attached to nodes. They appear at specific positions around nodes.
      
      ### Face Positions
      
      - `"branch-right"`: Right side of branch (after node)
      - `"branch-top"`: Above branch
      - `"branch-bottom"`: Below branch
      - `"aligned"`: Aligned column at tree edge (for leaves)
      
      ### TextFace
      
      ```python
      from ete3 import Tree, TreeStyle, TextFace
      
      tree = Tree("tree.nw")
      
      def layout(node):
          if node.is_leaf():
              # Add species name
              name_face = TextFace(node.name, fsize=12, fgcolor="black")
              node.add_face(name_face, column=0, position="branch-right")
      
              # Add additional text
              info_face = TextFace(f"Length: {node.dist:.3f}", fsize=8, fgcolor="gray")
              node.add_face(info_face, column=1, position="branch-right")
          else:
              # Add support value
              if node.support:
                  support_face = TextFace(f"{node.support:.2f}", fsize=8, fgcolor="red")
                  node.add_face(support_face, column=0, position="branch-top")
      
      ts = TreeStyle()
      ts.layout_fn = layout
      ts.show_leaf_name = False  # We're adding custom names
      
      tree.render("tree_textfaces.pdf", tree_style=ts)
      ```
      
      ### AttrFace
      
      Display node attributes directly:
      
      ```python
      from ete3 import Tree, TreeStyle, AttrFace
      
      tree = Tree("tree.nw")
      
      # Add custom attributes
      for leaf in tree:
          leaf.add_feature("habitat", "aquatic" if "fish" in leaf.name else "terrestrial")
          leaf.add_feature("temperature", 20)
      
      def layout(node):
          if node.is_leaf():
              # Display attribute directly
              habitat_face = AttrFace("habitat", fsize=10)
              node.add_face(habitat_face, column=0, position="aligned")
      
              temp_face = AttrFace("temperature", fsize=10)
              node.add_face(temp_face, column=1, position="aligned")
      
      ts = TreeStyle()
      ts.layout_fn = layout
      
      tree.render("tree_attrfaces.pdf", tree_style=ts)
      ```
      
      ### CircleFace
      
      ```python
      from ete3 import Tree, TreeStyle, CircleFace, TextFace
      
      tree = Tree("tree.nw")
      
      # Annotate with habitat
      for leaf in tree:
          leaf.add_feature("habitat", "marine" if "fish" in leaf.name else "land")
      
      def layout(node):
          if node.is_leaf():
              # Colored circle based on habitat
              color = "blue" if node.habitat == "marine" else "green"
              circle = CircleFace(radius=5, color=color, style="circle")
              node.add_face(circle, column=0, position="aligned")
      
              # Label
              name = TextFace(node.name, fsize=10)
              node.add_face(name, column=1, position="aligned")
      
      ts = TreeStyle()
      ts.layout_fn = layout
      ts.show_leaf_name = False
      
      tree.render("tree_circles.pdf", tree_style=ts)
      ```
      
      ### ImgFace
      
      Add images to nodes:
      
      ```python
      from ete3 import Tree, TreeStyle, ImgFace, TextFace
      
      tree = Tree("tree.nw")
      
      def layout(node):
          if node.is_leaf():
              # Add species image
              img_path = f"images/{node.name}.png"  # Path to image
              try:
                  img_face = ImgFace(img_path, width=50, height=50)
                  node.add_face(img_face, column=0, position="aligned")
              except:
                  pass  # Skip if image doesn't exist
      
              # Add name
              name_face = TextFace(node.name, fsize=10)
              node.add_face(name_face, column=1, position="aligned")
      
      ts = TreeStyle()
      ts.layout_fn = layout
      ts.show_leaf_name = False
      
      tree.render("tree_images.pdf", tree_style=ts)
      ```
      
      ### BarChartFace
      
      ```python
      from ete3 import Tree, TreeStyle, BarChartFace, TextFace
      
      tree = Tree("tree.nw")
      
      # Add data for bar charts
      for leaf in tree:
          leaf.add_feature("values", [1.2, 2.3, 0.5, 1.8])  # Multiple values
      
      def layout(node):
          if node.is_leaf():
              # Add bar chart
              chart = BarChartFace(
                  node.values,
                  width=100,
                  height=40,
                  colors=["red", "blue", "green", "orange"],
                  labels=["A", "B", "C", "D"]
              )
              node.add_face(chart, column=0, position="aligned")
      
              # Add name
              name = TextFace(node.name, fsize=10)
              node.add_face(name, column=1, position="aligned")
      
      ts = TreeStyle()
      ts.layout_fn = layout
      ts.show_leaf_name = False
      
      tree.render("tree_barcharts.pdf", tree_style=ts)
      ```
      
      ### PieChartFace
      
      ```python
      from ete3 import Tree, TreeStyle, PieChartFace, TextFace
      
      tree = Tree("tree.nw")
      
      # Add data
      for leaf in tree:
          leaf.add_feature("proportions", [25, 35, 40])  # Percentages
      
      def layout(node):
          if node.is_leaf():
              # Add pie chart
              pie = PieChartFace(
                  node.proportions,
                  width=30,
                  height=30,
                  colors=["red", "blue", "green"]
              )
              node.add_face(pie, column=0, position="aligned")
      
              name = TextFace(node.name, fsize=10)
              node.add_face(name, column=1, position="aligned")
      
      ts = TreeStyle()
      ts.layout_fn = layout
      ts.show_leaf_name = False
      
      tree.render("tree_piecharts.pdf", tree_style=ts)
      ```
      
      ### SequenceFace (for alignments)
      
      ```python
      from ete3 import PhyloTree, TreeStyle, SeqMotifFace
      
      tree = PhyloTree("tree.nw")
      tree.link_to_alignment("alignment.fasta")
      
      def layout(node):
          if node.is_leaf():
              # Display sequence
              seq_face = SeqMotifFace(node.sequence, seq_format="seq")
              node.add_face(seq_face, column=0, position="aligned")
      
      ts = TreeStyle()
      ts.layout_fn = layout
      ts.show_leaf_name = True
      
      tree.render("tree_alignment.pdf", tree_style=ts)
      ```
      
      ---
      
      ## Layout Functions
      
      Layout functions are Python functions that modify node appearance during rendering.
      
      ### Basic Layout Function
      
      ```python
      from ete3 import Tree, TreeStyle, TextFace
      
      tree = Tree("tree.nw")
      
      def my_layout(node):
          """Called for every node before rendering"""
      
          if node.is_leaf():
              # Add text to leaves
              name_face = TextFace(node.name.upper(), fsize=12, fgcolor="blue")
              node.add_face(name_face, column=0, position="branch-right")
          else:
              # Add support to internal nodes
              if node.support:
                  support_face = TextFace(f"BS: {node.support:.0f}", fsize=8)
                  node.add_face(support_face, column=0, position="branch-top")
      
      # Apply layout function
      ts = TreeStyle()
      ts.layout_fn = my_layout
      ts.show_leaf_name = False
      
      tree.render("tree_custom_layout.pdf", tree_style=ts)
      ```
      
      ### Dynamic Styling in Layout
      
      ```python
      from ete3 import Tree, TreeStyle, NodeStyle, TextFace
      
      tree = Tree("tree.nw")
      
      def layout(node):
          # Modify node style dynamically
          nstyle = NodeStyle()
      
          # Color by clade
          if "clade_A" in [l.name for l in node.get_leaves()]:
              nstyle["bgcolor"] = "lightblue"
          elif "clade_B" in [l.name for l in node.get_leaves()]:
              nstyle["bgcolor"] = "lightgreen"
      
          node.set_style(nstyle)
      
          # Add faces based on features
          if hasattr(node, "annotation"):
              text = TextFace(node.annotation, fsize=8)
              node.add_face(text, column=0, position="branch-top")
      
      ts = TreeStyle()
      ts.layout_fn = layout
      
      tree.render("tree_dynamic.pdf", tree_style=ts)
      ```
      
      ### Multiple Column Layout
      
      ```python
      from ete3 import Tree, TreeStyle, TextFace, CircleFace
      
      tree = Tree("tree.nw")
      
      # Add features
      for leaf in tree:
          leaf.add_feature("habitat", "aquatic")
          leaf.add_feature("temp", 20)
          leaf.add_feature("depth", 100)
      
      def layout(node):
          if node.is_leaf():
              # Column 0: Name
              name = TextFace(node.name, fsize=10)
              node.add_face(name, column=0, position="aligned")
      
              # Column 1: Habitat indicator
              color = "blue" if node.habitat == "aquatic" else "brown"
              circle = CircleFace(radius=5, color=color)
              node.add_face(circle, column=1, position="aligned")
      
              # Column 2: Temperature
              temp = TextFace(f"{node.temp}°C", fsize=8)
              node.add_face(temp, column=2, position="aligned")
      
              # Column 3: Depth
              depth = TextFace(f"{node.depth}m", fsize=8)
              node.add_face(depth, column=3, position="aligned")
      
      ts = TreeStyle()
      ts.layout_fn = layout
      ts.show_leaf_name = False
      
      tree.render("tree_columns.pdf", tree_style=ts)
      ```
      
      ---
      
      ## Advanced Visualization
      
      ### Highlighting Clades
      
      ```python
      from ete3 import Tree, TreeStyle, NodeStyle, TextFace
      
      tree = Tree("tree.nw")
      
      # Define clades to highlight
      clade_members = {
          "Clade_A": ["species1", "species2", "species3"],
          "Clade_B": ["species4", "species5"]
      }
      
      def layout(node):
          # Check if node is ancestor of specific clade
          node_leaves = set([l.name for l in node.get_leaves()])
      
          for clade_name, members in clade_members.items():
              if set(members).issubset(node_leaves):
                  # This node is ancestor of the clade
                  nstyle = NodeStyle()
                  nstyle["bgcolor"] = "yellow"
                  nstyle["size"] = 0
      
                  # Add label
                  if set(members) == node_leaves:  # Exact match
                      label = TextFace(clade_name, fsize=14, bold=True, fgcolor="red")
                      node.add_face(label, column=0, position="branch-top")
      
                  node.set_style(nstyle)
                  break
      
      ts = TreeStyle()
      ts.layout_fn = layout
      
      tree.render("tree_highlighted_clades.pdf", tree_style=ts)
      ```
      
      ### Collapsing Clades
      
      ```python
      from ete3 import Tree, TreeStyle, TextFace, NodeStyle
      
      tree = Tree("tree.nw")
      
      # Define which clades to collapse
      clades_to_collapse = ["clade1_species1", "clade1_species2"]
      
      def layout(node):
          if not node.is_leaf():
              node_leaves = [l.name for l in node.get_leaves()]
      
              # Check if this is a clade we want to collapse
              if all(l in clades_to_collapse for l in node_leaves):
                  # Collapse by hiding descendants
                  nstyle = NodeStyle()
                  nstyle["draw_descendants"] = False
                  nstyle["size"] = 20
                  nstyle["fgcolor"] = "steelblue"
                  nstyle["shape"] = "sphere"
                  node.set_style(nstyle)
      
                  # Add label showing what's collapsed
                  label = TextFace(f"[{len(node_leaves)} species]", fsize=10)
                  node.add_face(label, column=0, position="branch-right")
      
      ts = TreeStyle()
      ts.layout_fn = layout
      
      tree.render("tree_collapsed.pdf", tree_style=ts)
      ```
      
      ### Heat Map Visualization
      
      ```python
      from ete3 import Tree, TreeStyle, RectFace, TextFace
      import numpy as np
      
      tree = Tree("tree.nw")
      
      # Generate random data for heatmap
      for leaf in tree:
          leaf.add_feature("data", np.random.rand(10))  # 10 data points
      
      def layout(node):
          if node.is_leaf():
              # Add name
              name = TextFace(node.name, fsize=8)
              node.add_face(name, column=0, position="aligned")
      
              # Add heatmap cells
              for i, value in enumerate(node.data):
                  # Color based on value
                  intensity = int(255 * value)
                  color = f"#{255-intensity:02x}{intensity:02x}00"  # Green-red gradient
      
                  rect = RectFace(width=20, height=15, fgcolor=color, bgcolor=color)
                  node.add_face(rect, column=i+1, position="aligned")
      
      # Add column headers
      ts = TreeStyle()
      ts.layout_fn = layout
      ts.show_leaf_name = False
      
      # Add header
      for i in range(10):
          header = TextFace(f"C{i+1}", fsize=8, fgcolor="gray")
          ts.aligned_header.add_face(header, column=i+1)
      
      tree.render("tree_heatmap.pdf", tree_style=ts)
      ```
      
      ### Phylogenetic Events Visualization
      
      ```python
      from ete3 import PhyloTree, TreeStyle, TextFace, NodeStyle
      
      tree = PhyloTree("gene_tree.nw")
      tree.set_species_naming_function(lambda x: x.split("_")[0])
      tree.get_descendant_evol_events()
      
      def layout(node):
          # Style based on evolutionary event
          if hasattr(node, "evoltype"):
              nstyle = NodeStyle()
      
              if node.evoltype == "D":  # Duplication
                  nstyle["fgcolor"] = "red"
                  nstyle["size"] = 10
                  nstyle["shape"] = "square"
      
                  label = TextFace("DUP", fsize=8, fgcolor="red", bold=True)
                  node.add_face(label, column=0, position="branch-top")
      
              elif node.evoltype == "S":  # Speciation
                  nstyle["fgcolor"] = "blue"
                  nstyle["size"] = 6
                  nstyle["shape"] = "circle"
      
              node.set_style(nstyle)
      
      ts = TreeStyle()
      ts.layout_fn = layout
      ts.show_leaf_name = True
      
      tree.render("gene_tree_events.pdf", tree_style=ts)
      ```
      
      ### Custom Tree with Legend
      
      ```python
      from ete3 import Tree, TreeStyle, TextFace, CircleFace, NodeStyle
      
      tree = Tree("tree.nw")
      
      # Categorize species
      for leaf in tree:
          if "fish" in leaf.name.lower():
              leaf.add_feature("category", "fish")
          elif "bird" in leaf.name.lower():
              leaf.add_feature("category", "bird")
          else:
              leaf.add_feature("category", "mammal")
      
      category_colors = {
          "fish": "blue",
          "bird": "green",
          "mammal": "red"
      }
      
      def layout(node):
          if node.is_leaf():
              # Color by category
              nstyle = NodeStyle()
              nstyle["fgcolor"] = category_colors[node.category]
              nstyle["size"] = 10
              node.set_style(nstyle)
      
      ts = TreeStyle()
      ts.layout_fn = layout
      
      # Add legend
      ts.legend.add_face(TextFace("Legend:", fsize=12, bold=True), column=0)
      for category, color in category_colors.items():
          circle = CircleFace(radius=5, color=color)
          ts.legend.add_face(circle, column=0)
          label = TextFace(f" {category.capitalize()}", fsize=10)
          ts.legend.add_face(label, column=1)
      
      ts.legend_position = 1
      
      tree.render("tree_with_legend.pdf", tree_style=ts)
      ```
      
      ---
      
      ## Best Practices
      
      1. **Use layout functions** for complex visualizations - they're called during rendering
      2. **Set `show_leaf_name = False`** when using custom name faces
      3. **Use aligned position** for columnar data at leaf level
      4. **Choose appropriate units**: pixels for screen, mm/inches for print
      5. **Use vector formats (PDF/SVG)** for publications
      6. **Precompute styling** when possible - layout functions should be fast
      7. **Test interactively** with `show()` before rendering to file
      8. **Use NodeStyle for permanent** changes, layout functions for rendering-time changes
      9. **Align faces in columns** for clean, organized appearance
      10. **Add legends** to explain colors and symbols used
      
    • workflows.md 18.9 KB
      # ETE Toolkit Common Workflows
      
      This document provides complete workflows for common tasks using the ETE Toolkit.
      
      ## Table of Contents
      1. [Basic Tree Operations](#basic-tree-operations)
      2. [Phylogenetic Analysis](#phylogenetic-analysis)
      3. [Tree Comparison](#tree-comparison)
      4. [Taxonomy Integration](#taxonomy-integration)
      5. [Clustering Analysis](#clustering-analysis)
      6. [Tree Visualization](#tree-visualization)
      
      ---
      
      ## Basic Tree Operations
      
      ### Loading and Exploring a Tree
      
      ```python
      from ete3 import Tree
      
      # Load tree from file
      tree = Tree("my_tree.nw", format=1)
      
      # Display ASCII representation
      print(tree.get_ascii(show_internal=True))
      
      # Get basic statistics
      print(f"Number of leaves: {len(tree)}")
      print(f"Total nodes: {len(list(tree.traverse()))}")
      print(f"Tree depth: {tree.get_farthest_leaf()[1]}")
      
      # List all leaf names
      for leaf in tree:
          print(leaf.name)
      ```
      
      ### Extracting and Saving Subtrees
      
      ```python
      from ete3 import Tree
      
      tree = Tree("full_tree.nw")
      
      # Get subtree rooted at specific node
      node = tree.search_nodes(name="MyNode")[0]
      subtree = node.copy()
      
      # Save subtree to file
      subtree.write(outfile="subtree.nw", format=1)
      
      # Extract monophyletic clade
      species_of_interest = ["species1", "species2", "species3"]
      ancestor = tree.get_common_ancestor(species_of_interest)
      clade = ancestor.copy()
      clade.write(outfile="clade.nw")
      ```
      
      ### Pruning Trees to Specific Taxa
      
      ```python
      from ete3 import Tree
      
      tree = Tree("large_tree.nw")
      
      # Keep only taxa of interest
      taxa_to_keep = ["taxon1", "taxon2", "taxon3", "taxon4"]
      tree.prune(taxa_to_keep, preserve_branch_length=True)
      
      # Save pruned tree
      tree.write(outfile="pruned_tree.nw")
      ```
      
      ### Rerooting Trees
      
      ```python
      from ete3 import Tree
      
      tree = Tree("unrooted_tree.nw")
      
      # Method 1: Root by outgroup
      outgroup = tree & "Outgroup_species"
      tree.set_outgroup(outgroup)
      
      # Method 2: Midpoint rooting
      midpoint = tree.get_midpoint_outgroup()
      tree.set_outgroup(midpoint)
      
      # Save rooted tree
      tree.write(outfile="rooted_tree.nw")
      ```
      
      ### Annotating Nodes with Custom Data
      
      ```python
      from ete3 import Tree
      
      tree = Tree("tree.nw")
      
      # Add features to nodes based on metadata
      metadata = {
          "species1": {"habitat": "marine", "temperature": 20},
          "species2": {"habitat": "freshwater", "temperature": 15},
      }
      
      for leaf in tree:
          if leaf.name in metadata:
              leaf.add_features(**metadata[leaf.name])
      
      # Query annotated features
      for leaf in tree:
          if hasattr(leaf, "habitat"):
              print(f"{leaf.name}: {leaf.habitat}, {leaf.temperature}°C")
      
      # Save with custom features (NHX format)
      tree.write(outfile="annotated_tree.nhx", features=["habitat", "temperature"])
      ```
      
      ### Modifying Tree Topology
      
      ```python
      from ete3 import Tree
      
      tree = Tree("tree.nw")
      
      # Remove a clade
      node_to_remove = tree & "unwanted_clade"
      node_to_remove.detach()
      
      # Collapse a node (delete but keep children)
      node_to_collapse = tree & "low_support_node"
      node_to_collapse.delete()
      
      # Add a new species to existing clade
      target_clade = tree & "target_node"
      new_leaf = target_clade.add_child(name="new_species", dist=0.5)
      
      # Resolve polytomies
      tree.resolve_polytomy(recursive=True)
      
      # Save modified tree
      tree.write(outfile="modified_tree.nw")
      ```
      
      ---
      
      ## Phylogenetic Analysis
      
      ### Complete Gene Tree Analysis with Alignment
      
      ```python
      from ete3 import PhyloTree
      
      # Load gene tree and link alignment
      tree = PhyloTree("gene_tree.nw", format=1)
      tree.link_to_alignment("alignment.fasta", alg_format="fasta")
      
      # Set species naming function (e.g., gene_species format)
      def extract_species(node_name):
          return node_name.split("_")[0]
      
      tree.set_species_naming_function(extract_species)
      
      # Access sequences
      for leaf in tree:
          print(f"{leaf.name} ({leaf.species})")
          print(f"Sequence: {leaf.sequence[:50]}...")
      ```
      
      ### Detecting Duplication and Speciation Events
      
      ```python
      from ete3 import PhyloTree, Tree
      
      # Load gene tree
      gene_tree = PhyloTree("gene_tree.nw")
      
      # Set species naming
      gene_tree.set_species_naming_function(lambda x: x.split("_")[0])
      
      # Option 1: Species Overlap algorithm (no species tree needed)
      events = gene_tree.get_descendant_evol_events()
      
      # Option 2: Tree reconciliation (requires species tree)
      species_tree = Tree("species_tree.nw")
      events = gene_tree.get_descendant_evol_events(species_tree=species_tree)
      
      # Analyze events
      duplications = 0
      speciations = 0
      
      for node in gene_tree.traverse():
          if hasattr(node, "evoltype"):
              if node.evoltype == "D":
                  duplications += 1
                  print(f"Duplication at node {node.name}")
              elif node.evoltype == "S":
                  speciations += 1
      
      print(f"\nTotal duplications: {duplications}")
      print(f"Total speciations: {speciations}")
      ```
      
      ### Extracting Orthologs and Paralogs
      
      ```python
      from ete3 import PhyloTree
      
      gene_tree = PhyloTree("gene_tree.nw")
      gene_tree.set_species_naming_function(lambda x: x.split("_")[0])
      
      # Detect evolutionary events
      events = gene_tree.get_descendant_evol_events()
      
      # Find orthologs/paralogs of a query gene.
      # NOTE: EvolEvent.in_seqs / out_seqs are sets of leaf-NAME strings (not nodes),
      # event.etype is "S" (speciation) or "D" (duplication), and the relationship
      # is symmetric — the query can appear on either side, so check both.
      query = "species1_gene1"
      
      orthologs = set()
      paralogs = set()
      
      for event in events:
          in_seqs = set(event.in_seqs)
          out_seqs = set(event.out_seqs)
          if query in in_seqs:
              partners = out_seqs
          elif query in out_seqs:
              partners = in_seqs
          else:
              continue
          if event.etype == "S":      # Speciation -> orthologs
              orthologs.update(partners)
          elif event.etype == "D":    # Duplication -> paralogs
              paralogs.update(partners)
      
      print(f"Orthologs of {query}:")
      for name in sorted(orthologs):
          print(f"  {name}")
      
      print(f"\nParalogs of {query}:")
      for name in sorted(paralogs):
          print(f"  {name}")
      ```
      
      ### Splitting Gene Families by Duplication Events
      
      ```python
      from ete3 import PhyloTree
      
      gene_tree = PhyloTree("gene_family.nw")
      gene_tree.set_species_naming_function(lambda x: x.split("_")[0])
      gene_tree.get_descendant_evol_events()
      
      # Split into individual gene families
      subfamilies = gene_tree.split_by_dups()
      
      print(f"Gene family split into {len(subfamilies)} subfamilies")
      
      for i, subtree in enumerate(subfamilies):
          subtree.write(outfile=f"subfamily_{i}.nw")
          species = set([leaf.species for leaf in subtree])
          print(f"Subfamily {i}: {len(subtree)} genes from {len(species)} species")
      ```
      
      ### Collapsing Lineage-Specific Expansions
      
      ```python
      from ete3 import PhyloTree
      
      gene_tree = PhyloTree("expanded_tree.nw")
      gene_tree.set_species_naming_function(lambda x: x.split("_")[0])
      
      # Collapse lineage-specific duplications
      gene_tree.collapse_lineage_specific_expansions()
      
      print("After collapsing expansions:")
      print(gene_tree.get_ascii())
      
      gene_tree.write(outfile="collapsed_tree.nw")
      ```
      
      ### Testing Monophyly
      
      ```python
      from ete3 import Tree
      
      tree = Tree("tree.nw")
      
      # Test if a group is monophyletic
      target_species = ["species1", "species2", "species3"]
      is_mono, clade_type, base_node = tree.check_monophyly(
          values=target_species,
          target_attr="name"
      )
      
      if is_mono:
          print(f"Group is monophyletic")
          print(f"MRCA: {base_node.name}")
      elif clade_type == "paraphyletic":
          print(f"Group is paraphyletic")
      elif clade_type == "polyphyletic":
          print(f"Group is polyphyletic")
      
      # Get all monophyletic clades of a specific type
      # Annotate leaves first
      for leaf in tree:
          if leaf.name.startswith("species"):
              leaf.add_feature("type", "typeA")
          else:
              leaf.add_feature("type", "typeB")
      
      mono_clades = tree.get_monophyletic(values=["typeA"], target_attr="type")
      print(f"Found {len(mono_clades)} monophyletic clades of typeA")
      ```
      
      ---
      
      ## Tree Comparison
      
      ### Computing Robinson-Foulds Distance
      
      ```python
      from ete3 import Tree
      
      tree1 = Tree("tree1.nw")
      tree2 = Tree("tree2.nw")
      
      # Compute RF distance. Current ete3 returns more than 5 values
      # (the trailing two are discarded-edge sets), so slice the first 5.
      rf, max_rf, common_leaves, parts_t1, parts_t2 = tree1.robinson_foulds(tree2)[:5]
      
      print(f"Robinson-Foulds distance: {rf}")
      print(f"Maximum RF distance: {max_rf}")
      print(f"Normalized RF: {rf/max_rf:.3f}")
      print(f"Common leaves: {len(common_leaves)}")
      
      # Find unique partitions
      unique_in_t1 = parts_t1 - parts_t2
      unique_in_t2 = parts_t2 - parts_t1
      
      print(f"\nPartitions unique to tree1: {len(unique_in_t1)}")
      print(f"Partitions unique to tree2: {len(unique_in_t2)}")
      ```
      
      ### Comparing Multiple Trees
      
      ```python
      from ete3 import Tree
      import numpy as np
      
      # Load multiple trees
      tree_files = ["tree1.nw", "tree2.nw", "tree3.nw", "tree4.nw"]
      trees = [Tree(f) for f in tree_files]
      
      # Create distance matrix
      n = len(trees)
      dist_matrix = np.zeros((n, n))
      
      for i in range(n):
          for j in range(i+1, n):
              rf, max_rf = trees[i].robinson_foulds(trees[j])[:2]
              norm_rf = rf / max_rf if max_rf > 0 else 0
              dist_matrix[i, j] = norm_rf
              dist_matrix[j, i] = norm_rf
      
      print("Normalized RF distance matrix:")
      print(dist_matrix)
      
      # Find most similar pair
      min_dist = float('inf')
      best_pair = None
      
      for i in range(n):
          for j in range(i+1, n):
              if dist_matrix[i, j] < min_dist:
                  min_dist = dist_matrix[i, j]
                  best_pair = (i, j)
      
      print(f"\nMost similar trees: {tree_files[best_pair[0]]} and {tree_files[best_pair[1]]}")
      print(f"Distance: {min_dist:.3f}")
      ```
      
      ### Finding Consensus Topology
      
      ```python
      from ete3 import Tree
      
      # Load multiple bootstrap trees
      bootstrap_trees = [Tree(f"bootstrap_{i}.nw") for i in range(100)]
      
      # Get reference tree (first tree)
      ref_tree = bootstrap_trees[0].copy()
      
      # Count bipartitions
      bipartition_counts = {}
      
      for tree in bootstrap_trees:
          rf, max_rf, common, parts_ref, parts_tree = ref_tree.robinson_foulds(tree)[:5]
          for partition in parts_tree:
              bipartition_counts[partition] = bipartition_counts.get(partition, 0) + 1
      
      # Filter by support threshold
      threshold = 70  # 70% support
      supported_bipartitions = {
          k: v for k, v in bipartition_counts.items()
          if (v / len(bootstrap_trees)) * 100 >= threshold
      }
      
      print(f"Bipartitions with >{threshold}% support: {len(supported_bipartitions)}")
      ```
      
      ---
      
      ## Taxonomy Integration
      
      ### Building Species Trees from NCBI Taxonomy
      
      ```python
      from ete3 import NCBITaxa
      
      ncbi = NCBITaxa()
      
      # Define species of interest
      species = ["Homo sapiens", "Pan troglodytes", "Gorilla gorilla",
                 "Mus musculus", "Rattus norvegicus"]
      
      # Get taxids
      name2taxid = ncbi.get_name_translator(species)
      taxids = [name2taxid[sp][0] for sp in species]
      
      # Build tree
      tree = ncbi.get_topology(taxids)
      
      # Annotate with taxonomy info
      for node in tree.traverse():
          if hasattr(node, "sci_name"):
              print(f"{node.sci_name} - Rank: {node.rank} - TaxID: {node.taxid}")
      
      # Save tree
      tree.write(outfile="species_tree.nw")
      ```
      
      ### Annotating Existing Tree with NCBI Taxonomy
      
      ```python
      from ete3 import Tree, NCBITaxa
      
      tree = Tree("species_tree.nw")
      ncbi = NCBITaxa()
      
      # Map leaf names to species names (adjust as needed)
      leaf_to_species = {
          "Hsap_gene1": "Homo sapiens",
          "Ptro_gene1": "Pan troglodytes",
          "Mmur_gene1": "Microcebus murinus",
      }
      
      # Get taxids
      all_species = list(set(leaf_to_species.values()))
      name2taxid = ncbi.get_name_translator(all_species)
      
      # Annotate leaves
      for leaf in tree:
          if leaf.name in leaf_to_species:
              species_name = leaf_to_species[leaf.name]
              taxid = name2taxid[species_name][0]
      
              # Add taxonomy info
              leaf.add_feature("species", species_name)
              leaf.add_feature("taxid", taxid)
      
              # Get full lineage
              lineage = ncbi.get_lineage(taxid)
              names = ncbi.get_taxid_translator(lineage)
              leaf.add_feature("lineage", [names[t] for t in lineage])
      
              print(f"{leaf.name}: {species_name} (taxid: {taxid})")
      ```
      
      ### Querying NCBI Taxonomy
      
      ```python
      from ete3 import NCBITaxa
      
      ncbi = NCBITaxa()
      
      # Get all primates
      primates_taxid = ncbi.get_name_translator(["Primates"])["Primates"][0]
      all_primates = ncbi.get_descendant_taxa(primates_taxid, collapse_subspecies=True)
      
      print(f"Total primate species: {len(all_primates)}")
      
      # Get names for subset
      taxid2name = ncbi.get_taxid_translator(all_primates[:10])
      for taxid, name in taxid2name.items():
          rank = ncbi.get_rank([taxid])[taxid]
          print(f"{name} ({rank})")
      
      # Get lineage for specific species
      human_taxid = 9606
      lineage = ncbi.get_lineage(human_taxid)
      ranks = ncbi.get_rank(lineage)
      names = ncbi.get_taxid_translator(lineage)
      
      print("\nHuman lineage:")
      for taxid in lineage:
          print(f"{ranks[taxid]:15s} {names[taxid]}")
      ```
      
      ---
      
      ## Clustering Analysis
      
      ### Analyzing Hierarchical Clustering Results
      
      ```python
      from ete3 import ClusterTree
      
      # Load clustering tree with data matrix
      matrix = """#Names\tSample1\tSample2\tSample3\tSample4
      Gene1\t1.5\t2.3\t0.8\t1.2
      Gene2\t0.9\t1.1\t1.8\t2.1
      Gene3\t2.1\t2.5\t0.5\t0.9
      Gene4\t0.7\t0.9\t2.2\t2.4"""
      
      tree = ClusterTree("((Gene1,Gene2),(Gene3,Gene4));", text_array=matrix)
      
      # Calculate cluster quality metrics
      for node in tree.traverse():
          if not node.is_leaf():
              # Silhouette coefficient
              silhouette = node.get_silhouette()
      
              # Dunn index
              dunn = node.get_dunn()
      
              # Distances
              inter = node.intercluster_dist
              intra = node.intracluster_dist
      
              print(f"Node: {node.name}")
              print(f"  Silhouette: {silhouette:.3f}")
              print(f"  Dunn index: {dunn:.3f}")
              print(f"  Intercluster distance: {inter:.3f}")
              print(f"  Intracluster distance: {intra:.3f}")
      ```
      
      ### Validating Clusters
      
      ```python
      from ete3 import ClusterTree
      
      matrix = """#Names\tCol1\tCol2\tCol3
      ItemA\t1.2\t0.5\t0.8
      ItemB\t1.3\t0.6\t0.9
      ItemC\t0.1\t2.5\t2.3
      ItemD\t0.2\t2.6\t2.4"""
      
      tree = ClusterTree("((ItemA,ItemB),(ItemC,ItemD));", text_array=matrix)
      
      # Test different distance metrics
      metrics = ["euclidean", "pearson", "spearman"]
      
      for metric in metrics:
          print(f"\nUsing {metric} distance:")
      
          for node in tree.traverse():
              if not node.is_leaf():
                  silhouette = node.get_silhouette(distance=metric)
      
                  # Positive silhouette = good clustering
                  # Negative silhouette = poor clustering
                  quality = "good" if silhouette > 0 else "poor"
      
                  print(f"  Cluster {node.name}: {silhouette:.3f} ({quality})")
      ```
      
      ---
      
      ## Tree Visualization
      
      ### Basic Tree Rendering
      
      ```python
      from ete3 import Tree, TreeStyle
      
      tree = Tree("tree.nw")
      
      # Create tree style
      ts = TreeStyle()
      ts.show_leaf_name = True
      ts.show_branch_length = True
      ts.show_branch_support = True
      ts.scale = 50  # pixels per branch length unit
      
      # Render to file
      tree.render("tree_output.pdf", tree_style=ts)
      tree.render("tree_output.png", tree_style=ts, w=800, h=600, units="px")
      tree.render("tree_output.svg", tree_style=ts)
      ```
      
      ### Customizing Node Appearance
      
      ```python
      from ete3 import Tree, TreeStyle, NodeStyle
      
      tree = Tree("tree.nw")
      
      # Define node styles
      for node in tree.traverse():
          nstyle = NodeStyle()
      
          if node.is_leaf():
              nstyle["fgcolor"] = "blue"
              nstyle["size"] = 10
          else:
              nstyle["fgcolor"] = "red"
              nstyle["size"] = 5
      
          if node.support > 0.9:
              nstyle["shape"] = "sphere"
          else:
              nstyle["shape"] = "circle"
      
          node.set_style(nstyle)
      
      # Render
      ts = TreeStyle()
      tree.render("styled_tree.pdf", tree_style=ts)
      ```
      
      ### Adding Faces to Nodes
      
      ```python
      from ete3 import Tree, TreeStyle, TextFace, CircleFace, AttrFace
      
      tree = Tree("tree.nw")
      
      # Add features to nodes
      for leaf in tree:
          leaf.add_feature("habitat", "marine" if "fish" in leaf.name else "terrestrial")
          leaf.add_feature("temp", 20)
      
      # Layout function to add faces
      def layout(node):
          if node.is_leaf():
              # Add text face
              name_face = TextFace(node.name, fsize=10)
              node.add_face(name_face, column=0, position="branch-right")
      
              # Add colored circle based on habitat
              color = "blue" if node.habitat == "marine" else "green"
              circle_face = CircleFace(radius=5, color=color)
              node.add_face(circle_face, column=1, position="branch-right")
      
              # Add attribute face
              temp_face = AttrFace("temp", fsize=8)
              node.add_face(temp_face, column=2, position="branch-right")
      
      ts = TreeStyle()
      ts.layout_fn = layout
      ts.show_leaf_name = False  # We're adding custom names
      
      tree.render("tree_with_faces.pdf", tree_style=ts)
      ```
      
      ### Circular Tree Layout
      
      ```python
      from ete3 import Tree, TreeStyle
      
      tree = Tree("tree.nw")
      
      ts = TreeStyle()
      ts.mode = "c"  # Circular mode
      ts.arc_start = 0  # Degrees
      ts.arc_span = 360  # Full circle
      ts.show_leaf_name = True
      
      tree.render("circular_tree.pdf", tree_style=ts)
      ```
      
      ### Interactive Exploration
      
      ```python
      from ete3 import Tree
      
      tree = Tree("tree.nw")
      
      # Launch GUI (allows zooming, searching, modifying)
      # Changes persist after closing
      tree.show()
      
      # Can save changes made in GUI
      tree.write(outfile="modified_tree.nw")
      ```
      
      ---
      
      ## Advanced Workflows
      
      ### Complete Phylogenomic Pipeline
      
      ```python
      from ete3 import PhyloTree, NCBITaxa, TreeStyle
      
      # 1. Load gene tree
      gene_tree = PhyloTree("gene_tree.nw", alignment="alignment.fasta")
      
      # 2. Set species naming
      gene_tree.set_species_naming_function(lambda x: x.split("_")[0])
      
      # 3. Detect evolutionary events
      gene_tree.get_descendant_evol_events()
      
      # 4. Annotate with NCBI taxonomy
      ncbi = NCBITaxa()
      species_set = set([leaf.species for leaf in gene_tree])
      name2taxid = ncbi.get_name_translator(list(species_set))
      
      for leaf in gene_tree:
          if leaf.species in name2taxid:
              taxid = name2taxid[leaf.species][0]
              lineage = ncbi.get_lineage(taxid)
              names = ncbi.get_taxid_translator(lineage)
              leaf.add_feature("lineage", [names[t] for t in lineage])
      
      # 5. Identify and save ortholog groups
      ortho_groups = gene_tree.get_speciation_trees()
      
      for i, ortho_tree in enumerate(ortho_groups):
          ortho_tree.write(outfile=f"ortholog_group_{i}.nw")
      
      # 6. Visualize with evolutionary events marked
      def layout(node):
          from ete3 import TextFace
          if hasattr(node, "evoltype"):
              if node.evoltype == "D":
                  dup_face = TextFace("DUPLICATION", fsize=8, fgcolor="red")
                  node.add_face(dup_face, column=0, position="branch-top")
      
      ts = TreeStyle()
      ts.layout_fn = layout
      ts.show_leaf_name = True
      gene_tree.render("annotated_gene_tree.pdf", tree_style=ts)
      
      print(f"Pipeline complete. Found {len(ortho_groups)} ortholog groups.")
      ```
      
      ### Batch Processing Multiple Trees
      
      ```python
      from ete3 import Tree
      import os
      
      input_dir = "input_trees"
      output_dir = "processed_trees"
      os.makedirs(output_dir, exist_ok=True)
      
      for filename in os.listdir(input_dir):
          if filename.endswith(".nw"):
              # Load tree
              tree = Tree(os.path.join(input_dir, filename))
      
              # Process: root, prune, annotate
              midpoint = tree.get_midpoint_outgroup()
              tree.set_outgroup(midpoint)
      
              # Filter by branch length
              to_remove = []
              for node in tree.traverse():
                  if node.dist < 0.001 and not node.is_root():
                      to_remove.append(node)
      
              for node in to_remove:
                  node.delete()
      
              # Save processed tree
              output_file = os.path.join(output_dir, f"processed_{filename}")
              tree.write(outfile=output_file)
      
              print(f"Processed {filename}")
      ```
      
  • scripts
    • quick_visualize.py 7.3 KB
      #!/usr/bin/env python3
      """
      Quick tree visualization script with common customization options.
      
      Provides command-line interface for rapid tree visualization with
      customizable styles, layouts, and output formats.
      """
      
      import argparse
      import sys
      from pathlib import Path
      
      try:
          from ete3 import Tree, TreeStyle, NodeStyle
      except ImportError:
          print("Error: ete3 not installed. Install with: uv pip install ete3")
          print("(These scripts target the ete3 API; ete4 renames format= to parser=.)")
          sys.exit(1)
      
      
      def create_tree_style(args):
          """Create TreeStyle based on arguments."""
          ts = TreeStyle()
      
          # Basic display options
          ts.show_leaf_name = args.show_names
          ts.show_branch_length = args.show_lengths
          ts.show_branch_support = args.show_support
          ts.show_scale = args.show_scale
      
          # Layout
          ts.mode = args.mode
          ts.rotation = args.rotation
      
          # Circular tree options
          if args.mode == "c":
              ts.arc_start = args.arc_start
              ts.arc_span = args.arc_span
      
          # Spacing
          ts.branch_vertical_margin = args.vertical_margin
          if args.scale_factor:
              ts.scale = args.scale_factor
      
          # Title
          if args.title:
              from ete3 import TextFace
              title_face = TextFace(args.title, fsize=16, bold=True)
              ts.title.add_face(title_face, column=0)
      
          return ts
      
      
      def apply_node_styling(tree, args):
          """Apply styling to tree nodes."""
          for node in tree.traverse():
              nstyle = NodeStyle()
      
              if node.is_leaf():
                  # Leaf style
                  nstyle["fgcolor"] = args.leaf_color
                  nstyle["size"] = args.leaf_size
              else:
                  # Internal node style
                  nstyle["fgcolor"] = args.internal_color
                  nstyle["size"] = args.internal_size
      
                  # Color by support if enabled. Support may be on a 0-1 scale
                  # (posterior / aLRT) or a 0-100 scale (bootstrap); normalize to
                  # 0-1 so the thresholds work for either.
                  if args.color_by_support and hasattr(node, 'support') and node.support:
                      support = node.support / 100.0 if node.support > 1.0 else node.support
                      if support >= 0.9:
                          nstyle["fgcolor"] = "darkgreen"
                      elif support >= 0.7:
                          nstyle["fgcolor"] = "orange"
                      else:
                          nstyle["fgcolor"] = "red"
      
              node.set_style(nstyle)
      
      
      def visualize_tree(tree_file, output, args):
          """Load tree, apply styles, and render."""
          try:
              tree = Tree(str(tree_file), format=args.format)
          except Exception as e:
              print(f"Error loading tree: {e}")
              sys.exit(1)
      
          # Apply styling
          apply_node_styling(tree, args)
      
          # Create tree style
          ts = create_tree_style(args)
      
          # Render
          try:
              # Determine output parameters based on format
              output_path = str(output)
      
              render_args = {"tree_style": ts}
      
              if args.width:
                  render_args["w"] = args.width
              if args.height:
                  render_args["h"] = args.height
              if args.units:
                  render_args["units"] = args.units
              if args.dpi:
                  render_args["dpi"] = args.dpi
      
              tree.render(output_path, **render_args)
              print(f"Tree rendered successfully to: {output}")
      
          except Exception as e:
              print(f"Error rendering tree: {e}")
              sys.exit(1)
      
      
      def main():
          parser = argparse.ArgumentParser(
              description="Quick tree visualization with ETE toolkit",
              formatter_class=argparse.RawDescriptionHelpFormatter,
              epilog="""
      Examples:
        # Basic visualization
        %(prog)s tree.nw output.pdf
      
        # Circular tree
        %(prog)s tree.nw output.pdf --mode c
      
        # Large tree with custom sizing
        %(prog)s tree.nw output.png --width 1200 --height 800 --units px --dpi 300
      
        # Hide names, show support, color by support
        %(prog)s tree.nw output.pdf --no-names --show-support --color-by-support
      
        # Custom title
        %(prog)s tree.nw output.pdf --title "Phylogenetic Tree of Species"
      
        # Semicircular layout
        %(prog)s tree.nw output.pdf --mode c --arc-start -90 --arc-span 180
              """
          )
      
          parser.add_argument("input", help="Input tree file (Newick format)")
          parser.add_argument("output", help="Output image file (png, pdf, or svg)")
      
          # Tree format
          parser.add_argument("--format", type=int, default=0,
                              help="Newick format number (default: 0)")
      
          # Display options
          display = parser.add_argument_group("Display options")
          display.add_argument("--no-names", dest="show_names", action="store_false",
                               help="Don't show leaf names")
          display.add_argument("--show-lengths", action="store_true",
                               help="Show branch lengths")
          display.add_argument("--show-support", action="store_true",
                               help="Show support values")
          display.add_argument("--show-scale", action="store_true",
                               help="Show scale bar")
      
          # Layout options
          layout = parser.add_argument_group("Layout options")
          layout.add_argument("--mode", choices=["r", "c"], default="r",
                              help="Tree mode: r=rectangular, c=circular (default: r)")
          layout.add_argument("--rotation", type=int, default=0,
                              help="Tree rotation in degrees (default: 0)")
          layout.add_argument("--arc-start", type=int, default=0,
                              help="Circular tree start angle (default: 0)")
          layout.add_argument("--arc-span", type=int, default=360,
                              help="Circular tree arc span (default: 360)")
      
          # Styling options
          styling = parser.add_argument_group("Styling options")
          styling.add_argument("--leaf-color", default="blue",
                               help="Leaf node color (default: blue)")
          styling.add_argument("--leaf-size", type=int, default=6,
                               help="Leaf node size (default: 6)")
          styling.add_argument("--internal-color", default="gray",
                               help="Internal node color (default: gray)")
          styling.add_argument("--internal-size", type=int, default=4,
                               help="Internal node size (default: 4)")
          styling.add_argument("--color-by-support", action="store_true",
                               help="Color internal nodes by support value")
      
          # Size and spacing
          size = parser.add_argument_group("Size and spacing")
          size.add_argument("--width", type=int, help="Output width")
          size.add_argument("--height", type=int, help="Output height")
          size.add_argument("--units", choices=["px", "mm", "in"],
                            help="Size units (px, mm, in)")
          size.add_argument("--dpi", type=int, help="DPI for raster output")
          size.add_argument("--scale-factor", type=int,
                            help="Branch length scale factor (pixels per unit)")
          size.add_argument("--vertical-margin", type=int, default=10,
                            help="Vertical margin between branches (default: 10)")
      
          # Other options
          parser.add_argument("--title", help="Tree title")
      
          args = parser.parse_args()
      
          # Validate output format
          output_path = Path(args.output)
          valid_extensions = {".png", ".pdf", ".svg"}
          if output_path.suffix.lower() not in valid_extensions:
              print(f"Error: Output must be PNG, PDF, or SVG file")
              sys.exit(1)
      
          # Visualize
          visualize_tree(args.input, args.output, args)
      
      
      if __name__ == "__main__":
          main()
      
    • tree_operations.py 8.1 KB
      #!/usr/bin/env python3
      """
      Tree operations helper script for common ETE toolkit tasks.
      
      Provides command-line interface for basic tree operations like:
      - Format conversion
      - Rooting (outgroup, midpoint)
      - Pruning
      - Basic statistics
      - ASCII visualization
      """
      
      import argparse
      import sys
      from pathlib import Path
      
      try:
          from ete3 import Tree
      except ImportError:
          print("Error: ete3 not installed. Install with: uv pip install ete3")
          print("(These scripts target the ete3 API; ete4 renames format= to parser=.)")
          sys.exit(1)
      
      
      def load_tree(tree_file, format_num=0):
          """Load tree from file."""
          try:
              return Tree(str(tree_file), format=format_num)
          except Exception as e:
              print(f"Error loading tree: {e}")
              sys.exit(1)
      
      
      def convert_format(tree_file, output, in_format=0, out_format=1):
          """Convert tree between Newick formats."""
          tree = load_tree(tree_file, in_format)
          tree.write(outfile=str(output), format=out_format)
          print(f"Converted {tree_file} (format {in_format}) → {output} (format {out_format})")
      
      
      def reroot_tree(tree_file, output, outgroup=None, midpoint=False, format_num=0):
          """Reroot tree by outgroup or midpoint."""
          tree = load_tree(tree_file, format_num)
      
          if midpoint:
              midpoint_node = tree.get_midpoint_outgroup()
              tree.set_outgroup(midpoint_node)
              print("Rerooted tree using midpoint method")
          elif outgroup:
              try:
                  outgroup_node = tree & outgroup
                  tree.set_outgroup(outgroup_node)
                  print(f"Rerooted tree using outgroup: {outgroup}")
              except Exception as e:
                  print(f"Error: Could not find outgroup '{outgroup}': {e}")
                  sys.exit(1)
          else:
              print("Error: Must specify either --outgroup or --midpoint")
              sys.exit(1)
      
          tree.write(outfile=str(output), format=format_num)
          print(f"Saved rerooted tree to: {output}")
      
      
      def prune_tree(tree_file, output, keep_taxa, preserve_length=True, format_num=0):
          """Prune tree to keep only specified taxa."""
          tree = load_tree(tree_file, format_num)
      
          # Read taxa list
          taxa_file = Path(keep_taxa)
          if taxa_file.exists():
              with open(taxa_file) as f:
                  taxa = [line.strip() for line in f if line.strip()]
          else:
              taxa = [t.strip() for t in keep_taxa.split(",")]
      
          print(f"Pruning tree to {len(taxa)} taxa")
      
          try:
              tree.prune(taxa, preserve_branch_length=preserve_length)
              tree.write(outfile=str(output), format=format_num)
              print(f"Pruned tree saved to: {output}")
              print(f"Retained {len(tree)} leaves")
          except Exception as e:
              print(f"Error pruning tree: {e}")
              sys.exit(1)
      
      
      def tree_stats(tree_file, format_num=0):
          """Display tree statistics."""
          tree = load_tree(tree_file, format_num)
      
          print("\n=== Tree Statistics ===")
          print(f"File: {tree_file}")
          print(f"Number of leaves: {len(tree)}")
          print(f"Total nodes: {len(list(tree.traverse()))}")
      
          farthest_leaf, distance = tree.get_farthest_leaf()
          print(f"Tree depth: {distance:.4f}")
          print(f"Farthest leaf: {farthest_leaf.name}")
      
          # Branch length statistics
          branch_lengths = [node.dist for node in tree.traverse() if not node.is_root()]
          if branch_lengths:
              print("\nBranch length statistics:")
              print(f"  Mean: {sum(branch_lengths)/len(branch_lengths):.4f}")
              print(f"  Min: {min(branch_lengths):.4f}")
              print(f"  Max: {max(branch_lengths):.4f}")
      
          # Support values
          supports = [node.support for node in tree.traverse() if not node.is_leaf() and hasattr(node, 'support')]
          if supports:
              print("\nSupport value statistics:")
              print(f"  Mean: {sum(supports)/len(supports):.2f}")
              print(f"  Min: {min(supports):.2f}")
              print(f"  Max: {max(supports):.2f}")
      
          print()
      
      
      def show_ascii(tree_file, format_num=0, show_internal=True):
          """Display tree as ASCII art."""
          tree = load_tree(tree_file, format_num)
          print(tree.get_ascii(show_internal=show_internal))
      
      
      def list_leaves(tree_file, format_num=0):
          """List all leaf names."""
          tree = load_tree(tree_file, format_num)
          for leaf in tree:
              print(leaf.name)
      
      
      def main():
          parser = argparse.ArgumentParser(
              description="ETE toolkit tree operations helper",
              formatter_class=argparse.RawDescriptionHelpFormatter,
              epilog="""
      Examples:
        # Convert format
        %(prog)s convert input.nw output.nw --in-format 0 --out-format 1
      
        # Midpoint root
        %(prog)s reroot input.nw output.nw --midpoint
      
        # Reroot with outgroup
        %(prog)s reroot input.nw output.nw --outgroup "Outgroup_species"
      
        # Prune tree
        %(prog)s prune input.nw output.nw --keep-taxa "speciesA,speciesB,speciesC"
      
        # Show statistics
        %(prog)s stats input.nw
      
        # Display as ASCII
        %(prog)s ascii input.nw
      
        # List all leaves
        %(prog)s leaves input.nw
              """
          )
      
          subparsers = parser.add_subparsers(dest="command", help="Command to execute")
      
          # Convert command
          convert_parser = subparsers.add_parser("convert", help="Convert tree format")
          convert_parser.add_argument("input", help="Input tree file")
          convert_parser.add_argument("output", help="Output tree file")
          convert_parser.add_argument("--in-format", type=int, default=0, help="Input format (default: 0)")
          convert_parser.add_argument("--out-format", type=int, default=1, help="Output format (default: 1)")
      
          # Reroot command
          reroot_parser = subparsers.add_parser("reroot", help="Reroot tree")
          reroot_parser.add_argument("input", help="Input tree file")
          reroot_parser.add_argument("output", help="Output tree file")
          reroot_parser.add_argument("--outgroup", help="Outgroup taxon name")
          reroot_parser.add_argument("--midpoint", action="store_true", help="Use midpoint rooting")
          reroot_parser.add_argument("--format", type=int, default=0, help="Newick format (default: 0)")
      
          # Prune command
          prune_parser = subparsers.add_parser("prune", help="Prune tree to specified taxa")
          prune_parser.add_argument("input", help="Input tree file")
          prune_parser.add_argument("output", help="Output tree file")
          prune_parser.add_argument("--keep-taxa", required=True,
                                    help="Taxa to keep (comma-separated or file path)")
          prune_parser.add_argument("--no-preserve-length", action="store_true",
                                    help="Don't preserve branch lengths")
          prune_parser.add_argument("--format", type=int, default=0, help="Newick format (default: 0)")
      
          # Stats command
          stats_parser = subparsers.add_parser("stats", help="Display tree statistics")
          stats_parser.add_argument("input", help="Input tree file")
          stats_parser.add_argument("--format", type=int, default=0, help="Newick format (default: 0)")
      
          # ASCII command
          ascii_parser = subparsers.add_parser("ascii", help="Display tree as ASCII art")
          ascii_parser.add_argument("input", help="Input tree file")
          ascii_parser.add_argument("--format", type=int, default=0, help="Newick format (default: 0)")
          ascii_parser.add_argument("--no-internal", action="store_true",
                                    help="Don't show internal node names")
      
          # Leaves command
          leaves_parser = subparsers.add_parser("leaves", help="List all leaf names")
          leaves_parser.add_argument("input", help="Input tree file")
          leaves_parser.add_argument("--format", type=int, default=0, help="Newick format (default: 0)")
      
          args = parser.parse_args()
      
          if not args.command:
              parser.print_help()
              sys.exit(1)
      
          # Execute command
          if args.command == "convert":
              convert_format(args.input, args.output, args.in_format, args.out_format)
          elif args.command == "reroot":
              reroot_tree(args.input, args.output, args.outgroup, args.midpoint, args.format)
          elif args.command == "prune":
              prune_tree(args.input, args.output, args.keep_taxa,
                         not args.no_preserve_length, args.format)
          elif args.command == "stats":
              tree_stats(args.input, args.format)
          elif args.command == "ascii":
              show_ascii(args.input, args.format, not args.no_internal)
          elif args.command == "leaves":
              list_leaves(args.input, args.format)
      
      
      if __name__ == "__main__":
          main()
      
  • SKILL.md 6.1 KB
    ---
    name: alterlab-etetoolkit
    description: Manipulate, annotate, and render phylogenetic trees programmatically with the ETE Toolkit (ete3) — parse and edit Newick/NHX, detect duplication/speciation events, infer orthology and paralogy, query NCBI taxonomy, and export PDF/SVG figures. Use when traversing or reformatting tree files, doing phylogenomic comparative analysis, or producing publication tree graphics in Python. Part of the AlterLab Academic Skills suite.
    license: GPL-3.0
    allowed-tools: Read Write Edit Bash(python:*) Bash(uv:*)
    compatibility: "Self-contained — runs under `uv run python`. Examples target **ete3 3.1.3** (the last ete3 release, May 2023); `Tree.render()`/`show()` additionally need PyQt5. The maintained line is **ete4 4.4.0**, whose API differs (see the version note in the body). NCBITaxa downloads a ~300MB taxonomy database on first use. No API key or account required."
    metadata:
        skill-author: AlterLab
        version: "1.1.0"
        last_updated: "2026-09-23"
    ---
    
    # ETE Toolkit Skill
    
    ## Overview
    
    ETE (Environment for Tree Exploration) is a toolkit for phylogenetic and hierarchical
    tree analysis. Manipulate trees, analyze evolutionary events, visualize results, and
    integrate with biological databases for phylogenomic research and clustering analysis.
    
    ## ete3 or ete4?
    
    The examples in this skill and its references are written for **ete3 3.1.3**, which is
    still what most published pipelines import — but ete3 has had no release since May 2023,
    and **ete4 (4.4.0, Sept 2025)** is the maintained line. Port deliberately: the ete4 API is
    not drop-in.
    
    | ete3 | ete4 |
    |------|------|
    | `from ete3 import Tree` | `from ete4 import Tree` |
    | `Tree("t.nw", format=1)` | `Tree(open("t.nw"), parser=1)` — the keyword is `parser` |
    | `tree.iter_leaves()` / `get_leaves()` | `tree.leaves()` |
    | custom features via `node.add_feature()` | `node.props[...]` dictionary |
    | Qt-based `show()` / `render()` (needs PyQt5) | browser-based `explore()` with adaptive zoom, plus GTDB taxonomy support |
    
    Check which one is installed before writing code (`python -c "import ete4"`), and say which
    you targeted in the methods.
    
    ## When to Use
    
    - Parsing, traversing, or reformatting tree files (Newick / NHX / PhyloXML / NeXML)
    - Pruning, rooting, collapsing, or resolving polytomies in a tree
    - Detecting duplication/speciation events and inferring orthologs/paralogs from gene trees
    - Querying NCBI Taxonomy (taxid/name translation, lineages, taxonomy trees)
    - Producing publication-quality PDF/SVG/PNG tree figures
    - Comparing trees (Robinson-Foulds) or analyzing clustering dendrograms
    
    ## Core Capabilities
    
    ETE exposes six capability areas. Each has detailed, copy-ready code in the references
    (see Index below).
    
    1. **Tree manipulation** — I/O across formats, traversal (pre/post/levelorder), topology
       edits (prune, root, collapse), distances, RF tree comparison.
    2. **Phylogenetic analysis** — alignment linkage, species naming, duplication/speciation
       detection (Species Overlap or reconciliation), orthology/paralogy.
    3. **NCBI Taxonomy** — local cached DB, taxid↔name translation, lineage retrieval,
       taxonomy-tree building, tree annotation.
    4. **Visualization** — PNG/PDF/SVG export, rectangular/circular layouts, `NodeStyle`,
       `Face` objects, layout functions, interactive GUI.
    5. **Clustering analysis** — `ClusterTree`, data-matrix linking, silhouette/Dunn metrics,
       heatmap views.
    6. **Tree comparison** — Robinson-Foulds (raw + normalized), partition/bipartition analysis,
       batch pairwise distance matrices.
    
    ## Core Workflow
    
    The canonical minimal pattern — load, edit, save:
    
    ```python
    from ete3 import Tree
    
    # Load tree from file (format 1 = with internal node names)
    tree = Tree("tree.nw", format=1)
    
    # Prune to taxa of interest, preserving branch lengths
    tree.prune(["species1", "species2", "species3"], preserve_branch_length=True)
    
    # Midpoint root
    tree.set_outgroup(tree.get_midpoint_outgroup())
    
    # Save
    tree.write(outfile="rooted_tree.nw")
    ```
    
    For class selection: use `Tree`/`TreeNode` for generic topology work, `PhyloTree` for gene
    trees and evolutionary analysis, `ClusterTree` for dendrograms with data matrices, and
    `NCBITaxa` for taxonomy queries.
    
    ### Does NOT Trigger
    
    | Scenario | Use Instead |
    |----------|-------------|
    | Build a tree from unaligned sequences (align + model selection + ML inference) | `alterlab-phylogenetics` |
    | Distance matrices, diversity metrics, or ordination from alignments | `alterlab-scikit-bio` |
    | Tree I/O inside a broader Biopython pipeline (`Bio.Phylo`) | `alterlab-biopython` |
    | Generic graph algorithms on a non-tree network | `alterlab-networkx` |
    | Taxonomic classification of amplicon reads | `alterlab-qiime2-amplicon` |
    
    ## Command-Line Scripts
    
    - `scripts/tree_operations.py` — stats, format conversion, rerooting, pruning, ASCII view.
      Example: `python scripts/tree_operations.py reroot tree.nw rooted.nw --midpoint`
    - `scripts/quick_visualize.py` — rapid PDF/PNG rendering with circular layout, support
      coloring, and DPI control. Example:
      `python scripts/quick_visualize.py tree.nw out.pdf --mode c --color-by-support`
    
    ## Reference Index
    
    Load the relevant file when detailed information is needed:
    
    - **`references/api_reference.md`** — Complete API for all ETE classes/methods (`Tree`,
      `PhyloTree`, `ClusterTree`, `NCBITaxa`): parameters, return types, code examples.
    - **`references/workflows.md`** — Per-task workflow patterns (tree operations, phylogenetic
      analysis, comparison, taxonomy integration, clustering).
    - **`references/visualization.md`** — Full visualization guide: `TreeStyle`, `NodeStyle`,
      `Face`s, layout functions, advanced rendering.
    - **`references/use_cases.md`** — End-to-end worked use cases (phylogenomic pipeline, batch
      preprocessing, publication figures, automated multi-tree analysis).
    - **`references/setup_and_troubleshooting.md`** — Installation, NCBI Taxonomy first-run
      setup, and troubleshooting (imports, Qt rendering, memory, DB corruption).
    - **`references/newick_and_best_practices.md`** — Newick/NHX format specifications (0-100)
      and best-practice checklist.
    
    Part of the AlterLab Academic Skills suite.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related