本篇为Python networkx这个包的入门教程,翻译自官网的Tutorial(详情请戳:NetworkX documentation — NetworkX 1.10 documentation http://networkx.github.io/documentation/latest/index.html)。
翻译风格较随意,如有错误请在下方留言。感谢!
内容比较入门和基础,从创建图,节点,边开始,到图的操作,分析及可视化。
Tutorial目录如下:
- 创建一个图(Creating a graph)
- 节点(Nodes)
- 边(Edges)
- 节点和边的使用(What to use as nodes and edges)
- 访问边(Accessing edges)
- 为图,节点,边增加属性(Adding attributes to graphs, nodes and edges)
- 有向图(Directed graphs)
- 复合图(Multigraphs)
- 图生成器和图操作(Graph generators and graph operations)
- 图分析(Analyzing graphs)
- 图可视化(Drawing graphs)
那么现在就从使用NetworkX创建一个图开始。
- 创建一个图(Creating a graph)
创建一个没有节点和边的图。
>>> import networkx as nx
>>> G = nx.Graph()
根据图的定义(一个图是一个节点的集合,其中有确定的节点对,或者称为边或连接),在 NetworkX 中,节点可以被hash成任意对象,比方说字符串,图像,XML对象,一个图或自定义的节点对象等。(python中可hash的对象 – 困兽犹斗 – 博客频道 – CSDN.NET http://blog.csdn.net/breakback/article/details/7350310)
注意:Python 的 None 对象不应该被用来当做节点,因为在很多函数中,可选参数已经被赋值(没太理解这句话,原文:Python’s None object should not be used as a node as it determines whether optional function arguments have been assigned in many functions.)。
- 节点(Nodes)
图 G 可以通过多种方式进行“成长”。NetworkX 包含了很多的图生成函数和工具来对不同格式的图文件进行读写操作。我们先来几个简单的操作,比方说在一个图上增加一个节点。
>>> G.add_noed(1)
或者是以列表的形式批量添加节点。
>>> G.add_nodes_from([2, 3])
或者是使用 add_nodes_from 的方法,增加任意个节点。节点可以是列表,集合,图,文件等形式。下面使用 add_nodes_from 方法把图 H 中的节点添加进到图 G 中。
>>> H = nx.path_graph(10) # 实际 H 是一个图。该图是一个边两两相连的图,节点为0到9
>>> print H.edges() # [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8), (8, 9)]
>>> print H.nodes() # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> print type(H) # <class 'networkx.classes.graph.Graph'>
>>> print path_graph(10) # path_graph(10)
>>> G.add_nodes_from(H)
需要注意的是,我们也可以把一个图
作为节点。比方我们把图 H 作为图 G 的一个节点。
>>> G.add_node(H)
译者注:需要注意的是,add_nodes(GraphObj) 和 add_nodes_from(GraphObj) 二者是不同的,前者是把一个图对象作为一个节点添加,而后者是将一个图中的节点添加到另一个图中。
当前,图 G 包含图 H 作为一个节点。这种灵活性非常强大,因为它允许不同的对象作为节点(字符串,整型值,图,文件等)。所以,对于想要构建一个借助复杂网络包 NetworkX 的应用而言,很值得去思考如何来构建每一个节点对象。当然,如果您喜欢,也可以在图 G 中使用一个独特的身份的变量作为节点,同时也可以使用类似字典这样键值分开的信息存储形式作为节点。
需要注意的是,如果hash取决于内容,那么您不应该改变节点对象(译者注:不太懂这句。Note: You should not change the node object if the hash depends on its contents.)
- 边(Edges)
图 G 也可以通过每次增加一条边来形成:
>>> G.add_edge(1,2)
>>> e=(2,3)
>>> G.add_edge(*e) # unpack edge tuple*
通过一个边 list 来增加(其中 list 里面的一级元素是tuple类型的,每个tuple存储的是两个值,即构成边的两个节点):
>>> G.add_edges_from([(1,2),(1,3)])
或者通过一串边的集合 ebunch , 一种支持迭代的边元组(edge-tuple)容器。一个边元组(edge-tuple)可以是有两个元素的 tuple ,也可以是三个元素的tuple。如果是两个元素的tuple很好理解,这两个元素就是构成这条边的两个节点,而如果是三个元素的话,额外增加的这个元素的作用是这条边的属性,通常来说是以键值对的形式存在的,比方说(2, 3, [‘weight’: 3.1415]),边的属性 weight 的值为3.1415。别着急,我们会在后面讨论更多有关边属性的问题。
与 add_nodes_from 方法类似,我们也有 add_edges_from 方法,可以从不同种集合(list,tuple,set,其里面的元素一般为edge-tuple)中获取边信息。例如,从图 H 中获取边信息作为图 G 的边:
>>> G.add_edges_from(H.edges())
我们也有类似的方法来对一个图进行拆解(即拆边),使用 Graph.remove_node() 逐个节点进行移除,或者是通过 Graph.remove_nodes_from() 方法批量移除节点,当然还有边的移除方法 Graph.remove_edge() 和 Graph.remove_edges_from() 方法。比方,移除图 G 中一个为图 H 的节点:
>>> G.remove_node(H)
当添加已存在的边或者节点时,不会有任何异常提示。下面演示这个例子,首先把图 G 上的节点和边全部清除:
>>> 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 由 8 个节点和 2 条边构成:
>>> G.number_of_nodes()
8
>>> G.number_of_edges()
2
我们可以通过下面的方法来进行检查:
>>> G.nodes()
['a', 1, 2, 3, 'spam', 'm', 'p', 's']
>>> G.edges()
[(1, 2), (1, 3)]
>>> G.neighbors(1)
[2, 3]
与添加节点或边的语法类似,移除节点或边可以写成:
>>> G.remove_nodes_from("spam")
>>> G.nodes()
[1, 2, 3, 'spam']
>>> G.remove_edge(1,3)
当使用图的类进行图创建(或说是图的实例化)时,您可以用不同的形式:
>>> H=nx.DiGraph(G) # create a DiGraph using the connections from G >>> H.edges() [(1, 2), (2, 1)] >>> edgelist=[(0,1),(1,2),(2,3)] >>> H=nx.Graph(edgelist)
节点和边的使用(What to use as nodes and edges)
您也许会注意到节点和边没有在 NetworkX 中作为确定的对象。这就使得您可按照您自己的想法来对边和节点对象进行创建。最常见的节点选择是数值型和字符串,但是一个节点可以是任何可以hash的对象(不包含 None ),边可以通过 G.add_edge(n1, n2, object=x) 与任意的 x 对象进行关联。
举个例子, n1 和 n2 是来自RCSB蛋白质数据银行的蛋白质对象,同时 x 指的是一个 XML 对象,它记录了在实验中两个蛋白质对象间的相互作用。
我们发现一个非常有用的方法,但对不熟悉Python的人而言,滥用可能造成不可预料的后果。如果您对此便是怀疑,那就使用 convert_node_labels_to_integers() 方法来获取一个更加传统的图(其节点都是整型数值)。
- 访问边(Accessing edges)
除了方法 Graph.nodes() 和 Graph.edges() 方法,我们还有 Graph.neighbors() 方法。迭代器版本可以帮您节省资源,当您想要对超大型列表中的元素进行遍历的时候,您就可以使用这样迭代的方法。
您也可以通过列表的元素下标快速直接的访问图数据结构。
注意:不要改变返回字典形式的数据结构,因为它是图数据结构的一部分,同时直接的操作有可能导致图的不一致状态(Do not change the returned dict–it is part of the graph data structure and direct manipulation may leave the graph in an inconsistent state.)。
>>> G.add_edge(1,3)
>>> G[1][3]['color']='blue'
所有边的快速加测是使用邻接迭代器(adjacency iterators)完成的。需要注意的是,对于无向图来说,看起来要在每一条边上循环两次。
>>> 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.adjacency_iter():
... for nbr,eattr in nbrs.items():
... data=eattr['weight']
... if data<0.5: print('(%d, %d, %.3f)' % (n,nbr,data))
(1, 2, 0.125)
(2, 1, 0.125)
(3, 4, 0.375)
(4, 3, 0.375)
下面的是访问所有边较为方便的方法。
>>> for (u,v,d) in FG.edges(data='weight'): ... if d<0.5: print('(%d, %d, %.3f)'%(n,nbr,d)) (1, 2, 0.125) (3, 4, 0.375)
为图,节点,边增加属性(Adding attributes to graphs, nodes and edges)
例如权重,标记,颜色或者任何您想要的Python对象,都可以和图,节点或者边进行关联,附属使其成为一个属性。
每个图,节点和边都可以使用键值对的字典属性形式进行关联(但其中,键值对的键必须是可哈希的,hashable)。默认情况下属性是空的,但属性可以加入或者修改,通过使用add_edge,add_node或者直接地对属性字典(G.graph,G.node,G.edge)进行操作。
6.1 图属性(Graph attributes)
我们可以在创建一个新图的时候对图的属性进行赋值。
>>> G = nx.Graph(day="Friday")
>>> G.graph
{'day': 'Friday'}
当然,您也可以在创建完成以后对属性修改。
>>> G.graph['day']='Monday'
>>> G.graph
{'day': 'Monday'}
6.2 节点属性(Nodes attributes)
使用 add_node() , add_nodes_from 或者 G.node 方法来增加节点属性。
>>> G.add_node(1, time='5pm')
>>> G.add_nodes_from([3], time='2pm')
>>> G.node[1]
{'time': '5pm'}
>>> G.node[1]['room'] = 714
>>> G.nodes(data=True)
[(1, {'room': 714, 'time': '5pm'}), (3, {'time': '2pm'})]
需要注意的是,往图 G.node 中增加一个节点并不会加入到图中,使用 G.add_node() 添加新节点。(译者注:这句话没理解。“Note that adding a node to G.node does not add it to the graph, use G.add_node() to add new nodes.”)
6.3 边属性
我们可以使用 add_edge() ,add_edge_from() 方法,下标或者 G.edge 来增加一个边属性。
>>> 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.edge[1][2]['weight'] = 4
特别属性权重“weight”是数值型的,该属性值用于某些需要边权重的算法。
- 有向图(Directed graphs)
DiGraph 类提供了附加的方法来定义有向图及其边,比方说 DiGraph.out_edges() , DiGraph.in_degree() , DiGraph.predecessors() , DiGraph.successors() 方法等(译者注: successors 译为后继节点,通常后继是在有向图中的)。为了两个类的兼容性(译者注:这两个类应该是指无向图和有向图的类), neighbors() 和 degree() 方法在有向图的版本里分别等价于 successors() 方法以及 in_degree() 和 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
>>> DG.successors(1)
[2]
>>> DG.neighbors(1)
[2]
某些方法只应用于有向图,还有些方法针对有向图并没有很好地进行定义。实际上,我们在对于有向图和无向图一起定义是比较危险的。如果您想要计算一个有向图的某些参数,需要用到无向图的方法,我们建议您使用 Graph.to_undirected() 方法来进行有向图到无向图的转换:
>>> H = nx.Graph(G) # 把有向图 G 强制转换为无向图
复合图(Multigraphs)
NetworkX 提供图的类使得在任意一对相同节点间定义多条边。 MultiGraph 和 MultiDiGraph 类可以为您增加相同的边两次,当然边带有不同的数据(译者注:即同一条边的不同属性权重值)。这一特点可以在某些应用中发挥作用,但很多算法并不能在这样的图上进行很好地定义,其中最短路径算法就是这样的。然而,有一些我们已实现的函数是被定义好,例如 MultiGraph.degree() 方法(译者注:不太确定对这句的翻译。“Where results are well defined, e.g. MultiGraph.degree() we provide the function.”)。否则您应该将图以某种方法转换为一个有那个函数方法的图(译者注:比方说图 G 当前是无向图,但是我们要用某些有向图的函数方法,那么我们就要将其转换为有向图,再使用针对有向图定义好的函数方法)。
-
>>> MG=nx.MultiGraph() >>> MG.add_weighted_edges_from([(1,2,.5), (1,2,.75), (2,3,.5)]) >>> MG.degree(weight='weight') {1: 1.25, 2: 1.75, 3: 0.5} >>> GG=nx.Graph() >>> for n,nbrs in MG.adjacency_iter(): ... 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]
图生成器和图操作(Graph generators and graph operations)
我们可以通过逐个节点或者逐条边添加的方式构成图,此外,我们也可以用以下方式生成:
9.1 应用经典的图操作,例如边
- subgraph(G, nbunch) – induce 1subgraph of G on nodes in nbunch(译者注:没看懂)
- union(G1,G2) – 两个图像合并
- disjoint_union(G1,G2) – 假设两个图的节点都不同的情况下进行合并
- cartesian_product(G1,G2) – 返回图 G1 和图 G2 的笛卡尔乘积图
- compose(G1,G2) – 结合两个图共有的节点
- complement(G) – 图补全为完全图
- create_empty_copy(G) – 返回与图 G 一样的类
- convert_to_undirected(G) – 返回无向的图 G 表示
- convert_to_directed(G) – 返回图 G 的有向图表示
9.2 调用经典的小图生成函数,例如:
>>> petersen=nx.petersen_graph()
>>> tutte=nx.tutte_graph()
>>> maze=nx.sedgewick_maze_graph()
>>> tet=nx.tetrahedral_graph()
9.3 使用经典图生成器,例如:
>>> 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)
9.4 使用随机图像生成器,例如:
>>> 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)
9.5 读取存在文件中的图
文件使用常见的图像格式,例如边列表(edge lists),邻接列表(adjacency lists),GML,GraphML,pickle,LEDA或者其它格式。
>>> nx.write_gml(red,"path.to.file") >>> mygraph=nx.read_gml("path.to.file")
更多有关图格式的内容参考: NetworkX 的 /reference/readwrite- 更多有关图生成函数的内容参考: NetworkX 的 /reference/generators
- 图分析(Analyzing)
图 G 的结构可以使用不同的图理论函数进行分析,例如:
>>> G=nx.Graph()
>>> G.add_edges_from([(1,2),(1,3)])
>>> G.add_node("spam") # 加入标签名为"spam"的节点
>>> nx.connected_components(G)
[[1, 2, 3], ['spam']]
>>> sorted(nx.degree(G).values())
[0, 1, 1, 2]
>>> nx.clustering(G)
{1: 0.0, 2: 0.0, 3: 0.0, 'spam': 0.0}
函数会返回节点的属性以字典的形式标识出来,其中,键一般是节点的标签。
在计算节点度的问题上。您可以提供单一节点或者一组节点做为 nx.degree() 的参数,如果单个节点作为参数,那么将会返回单个值。如果一组节点作为参数,那么函数将会返回一个字典变量(译者注:包含所有组节点的值)。
更多有关图算法的可以查看参考文献(译者注:本篇 Tutorial 没有参考文献,相关的内容可以在 NetworkX 的官方文档中的 /reference/algorithms 查看)。
- 图可视化(Drawing graphs)
NetworkX 主要并不是用来绘制图形的包,但是借助 Matplotlib 库的基本绘制功能,或者是开源的 Graphviz 软件包也可以实现图的可视化。本节内容将会介绍 network.drawing 包。更多内容请参考 /reference/drawing 。
需要注意的是,在 NetworkX 中的 drawing 包并不兼容 Python 3.0 及其以上版本。
首先导入 Matplotlib 的绘制界面包(也可以使用 pylab 包)。
>>> import matplotlib.pyplot as plt
您可能发现交互地测试代码是非常高效用的,您可以使用“ipython -pylab”命令,它集合了 ipython 强大的能力以及 matplotlib 库方便的交互模式。
您可以使用下面的命令测试 networkx.drawing 是否成功导入:
>>> nx.draw(G)
>>> nx.draw_random(G)
>>> nx.draw_circular(G)
>>> nx.draw_spectral(G)
需要注意的是,当绘制一个可交互的图像时,您需要提前声明 matplotlib 库。
>>> plt.show()
如果您的命令没有在 matplotlib 在交互模式下:(请查看 Matplotlib 的常见疑问)
保存绘画的图像,可以用下面的命令:
>>> nx.draw(G)
>>> plt.savefig("path.png")
命令会把绘制出的图像写到当前目录名为“path.png”的文件中。如果是使用 Graphviz 或 pydot库,您也可以使用下面的命令保存:
>>> nx.draw_graphviz(G)
>>> nx.write_dot(G,'file.dot')
更多详情请参考 NetworkX 库在绘制图像的参考/reference/drawing。