教程

SplitAligner:从头到尾走一遍

把一棵物种树和一组基因树,变成一张枝坐标矩阵——每根枝要么携带数值,要么明确说出它为什么不能。本教程使用随附的示例,你可以在自己的机器上复现每一份输出。

SplitAligner 是一个小巧、几乎无依赖的 Perl 程序。无需编译——克隆仓库、运行 SplitAligner.pl 即可。下面的命令对应 examples/run.sh 中的实际步骤。
为什么

分枝身份为什么是个问题

比较基因组学的大多数问题,都是靠比较「挂在某根分枝上的一个数值」——速率、残差、路径长度——在许多基因之间的差异来回答的。这个系统的速率上升了吗?这里的约束放松了吗?只有当那根分枝在每一个参与比较的基因里都指同一个进化对象时,这种比较才有意义。

实际情况往往不是这样。不同基因回收到的类群不同,基因树也未必与物种树一致。当某个类群缺失时,参考树上相邻的两根分枝可能坍缩成一根。融合后那根分枝的长度完全正确——它是各组成部分之和——但它已经不再是形成它的任何一根分枝了。

把这个数值记在其中一根名下的流程,产生的是一个数值正确、而生物学归属错误的值。程序不会崩溃,不会出现 NA,任何只检查算术是否正确的检验都抓不到它。实际后果是:一个基因在某个系统上看起来异常快或异常慢,原因可能与那个系统上的进化毫无关系;而如果没有一份「哪些单元格受了影响」的记录,就无法把这类情况和真实情况区分开。

SplitAligner 的应对是把分枝身份显式化,并为分枝失去可观测性的每一种方式各起一个名字。动画讲解用视觉方式讲同一个论证;本页余下部分讲的是怎么运行它。

思想

一根枝,就是它所作的那个划分

SplitAligner 识别一根枝,靠的不是它画在哪里、也不是节点顺序,而是它在类群上诱导出的二分划分(split)。对于只采样了部分类群 Tg 的基因 g,把物种树的每根枝 b 投影到实际存在的类群上:

σg(b) = ( Ab ∩ Tg | Bb ∩ Tg )

在与物种树限制拓扑一致的基准层中,两根未被删除的原始枝投影划分相同,当且仅当它们落到同一条约化枝上。这并不是说两根原始枝变成了同一根;而是它们在该基因的类群集合下无法再独立区分,形成融合。若投影后有一侧为空,则是结构性缺失。对于经验 free 拓扑基因树,SplitAligner 还会另行判断每个可独立观测的投影划分是否被找回。

SplitAligner 的算法是表示非依赖的。既然枝的身份就是它的划分,一棵树怎么写就无关紧要:比较之前,基因树的每一条枝都会先归约为规范化的无根划分。因此基因树可以有根或无根、二叉或多叉、子节点顺序任意;有根的输入一律按无根树处理:二度根处的两条半枝合并为同一个划分;两者都是有限数值时枝长相加,否则该划分不带数值枝长。拓扑可以约束在物种树上(fix),也可以完全自由。实际使用只有两点要求:每棵基因树的类群必须是物种树类群的子集,名称完全一致;基因树应带枝长,因为枝长正是要填进矩阵的值。这一点已由自带示例上的序列化与输入变体回归测试验证:等价输入给出相同的矩阵,物种树不变时逐字节相同,物种树换一种写法时经 B 别名映射后逐格相同。

# Split-based branch-mapping algorithm

# 第一层 —— 覆盖层:只用 S 和 T_g,完全不看基因树
for 每个基因 g:
    T_g ← 基因树的类群(S 的子集)                               # 步骤 1
    for 每根物种树枝 b:                                     # 步骤 2–3
        σ_g(b) ← canon(A_b ∩ T_g | B_b ∩ T_g)                 # 投影
        if σ_g(b) 有一侧为空:
            state(g,b) ← NA_struct                            # 没有投影身份
        else if b 是内部枝且 σ_g(b) 有一侧只剩一个类群 x:
            端点塌缩:σ_g(b) 与 x 的终端枝划分相同,
            所以 b 只做融合记账,永远不作为独立枝
    把保留下来的枝按 σ_g(b) 分组                                # 步骤 4
        成员 ≥ 2 的组是融合组:成员在 T_g 上无法区分,
        共用一个复合坐标 b|b'
    此时每个保留的 (g,b) 身份已定,值尚未知

# 第二层 —— 基因树层:每个保留的投影划分查一次基因树
    Σ(G_g) ← G_g 的规范无根划分                               # 步骤 5
    for 每个保留的投影划分 σ 及其分组:
        if σ ∉ Σ(G_g):            所有成员记 generic NA        # 原因尚未命名
        else if 组内成员 ≥ 2:
                                所有成员记 NA_fuse              # 枝长记在 b|b' 上
        else:                   Mapped —— 记录枝长

# 第三层 —— finalize:用配对的 fixed/free 证据给剩下的 NA 命名
    NA_fuse 只在复合坐标 b|b' 带有数值枝长时成立
    free 侧的 generic NA 只有在 fixed 侧原始坐标为 Mapped 且带数值时
    才成为 NA_topo;否则保持为 residual NA

整个流程有两个模式:matrix 构建坐标系并把所有基因投影上去;finalize 再根据成对证据区分缺失状态。

1

安装

你需要 Perl 5(macOS 和大多数 Linux 自带)。SplitAligner 只用核心模块——无需安装任何 CPAN 包。

# 克隆仓库
git clone https://github.com/wujiaqi06/SplitAligner
cd SplitAligner

# 确认可运行(会打印用法说明)
perl SplitAligner.pl

安装到此为止。主控脚本是 SplitAligner.pl;各步骤的辅助脚本在 scripts/ 中,会被自动调用。

2

准备输入

物种树 —— 一棵 Newick 树。允许带枝长和支持度,在基于划分的映射中会被直接忽略。

((A:0.1,B:0.2):0.2,(C:0.1,D:0.1):0.1):0.1;

基因树 —— Newick 格式,一行一条记录,每行以基因标识符开头,紧接着是该基因的树。类群名必须与物种树一致。

GeneA((A:0.1,B:0.2):0.2,(C:0.1,D:0.1):0.1):0.1;
GeneB((A:0.2,C:0.1):0.1,(B:0.1,D:0.2):0.1):0.1;

SplitAligner 区分两类基因树输入,通常两者都要提供:

  • free —— 每个基因独立推断的树(拓扑可以与物种树不一致);
  • fix —— 在物种树拓扑约束下推断的树(只有枝长)。

比较这两者,正是之后区分覆盖度导致的缺失与不一致导致的缺失的关键。

3

快速开始:示例数据

仓库在 examples/302mammal/ 下附带了一个小示例(free / fix 基因树的子集),用于快速的端到端冒烟测试。在仓库根目录下运行:

bash examples/run.sh toy

它会依次运行下面三次程序调用。教程余下部分逐一拆解每一步做了什么、产出什么。

4

matrix 模式 —— 构建坐标系

每组基因树运行一次 matrix。它给物种树的枝打标签,把所有基因投影上去,写出一张基因 × 枝矩阵。

# 从仓库根目录开始
cd examples/302mammal

perl ../../SplitAligner.pl --mode matrix \
  --species input/speciesTree302.nwk \
  --gene input/free_tree.examples.nwk \
  --label free

perl ../../SplitAligner.pl --mode matrix \
  --species input/speciesTree302.nwk \
  --gene input/fix_tree.examples.nwk \
  --label fix

主要输出(以 --label 为前缀):

  • species_tree.splits.txt —— 标准的枝坐标系(物种树骨架上的 B1、B2……)。这是所有基因据以度量的共同坐标轴。
  • <label>.matrix_no_fuse.txt —— 仅原始枝。
  • <label>.matrix_with_fuse.txt —— 在此基础上,额外加入显式的融合枝列(如 B12|B47),对应因类群修剪而合并的枝。
  • <label>_splits/ 和 <label>_split_branch_label/ —— 每个基因的划分表示,及其到骨架的映射。

若枝的投影划分被找回,单元格即为其枝长(数值);否则为 NA——但只是暂时的。为这些 NA 命名,就是下一步。

gene   B1           B2   B3           B4           ...
A1BG   0.1809315089 NA   0.0047384154 0.0057110032 ...
A1CF   0.0376651404 0.0583312140 0.0032591533 ...
free.matrix_with_fuse.txt(节选)

显示根不是坐标。SplitAligner 的生物学坐标是规范化无根 split,而不是绘制出来的根位置、内部节点编号、遍历顺序或子节点顺序。若两个 Newick 表示编码的是同一个加权无根 split 对象,那么仅改变显示根或序列化方式,并不会改变底层的生物学 split 身份。

便于阅读的 B 别名是序列化局部的,在独立序列化的物种树之间可能不同。比较独立表示时,权威身份是规范化 split key,而不是 B 编号本身。

在需要方向或祖先关系的分析中,生根仍然可以承载生物学含义;它只是不定义 SplitAligner 的枝坐标身份。

表示 A — 在内部边上生根 AB CD 表示 B — 在枝 A 上生根 AB CD 规范化 split key 相同 A,B | C,D B 别名可能不同
5

finalize 模式 —— 区分缺失状态

把两张矩阵都交给 finalize。它先识别由数值融合坐标解释的原始枝 NA,标为 NA_fuse;再在共享基因上比较 free 与 fix 矩阵,把结构性缺失(NA_struct)与拓扑导致的缺失(NA_topo)分开。若 fix 侧没有可供判断的数值证据,则保留残余 NA。传入 --species_tree 时,还会计算枝级支持度。

perl ../../SplitAligner.pl --mode finalize \
  --free free.matrix_with_fuse.txt \
  --fix  fix.matrix_with_fuse.txt \
  --final_label final \
  --species_tree species_tree.forSplit.nwk

输出:

  • final.free.na_classified.txt / final.fix.na_classified.txt —— 完成状态分类的矩阵;fix 侧缺少数值证据时会保留残余 NA;
  • <input>.na_fuse.txt —— 融合救回的中间表;
  • final.support_b.txt 和 species_tree.support_b.nwk —— 枝级支持度(见第 7 步)。

比较只对两个输入都存在的基因有定义;若没有共享基因,SplitAligner 会直接报错停止,而不去猜。

gene   B1           B2         B3           ...
A1BG   0.1809315089 NA_struct  0.0047384154 ...
final.free.na_classified.txt —— B2 处那个笼统的 NA 现在有了名字
6

读懂各状态

每个基因 × 枝单元格最终都恰好取下列一种值。残余 NA 是有意保留的记账状态:没有 fix 侧数值证据时,不能把它升级为 NA_topo。

单元格取值含义
数值Mapped(已映射)。该枝的投影划分在此基因中被观测到;数值即其枝长。
NA_fuse类群修剪后,该原始枝不再能被独立观测。一个融合坐标可以带有数值枝长,但该数值不是这根原始枝的独立估计。
NA_struct投影后有一侧为空,该枝对此基因没有投影身份、无法评估(覆盖度效应)。
NA_topofix 侧有原始枝数值证据,但该投影划分没有在推断出的 free 拓扑树中找回——与拓扑导致的不一致相符。
NA残余:当 fix 侧没有可据以判定的数值证据时保留。

这些类别对坐标轴构成一个划分——加起来就是整本台账:

|C| = Mapped + NA_fuse + NA_struct + NA_topo + 残余 NA

一旦 NA 按原因拆开,下游的速率或选择分析就能明确地处理、过滤和审计各类状态,而不是把所有缺失都压成同一个不透明的值。

7

枝级 Support

当你传入 --species_tree,finalize 会写出 final.support_b.txt:对骨架上的每根枝,给出它在共享基因上、free 树相对 fix 树保留了多少数值证据。

branch_id  branch_type  n_shared_genes  n_fix_non_na  n_free_non_na  support_percent  discordance_percent
B1         terminal     5               5             5              100.0000000000   0.0000000000
B2         terminal     5               4             4              100.0000000000   0.0000000000
B3         terminal     5               4             4              100.0000000000   0.0000000000
final.support_b.txt(表头)

配套的 species_tree.support_b.nwk 是一棵标准 Newick 树,把每根内部枝的 Support 写在自展值(bootstrap)位置上,可在任意树浏览器(如 FigTree)中打开,直接把一致度映射到拓扑上查看。

8

扩展到全量

同样这两个模式,在全量规模上照样运行。随附的 preprint 示例使用了 302 种哺乳类、2,275 棵 free 拓扑与 2,275 棵 fix 拓扑基因树:

bash examples/run.sh preprint
9

动手玩 Catnip10

这里只让类群缺失发生,不引入基因树不一致。完整的 B1–B17 坐标轴始终冻结;每次删除后,由图决定的台账会更新为 observed、NA_fuse 或 NA_struct。在这个实验里,NA_topo 始终为零。