教程#

本指南可以帮助您开始使用 NetworkX。

创建图#

创建一个没有节点和边的空图。

import networkx as nx
G = nx.Graph()

根据定义,Graph 是节点(顶点)的集合,以及已识别的节点对(称为边、链接等)。在 NetworkX 中,节点可以是任何可哈希的对象,例如文本字符串、图像、XML 对象、另一个图、自定义节点对象等。

注意

不允许将 Python 的 None 对象用作节点。它在许多函数中用于确定是否已为可选函数参数赋值。

节点#

G 可以通过几种方式增长。NetworkX 包含许多图生成器函数以及以多种格式读写图的功能。不过,首先我们将看看简单的操作。您可以一次添加一个节点,

G.add_node(1)

或者从任何可迭代的容器中添加节点,例如列表

G.add_nodes_from([2, 3])

如果您的容器产生形式为 (node, node_attribute_dict) 的 2 元组,您还可以添加节点以及节点属性

G.add_nodes_from([(4, {"color": "red"}), (5, {"color": "green"})])

节点属性将在下方进一步讨论。

一个图中的节点可以并入另一个图中

H = nx.path_graph(10)
G.add_nodes_from(H)

G 现在包含 H 的节点作为 G 的节点。相反,您可以将图 H 作为 G 中的一个节点来使用。

G.add_node(H)

G 现在包含 H 作为节点。这种灵活性非常强大,因为它允许图的图、文件的图、函数的图等等。值得思考如何构建您的应用程序,以便节点成为有用的实体。当然,如果您愿意,您始终可以在 G 中使用唯一的标识符,并拥有一个以标识符为键的独立字典来存储节点信息。

注意

如果节点的哈希值取决于其内容,则不应更改节点对象。

#

G 也可以通过一次添加一条边来增长,

G.add_edge(1, 2)
e = (2, 3)
G.add_edge(*e)  # unpack edge tuple*

通过添加边的列表,

G.add_edges_from([(1, 2), (1, 3)])

或者添加任何ebunch边。一个 ebunch 是任何可迭代的边元组容器。一个边元组可以是包含两个节点的 2 元组,或者包含两个节点后跟一个边属性字典的 3 元组,例如 (2, 3, {'weight': 3.1415})。边属性将在下方进一步讨论。

G.add_edges_from(H.edges)

添加现有节点或边时不会有任何抱怨。例如,在移除所有节点和边之后,

G.clear()

我们添加新的节点/边,NetworkX 会默默地忽略任何已经存在的节点/边。

G.add_edges_from([(1, 2), (1, 3)])
G.add_node(1)
G.add_edge(1, 2)
G.add_node("spam")        # adds node "spam"
G.add_nodes_from("spam")  # adds 4 nodes: 's', 'p', 'a', 'm'
G.add_edge(3, 'm')

此时,图 G 包含 8 个节点和 3 条边,可以通过以下方式查看

G.number_of_nodes()
8
G.number_of_edges()
3

注意

邻接报告的顺序(例如 G.adjG.successorsG.predecessors)是边添加的顺序。然而,G.edges 的顺序是邻接的顺序,它包括节点的顺序以及每个节点的邻接。请参阅下面的示例

DG = nx.DiGraph()
DG.add_edge(2, 1)   # adds the nodes in order 2, 1
DG.add_edge(1, 3)
DG.add_edge(2, 4)
DG.add_edge(1, 2)
assert list(DG.successors(2)) == [1, 4]
assert list(DG.edges) == [(2, 1), (2, 4), (1, 3), (1, 2)]

检查图的元素#

我们可以检查节点和边。四个基本图属性有助于报告:G.nodesG.edgesG.adjG.degree。这些是图的节点、边、邻居(邻接)和度的类集合视图。它们提供了对图结构的持续更新的只读视图。它们也类似于字典,您可以通过这些视图查找节点和边数据属性,并使用 .items().data() 方法迭代具有数据属性的元素。如果您想要特定容器类型而不是视图,可以指定一个。这里我们使用列表,尽管在其他情况下集合、字典、元组和其他容器可能更好。

list(G.nodes)
[1, 2, 3, 'spam', 's', 'p', 'a', 'm']
list(G.edges)
[(1, 2), (1, 3), (3, 'm')]
list(G.adj[1])  # or list(G.neighbors(1))
[2, 3]
G.degree[1]  # the number of edges incident to 1
2

可以使用nbunch指定报告所有节点子集中的边和度。一个 nbunch 可以是以下任何一种:None(表示所有节点)、一个节点,或者一个可迭代的节点容器,该容器本身不是图中的节点。

G.edges([2, 'm'])
EdgeDataView([(2, 1), ('m', 3)])
G.degree([2, 3])
DegreeView({2: 1, 3: 2})

从图中移除元素#

可以通过与添加类似的方式从图中移除节点和边。使用方法 Graph.remove_node()Graph.remove_nodes_from()Graph.remove_edge()Graph.remove_edges_from(),例如

G.remove_node(2)
G.remove_nodes_from("spam")
list(G.nodes)
[1, 3, 'spam']
G.remove_edge(1, 3)
list(G)
[1, 3, 'spam']

使用图构造函数#

图对象不必增量构建 - 指定图结构的数据可以直接传递给各种图类的构造函数。通过实例化其中一个图类来创建图结构时,您可以用几种格式指定数据。

G.add_edge(1, 2)
H = nx.DiGraph(G)  # create a DiGraph using the connections from G
list(H.edges())
[(1, 2), (2, 1)]
edgelist = [(0, 1), (1, 2), (2, 3)]
H = nx.Graph(edgelist)  # create a graph from an edge list
list(H.edges())
[(0, 1), (1, 2), (2, 3)]
adjacency_dict = {0: (1, 2), 1: (0, 2), 2: (0, 1)}
H = nx.Graph(adjacency_dict)  # create a Graph dict mapping nodes to nbrs
list(H.edges())
[(0, 1), (0, 2), (1, 2)]

节点和边使用什么#

您可能会注意到,节点和边没有被指定为 NetworkX 对象。这使您可以自由地使用有意义的项作为节点和边。最常见的选择是数字或字符串,但节点可以是任何可哈希的对象(除了 None),并且可以使用 G.add_edge(n1, n2, object=x) 将边与任何对象 x 相关联。

举例来说,n1n2 可以是来自 RCSB 蛋白质数据库的蛋白质对象,而 x 可以是指向详细描述它们相互作用的实验观察结果的出版物 XML 记录。

我们发现这个功能非常有用,但滥用它可能会导致令人惊讶的行为,除非熟悉 Python。如有疑问,请考虑使用 convert_node_labels_to_integers() 来获得具有整数标签的更传统的图。

访问边和邻居#

除了视图 Graph.edgesGraph.adj 之外,还可以使用下标符号访问边和邻居。

G = nx.Graph([(1, 2, {"color": "yellow"})])
G[1]  # same as G.adj[1]
AtlasView({2: {'color': 'yellow'}})
G[1][2]
{'color': 'yellow'}
G.edges[1, 2]
{'color': 'yellow'}

如果边已存在,您可以使用下标符号获取/设置边的属性。

G.add_edge(1, 3)
G[1][3]['color'] = "blue"
G.edges[1, 2]['color'] = "red"
G.edges[1, 2]
{'color': 'red'}

使用 G.adjacency()G.adj.items() 可以快速检查所有(节点,邻接)对。请注意,对于无向图,邻接迭代会看到每条边两次。

FG = nx.Graph()
FG.add_weighted_edges_from([(1, 2, 0.125), (1, 3, 0.75), (2, 4, 1.2), (3, 4, 0.375)])
for n, nbrs in FG.adj.items():
   for nbr, eattr in nbrs.items():
       wt = eattr['weight']
       if wt < 0.5: print(f"({n}, {nbr}, {wt:.3})")
(1, 2, 0.125)
(2, 1, 0.125)
(3, 4, 0.375)
(4, 3, 0.375)

使用 edges 属性可以方便地访问所有边。

for (u, v, wt) in FG.edges.data('weight'):
    if wt < 0.5:
        print(f"({u}, {v}, {wt:.3})")
(1, 2, 0.125)
(3, 4, 0.375)

向图、节点和边添加属性#

权重、标签、颜色或任何您喜欢的 Python 对象等属性可以附加到图、节点或边上。

每个图、节点和边都可以在关联的属性字典中保存键/值属性对(键必须是可哈希的)。默认情况下,这些字典是空的,但可以使用 add_edgeadd_node 或直接操作图 G 的属性字典 G.graphG.nodesG.edges 来添加或更改属性。

图属性#

创建新图时分配图属性

G = nx.Graph(day="Friday")
G.graph
{'day': 'Friday'}

或者您稍后可以修改属性

G.graph['day'] = "Monday"
G.graph
{'day': 'Monday'}

节点属性#

使用 add_node()add_nodes_from()G.nodes 添加节点属性

G.add_node(1, time='5pm')
G.add_nodes_from([3], time='2pm')
G.nodes[1]
{'time': '5pm'}
G.nodes[1]['room'] = 714
G.nodes.data()
NodeDataView({1: {'time': '5pm', 'room': 714}, 3: {'time': '2pm'}})

请注意,将节点添加到 G.nodes 不会将其添加到图中,使用 G.add_node() 添加新节点。边也类似。

边属性#

使用 add_edge()add_edges_from() 或下标符号添加/更改边属性。

G.add_edge(1, 2, weight=4.7 )
G.add_edges_from([(3, 4), (4, 5)], color='red')
G.add_edges_from([(1, 2, {'color': 'blue'}), (2, 3, {'weight': 8})])
G[1][2]['weight'] = 4.7
G.edges[3, 4]['weight'] = 4.2

特殊属性 weight 应该是数值类型,因为它会被需要带权边的算法使用。

有向图#

DiGraph 类提供了特定于有向边的额外方法和属性,例如 DiGraph.out_edgesDiGraph.in_degreeDiGraph.predecessors()DiGraph.successors() 等。为了让算法能够轻松地同时处理这两种类,有向图版本的 neighbors 等同于 successors,而 DiGraph.degree 报告 DiGraph.in_degreeDiGraph.out_degree 的总和,尽管有时这可能感觉不一致。

DG = nx.DiGraph()
DG.add_weighted_edges_from([(1, 2, 0.5), (3, 1, 0.75)])
DG.out_degree(1, weight='weight')
0.5
DG.degree(1, weight='weight')
1.25
list(DG.successors(1))
[2]
list(DG.neighbors(1))
[2]

有些算法仅适用于有向图,而另一些算法对于有向图则没有明确定义。将有向图和无向图混为一谈确实很危险。如果您出于某种测量目的想将有向图视为无向图,您应该考虑使用 Graph.to_undirected() 或通过以下方式将其转换

H = nx.Graph(G)  # create an undirected graph H from a directed graph G

多重图#

NetworkX 为允许任意一对节点之间存在多条边的图提供了类。MultiGraphMultiDiGraph 类允许您添加同一条边两次,可能带有不同的边数据。这对于某些应用来说可能很强大,但许多算法在此类图上没有明确定义。如果结果明确定义(例如 MultiGraph.degree()),我们提供该函数。否则,您应该以使测量明确定义的方式转换为标准图。

MG = nx.MultiGraph()
MG.add_weighted_edges_from([(1, 2, 0.5), (1, 2, 0.75), (2, 3, 0.5)])
dict(MG.degree(weight='weight'))
{1: 1.25, 2: 1.75, 3: 0.5}
GG = nx.Graph()
for n, nbrs in MG.adjacency():
   for nbr, edict in nbrs.items():
       minvalue = min([d['weight'] for d in edict.values()])
       GG.add_edge(n, nbr, weight = minvalue)

nx.shortest_path(GG, 1, 3)
[1, 2, 3]

图生成器和图操作#

除了逐节点或逐边构建图之外,还可以通过以下方式生成图

1. 应用经典图操作,例如:#

subgraph(G, nbunch)

返回 nbunch 中节点导出的子图。

union(G, H[, rename])

合并图 G 和 H。

disjoint_union(G, H)

合并图 G 和 H。

cartesian_product(G, H)

返回 G 和 H 的笛卡尔积。

compose(G, H)

通过将节点和边组合成一个图来合成图 G 和 H。

complement(G)

返回 G 的补图。

create_empty_copy(G[, with_data])

返回图 G 的一个副本,其中移除了所有边。

to_undirected(graph)

返回图 graph 的无向视图。

to_directed(graph)

返回图 graph 的有向视图。

2. 调用经典小图之一,例如:#

petersen_graph([create_using])

返回 Petersen 图。

tutte_graph([create_using])

返回 Tutte 图。

sedgewick_maze_graph([create_using])

返回一个包含环的小迷宫图。

tetrahedral_graph([create_using])

返回 3 正则的柏拉图四面体图。

3. 使用经典图的(构造性)生成器,例如:#

complete_graph(n[, create_using])

返回具有 n 个节点的完全图 K_n

complete_bipartite_graph(n1, n2[, create_using])

返回完全二分图 K_{n_1,n_2}

barbell_graph(m1, m2[, create_using])

返回杠铃图:两个完全图由一条路径连接。

lollipop_graph(m, n[, create_using])

返回棒棒糖图;K_m 连接到 P_n

例如

K_5 = nx.complete_graph(5)
K_3_5 = nx.complete_bipartite_graph(3, 5)
barbell = nx.barbell_graph(10, 10)
lollipop = nx.lollipop_graph(10, 20)

4. 使用随机图生成器,例如:#

erdos_renyi_graph(n, p[, seed, directed, ...])

返回一个 \(G_{n,p}\) 随机图,也称为 Erdős-Rényi 图或二项图。

watts_strogatz_graph(n, k, p[, seed, ...])

返回 Watts–Strogatz 小世界图。

barabasi_albert_graph(n, m[, seed, ...])

使用 Barabási–Albert 优先连接返回随机图

random_lobster(n, p1, p2[, seed, create_using])

返回随机龙虾图。

例如

er = nx.erdos_renyi_graph(100, 0.15)
ws = nx.watts_strogatz_graph(30, 3, 0.1)
ba = nx.barabasi_albert_graph(100, 5)
red = nx.random_lobster(100, 0.9, 0.9)

5. 使用常见图格式读取存储在文件中的图#

NetworkX 支持许多流行格式,例如边列表、邻接列表、GML、GraphML、LEDA 等。

nx.write_gml(red, "path.to.file")
mygraph = nx.read_gml("path.to.file")

有关图格式的详细信息,请参阅读取和写入图;有关图生成器函数的详细信息,请参阅图生成器

分析图#

G 的结构可以使用各种图论函数进行分析,例如

G = nx.Graph()
G.add_edges_from([(1, 2), (1, 3)])
G.add_node("spam")       # adds node "spam"
list(nx.connected_components(G))
[{1, 2, 3}, {'spam'}]
sorted(d for n, d in G.degree())
[0, 1, 1, 2]
nx.clustering(G)
{1: 0, 2: 0, 3: 0, 'spam': 0}

一些输出较大的函数会迭代 (节点, 值) 2 元组。如果您愿意,可以轻松地将这些存储在 dict 结构中。

sp = dict(nx.all_pairs_shortest_path(G))
sp[3]
{3: [3], 1: [3, 1], 2: [3, 1, 2]}

有关支持的图算法的详细信息,请参阅算法

使用 NetworkX 后端#

NetworkX 可以配置使用单独的第三方后端来提高性能和增加功能。后端是可选的,需要单独安装,并且可以通过用户的代码或通过环境变量启用。

有几个后端可用,可以使用 GPU、并行处理和其他优化来加速 NetworkX(通常显着提高),而其他后端则添加了额外的功能,例如图数据库集成。可以同时使用多个后端来组成针对特定系统或用例优化的 NetworkX 运行时环境。

注意

请参阅后端部分,查看已知可与当前 NetworkX 稳定版本一起使用的可用后端列表。

NetworkX 使用后端的方式是,在运行时将函数调用调度到后端提供的相应函数,可以通过配置变量自动进行,也可以通过函数的硬编码参数显式进行。

自动调度#

自动调度可能是用户将后端与 NetworkX 代码一起使用的最简单且侵入性最小的方法。这种技术对于想要编写可在没有特定后端的系统上运行的可移植代码的用户,或者只是想在不修改现有代码的情况下使用后端的用户非常有用。

下面的示例配置 NetworkX 自动将所有 fast_backend 支持的 NetworkX 函数调度到名为 fast_backend 的后端。

  • 如果应用程序使用的某个 NetworkX 函数 fast_backend 不支持,则将使用该函数的默认 NetworkX 实现。

  • 如果在运行此代码的系统上未安装 fast_backend,则会引发异常。

bash$> NETWORKX_BACKEND_PRIORITY=fast_backend python my_script.py
my_script.py#
import networkx as nx
G = nx.karate_club_graph()
pr = nx.pagerank(G)  # runs using backend from NETWORKX_BACKEND_PRIORITY, if set

可以通过 NetworkX config 全局参数直接在代码中应用等效配置,这在不适合使用环境变量时可能很有用。这将覆盖相应的环境变量,从而允许在 Python 代码中以编程方式启用后端。然而,缺点是可移植性稍差,因为更新后端规范可能需要少量代码更改,而不是简单地更新环境变量。

nx.config.backend_priority = ["fast_backend"]
pr = nx.pagerank(G)

使用 NETWORKX_BACKEND_PRIORITY 环境变量或 nx.config.backend_priority 全局配置进行自动调度也允许指定多个后端,并根据 NetworkX 应该尝试调度的优先级进行排序。

以下示例都配置 NetworkX,首先将函数调度到 fast_backend(如果它支持该函数),如果 fast_backend 不支持,则调度到 other_backend,最后如果指定的所有后端都无法处理该调用,则使用默认的 NetworkX 实现。

bash$> NETWORKX_BACKEND_PRIORITY="fast_backend,other_backend" python my_script.py
nx.config.backend_priority = ["fast_backend", "other_backend"]

提示

NetworkX 使用 Python 标准日志机制包含调试日志调用。可以启用这些日志以帮助用户了解何时以及如何使用后端。

仅在 NetworkX 模块中启用调试日志

import logging
_l = logging.getLogger("networkx")
_l.addHandler(_h:=logging.StreamHandler())
_h.setFormatter(logging.Formatter("%(levelname)s:NetworkX:%(message)s"))
_l.setLevel(logging.DEBUG)

或全局启用

logging.basicConfig(level=logging.DEBUG)

显式调度#

后端也可以通过使用 backend= 关键字参数指定后端,在每次函数调用时显式使用。这种技术不仅要求后端已安装,而且*还*要求后端实现了该函数,因为如果使用 backend= 指定了后端,NetworkX 将不会回退到默认的 NetworkX 实现。

这可能是可移植性最低的选项,但其优点是如果无法使用 fast_backend,NetworkX 将引发异常,这对于需要特定实现的用户非常有用。显式调度还可以提供更具交互性的体验,并且对于演示、实验和调试特别有用。

pr = nx.pagerank(G, backend="fast_backend")

高级调度选项#

NetworkX 调度器允许用户以本教程未涵盖的非常特定的方式使用 NetworkX 代码的后端。请参阅后端参考部分,了解诸如以下主题的详细信息

  • 控制如何将特定函数类型(算法与生成器)调度到特定后端

  • 关于后端图和 NetworkX 图之间进行调度和回退时的自动转换的详细信息

  • 缓存图转换

  • 显式后端图实例化和基于后端图类型的调度

  • 等等…

绘图#

NetworkX 主要不是一个绘图包,但包含了使用 Matplotlib 进行基本绘图的功能以及使用开源软件 Graphviz 的接口。这些是 networkx.drawing 模块的一部分,如果可能将被导入。

首先导入 Matplotlib 的绘图接口 (pylab 也可以)

import matplotlib.pyplot as plt

为了测试导入 ~networkx.drawing.nx_pylab 是否成功,请使用以下方法之一绘制 G

G = nx.petersen_graph()
subax1 = plt.subplot(121)
nx.draw(G, with_labels=True, font_weight='bold')
subax2 = plt.subplot(122)
nx.draw_shell(G, nlist=[range(5, 10), range(5)], with_labels=True, font_weight='bold')
_images/b91042efb2e607c59c4207e1d1118cad7fc78e2b5dd01ae957bb1067cbf8edcd.png

当绘制到交互式显示器时。请注意,如果您未在交互模式下使用 matplotlib,可能需要发出 Matplotlib

plt.show()

命令。

options = {
    'node_color': 'black',
    'node_size': 100,
    'width': 3,
}
subax1 = plt.subplot(221)
nx.draw_random(G, **options)
subax2 = plt.subplot(222)
nx.draw_circular(G, **options)
subax3 = plt.subplot(223)
nx.draw_spectral(G, **options)
subax4 = plt.subplot(224)
nx.draw_shell(G, nlist=[range(5,10), range(5)], **options)
_images/33974b662ab27058ce0a6a9175e7474821b7f7aaa40bea306cac11362889d775.png

您可以通过 draw_networkx() 找到更多选项,并通过 layout module 找到布局。您可以使用 draw_shell() 绘制多个 shell。

G = nx.dodecahedral_graph()
shells = [[2, 3, 4, 5, 6], [8, 1, 0, 19, 18, 17, 16, 15, 14, 7], [9, 10, 11, 12, 13]]
nx.draw_shell(G, nlist=shells, **options)
_images/869502982e49e8a4db02c37c3f5d8b5b9d19a340e66423009a619c6523ee3560.png

例如,要将图保存到文件,请使用

>>> nx.draw(G)
>>> plt.savefig("path.png")

此函数会将图像写入本地目录中的文件 path.png。如果您的系统上安装了 Graphviz 和 PyGraphviz 或 pydot,您还可以使用 networkx.drawing.nx_agraph.graphviz_layoutnetworkx.drawing.nx_pydot.graphviz_layout 来获取节点位置,或者将图以 dot 格式写入以便进一步处理。

>>> from networkx.drawing.nx_pydot import write_dot
>>> pos = nx.nx_agraph.graphviz_layout(G)
>>> nx.draw(G, pos=pos)
>>> write_dot(G, 'file.dot')

更多详细信息请参阅绘图

NX 指南#

如果您有兴趣了解更多关于 NetworkX、图论和网络分析的知识,那么您应该查看nx-guides。在那里您可以找到教程、实际应用以及对图和网络算法的深入研究。所有材料均为官方内容,由 NetworkX 社区开发和策划。