Newick树表示为scipy.cluster.hierarchy链接矩阵格式

时间:2015-06-24 18:05:20

标签: python scipy hierarchical-clustering phylogeny

我有一组基于DNA序列进行比对和聚类的基因,我用Newick树表示这组基因(https://en.wikipedia.org/wiki/Newick_format)。有谁知道如何将此格式转换为scipy.cluster.hierarchy.linkage矩阵格式?从链接矩阵的scipy docs:

  

返回A(n-1)乘4矩阵Z.在第i次迭代中,聚类   将索引Z [i,0]和Z [i,1]组合以形成簇n + i。一个   索引小于n的簇对应于n个原始中的一个   观察结果。簇Z [i,0]和Z [i,1]之间的距离是   由Z [i,2]给出。第四个值Z [i,3]表示数量   在新形成的集群中的原始观察。

至少从scipy docs来看,他们对这种连接矩阵结构的描述相当令人困惑。它们是什么意思"迭代"?此外,该表示如何跟踪哪个群集中的原始观察结果?

我想弄清楚如何进行这种转换,因为我的项目中的其他聚类分析的结果已经用scipy表示完成,并且我一直在使用它进行绘图。

2 个答案:

答案 0 :(得分:4)

我从树表示中得到了如何生成链接矩阵,感谢@cel澄清。让我们从Newick wiki页面(https://en.wikipedia.org/wiki/Newick_format

中获取示例

字符串格式的树是:

(A:0.1,B:0.2,(C:0.3,D:0.4):0.5);

首先,应该计算所有叶子之间的距离。例如,如果我们希望计算距离A和B,则方法是通过最近的分支遍历树从A到B.因为在Newick格式中,我们给出了每个叶子和分支之间的距离,从A到B的距离很简单 0.1 + 0.2 = 0.3。对于A到D,我们必须做0.1 + (0.5 + 0.4) = 1.0,因为从D到最近的分支的距离是0.4,并且从D分支到A的距离是0.5。因此距离矩阵看起来像这样(带有索引A=0B=1C=2D=3):

distance_matrix=
 [[0.0, 0.3, 0.9, 1.0],
  [0.3, 0.0, 1.0, 1.1],
  [0.9, 1.0, 0.0, 0.7],
  [1.0, 1.1, 0.1, 0.0]]

从这里,链接矩阵很容易找到。由于我们已将n=4个群集(ABCD)作为原始观察,因此我们需要找到其他n-1树的群集。每个步骤简单地将两个聚类组合成一个新聚类,并且我们将两个聚类彼此最接近。在这种情况下,A和B最接近,因此链接矩阵的第一行将如下所示:

[A,B,0.3,2]

从现在开始,我们对待A& B作为一个簇,其距离最近的分支的距离是A& A之间的距离。 B.

现在我们剩下3个群集,ABCD。我们可以更新距离矩阵以查看哪些聚类最接近。让AB在更新的距离矩阵中有索引0

distance_matrix=
[[0.0, 1.1, 1.2],
 [1.1, 0.0, 0.7],
 [1.2, 0.7, 0.0]]

我们现在可以看到C和D彼此最接近,所以让我们将它们组合成一个新的集群。链接矩阵中的第二行现在是

[C,D,0.7,2]

现在,我们只剩下两个群集,ABCD。从这些簇到根分支的距离分别为0.3和0.7,因此它们的距离为1.0。链接矩阵的最后一行是:

[AB,CD,1.0,4]

现在,scipy矩阵实际上并没有像我在这里看到的那样使用字符串,我们可以使用索引方案,因为我们先将A和B组合在一起,AB会有索引4而CD会有索引5.所以我们应该在scipy链接矩阵中看到的实际结果是:

[[0,1,0.3,2],
 [2,3,0.7,2],
 [4,5,1.0,4]]

这是从树表示到scipy连接矩阵表示的一般方法。但是,已经存在来自其他python包的工具以Newick格式读取树,并且从这些工具中,我们可以相当容易地找到距离矩阵,然后将其传递给scipy的连接函数。下面是一个小脚本,完全适用于此示例。

from ete2 import ClusterTree, TreeStyle
import scipy.cluster.hierarchy as sch
import scipy.spatial.distance
import matplotlib.pyplot as plt
import numpy as np
from itertools import combinations


tree = ClusterTree('(A:0.1,B:0.2,(C:0.3,D:0.4):0.5);')
leaves = tree.get_leaf_names()
ts = TreeStyle()
ts.show_leaf_name=True
ts.show_branch_length=True
ts.show_branch_support=True

idx_dict = {'A':0,'B':1,'C':2,'D':3}
idx_labels = [idx_dict.keys()[idx_dict.values().index(i)] for i in range(0, len(idx_dict))]

#just going through the construction in my head, this is what we should get in the end
my_link = [[0,1,0.3,2],
        [2,3,0.7,2],
        [4,5,1.0,4]]

my_link = np.array(my_link)


dmat = np.zeros((4,4))

for l1,l2 in combinations(leaves,2):
    d = tree.get_distance(l1,l2)
    dmat[idx_dict[l1],idx_dict[l2]] = dmat[idx_dict[l2],idx_dict[l1]] = d

print 'Distance:'
print dmat


schlink = sch.linkage(scipy.spatial.distance.squareform(dmat),method='average',metric='euclidean')

print 'Linkage from scipy:'
print schlink

print 'My link:'
print my_link

print 'Did it right?: ', schlink == my_link

dendro = sch.dendrogram(my_link,labels=idx_labels)
plt.show()

tree.show(tree_style=ts)

答案 1 :(得分:1)

我找到了这个解决方案:

import numpy as np
import pandas as pd
from ete3 import ClusterTree
from scipy.spatial.distance import pdist
from scipy.cluster.hierarchy import linkage
import logging


def newick_to_linkage(newick: str, label_order: [str] = None) -> (np.ndarray, [str]):
    """
    Convert newick tree into scipy linkage matrix

    :param newick: newick string, e.g. '(A:0.1,B:0.2,(C:0.3,D:0.4):0.5);'
    :param label_order: list of labels, e.g. ['A', 'B', 'C']
    :returns: linkage matrix and list of labels
    """
    # newick string -> cophenetic_matrix
    tree = ClusterTree(newick)
    cophenetic_matrix, newick_labels = tree.cophenetic_matrix()
    cophenetic_matrix = pd.DataFrame(cophenetic_matrix, columns=newick_labels, index=newick_labels)

    if label_order is not None:
        # sanity checks
        missing_labels = set(label_order).difference(set(newick_labels))
        superfluous_labels = set(newick_labels).difference(set(label_order))
        assert len(missing_labels) == 0, f'Some labels are not in the newick string: {missing_labels}'
        if len(superfluous_labels) > 0:
            logging.warning(f'Newick string contains unused labels: {superfluous_labels}')

        # reorder the cophenetic_matrix
        cophenetic_matrix = cophenetic_matrix.reindex(index=label_order, columns=label_order)

    # reduce square distance matrix to condensed distance matrices
    pairwise_distances = pdist(cophenetic_matrix)

    # return linkage matrix and labels
    return linkage(pairwise_distances), list(cophenetic_matrix.columns)

基本用法:

>>> linkage_matrix, labels = newick_to_linkage(
...     newick='(A:0.1,B:0.2,(C:0.3,D:0.4):0.5);'
... )
>>> print(linkage_matrix)
[[0.        1.        0.4472136 2.       ]
 [2.        3.        1.        2.       ]
 [4.        5.        1.4832397 4.       ]]
>>> print(labels)
['A', 'B', 'C', 'D']

cophenetic 矩阵的样子:

>>> print(cophenetic_matrix)
     A    B    C    D
A  0.0  0.3  0.9  1.0
B  0.3  0.0  1.0  1.1
C  0.9  1.0  0.0  0.7
D  1.0  1.1  0.7  0.0

高级用法:

>>> linkage_matrix, labels = newick_to_linkage(
...     newick='(A:0.1,B:0.2,(C:0.3,D:0.4):0.5);',
...     label_order=['C', 'B', 'A']
... )
WARNING:root:Newick string contains unused labels: {'D'}
>>> print(linkage_matrix)
[[1.         2.         0.43588989 2.        ]
 [0.         3.         1.4525839  3.        ]]
>>> print(labels)
['C', 'B', 'A']
相关问题