Skip to content
Back to skills

Bio Core Phylogenetics

ASecurity

Phylogenetics with BioPython: distance models (p-distance, JC69, K2P), UPGMA vs NJ tree construction, Newick parsing, and bootstrap interpretation.

  • 4 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added May 27, 2026
data-aipythongobashnode

Works with

  • terminal

Security analysis

A100/100

Scanned May 27, 2026

npx -y skills add Pavel-Kravchenko/Bioinformatics --skill bio-core-phylogenetics --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Bio Core Phylogenetics?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Bio Core Phylogenetics
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/pavel-kravchenko-bio-core-phylogenetics/badge)](https://www.skillsdirectory.com/skills/pavel-kravchenko-bio-core-phylogenetics)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: bio-core-phylogenetics
description: "Phylogenetics with BioPython: distance models (p-distance, JC69, K2P), UPGMA vs NJ tree construction, Newick parsing, and bootstrap interpretation."
tool_type: python
primary_tool: NumPy
---

# Phylogenetics: Reconstructing Evolutionary History

## Pitfalls

- **Tip order does not imply closeness:** Only shared branching points (nodes) indicate relationships. Rotating branches around any internal node produces an equivalent tree. Always look at which node is shared, not which tips are adjacent.
- **UPGMA assumes a molecular clock:** Produces rooted, ultrametric trees (all tips equidistant from root). This assumption is frequently violated. Neighbor-Joining (NJ) does not assume a clock — prefer NJ for most datasets.
- **Branch lengths have units:** In sequence-based trees, branch lengths = expected substitutions per site. They are not time units unless the tree is explicitly time-calibrated.
- **Bootstrap values are NOT probabilities:** Bootstrap 95 means 95% of replicates recovered that clade — not 95% probability of being correct. Thresholds: ≥70 for NJ/parsimony, ≥80 for ML.
- **Model selection matters for ML:** Default to IQ-TREE's built-in model selection (`-m TEST`) rather than hardcoding JC69. GTR+Γ+I is common for nucleotide data; LG or WAG for protein.
- **p-distance underestimates true distance:** Multiple hits at the same site are invisible. JC69 correction becomes undefined at p ≥ 0.75 (saturation).

## Distance Models

| Model | Formula | Assumption |
|---|---|---|
| p-distance | diffs / compared_sites | No correction |
| JC69 | −3/4 · ln(1 − 4p/3) | Equal rates, equal base freq |
| K2P | −½·ln(1−2S−V) − ¼·ln(1−2V) | Distinguishes transitions (S) from transversions (V) |

```python
import numpy as np

def p_distance(seq1, seq2):
    pairs = [(a, b) for a, b in zip(seq1, seq2) if a != '-' and b != '-']
    diffs = sum(1 for a, b in pairs if a != b)
    return diffs / len(pairs) if pairs else 0

def jukes_cantor(seq1, seq2):
    p = p_distance(seq1, seq2)
    if p >= 0.75:
        return float('inf')
    return -0.75 * np.log(1 - 4 * p / 3)

def kimura_k2p(seq1, seq2):
    purines, pyrimidines = set('AG'), set('CT')
    pairs = [(a, b) for a, b in zip(seq1, seq2) if a != '-' and b != '-']
    n = len(pairs)
    S = sum(1 for a, b in pairs if a != b and
            ((a in purines and b in purines) or (a in pyrimidines and b in pyrimidines))) / n
    V = sum(1 for a, b in pairs if a != b and
            not ((a in purines and b in purines) or (a in pyrimidines and b in pyrimidines))) / n
    t1, t2 = 1 - 2*S - V, 1 - 2*V
    if t1 <= 0 or t2 <= 0:
        return float('inf')
    return -0.5 * np.log(t1) - 0.25 * np.log(t2)
```

## BioPython Tree Construction

```python
from Bio import AlignIO, Phylo
from Bio.Phylo.TreeConstruction import DistanceCalculator, DistanceTreeConstructor
import io

# From an alignment
aln = AlignIO.read("alignment.fasta", "fasta")
calc = DistanceCalculator('identity')   # or 'blosum62' for protein
dm = calc.get_distance(aln)

constructor = DistanceTreeConstructor()
nj_tree   = constructor.nj(dm)      # Neighbor-Joining (preferred)
upgma_tree = constructor.upgma(dm)  # UPGMA (assumes clock)

Phylo.draw_ascii(nj_tree)
nj_tree.distance('Human', 'Chimp')  # pairwise distance on tree
```

## Newick Format

```python
import io
from Bio import Phylo

newick = "((Human:0.01,Chimp:0.012):0.02,(Mouse:0.25,Rat:0.23):0.3,Zebrafish:0.6);"
tree = Phylo.read(io.StringIO(newick), "newick")

# Navigate
tree.get_terminals()          # leaf nodes
tree.get_nonterminals()       # internal nodes
tree.distance('Human', 'Chimp')
tree.common_ancestor('Human', 'Chimp')
```

## Assembler/Tool Reference

| Tool | Algorithm | Use case |
|---|---|---|
| BioPython `DistanceTreeConstructor` | NJ, UPGMA | Quick trees from distance matrix |
| IQ-TREE2 | ML + model selection | Publication-quality ML trees |
| RAxML-NG | ML | Large datasets, fast |
| MrBayes | Bayesian | Posterior probability support |
| FastTree | Approximate ML | Very large alignments |

```bash
# IQ-TREE with automatic model selection and 1000 ultrafast bootstraps
iqtree2 -s alignment.fasta -m TEST -B 1000 -T AUTO
```

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…