第40单元 使用 networkx
networkx模块包含构建、修改、探索、绘制、导出和导入网络的基本工具。它支持简单图、有向图和多图。你将学会如何通过添加和删除节点、边以及属性来构建和修改网络,如何计算各种网络度量(比如中心性),以及如何探索网络社区结构。
构建和修改网络
我们利用维基百科的数据3,根据已知的国际陆地边界信息(包括其长度信息)来构建一个关于国家的网络,并发掘该网络包含的信息。该网络图是无向的,不包含循环和平行边。
3en.wikipedia.org/wiki/List_of_countries_and_territories_by_land_borders
import networkx as nx borders = nx.Graph() not_borders1 = nx.DiGraph() # 仅作为参考 not_borders2 = nx.MultiGraph() # 仅作为参考
可以通过添加或删除单个节点(或单条边),甚至是一组节点(或一组边)来修改现有网络图。当某个节点被删除时,与其相连的所有边也随之删除。当某条边被添加时,边的端点对应的节点也被添加到图中,除非它们已经存在于图中。可以使用数字或字符串对节点进行标记:
borders.add_node("Zimbabwe") borders.add_nodes_from(["Lugandon", "Zambia", "Portugal", "Kuwait", "Colombia"]) borders.remove_node("Lugandon") borders.add_edge("Zambia", "Zimbabwe") borders.add_edges_from([("Uganda", "Rwanda"), ("Uganda", "Kenya"), ("Uganda", "South Sudan"), ("Uganda", "Tanzania"), ("Uganda", "Democratic Republic of the Congo")])
当所有领土以及它们之间的连接添加完成后,你将看到一幅可读性很强的图,如下所示。
图中的节点大小表示其对应国家的陆地边界总长度,节点的不同颜色对应不同的网络社区(将在下一小节介绍)。
最后,可以使用clear()函数删除图中所有的节点和边,不过你不太可能会经常用到这个函数。
探索和分析网络
使用networkx进行网络分析和探索就如同调用几个函数和查看几个属性的值一样简单。下面使用len()函数返回图的“长度”(即图的节点数)。鉴于本书多次调用过Python的内置函数len(),相信你对于此处这样的用法应该不会感到惊讶。
len(borders) ➾ 181
实际的节点列表可以通过nodes()函数、node属性和edge属性来获得(后一个属性也包含边的字典)。node和edge这两个属性都是只读的,不能通过修改它们来添加边或节点。(即使你尝试修改它们,networkx也不会保存你的更改。)边的列表也可通过函数edges()来获得:
borders.nodes() ➾ ['Iran', 'Palestinian territories', 'Chad', 'Bulgaria', 'France', «...»] borders.node ➾ {{'Iran': {'L': 5440.0}, 'Palestinian territories': {}, ➾ 'Chad': {'L': 5968.0}, 'Bulgaria': {'L': 1808.0}, «...»}
可以看到,字典给出了节点的属性(在我们的例子中,节点的属性是陆地边界的总长度):
>borders.edge > >➾ {'Iran': {'Nagorno-Karabakh Republic': {}, 'Turkey': {}, 'Pakistan': {}, >➾ 'Afghanistan': {}, 'Iraq': {}, 'Turkmenistan': {}, 'Armenia': {}, >➾ 'Azerbaijan': {}}, «...»}
edge属性是一个字典,每个节点都对应字典的一个条目。边的属性,比如“权重”,也包含在字典中。
borders.edges()[:5] ➾ [('Iran', 'Nagorno-Karabakh Republic'), ('Iran', 'Turkey'), ➾ ('Iran', 'Pakistan'), ('Iran', 'Afghanistan'), ('Iran', 'Iraq')]
可以通过neighbors()函数获取节点的邻居列表:
borders.neighbors("Germany") ➾ ['Czech Republic', 'France', 'Netherlands, Kingdom of the', 'Denmark', ➾ 'Switzerland', 'Belgium', 'Netherlands', 'Luxembourg', 'Poland', 'Austria']
调用函数degree()、indegree()或outdegree()能算出节点邻域的长度,从而得出节点的度、入度和出度。当以无参数的方式调用这些函数时,它们返回由节点标签索引的度数字典。当使用节点标签作为参数调用它们时,它们返回该节点的度数。
borders.degree("Poland") ➾ 7 borders.degree() ➾ {'Iran': 8, 'Nigeria': 4, 'Chad': 6, 'Bulgaria': 5, 'France': 14, ➾ 'Lebanon': 2, 'Namibia': 4, «...»}
下面我们可以看一下哪个国家拥有最多的邻居:
degrees = pandas.DataFrame(list(borders.degree().items()), columns=("country", "degree")).set_index("country") degrees.sort("degree").tail(4) ➾ Country ➾ Brazil 11 ➾ Russia 14 ➾ France 14 ➾ People's Republic of China 17
一个大型网络的大型套件
使用真正的大型网络对于我们来说并非少见。(例如,Facebook的社交网络图有15.9亿个节点。初学者该如何使用这样的大型网络呢?)就像在纯Python中的实现一样,networkx并不是以高性能著称的。对于大型网络来说,应该使用NetworKit,它是一种高效的、可并行化的网络分析工具包。NetworKit的开发商声称:“在一台16核服务器上,只需几分钟就能完成拥有30亿条边的网络图的社区检测。”4对于community模块而言,这是望尘莫及的。最重要的是,NetworKit模块可以与matplotlib、scipy、numpy、pandas和networkx相结合,这进一步增强了这个模块的吸引力。
有向图不存在集聚系数的定义,但必要时可以将有向图转换为无向图。clustering()函数返回包含所有给定节点的集聚系数构成的一个字典:
nx.clustering(not_borders1) # clustering()函数不能作用在有向图上! nx.clustering(nx.Graph(not_borders1)) # 需要先转换为无向图! nx.clustering(borders) ➾ {'Iran': 0.2857142857142857, 'Nigeria': 0.5, 'Chad': 0.4, 'Bulgaria': 0.4, ➾ 'France': 0.12087912087912088, 'Lebanon': 1.0, 'Namibia': 0.5, «...» } nx.clustering(borders, "Lithuania") ➾ 0.8333333333333334
函数connected_components()、weakly_connected_components()和strong_connec ted_components()从图中返回各种连通分量(表示为节点的标签列表)的列表生成器。可以在迭代器表达式(for循环或列表解析)中使用生成器,或者使用内置的list()函数将其转换为列表。使用函数subgraph(G,n)可以得到由图G中节点列表n定义的子图。另外,也可以使用connected_component_subgraphs()的一系列函数等来计算连通分量,并将结果作为图的列表生成器:
list(nx.weakly_connected_components(borders)) # 此句代码不能运行! list(nx.connected_components(borders)) # 此句代码可以运行! ➾ [{'Iran', 'Chad', 'Bulgaria', 'Latvia', 'France', 'Western Sahara', «...»}] [len(x) for x in nx.connected_component_subgraphs(borders)] ➾ [179, 2]
Gephi它!
Gephi是“各种网络和复杂系统的交互式可视化探索平台”5,有人将它称为“网络分析的画笔”。虽然networkx具有自己的图形可视化支持(通过matplotlib的方式,详情参阅第41单元),但我更喜欢使用Gephi,因为它用途广泛,而且具有即时反馈的功能。
所有计算中心性的函数都返回一个以节点标签为索引的中心性字典或者单个节点的中心性。这些字典是用于构建pandas数据frame和具有索引的series的极佳组件。下面代码中的注释给出了不同中心性中具有最大值的国家:
nx.degree_centrality(borders) # 中国 nx.in_degree_centrality(borders) nx.out_degree_centrality(borders) nx.closeness_centrality(borders) # 法国 nx.betweenness_centrality(borders) # 法国 nx.eigenvector_centrality(borders) # 俄罗斯
管理属性
networkx使用字典实现图及其节点和边的属性。图具有节点的字典接口,节点具有其边的字典接口,边具有其属性的字典接口。你可以将属性名称和值作为可选参数传递给函数add_node()、add_nodes_from()、add_edge()和add_edges_from():
# 边属性 borders["Germany"]["Poland"]["weight"] = 456.0 # 节点属性 borders.node["Germany"]["area"] = 357168 borders.add_node("Penguinia", area=14000000)
使用可选参数data=True调用nodes()和edges()函数时,它们分别返回包含了所有属性的全部节点和边的列表:
borders.nodes(data=True) ➾ [«...», ('Germany', {'area': 357168}), «...»] borders.edges(data=True) ➾ [('Uganda', 'Rwanda', {'weight': 169.0}), ➾ ('Uganda', 'Kenya', {'weight': 933.0}), ➾ ('Uganda', 'South Sudan', {'weight': 435.0}), «...»]
团和社区结构
函数find_cliques()和isolates()能检测出图中最大的团和孤立节点(即零度节点)。函数find_cliques()不能直接用于有向图(有向图应首先强制转换为无向图)。该函数返回一个节点列表的生成器,如下所示:
nx.find_cliques(not_borders1) # 不能直接用于有向图! nx.find_cliques(nx.Graph(not_borders1)) # 转换为无向图才能正常运行 list(nx.find_cliques(borders)) ➾ [['Iran', 'Nagorno-Karabakh Republic', 'Armenia', 'Azerbaijan'], ➾ ['Iran', 'Afghanistan', 'Pakistan'], «...»] nx.isolates(borders) ➾ ['Penguinia']
用于社区检测的community模块并不包含在Anaconda发行版中,必须单独安装,而且该模块不支持有向图。
函数best_partition()使用Louvain方法并返回社区的划分结果,划分结果是一个以节点标签作为索引的字典,并用不同的数字序号区分不同的社区。函数modularity()给出社区的模块度:
import community partition = community.best_partition(borders) ➾ {'Uganda': 0, 'Zambia': 1, 'Portugal': 2, 'Bulgaria': 2, «...»} community.modularity(partition, borders) ➾ 0.7462013020754683
如果模块度太低(远小于0.5),网络就不存在清晰的社区结构,至少你不能依赖给出的划分结果。
输入和输出
networkx具有一系列从文件读取网络数据并将数据写入文件的读写函数,我们只需要负责打开和关闭文件(必要时负责创建文件)。某些函数要求文件以二进制模式打开。通过下表可以了解其中的部分函数。
表4 networkx的一些输入和输出函数
类型 | 读 | 写 | 文件后缀 |
邻接列表 | read_adjlist(f) | write_adjlist(G, f) | 无特定后缀 |
Edge list | read_edgelist(f) | write_edgelist(G, f) | 无特定后缀 |
GML | read_gml(f) | write_gml(G, f) | .gml |
GraphML | read_graphml(f) | write_graphml(G, f) | .graphml |
Pajek | read_pajek(f) | write_pajek(G, f) | .net |
with open("borders.graphml", "wb") as netfile: nx.write_pajek(borders, netfile) with open("file.net", "rb") as netfile: borders = nx.read_pajek(netfile)
不是任意格式的文件都能支持所有网络相关的特性。你可以在Gephi网站6上查看关于不同文件格式和网络特性的更多信息,了解这些内容有助于为你的图选择正确的输出格式!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论