本节摘要:系统发育树把"共同祖先"从论断变成可计算的对象。本节讲三类建树方法的思想差异——距离法(UPGMA/邻接)、简约法、似然法/贝叶斯——并用纯 Python 实现一个 UPGMA,亲手从距离矩阵长出一棵树;同时明确树的读法(节点、单系群)与三大陷阱(长枝吸引、基因树≠物种树、并系群误用)。
一棵树的要素:末端 taxa(现存或化石类群)、内部节点(推断的共同祖先)、分支(祖先态到后代态的演化线)。关键守则是:旋转不改变亲缘——同一节点下的左右子树可以任意翻转,长相"靠近"毫无意义,只有"共享最近共同祖先"才是亲缘。所谓单系群,即"包含某共同祖先及其全部后代"的群;并系群(掐掉部分后代,如"爬行类"不含鸟类)与复系群(按趋同性状凑堆,如"会飞的")在现代分类里都不合法。
三类方法的分工直觉:
UPGMA(非加权组平均法)假设分子钟近似成立,不断把"距离最小的两个类簇"合并,节点高度取两簇距离的一半。
def upgma(D, taxa): """D: 距离 dict{(i,j):d}; taxa: 类群名列表; 返回 Newick 串与合并记录""" clusters = {t: [t] for t in taxa} dist = {(min(a,b), max(a,b)): v for (a,b), v in D.items()} steps = [] while len(clusters) > 1: # 找当前距离最小的一对簇 (a, b), dmin = min( ((k, v) for k, v in dist.items() if k[0] in clusters and k[1] in clusters), key=lambda kv: kv[1]) h = dmin / 2 newname = f"({a},{b})" steps.append(f"合并 {a} + {b},节点高度 {h:.2f}") # 新簇与旧簇的平均距离 members = clusters.pop(a) + clusters.pop(b) clusters[newname] = members for c in list(clusters): if c == newname: continue vals = [dist.get((min(x,y), max(x,y)), 0.0) for x in members for y in clusters[c]] dist[(min(c,newname), max(c,newname))] = sum(vals)/len(vals) # 清掉已合并簇的距离条目 dist = {k: v for k, v in dist.items() if k[0] in clusters and k[1] in clusters} root = next(iter(clusters)) return root + ";", steps taxa = ["人", "黑猩猩", "大猩猩", "猩猩", "长臂猿"] D = {("人","黑猩猩"):0.10, ("人","大猩猩"):0.14, ("人","猩猩"):0.24, ("人","长臂猿"):0.32, ("黑猩猩","大猩猩"):0.13, ("黑猩猩","猩猩"):0.25, ("黑猩猩","长臂猿"):0.33, ("大猩猩","猩猩"):0.26, ("大猩猩","长臂猿"):0.34, ("猩猩","长臂猿"):0.35} newick, steps = upgma(D, taxa) for s in steps: print(s) print("Newick:", newick)
输出顺序是:人与黑猩猩先并、再揽入大猩猩、再猩猩、最后长臂猿——与类人猿系统发育的共识一致。steps 里每一步的节点高度还能读出大致分化时间(分子钟标定后)。注意 UPGMA 的钟假设是它的命门:各谱系速率若不齐(现实中常见),它会强行把树压成等高的错误形状,实战多用放松钟假设的邻接法或似然法。
import random def incomplete_lineage_sort(n_loci=100, seed=0): """祖先群体多态性在不同基因上随机归类,导致部分基因树与物种树不一致""" rng = random.Random(seed) # 物种树: ((人, 黑猩猩), 大猩猩)。分化快、祖先群体大时冲突升高 agree = 0 for _ in range(n_loci): # 每个基因座:祖先等位在"人-黑猩猩祖先群"里随机分家 if rng.random() < 0.72: # 一致概率约 0.7(受 4Nc 影响) agree += 1 return agree / n_loci for trial in range(3): print(f"基因树与物种树一致比例: {incomplete_lineage_sort(seed=trial):.0%}")
约三成基因座给出与物种树冲突的拓扑——这就是不完全谱系分选(incomplete lineage sorting)。人与黑猩猩分离太快、共同祖先群体太大,祖先多态尚未梳理干净就各自固定,不同基因座随机保留不同的旧组合。它是"基因树 ≠ 物种树"的首要原因,也正是人-黑猩猩-大猩猩关系需要用多基因合并法(如 ASTRAL 类思路)裁决的原因。
⚠️ 三大陷阱备忘:长枝吸引(两条快速演化谱系被简约法错聚,因趋同噪声叠加);HGT 与不完全分选(见上);并系群表述("鱼类"不含四足类,却含其祖先——日常语言方便,科学分类非法)。
| 方法 | 输入 | 假设 | 速度 | 适用 |
|---|---|---|---|---|
| 距离法 UPGMA | 距离矩阵 | 严格分子钟 | 极快 | 初筛、教学 |
| 距离法 邻接 | 距离矩阵 | 近似可加 | 快 | 大数据快速建树 |
| 最大简约 | 比对序列 | 少事件为真 | 中 | 近缘、演化慢 |
| 最大似然/贝叶斯 | 比对序列+模型 | 显式替换模型 | 慢 | 正式发表的默认 |
第 5 章完成了"从序列到全局"。下一章回到可实时观察的进化:野外与实验室里正在发生的适应。