我有一组基于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表示完成,并且我一直在使用它进行绘图。
答案 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=0
,B=1
,C=2
,D=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
个群集(A
,B
,C
,D
)作为原始观察,因此我们需要找到其他n-1
树的群集。每个步骤简单地将两个聚类组合成一个新聚类,并且我们将两个聚类彼此最接近。在这种情况下,A和B最接近,因此链接矩阵的第一行将如下所示:
[A,B,0.3,2]
从现在开始,我们对待A& B作为一个簇,其距离最近的分支的距离是A& A之间的距离。 B.
现在我们剩下3个群集,AB
,C
和D
。我们可以更新距离矩阵以查看哪些聚类最接近。让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]
现在,我们只剩下两个群集,AB
和CD
。从这些簇到根分支的距离分别为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']