install
source · Clone the upstream repo
git clone https://github.com/FreedomIntelligence/OpenClaw-Medical-Skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/FreedomIntelligence/OpenClaw-Medical-Skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/bio-phylo-tree-manipulation" ~/.claude/skills/freedomintelligence-openclaw-medical-skills-bio-phylo-tree-manipulation && rm -rf "$T"
OpenClaw · Install into ~/.openclaw/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/FreedomIntelligence/OpenClaw-Medical-Skills "$T" && mkdir -p ~/.openclaw/skills && cp -r "$T/skills/bio-phylo-tree-manipulation" ~/.openclaw/skills/freedomintelligence-openclaw-medical-skills-bio-phylo-tree-manipulation && rm -rf "$T"
manifest:
skills/bio-phylo-tree-manipulation/SKILL.mdsource content
<!--
# COPYRIGHT NOTICE
# This file is part of the "Universal Biomedical Skills" project.
# Copyright (c) 2026 MD BABU MIA, PhD <md.babu.mia@mssm.edu>
# All Rights Reserved.
#
# This code is proprietary and confidential.
# Unauthorized copying of this file, via any medium is strictly prohibited.
#
# Provenance: Authenticated by MD BABU MIA
-->
name: bio-phylo-tree-manipulation description: 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. tool_type: python primary_tool: Bio.Phylo measurable_outcome: Execute skill workflow successfully with valid output within 15 minutes. allowed-tools:
- read_file
- run_shell_command
Tree Manipulation
Modify phylogenetic tree structure: rooting, pruning, ladderizing, and subtree extraction.
Required Import
from Bio import Phylo from io import StringIO
Rooting Trees
Root with Outgroup
tree = 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')
Root at Midpoint
tree = Phylo.read('tree.nwk', 'newick') tree.root_at_midpoint()
Check Rooting Status
# 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
Ladderizing
Sort clades for consistent visual presentation.
tree = 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')
Pruning Trees
Remove Specific Taxa
tree = 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)
Keep Only Specified Taxa
tree = 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)
Collapsing Clades
Collapse branches below a threshold.
tree = 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)
Extracting Subtrees
Get Clade as Subtree
tree = 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}')
Extract Subtree by Common Ancestor
tree = 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}')
Tree Traversal
tree = 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)
Finding Clades
tree = 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))
Getting Path Between Nodes
tree = 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')
Checking Tree Properties
tree = 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()}')
Modifying Branch Lengths
tree = 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
Renaming Taxa
tree = 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')
Counting Nodes
tree = 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}')
Tree Depths
tree = 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}')
Splitting Clades
tree = 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
Generating Random Trees
from 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)
Quick Reference: Tree Methods
| Method | Description |
|---|---|
| Reroot using outgroup |
| Reroot at midpoint |
| Sort branches by size |
| Remove a clade |
| Collapse a clade into polytomy |
| Collapse all matching clades |
| Split clade into children |
| Get path between two clades |
| Generate random tree |
| Find MRCA of taxa |
| Find first matching clade |
| Find all matching clades |
| Get path from root to clade |
| Get depth of all clades |
| Check if taxa form clade |
| Check if tree is binary |
Related Skills
- tree-io - Read and write tree files
- tree-visualization - Draw modified trees
- distance-calculations - Build trees from alignments