如何在python中有效地计算无向图中的三元组普查
我正在为我的无向网络计算如何在python中有效地计算无向图中的三元组普查,python,networkx,graph-theory,network-analysis,Python,Networkx,Graph Theory,Network Analysis,我正在为我的无向网络计算黑社会普查,如下所示 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(c
黑社会普查
,如下所示
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个节点的网络上运行现有代码时,运行需要几天时间。有没有更有效的方法
我目前的网络基本上是稀疏的。i、 e.节点之间只有很少的连接。在这种情况下,我是否可以保留未连接的节点,先进行计算,然后将未连接的节点添加到输出中
我也很高兴在不计算每一个组合的情况下得到近似的答案
三合会普查示例:
Triad census将Triad(3个节点)划分为下图所示的四个类别
例如,考虑下面的网络。
这四个阶层的三合会普查包括:{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')]}
如果需要,我很乐意提供更多细节
编辑:
我能够通过注释行#print(len(列表(组合(G.nodes,3))
来解决内存错误。然而,我的程序仍然很慢,即使有1000个节点的网络,也需要几天才能运行。我正在寻找一种在python中实现这一点的更有效的方法
我不局限于networkx
,也乐于接受使用其他库和语言的答案。
像往常一样,我很乐意根据需要提供更多细节
当您试图将所有组合转换为列表时,程序很可能会崩溃:print(len(list(组合(G.nodes,3)))
。永远不要这样做,因为组合
返回的迭代器消耗少量内存,但列表很容易消耗千兆字节的内存
如果您有稀疏图,则在以下位置查找空间坐标轴更为合理:nx.connected\u components(G)
Networkx有子模块,但看起来不适合您。我已经修改了networkx.algorithms.triads代码以返回空间坐标轴,而不是它们的计数。你可以找到它。注意,它使用有向图。如果要将其用于无向图,应首先将其转换为有向图
让我们核对一下数字。设n为顶点数,e为边数
0个三元组位于O(n^3)中
1个三和弦在O(e*n)中
O(e)中有2+3个三和弦
要获得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个空间坐标轴都是连接的节点+1个未连接的节点,因此我们通过计算连接节点的数量+1个其他节点来获得该数量,并减去其他节点连接的情况(2和3个空间坐标轴)
0空间坐标轴只是所有节点的组合减去其他空间坐标轴
如果你真的需要列出三元组,那你就太不走运了,因为无论你做什么,列出0三元组都是在O(n^3)中,一旦图形变大,你就会被杀死
上述2+3三元组的算法是O(e*max(#neighbories)),其他部分是O(e+n),用于计算节点和边。比O(n^3)好得多,您需要明确列出0个三元组。列出1个三元组仍然可以在O(e*n)中完成。想法很简单:我不直接处理图形,而是使用邻接矩阵。我认为这样会更有效率,而且似乎我是对的
在邻接矩阵中,A1表示两个节点之间有一条边,例如,第一行可以被读取为“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个节点,我花了大约7分钟的时间(在一台配备i7-8565U CPU的机器上),这仍然相对较慢,但与当前运行解决方案所需的几天相比,相差甚远。我已经从您的图片中包括了示例,以便您可以验证结果。代码生成的图形与下面的示例不同。代码中的示例图和邻接矩阵都引用了您包含的图片
具有1000个节点的示例使用。1000是节点数,0.1是创建边的概率,种子只是为了一致性。我已经设置了创建边的概率,因为你提到了你的图是稀疏的
:“如果您想要纯Python邻接矩阵表示,请尝试networkx.convert.to_dict_of_dicts,它将返回一个字典字典格式,可以作为稀疏矩阵进行寻址。”
字典结构有M
字典(=行),最多有M个
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)
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)