如何在Python中集群图形?

时间:2009-03-17 09:21:08

标签: python sorting matrix cluster-analysis graph-theory

设G是图。所以G是一组节点和一组链接。我需要找到一种快速分割图形的方法。我现在正在使用的图表只有120 * 160个节点,但我可能很快会在另一个上下文(不是医学,但是网站开发)中处理同等问题,有数百万个节点。

所以,我所做的是将所有链接存储到图表矩阵中:

M=numpy.mat(numpy.zeros((len(data.keys()),len(data.keys()))))

如果节点s连接到节点t,则M在位置s,t中保持1。我确保M是对称的M [s,t] = M [t,s],并且每个节点都链接到它自己M [s,s] = 1.

如果我记得很好,如果我将M与M相乘,则结果是一个矩阵,表示连接通过两个步骤到达的顶点的图形。

因此,我继续将M与自身进行对比,直到矩阵中的零数不再减少为止。现在我有连接组件的列表。 现在我需要对这个矩阵进行聚类。

到目前为止,我对算法非常满意。我认为它简单,优雅,而且速度相当快。我在这部分遇到了麻烦。

基本上我需要将此图分割为其连接的组件。

我可以浏览所有节点,看看它们连接的是什么。

但是如何排序矩阵重新排序。但我不知道是否可以这样做。

到目前为止,代码如下:

def findzeros(M):
    nZeros=0
    for t in M.flat:
        if not t:
            nZeros+=1
    return nZeros

M=numpy.mat(numpy.zeros((len(data.keys()),len(data.keys()))))    
for s in data.keys():
    MatrixCells[s,s]=1
    for t in data.keys():
        if t<s:
            if (scipy.corrcoef(data[t],data[s])[0,1])>threashold:
                M[s,t]=1
                M[t,s]=1

nZeros=findzeros(M)
M2=M*M
nZeros2=findzeros(M2)

while (nZeros-nZeros2):
    nZeros=nZeros2
    M=M2
    M2=M*M
    nZeros2=findzeros(M2)

编辑:

有人建议我使用SVD分解。以下是5x5图表上问题的简单示例。我们将使用它,因为使用19200x19200方阵并不容易看到簇。

import numpy
import scipy

M=numpy.mat(numpy.zeros((5,5)))

M[1,3]=1
M[3,1]=1
M[1,1]=1
M[2,2]=1
M[3,3]=1
M[4,4]=1
M[0,0]=1

print M

u,s,vh = numpy.linalg.linalg.svd(M)
print u
print s
print vh

基本上这里有4个集群:(0),(1,3),(2),(4) 但我仍然没有看到svn在这种情况下如何提供帮助。

8 个答案:

答案 0 :(得分:12)

为什么不使用真实的图表库,例如Python-Graph?它有function to determine connected components(虽然没有提供示例)。我想象一个专用的库会比你编写的任何特殊的图形代码更快。

编辑:NetworkX似乎比python-graph更好的选择;它的documentation (here for the connected components function)肯定是。

答案 1 :(得分:7)

在SciPy中,您可以使用sparse matrices。还要注意,有更有效的方法可以将矩阵自身相乘。无论如何,你要做的是通过SVD分解完成的。

Introduction with useful links

答案 2 :(得分:3)

还有graph_toolnetworkit具有连接组件的高效例程,并且都能有效地存储网络。如果您要使用数百万个节点,那么networkx可能就不够了(这是纯粹的python afaik)。这两个工具都是用C ++编写的,因此可以处理具有合理运行时间的大图的分析。

正如菲尔指出的那样,你的方法对于大型图形(我们说的是几天,几周,几个月......)的计算时间非常长,而你对一百万个节点图表的表示将需要一百万个像千兆字节的内存!

答案 3 :(得分:2)

这是一些天真的实现,它使用depth first search找到连接的组件,我在前一段时间写过。虽然它非常简单,但它可以很好地扩展到数万个顶点和边缘......


import sys
from operator import gt, lt

class Graph(object):
    def __init__(self):
        self.nodes = set()
        self.edges = {}
        self.cluster_lookup = {}
        self.no_link = {}

    def add_edge(self, n1, n2, w):
        self.nodes.add(n1)
        self.nodes.add(n2)
        self.edges.setdefault(n1, {}).update({n2: w})
        self.edges.setdefault(n2, {}).update({n1: w})

    def connected_components(self, threshold=0.9, op=lt):
        nodes = set(self.nodes)
        components, visited = [], set()
        while len(nodes) > 0:
            connected, visited = self.dfs(nodes.pop(), visited, threshold, op)
            connected = set(connected)
            for node in connected:
                if node in nodes:
                    nodes.remove(node)

            subgraph = Graph()
            subgraph.nodes = connected
            subgraph.no_link = self.no_link
            for s in subgraph.nodes:
                for k, v in self.edges.get(s, {}).iteritems():
                    if k in subgraph.nodes:
                        subgraph.edges.setdefault(s, {}).update({k: v})
                if s in self.cluster_lookup:
                    subgraph.cluster_lookup[s] = self.cluster_lookup[s]

            components.append(subgraph)
        return components

    def dfs(self, v, visited, threshold, op=lt, first=None):
        aux = [v]
        visited.add(v)
        if first is None:
            first = v
        for i in (n for n, w in self.edges.get(v, {}).iteritems()
                  if op(w, threshold) and n not in visited):
            x, y = self.dfs(i, visited, threshold, op, first)
            aux.extend(x)
            visited = visited.union(y)
        return aux, visited

def main(args):
    graph = Graph()
    # first component
    graph.add_edge(0, 1, 1.0)
    graph.add_edge(1, 2, 1.0)
    graph.add_edge(2, 0, 1.0)

    # second component
    graph.add_edge(3, 4, 1.0)
    graph.add_edge(4, 5, 1.0)
    graph.add_edge(5, 3, 1.0)

    first, second = graph.connected_components(op=gt)
    print first.nodes
    print second.nodes

if __name__ == '__main__':
    main(sys.argv)

答案 4 :(得分:2)

正如其他人所指出的,不需要重新发明轮子。已经将很多想法付诸于最佳聚类技术。 Here是一个众所周知的集群计划。

答案 5 :(得分:2)

找到最佳图分区是NP难问题,因此无论算法如何,它都将是近似或启发式算法。毫不奇怪,不同的聚类算法产生(疯狂地)不同的结果。

Newman模块化算法的Python实现: modularity

另外:MCLMCODECFinderNeMoclusterONE

答案 6 :(得分:0)

看起来有一个库PyMetis,它会根据链接列表为您划分图表。通过传递链接节点的原始列表(而不是矩阵乘法导出的链接节点),从图表中提取链接列表应该相当容易。

重复执行M'= MM对于M的大阶数不会有效。对于N阶矩阵的完全矩阵乘法将花费N次乘法和每个元素的N-1次加法,其中有N 2 ,即O(N 3 )操作。如果你将其缩放到“数百万个节点”,那么每个矩阵 - 矩阵乘法就是O(10 18 )运算,你想要做几个。

简而言之,你不想这样做。来自Vartec的SVD suggestion将是唯一合适的选择。您最好的选择就是使用PyMetis,而不是尝试重新发明图分区。

答案 7 :(得分:0)

SVD算法不适用于此,但Phil H是正确的。