Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Phylogenetic tree toolkit (ETE). Tree manipulation (Newick/NHX), evolutionary event detection, orthology/paralogy, NCBI taxonomy, visualization (PDF/SVG), for phylogenomics.
.claude/skills/etetoolkit/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-07 | ✗→✓ | ▲ Improved | — | — |
| case-02 | ✗→✓ | ▲ Improved | — | — |
| case-12 | ✗→✓ | ▲ Improved | — | — |
| case-01 | ✗→✓ | ▲ Improved | — | — |
| case-21 | ✗→✓ | ▲ Improved | — | — |
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.
Load, manipulate, and analyze hierarchical tree structures with support for:
Common patterns:
pythonfrom ete3 import Tree # Load tree from file tree = Tree("tree.nw", format=1) # Basic statistics print(f"Leaves: {len(tree)}") print(f"Total nodes: {len(list(tree.traverse()))}") # Prune to taxa of interest taxa_to_keep = ["species1", "species2", "species3"] tree.prune(taxa_to_keep, preserve_branch_length=True) # Midpoint root midpoint = tree.get_midpoint_outgroup() tree.set_outgroup(midpoint) # Save modified tree tree.write(outfile="rooted_tree.nw")
Use scripts/tree_operations.py for command-line tree manipulation:
bash# Display tree statistics python scripts/tree_operations.py stats tree.nw # Convert format python scripts/tree_operations.py convert tree.nw output.nw --in-format 0 --out-format 1 # Reroot tree python scripts/tree_operations.py reroot tree.nw rooted.nw --midpoint # Prune to specific taxa python scripts/tree_operations.py prune tree.nw pruned.nw --keep-taxa "sp1,sp2,sp3" # Show ASCII visualization python scripts/tree_operations.py ascii tree.nw
Analyze gene trees with evolutionary event detection:
Workflow for gene tree analysis:
pythonfrom ete3 import PhyloTree # Load gene tree with alignment tree = PhyloTree("gene_tree.nw", alignment="alignment.fasta") # Set species naming function def get_species(gene_name): return gene_name.split("_")[0] tree.set_species_naming_function(get_species) # Detect evolutionary events events = tree.get_descendant_evol_events() # Analyze events for node in tree.traverse(): if hasattr(node, "evoltype"): if node.evoltype == "D": print(f"Duplication at {node.name}") elif node.evoltype == "S": print(f"Speciation at {node.name}") # Extract ortholog groups ortho_groups = tree.get_speciation_trees() for i, ortho_tree in enumerate(ortho_groups): ortho_tree.write(outfile=f"ortholog_group_{i}.nw")
Finding orthologs and paralogs:
python# Find orthologs to query gene query = tree & "species1_gene1" orthologs = [] paralogs = [] for event in events: if query in event.in_seqs: if event.etype == "S": orthologs.extend([s for s in event.out_seqs if s != query]) elif event.etype == "D": paralogs.extend([s for s in event.out_seqs if s != query])
Integrate taxonomic information from NCBI Taxonomy database:
Building taxonomy-based trees:
pythonfrom ete3 import NCBITaxa ncbi = NCBITaxa() # Build tree from species names species = ["Homo sapiens", "Pan troglodytes", "Mus musculus"] name2taxid = ncbi.get_name_translator(species) taxids = [name2taxid[sp][0] for sp in species] # Get minimal tree connecting taxa tree = ncbi.get_topology(taxids) # Annotate nodes with taxonomy info for node in tree.traverse(): if hasattr(node, "sci_name"): print(f"{node.sci_name} - Rank: {node.rank} - TaxID: {node.taxid}")
Annotating existing trees:
python# Get taxonomy info for tree leaves for leaf in tree: species = extract_species_from_name(leaf.name) taxid = ncbi.get_name_translator([species])[species][0] # Get lineage lineage = ncbi.get_lineage(taxid) ranks = ncbi.get_rank(lineage) names = ncbi.get_taxid_translator(lineage) # Add to node leaf.add_feature("taxid", taxid) leaf.add_feature("lineage", [names[t] for t in lineage])
Create publication-quality tree visualizations:
Basic visualization workflow:
pythonfrom ete3 import Tree, TreeStyle, NodeStyle tree = Tree("tree.nw") # Configure tree style ts = TreeStyle() ts.show_leaf_name = True ts.show_branch_support = True ts.scale = 50 # pixels per branch length unit # Style nodes for node in tree.traverse(): nstyle = NodeStyle() if node.is_leaf(): nstyle["fgcolor"] = "blue" nstyle["size"] = 8 else: # Color by support if node.support > 0.9: nstyle["fgcolor"] = "darkgreen" else: nstyle["fgcolor"] = "red" nstyle["size"] = 5 node.set_style(nstyle) # Render to file tree.render("tree.pdf", tree_style=ts) tree.render("tree.png", w=800, h=600, units="px", dpi=300)
Use scripts/quick_visualize.py for rapid visualization:
bash# Basic visualization python scripts/quick_visualize.py tree.nw output.pdf # Circular layout with custom styling python scripts/quick_visualize.py tree.nw output.pdf --mode c --color-by-support # High-resolution PNG python scripts/quick_visualize.py tree.nw output.png --width 1200 --height 800 --units px --dpi 300 # Custom title and styling python scripts/quick_visualize.py tree.nw output.pdf --title "Species Phylogeny" --show-support
Advanced visualization with faces:
pythonfrom ete3 import Tree, TreeStyle, TextFace, CircleFace tree = Tree("tree.nw") # Add features to nodes for leaf in tree: leaf.add_feature("habitat", "marine" if "fish" in leaf.name else "land") # Layout function def layout(node): if node.is_leaf(): # Add colored circle color = "blue" if node.habitat == "marine" else "green" circle = CircleFace(radius=5, color=color) node.add_face(circle, column=0, position="aligned") # Add label label = TextFace(node.name, fsize=10) node.add_face(label, column=1, position="aligned") ts = TreeStyle() ts.layout_fn = layout ts.show_leaf_name = False tree.render("annotated_tree.pdf", tree_style=ts)
Analyze hierarchical clustering results with data integration:
Clustering workflow:
pythonfrom ete3 import ClusterTree # Load tree with data matrix matrix = """#Names\tSample1\tSample2\tSample3 Gene1\t1.5\t2.3\t0.8 Gene2\t0.9\t1.1\t1.8 Gene3\t2.1\t2.5\t0.5""" tree = ClusterTree("((Gene1,Gene2),Gene3);", text_array=matrix) # Evaluate cluster quality for node in tree.traverse(): if not node.is_leaf(): silhouette = node.get_silhouette() dunn = node.get_dunn() print(f"Cluster: {node.name}") print(f" Silhouette: {silhouette:.3f}") print(f" Dunn index: {dunn:.3f}") # Visualize with heatmap tree.show("heatmap")
Quantify topological differences between trees:
Compare two trees:
pythonfrom ete3 import Tree tree1 = Tree("tree1.nw") tree2 = Tree("tree2.nw") # Calculate RF distance rf, max_rf, common_leaves, parts_t1, parts_t2 = tree1.robinson_foulds(tree2) print(f"RF distance: {rf}/{max_rf}") print(f"Normalized RF: {rf/max_rf:.3f}") print(f"Common leaves: {len(common_leaves)}") # Find unique partitions unique_t1 = parts_t1 - parts_t2 unique_t2 = parts_t2 - parts_t1 print(f"Unique to tree1: {len(unique_t1)}") print(f"Unique to tree2: {len(unique_t2)}")
Compare multiple trees:
pythonimport numpy as np trees = [Tree(f"tree{i}.nw") for i in range(4)] # 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]) norm_rf = rf / max_rf if max_rf > 0 else 0 dist_matrix[i, j] = norm_rf dist_matrix[j, i] = norm_rf
Install ETE toolkit:
bash# Basic installation uv pip install ete3 # 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 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:
pythonfrom ete3 import NCBITaxa ncbi = NCBITaxa() # Downloads database on first run
Update taxonomy database:
pythonncbi.update_taxonomy_database() # Download latest NCBI data
Complete workflow from gene tree to ortholog identification:
pythonfrom 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")
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
Create styled visualizations:
pythonfrom 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
Process multiple trees systematically:
pythonfrom 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)
For comprehensive API documentation, code examples, and detailed guides, refer to the following resources in the references/ directory:
api_reference.md: Complete API documentation for all ETE classes and methods (Tree, PhyloTree, ClusterTree, NCBITaxa), including parameters, return types, and code examplesworkflows.md: Common workflow patterns organized by task (tree operations, phylogenetic analysis, tree comparison, taxonomy integration, clustering analysis)visualization.md: Comprehensive visualization guide covering TreeStyle, NodeStyle, Faces, layout functions, and advanced visualization techniquesLoad these references when detailed information is needed:
python# To use API reference # Read references/api_reference.md for complete method signatures and parameters # To implement workflows # Read references/workflows.md for step-by-step workflow examples # To create visualizations # Read references/visualization.md for styling and rendering options
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:
pythonfrom 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)
ETE supports multiple Newick format specifications (0-100):
Specify format when reading/writing:
pythontree = Tree("tree.nw", format=1) tree.write(outfile="output.nw", format=5)
NHX (New Hampshire eXtended) format preserves custom features:
pythontree.write(outfile="tree.nhx", features=["habitat", "temperature", "depth"])
preserve_branch_length=True when pruning for phylogenetic analysisget_cached_content() for repeated access to node contents on large treesiter_* methods for memory-efficient processing of large treestree.show() to test visualizations before rendering to file| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-04 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted, and 21 counted toward the lift figure. The other 1 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +32 percentage points is the difference between those two pass rates over the 21 comparable cases.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.