在 GitHub 上編輯此頁面

使用 Bio.Nexus 模組串聯多個比對的 NEXUS 檔案。

問題

從多個基因或蛋白質進行物種層級的親緣關係推斷是很常見的。人口統計(和其他)過程可能導致單一基因樹與物種樹分歧,因此,來自多個基因對相同樹拓撲結構的支持,被認為比單一基因推斷更強(當然,我們仍然需要測試每個基因是否講述相同的故事)。

這通常透過分別比對每個基因,然後從個別基因比對建立一個單一的「超級矩陣」來處理,也就是說,您建立一個單一的比對,其中每個分類群對應一行,而每行的資料是該分類群串聯的比對基因序列。在 NEXUS 檔案(由親緣關係軟體 PAUP*、MrBayes 等使用)中,多個基因可以明確地表示為資料矩陣中的不同「字符分割」或「集合」,該矩陣包含每個分類群的一個長序列。這樣,您可以建立一個超級矩陣,但仍然可以對其中的每個基因應用不同的替換模型,或運行 PAUP* 的分割同質性測試,以檢查每個基因樹的速率/拓撲結構是否存在顯著差異。

Bio.Nexus 模組使得將多個比對串聯成超級矩陣相對簡單。

解決方案

假設我們有三個基因的 NEXUS 檔案,btCOI.nexbtCOII.nexbtITS.nex,包含比對結果

#COI
bt1 GGGGGGGGGGGG
bt2 GGGGGGGGGGGG
bt3 GGGGGGGGGGGG
#COII
bt1 AAAAAAAAAAAA
bt2 AAAAAAAAAAAA
bt3 AAAAAAAAAAAA
#ITS
bt1 -TTTTTTT
bt2 -TTTTTTT
bt3 -TTTTTTT
bt4 -TTTTTTT

我們可以使用 Nexus 模組來建立一個超級矩陣

from Bio.Nexus import Nexus

# the combine function takes a list of tuples [(name, nexus instance)...],
# if we provide the file names in a list we can use a list comprehension to
# create these tuples

file_list = ["btCOI.nex", "btCOII.nex", "btITS.nex"]
nexi = [(fname, Nexus.Nexus(fname)) for fname in file_list]

combined = Nexus.combine(nexi)
combined.write_nexus_data(filename=open("btCOMBINED.nex", "w"))

這很簡單!讓我們看看合併的檔案

#NEXUS
begin data;
    dimensions ntax=4 nchar=32;
    format datatype=dna missing=? gap=-;
matrix
bt1 GGGGGGGGGGGG-TTTTTTTAAAAAAAAAAAA
bt2 GGGGGGGGGGGG-TTTTTTTAAAAAAAAAAAA
bt3 GGGGGGGGGGGG-TTTTTTTAAAAAAAAAAAA
bt4 ????????????-TTTTTTT????????????
;
end;

begin sets;
charset btITS.nex = 13-20;
charset btCOI.nex = 1-12;
charset btCOII.nex = 21-32;
charpartition combined = btCOI.nex: 1-12, btITS.nex: 13-20, btCOII.nex: 21-32;
end;

啊,太容易了。矩陣已合併,且字符集和分割已設定,但 ITS 檔案有一個其他檔案中沒有的分類群 (bt4)。在這些情況下,combine 函式會將該分類群新增,並使用遺失的資料('?')作為其他字符分割。有時這可能是您想要的結果,但擁有少數像這樣的分類群也是讓分割同質性測試運行一週的好方法。讓我們編寫一個函數,測試同一組分類群是否出現在一組 nexus 實例中,如果沒有,則提供有用的錯誤訊息(也就是說,如果您希望它們順利合併,要從您的 NEXUS 檔案中刪除什麼)。

def check_taxa(matrices):
    """Verify Nexus instances have the same taxa information.

    Checks that nexus instances in a list [(name, instance)...] have
    the same taxa, provides useful error if not and returns None if
    everything matches
    """
    first_taxa = matrices[0][1].taxlabels
    for name, matrix in matrices[1:]:
        first_only = [t for t in first_taxa if t not in matrix.taxlabels]
        new_only = [t for t in matrix.taxlabels if t not in first_taxa]
        if first_only:
            missing = ", ".join(first_only)
            msg = "%s taxa %s not in martix %s" % (nexi[0][0], missing, name)
            raise Nexus.NexusError(msg)
        elif new_only:
            missing = ", ".join(new_only)
            msg = "%s taxa %s not in all matrices" % (name, missing)
            raise Nexus.NexusError(msg)
    return None  # will only get here if it hasn't thrown an exception


def concat(file_list, same_taxa=True):
    """Combine multiple nexus data matrices in one partitioned file.

    By default this will only work if the same taxa are present in each file
    use same_taxa=False if you are not concerned by this
    """
    nexi = [(fname, Nexus.Nexus(fname)) for fname in file_list]
    if same_taxa:
        if not check_taxa(nexi):
            return Nexus.combine(nexi)
    else:
        return Nexus.combine(nexi)

現在,使用我們的新函數

>>> handles = [open('btCOI.nex', 'r'), open('btCOII.nex', 'r'), open('btITS.nex', 'r')]
# If we combine them all we should get an error and the taxon/taxa that caused it
>>> concat(handles)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in concat
  File "<stdin>", line 16, in check_taxa
Bio.Nexus.Nexus.NexusError: btITS.nex taxa bt4 not in all matrices

# But if we use just the first two, which do have matching taxa, it should be fine
>>> concat(handles[:2]).taxlabels
['bt1', 'bt2', 'bt3']

# Ok, can we still munge them together if we want to?
>>> concat(handle, same_taxa=False).taxlabels
['bt1', 'bt2', 'bt3', 'bt4']

討論

Nexus 類的詳細資訊在 API 文件中提供。