最近学习社会媒体挖掘,需要完成几个实验,Python的第三方模块networkx几乎都囊括了这些实现的接口,Python的好处就是开源,所以它的第三方模块的源码都可以看到,我看了实现的源码做了部分修改,算是完成了实验。下面对这个库做一些介绍。
1. introduction
networkX是一款Python的软件包,用于创造、操作复杂网络,以及学习复杂网络的结构、动力学及其功能。
有了networkX你就可以用标准或者不标准的数据格式加载或者存储网络,它可以产生许多种类的随机网络或经典网络,也可以分析网络结构,建立网络模型,设计新的网络算法,绘制网络等等。
2. install
对于已经装了pip的环境,安装第三方模块很简单,只需要pip install networkx即可。没有安装pip环境的机器可以先安装pip,Python3默认都是安装了pip的。
3. tutorial
Creating a graph
创建一个空图,不包含任何结点和边。
import networkx as nx G = nx.Graph()
图是顶点集和确定的顶点之间的边集组成。在NetworkX中,顶点可以是任何可以哈希的对象,比如文本,图片,XML对象,其他的图对象,任意定制的节点对象,等等。(Python中的None对象不可以作为结点类型。)
Nodes
图的增长有几种方式,NetworkX包含了许多图的产生函数和一些读写图的工具。
一次添加一个顶点。
G.add_node(1)
也可以添加顶点的列表。
G.add_nodes_from([2, 3])
或者从nbunch中添加节点,nbunch是任何可迭代的节点容器(如list、set、graph、file等),nbunch本身不是图中的一个节点。
H = nx.path_graph(10) G.add_nodes_from(H)
此时图G中就包含了图H中的10个节点。作为比较,我们可以使图H作为图G的一个节点.
G.add_node(H)
现在图G就包含了一个节点H,即该节点是一个图。可以看到这种灵活性是非常强大的,它允许图的图,文件的图,函数的图等等。因此我们应该好好思考如何构建我们的应用程序才能使我们的节点是有用的实体。当然我们可以在图中使用一个唯一的标识符或者使用一个不同的字典的键来标识节点信息。(如果该hash依赖于它的内容,则我们不应该改变节点对象)
Edges
图可以通过一次加一条边来增长。
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元组(3元组还有一个元素是边的权值,比如(1,2,{‘weight’:100}))。下面将进一步讨论边的属性:
G.add_edges_from(H.edges)
我们可以用类似的方法拆除图:
G.remove_node(),G.remove_nodes_from(),G.remove_edge(),G.remove_edges_from()
比如:
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条边。
我们可以通过如下函数查看:
print G.number_of_nodes() 8 print G.number_of_edges() 3
What to use as nodes and edges
你可能已经注意到在NetworkX中节点和边并没有被指定一个对象,因此你就可以自由地指定节点和边的对象。最常见的对象是数值和字符串,但是一个节点可以是任意hash对象(除了None对象),一条边也可以关联任意的对象x,比如:
G.add_edge(a,b,object=x)。
举个关于边关联对象的例子,加入a和b是两个人,而他们两个人之间的联系(边),可以是一个概率,即边的对象是一个概率值,表示这两个人之间每天通电话的可能性。
可以看到这是十分强大而且有用的,但是如果你滥用该方法将会导致意想不到的后果,除非你对Python真的很熟悉。如果你不是很确定,你可以考虑使用conver_node_label_to_integers(),他可以将一个图的所有节点按顺序转化为整数对象赋给另一个图。
Accessing edges and neighbors
除了上面提到的那些访问节点和边的方法以外( eg:Graph.nodes(), Graph.edges(), Graph.neighbors()…),也可以利用下标表示来获取边和邻居结点。
>>> G[1] # same as G.adj[1] AtlasView({2: {}}) >>> G[1][2] {} >>> G.edges[1, 2] {}
如果边已经存在时,也可以利用下标表示来设置边的属性。
>>> G.add_edge(1, 3) >>> G[1][3]['color'] = "blue" >>> G.edges[1, 2]['color'] = "red"
可以使用G.adjacency()或者G.adj.items()来快速访问每一条边,注意在无向图中adjacency迭代器可以看到边出现了2次。
>>> 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('(%d, %d, %.3f)' % (n, nbr, wt)) (1, 2, 0.125) (2, 1, 0.125) (3, 4, 0.375) (4, 3, 0.375)
可以通过边的属性方便的访问所有的边。
>>> for (u, v, wt) in FG.edges.data('weight'): ... if wt < 0.5: print('(%d, %d, %.3f)' % (u, v, wt)) (1, 2, 0.125) (3, 4, 0.375) `` #### Adding attributes to graphs, nodes, and edges `属性诸如weight,labels,colors,或者任何对象,你都可以附加到图、节点或边上。 对于每一个图、节点和边都可以在关联的属性字典中保存一个(多个)键-值对。默认情况下这些是一个空的字典,但是我们可以增加或者是改变这些属性通过使用add\_edge,add\_node或者字典操作这些属性字典,比如G.graph,G.node或者G.edge。 ##### Graph attributes 在创建新图时指派图的属性 `` >>> G = nx.Graph(day="Friday") >>> G.graph {'day': 'Friday'}
随后也可以修改属性
>>> G.graph['day'] = "Monday" >>> G.graph {'day': 'Monday'}
Node attributes
使用add_nodes(), 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: {'room': 714, 'time': '5pm'}, 3: {'time': '2pm'}})
注意添加一个节点是G.add_node而不是G.node。
Edge Attributes
使用add_edges(), 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应该是一个数值型的,并且在算法需要使用weight时保存该数值。
Directed graphs
DiGraph类提供了许多有向图中的额外算法,比如DiGraph.out_edges(),DiGraph.in_degree(),DiGraph.predecessors(),DiGraph.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 >>> list(DG.successors(1)) [2] >>> list(DG.neighbors(1)) [2]
有些算法只能在有向图中使用,而有些图并没有为有向图定义。确实将有向图和无向图混在在一起是很危险的一件事情,所以,如果有些实验你想把一个有向图看做一个无向图,你可以将有向图转化为无向图,通过:
H=DG.to_undirected() #或者 H=nx.Graph(DG)
Multigraphs
NetworkX提供了一个类,它可以允许任何一对节点之间有多条边。类MultiGraph和类MultiDiGraph允许添加相同的边两次,这两条边可能附带不同的权值。对于有些应用程序这是非常有用的类,但是许多算法不能够很好的在这样的图中定义,比如最短路径算法,但是像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]
Graph generators and graph operations
除了通过节点和边生成图,也可以通过以下方法产生:
使用典型的图形操作:
subgraph(G, nbunch) - 产生nbunch节点的子图
union(G1,G2) - 结合图
disjoint_union(G1,G2) - 假设所有节点都不同,然后结合图
cartesian_product(G1,G2) - 返回笛卡尔乘积图
compose(G1,G2) - 结合两个图并表示两者共同的节点
complement(G) - 图G的补图
create_empty_copy(G) - 返回同一类图的无边副本
convert_to_undirected(G) - 返回G的无向图
convert_to_directed(G) - 返回G的有向图
调用经典的小图
>>> petersen = nx.petersen_graph() >>> tutte = nx.tutte_graph() >>> maze = nx.sedgewick_maze_graph() >>> tet = nx.tetrahedral_graph()
使用生成器来构造一些典型的图
>>> 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)
使用随机图发生器
>>> 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)
通过读取存储在文件中的一些标准图形格式,例如边表,邻接表,GML,GraphML,pickle,LEAD或者其他的一些格式:
>>> nx.write_gml(red, "path.to.file") >>> mygraph = nx.read_gml("path.to.file")
Analyzing graphs
图G的结构可以通过各种图论的函数来分析,例如:
>>> G = nx.Graph() >>> G.add_edges_from([(1, 2), (1, 3)]) >>> G.add_node("spam") # adds node "spam" >>> list(nx.connected_components(G)) [set([1, 2, 3]), set(['spam'])] >>> sorted(d for n, d in G.degree()) [0, 1, 1, 2] >>> nx.clustering(G) {1: 0, 2: 0, 3: 0, 'spam': 0}
返回节点属性的函数是通过返回一个以节点为键的字典来实现的
>>> sp = dict(nx.all_pairs_shortest_path(G)) >>> sp[3] {1: [3, 1], 2: [3, 1, 2], 3: [3]}
Drawing graphs
NetworkX并不是专门用来绘图的包,但是Matplotlib以及一些接口却提供了很好的绘图功能。
首先我们导入Matplotlib的plot接口(pylab也可以)
>>> import matplotlib.pyplot as plt
你可能会发现使用“ipthon -pylab”q去交互测试代码是很有用的,它提供了强大的ipython和matplotlib,也提供了一个方便的交互模式。
测试networkx.drawing是是否导入成功,可以通过:
>>> G = nx.petersen_graph() >>> plt.subplot(121) <matplotlib.axes._subplots.AxesSubplot object at ...> >>> nx.draw(G, with_labels=True, font_weight='bold') >>> plt.subplot(122) <matplotlib.axes._subplots.AxesSubplot object at ...> >>> nx.draw_shell(G, nlist=[range(5, 10), range(5)], with_labels=True, font_weight='bold')
你要将这个图形绘制到屏幕,你可能需要Matplotlib:
>>> plt.show()
没有在交互模式下使用matplotlib的一些命令
>>> options = { ... 'node_color': 'black', ... 'node_size': 100, ... 'width': 3, ... } >>> plt.subplot(221) <matplotlib.axes._subplots.AxesSubplot object at ...> >>> nx.draw_random(G, **options) >>> plt.subplot(222) <matplotlib.axes._subplots.AxesSubplot object at ...> >>> nx.draw_circular(G, **options) >>> plt.subplot(223) <matplotlib.axes._subplots.AxesSubplot object at ...> >>> nx.draw_spectral(G, **options) >>> plt.subplot(224) <matplotlib.axes._subplots.AxesSubplot object at ...> >>> nx.draw_shell(G, nlist=[range(5,10), range(5)], **options)
可以通过draw_networkx()进行其他的操作或者layout进行布局。可以使用draw_shell()使用多重的shells。
>>> 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)
通过下面的方式可以将图保存到文件
>>> nx.draw(G) >>> plt.savefig("path.png")