Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Modify phylogenetic tree structure using Biopython Bio.Phylo. Use when rooting trees with outgroups or midpoint, pruning taxa, collapsing clades, ladderizing branches, or extracting subtrees.
.claude/skills/bio-phylo-tree-manipulation/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 35% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 26% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 250% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 56% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 108% | 0% |
<!--
#
#
-->
Modify phylogenetic tree structure: rooting, pruning, ladderizing, and subtree extraction.
pythonfrom Bio import Phylo from io import StringIO
pythontree = Phylo.read('tree.nwk', 'newick') # Root with single taxon tree.root_with_outgroup({'name': 'Outgroup'}) # Root with multiple taxa (must be monophyletic) outgroup = [{'name': 'TaxonA'}, {'name': 'TaxonB'}] if tree.is_monophyletic(outgroup): tree.root_with_outgroup(*outgroup) else: print('Outgroup is not monophyletic')
pythontree = Phylo.read('tree.nwk', 'newick') tree.root_at_midpoint()
python# Check if tree is rooted (bifurcating at root) print(f'Is bifurcating: {tree.is_bifurcating()}') # Count children of root root = tree.root print(f'Root has {len(root.clades)} children') # 2 children = rooted, 3+ children = unrooted
Sort clades for consistent visual presentation.
pythontree = Phylo.read('tree.nwk', 'newick') # Larger clades at bottom tree.ladderize() # Larger clades at top tree.ladderize(reverse=True) Phylo.write(tree, 'ladderized.nwk', 'newick')
pythontree = Phylo.read('tree.nwk', 'newick') # Find and remove a taxon target = tree.find_any(name='TaxonToRemove') if target: tree.prune(target) # Remove multiple taxa for name in ['TaxonA', 'TaxonB', 'TaxonC']: target = tree.find_any(name=name) if target: tree.prune(target)
pythontree = Phylo.read('tree.nwk', 'newick') keep_taxa = {'Human', 'Chimp', 'Gorilla'} terminals = tree.get_terminals() for term in terminals: if term.name not in keep_taxa: tree.prune(term)
Collapse branches below a threshold.
pythontree = Phylo.read('tree.nwk', 'newick') # Collapse single clade target = tree.find_any(name='SomeInternalNode') if target: tree.collapse(target) # Collapse all clades matching criteria (branch length threshold) tree.collapse_all(lambda c: c.branch_length and c.branch_length < 0.01) # Collapse all poorly-supported nodes tree.collapse_all(lambda c: c.confidence is not None and c.confidence < 70)
pythontree = Phylo.read('tree.nwk', 'newick') # Find common ancestor of taxa clade = tree.common_ancestor({'name': 'Human'}, {'name': 'Chimp'}) # The clade itself can be treated as a subtree Phylo.draw_ascii(clade) # Get all terminals in this clade subtree_taxa = [t.name for t in clade.get_terminals()] print(f'Subtree contains: {subtree_taxa}')
pythontree = Phylo.read('tree.nwk', 'newick') # Find MRCA (Most Recent Common Ancestor) taxa = [{'name': 'Human'}, {'name': 'Chimp'}, {'name': 'Gorilla'}] mrca = tree.common_ancestor(*taxa) print(f'MRCA branch length: {mrca.branch_length}')
pythontree = Phylo.read('tree.nwk', 'newick') # Iterate all clades (preorder by default) for clade in tree.find_clades(): print(clade.name, clade.branch_length) # Level-order traversal (breadth-first) for clade in tree.find_clades(order='level'): print(clade.name) # Postorder traversal for clade in tree.find_clades(order='postorder'): print(clade.name) # Only terminal nodes for term in tree.get_terminals(): print(term.name) # Only internal nodes for internal in tree.get_nonterminals(): print(internal)
pythontree = Phylo.read('tree.nwk', 'newick') # Find by name clade = tree.find_any(name='Human') # Find all matching criteria matches = tree.find_clades(branch_length=lambda x: x and x > 0.5) for m in matches: print(f'{m.name}: {m.branch_length}') # Find by terminal status terminals = list(tree.find_clades(terminal=True)) internals = list(tree.find_clades(terminal=False))
pythontree = Phylo.read('tree.nwk', 'newick') # Path from root to a node target = tree.find_any(name='Human') path = tree.get_path(target) print(f'Path from root to Human: {len(path)} nodes') for clade in path: print(f' {clade.name}: {clade.branch_length}') # Trace path between any two nodes human = tree.find_any(name='Human') mouse = tree.find_any(name='Mouse') trace = tree.trace(human, mouse) print(f'Path Human to Mouse: {len(trace)} nodes')
pythontree = Phylo.read('tree.nwk', 'newick') # Check if monophyletic taxa = [tree.find_any(name='Human'), tree.find_any(name='Chimp')] taxa = [t for t in taxa if t is not None] print(f'Is monophyletic: {tree.is_monophyletic(taxa)}') # Check if bifurcating print(f'Is bifurcating: {tree.is_bifurcating()}') # Check if preterminal (parent of only terminals) for clade in tree.get_nonterminals(): print(f'{clade}: is_preterminal={clade.is_preterminal()}')
pythontree = Phylo.read('tree.nwk', 'newick') # Set missing branch lengths for clade in tree.find_clades(): if clade.branch_length is None: clade.branch_length = 0.0 # Scale all branch lengths scale_factor = 100 # Convert to percent divergence for clade in tree.find_clades(): if clade.branch_length: clade.branch_length *= scale_factor # Remove branch lengths (convert to cladogram) for clade in tree.find_clades(): clade.branch_length = None
pythontree = Phylo.read('tree.nwk', 'newick') # Rename individual taxon target = tree.find_any(name='OldName') if target: target.name = 'NewName' # Batch rename from mapping name_map = {'Hsap': 'Human', 'Ptro': 'Chimp', 'Mmus': 'Mouse'} for term in tree.get_terminals(): if term.name in name_map: term.name = name_map[term.name] Phylo.write(tree, 'renamed.nwk', 'newick')
pythontree = Phylo.read('tree.nwk', 'newick') n_terminals = len(tree.get_terminals()) n_internals = len(tree.get_nonterminals()) n_total = tree.count_terminals() + len(tree.get_nonterminals()) print(f'Terminals: {n_terminals}') print(f'Internal nodes: {n_internals}') print(f'Total nodes: {n_total}')
pythontree = Phylo.read('tree.nwk', 'newick') # Get depths from root depths = tree.depths() for clade, depth in depths.items(): if clade.is_terminal(): print(f'{clade.name}: depth={depth:.3f}') # Get maximum depth (tree height) max_depth = max(depths.values()) print(f'Tree height: {max_depth:.3f}')
pythontree = Phylo.read('tree.nwk', 'newick') # Split a terminal into multiple children target = tree.find_any(name='TaxonA') if target and target.is_terminal(): target.split(n=2, branch_length=0.05) # Creates 2 children # Split with specific branch lengths target.split(branch_length=[0.1, 0.2, 0.3]) # Creates 3 children
pythonfrom Bio.Phylo.BaseTree import Tree # Generate random bifurcating tree taxa = ['Human', 'Chimp', 'Gorilla', 'Mouse', 'Rat'] random_tree = Tree.randomized(taxa) Phylo.draw_ascii(random_tree) # With branch lengths random_tree = Tree.randomized(taxa, branch_length=1.0)
| Method | Description | |--------|-------------| | root_with_outgroup() | Reroot using outgroup | | root_at_midpoint() | Reroot at midpoint | | ladderize() | Sort branches by size | | prune() | Remove a clade | | collapse() | Collapse a clade into polytomy | | collapse_all() | Collapse all matching clades | | split() | Split clade into children | | trace() | Get path between two clades | | Tree.randomized() | Generate random tree | | common_ancestor() | Find MRCA of taxa | | find_any() | Find first matching clade | | find_clades() | Find all matching clades | | get_path() | Get path from root to clade | | depths() | Get depth of all clades | | is_monophyletic() | Check if taxa form clade | | is_bifurcating() | Check if tree is binary |
<!-- AUTHOR_SIGNATURE: 9a7f3c2e-MD-BABU-MIA-2026-MSSM-SECURE -->
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 17,436 | 9,189 | -47% | 1 | 1 | 0% | 3,503 | 4,738 | +35% | 0 | 0 | — |
case-02 | fail→pass | 17,791 | 8,480 | -52% | 1 | 1 | 0% | 3,662 | 4,623 | +26% | 0 | 0 | — |
case-03 | fail→pass | 5,581 | 6,207 | +11% | 1 | 1 | 0% | 1,113 | 3,895 | +250% | 0 | 0 | — |
case-04 | pass→pass | 9,875 | 5,640 | -43% | 1 | 1 | 0% | 2,070 | 3,990 | +93% | 0 | 0 | — |
case-05 | pass→pass | 9,192 | 3,359 | -63% | 1 | 1 | 0% | 1,740 | 3,268 | +88% | 0 | 0 | — |
case-06 | fail→pass | 10,446 | 2,427 | -77% | 1 | 1 | 0% | 2,104 | 3,272 | +56% | 0 | 0 | — |
case-11 | pass→pass | 8,958 | 4,292 | -52% | 1 | 1 | 0% | 1,659 | 3,223 | +94% | 0 | 0 | — |
case-07 | pass→pass | 8,437 | 5,686 | -33% | 1 | 1 | 0% | 1,856 | 3,978 | +114% | 0 | 0 | — |
case-08 | fail→pass | 7,856 | 3,966 | -50% | 1 | 1 | 0% | 1,722 | 3,582 | +108% | 0 | 0 | — |
case-09 | pass→pass | 8,836 | 3,199 | -64% | 1 | 1 | 0% | 1,672 | 3,402 | +103% | 0 | 0 | — |
case-10 | pass→pass | 4,888 | 3,340 | -32% | 1 | 1 | 0% | 927 | 3,352 | +262% | 0 | 0 | — |
case-12 | pass→pass | 9,180 | 2,696 | -71% | 1 | 1 | 0% | 1,791 | 3,328 | +86% | 0 | 0 | — |
case-13 | pass→pass | 5,000 | 3,217 | -36% | 1 | 1 | 0% | 1,074 | 3,531 | +229% | 0 | 0 | — |
case-14 | pass→pass | 11,536 | 2,360 | -80% | 1 | 1 | 0% | 1,767 | 3,274 | +85% | 0 | 0 | — |
case-15 | pass→pass | 9,590 | 3,837 | -60% | 1 | 1 | 0% | 1,868 | 3,559 | +91% | 0 | 0 | — |
case-16 | fail→pass | 9,621 | 4,086 | -58% | 1 | 1 | 0% | 1,949 | 3,590 | +84% | 0 | 0 | — |
case-17 | fail→pass | 10,243 | 3,132 | -69% | 1 | 1 | 0% | 2,113 | 3,471 | +64% | 0 | 0 | — |
case-18 | fail→pass | 7,527 | 5,265 | -30% | 1 | 1 | 0% | 1,452 | 3,795 | +161% | 0 | 0 | — |
case-19 | pass→pass | 10,039 | 5,177 | -48% | 1 | 1 | 0% | 1,953 | 3,774 | +93% | 0 | 0 | — |
case-20 | pass→pass | 8,319 | 4,832 | -42% | 1 | 1 | 0% | 1,730 | 3,817 | +121% | 0 | 0 | — |
case-21 | pass→pass | 5,697 | 4,300 | -25% | 1 | 1 | 0% | 1,148 | 3,599 | +214% | 0 | 0 | — |
case-22 | pass→pass | 11,712 | 4,966 | -58% | 1 | 1 | 0% | 2,154 | 3,724 | +73% | 0 | 0 | — |
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. The headline lift of +36 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
| Model | Method | Date | Lift |
|---|---|---|---|
| gemini-3.6-flash | verified | 7/24/2026 | +25% |
Other measured skills in the registry, with their headline benchmark lift.