如何在python中无向图中有效计算三合会普查

时间:2019-06-11 06:15:50

标签: python networkx graph-theory network-analysis

我正在为我的triad census计算undirected network

import networkx as nx
G = nx.Graph()
G.add_edges_from(
    [('A', 'B'), ('A', 'C'), ('D', 'B'), ('E', 'C'), ('E', 'F'),
     ('B', 'H'), ('B', 'G'), ('B', 'F'), ('C', 'G')])

from itertools import combinations
#print(len(list(combinations(G.nodes, 3))))

triad_class = {}
for nodes in combinations(G.nodes, 3):
    n_edges = G.subgraph(nodes).number_of_edges()
    triad_class.setdefault(n_edges, []).append(nodes)
print(triad_class)

它适用于小型网络。但是,现在我有一个较大的网络,大约有4000-8000个节点。当我尝试使用1000个节点的网络运行现有代码时,需要花费几天的时间。有更有效的方法吗?

我当前的网络大部分是稀疏的。即节点之间只有很少的连接。在那种情况下,我可以离开未连接的节点,先进行计算,然后再将未连接的节点添加到输出中吗?

我也很乐意获得近似答案,而无需计算每个组合。

黑社会人口普查示例:

三合会人口普查将三合会(3个节点)分为下图所示的四个类别。

Four classes of triad census

例如,考虑下面的网络。

enter image description here

这四个阶层的三合会人口普查是

{3: [('A', 'B', 'C')], 
2: [('A', 'B', 'D'), ('B', 'C', 'D'), ('B', 'D', 'E')], 
1: [('A', 'B', 'E'), ('A', 'B', 'F'), ('A', 'B', 'G'), ('A', 'C', 'D'), ('A', 'C', 'E'), ('A', 'C', 'F'), ('A', 'C', 'G'), ('A', 'D', 'E'), ('A', 'F', 'G'), ('B', 'C', 'E'), ('B', 'C', 'F'), ('B', 'C', 'G'), ('B', 'D', 'F'), ('B', 'D', 'G'), ('B', 'F', 'G'), ('C', 'D', 'E'), ('C', 'F', 'G'), ('D', 'E', 'F'), ('D', 'E', 'G'), ('D', 'F', 'G'), ('E', 'F', 'G')], 
0: [('A', 'D', 'F'), ('A', 'D', 'G'), ('A', 'E', 'F'), ('A', 'E', 'G'), ('B', 'E', 'F'), ('B', 'E', 'G'), ('C', 'D', 'F'), ('C', 'D', 'G'), ('C', 'E', 'F'), ('C', 'E', 'G')]}

很高兴在需要时提供更多详细信息。

编辑:

我能够通过按照答案中的建议注释memory error行来解决#print(len(list(combinations(G.nodes, 3))))。但是,我的程序仍然很慢,即使有1000个节点的网络也要花费几天的时间。我正在寻找在python中执行此操作的更有效方法。

我不仅限于networkx,也很乐意接受使用其他库和语言的答案。

一如既往,我很乐意根据需要提供更多详细信息。

4 个答案:

答案 0 :(得分:5)

让我们检查一下数字。设 n 为顶点数, e 为边数。

0个三合会出现在O( n ^ 3)

1个三合会在O( e * n

2 + 3个三合会在O( e

要获得2 + 3个三合会:

For every node a:
   For every neighbor of a b:
      For every neighbor of b c:
        if a and c are connected, [a b c] is a 3 triad
        else [a b c] is a 2 triad
   remove a from list of nodes (to avoid duplicate triads)

下一步取决于目标是什么。如果您只需要1个和0个三合会的数目,那就足够了:

#(1 triads) = e * (n -2) - #(2 triads) - #(3 triads)

#(0 triads) = {n \choose 3} - #(3 triads) - #(2 triads) - #(1 triads)

说明:

1个三元组都是连接的节点+ 1个未连接的节点,因此我们通过计算连接的节点数+ 1个其他节点来获得数量,然后减去另一个节点已连接的情况(2个和3个三合组)

0个三合会就是节点的所有组合减去其他三合会。

如果您需要实际列出三合会,那么您就很不走运了,因为无论您做什么,列出0个三合会都在O(n ^ 3)中,一旦图表变大就会杀死您。

上面2 + 3个三合会的算法在O(e * max(#邻居))中,其他部分在O(e + n)中,用于计算节点和边。要比O(n ^ 3)好得多,后者需要明确列出0个三合会。列出1个三合会仍然可以在O(e * n)中完成。

答案 1 :(得分:5)

这个想法很简单:我不是使用直接邻接图,而是直接在图形上工作。我以为这样会更有效率,看来我是对的。

Adjacency matrix for example

在邻接矩阵中,a 1表示两个节点之间存在一条边,例如,第一行可以读取为“ A和B以及C之间都有链接”

从那里我查看了您的四种类型,并发现了以下内容:

  • 对于类型3,在N1和N2,N1和N3之间以及N2和N3之间必须有一条边。在邻接矩阵中,我们可以通过遍历每一行(其中每一行代表一个节点及其连接,这是N1)并找到与其连接的节点(即N2)来找到它。然后,在N2的行中,我们检查所有连接的节点(即N3),并保留那些在N1的行中存在正条目的节点。例如,“ A,B,C”,A与B有连接。B与C有连接,A也与C有连接

  • 类型2的
  • 几乎与类型3相同。除了现在,我们要为N1行中的N3列找到0。例如“ A,B,D”。 A与B有连接,B在D列中有1,但A没有。

  • 对于类型1,我们仅查看N2的行并找到所有N1行和N2行都为0的列。

  • 最后,对于类型0,请查看N1行中条目为0的所有列,然后检查其中的行,并找到所有具有0的列。

此代码应为您工作。对于1000个节点,(在装有i7-8565U CPU的计算机上)花了大约7分钟的时间,这仍然相对较慢,但与当前运行解决方案所需的几天时间相去甚远。我已经在您的图片中包含了示例,以便您可以验证结果。您的代码生成的图形与您在下面显示的示例不同。代码中的示例图和邻接矩阵均引用您包含的图片。

具有1000个节点的示例使用networkx.generators.random_graphs.fast_gnp_random_graph。 1000是节点数,0.1是创建边的概率,而种子只是为了保持一致性。我已经设置了创建边缘的可能性,因为您提到图形稀疏。

networkx.linalg.graphmatrix.adjacency_matrix:“如果您想要纯Python邻接矩阵表示,请尝试networkx.convert.to_dict_of_dicts,它将返回字典格式的字典,该字典格式可以作为稀疏矩阵处理。”

字典结构具有M个字典(=行),其中最多嵌套M个字典。请注意,嵌套字典为空,因此检查它们中键的存在等同于如上所述检查1或0。

import time

import networkx as nx


def triads(m):
    out = {0: set(), 1: set(), 2: set(), 3: set()}
    nodes = list(m.keys())
    for i, (n1, row) in enumerate(m.items()):
        print(f"--> Row {i + 1} of {len(m.items())} <--")
        # get all the connected nodes = existing keys
        for n2 in row.keys():
            # iterate over row of connected node
            for n3 in m[n2]:
                # n1 exists in this row, all 3 nodes are connected to each other = type 3
                if n3 in row:
                    if len({n1, n2, n3}) == 3:
                        t = tuple(sorted((n1, n2, n3)))
                        out[3].add(t)
                # n2 is connected to n1 and n3 but not n1 to n3 = type 2
                else:
                    if len({n1, n2, n3}) == 3:
                        t = tuple(sorted((n1, n2, n3)))
                        out[2].add(t)
            # n1 and n2 are connected, get all nodes not connected to either = type 1
            for n3 in nodes:
                if n3 not in row and n3 not in m[n2]:
                    if len({n1, n2, n3}) == 3:
                        t = tuple(sorted((n1, n2, n3)))
                        out[1].add(t)
        for j, n2 in enumerate(nodes):
            if n2 not in row:
                # n2 not connected to n1
                for n3 in nodes[j+1:]:
                    if n3 not in row and n3 not in m[n2]:
                        # n3 is not connected to n1 or n2 = type 0
                        if len({n1, n2, n3}) == 3:
                            t = tuple(sorted((n1, n2, n3)))
                            out[0].add(t)
    return out


if __name__ == "__main__":
    g = nx.Graph()
    g.add_edges_from(
        [("E", "D"), ("G", "F"), ("D", "B"), ("B", "A"), ("B", "C"), ("A", "C")]
    )
    _m = nx.convert.to_dict_of_dicts(g)
    _out = triads(_m)
    print(_out)

    start = time.time()
    g = nx.generators.fast_gnp_random_graph(1000, 0.1, seed=42)
    _m = nx.convert.to_dict_of_dicts(g)
    _out = triads(_m)
    end = time.time() - start
    print(end)

答案 2 :(得分:2)

  1. 当您尝试将所有组合转换为列表static void Main(string[] args) { string keyPath = "SOFTWARE\\Apps"; var subKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64).OpenSubKey(keyPath, true); // This method accepts the RegistryView parameter. if (subKey != null) { subKey.DeleteSubKey("Application"); Console.WriteLine("DELETED"); } else { Console.WriteLine("NOT FOUND"); } } 时,您的程序很可能崩溃。永远不要这样做,因为print(len(list(combinations(G.nodes, 3))))返回一个消耗少量内存的迭代器,但是list可以轻易吃掉千兆字节的内存。

  2. 如果您有稀疏图,则在connected componentscombinations

  3. 中查找三合会更为合理。
  4. Networkx具有triads子模块,但看起来不适合您。我已经修改了networkx.algorithms.triads代码以返回三合会,而不是它们的计数。您可以找到它here。请注意,它使用DiGraphs。如果要与无向图一起使用,则应先将它们转换为有向图。

答案 3 :(得分:2)

import networkx as nx
from time import sleep
from itertools import combinations


G = nx.Graph()
arr=[]
for i in range(1000):
    arr.append(str(i))

for i,j in combinations(arr, 2):
    G.add_edges_from([(i,j)])

#print(len(list(combinations(G.nodes, 3))))
triad_class = [[],[],[],[]]

for nodes in combinations(G.subgraph(arr).nodes, 3):
            n_edges = G.subgraph(nodes).number_of_edges()
            triad_class[n_edges].append(nodes)


print(triad_class)

我认为使用list会比字典快速插入,因为字典会成倍增长,并且会花费更多时间。