Lesson 5 of 13 · 11 min
Distance Methods and Neighbor-Joining
Distance methods are the fastest route from a multiple sequence alignment to a tree. Instead of scoring every possible topology, they compress each pair of sequences into a single number, the evolutionary distance, and then reconstruct a tree from that matrix of numbers.
From alignment to distances
The simplest distance is the p-distance, the proportion of aligned columns at which two sequences differ. It is easy to compute but underestimates change on long branches, because two or more substitutions at the same site are counted as one. Model-corrected distances fix this: the Jukes-Cantor correction rescales a DNA p-distance as d = -3/4 ln(1 - 4/3 p), and richer models add corrections for transition and transversion bias or amino acid exchangeabilities.
from Bio import AlignIO
from Bio.Phylo.TreeConstruction import DistanceCalculator
aln = AlignIO.read("hbb_aligned.fasta", "fasta")
# "identity" gives the raw p-distance: 1 - (matching columns / total columns)
pdist = DistanceCalculator("identity").get_distance(aln)
print(pdist)human 0
chimp 0.018349 0
mouse 0.132420 0.135780 0
chicken 0.271560 0.269720 0.288991 0
human chimp mouse chicken
Neighbor-joining versus UPGMA
UPGMA builds a tree by repeatedly joining the closest clusters and assumes a molecular clock, meaning every lineage evolves at the same rate, so all tips end up equidistant from the root. Real sequences rarely obey that. Neighbor-joining drops the clock: it adjusts each pairwise distance for how diverged each sequence is from everything else, joins the pair that minimizes total tree length, and returns an unrooted tree whose branch lengths can vary freely between lineages.
# rapidnj reads the FASTA alignment directly, corrects distances with Jukes-Cantor,
# and writes an unrooted Newick tree
rapidnj hbb_aligned.fasta -i fa -a jc -o t -x hbb_nj.nwk
cat hbb_nj.nwk((human:0.0092,chimp:0.0092):0.0561,mouse:0.0714,chicken:0.1503);
In MEGA the same workflow is point and click: load the aligned FASTA, choose Phylogeny, then Construct/Test Neighbor-Joining Tree. In the dialog set Substitutions Type to Nucleotide, pick a Model/Method such as p-distance, Jukes-Cantor, or Kimura 2-parameter, set Bootstrap to 500 or 1000 replicates, and click Compute. Those are the same two decisions you make on the command line: which distance correction to apply, and how many bootstrap replicates to run.
Try it yourself: Build both trees from the same alignment and see where the clock assumption bites. In Biopython import DistanceTreeConstructor from Bio.Phylo.TreeConstruction, run constructor = DistanceTreeConstructor(), then nj = constructor.nj(pdist) and upgma = constructor.upgma(pdist) on the matrix from earlier, and print each with Bio.Phylo.draw_ascii. Check whether UPGMA forces the fast-evolving mouse lineage to the same root depth as the others, and note how the NJ branch lengths differ.
Neighbor-joining is roughly O(n^3), and heuristic implementations like rapidnj scale to thousands of sequences in seconds, but it commits to a single topology built from summary distances and cannot recover information lost in that compression, so it can be misled by saturated sites or a poorly chosen model. Treat NJ as a fast first look to sanity-check an alignment, seed a starting tree, or scan a large dataset, then move to maximum likelihood or Bayesian inference when the result has to hold up.