教程:使用NetworkX API进行图操作¶
NetworkX 是一个用于单机环境下图数据操作和功能实现的Python包。然而,它缺乏在分布式环境中处理大规模图数据的能力。幸运的是,GraphScope兼容NetworkX的API接口,因此使用NetworkX编写的程序只需稍作修改即可直接在GraphScope上运行。本教程将首先介绍如何使用NetworkX API操作图数据。
创建一个空图¶
要创建一个没有节点和边的空图,我们只需要简单地新建一个Graph对象。
# Import the graphscope and graphscope networkx module.
import graphscope
import graphscope.nx as nx
G = nx.Graph()
添加节点¶
图G的节点可以通过多种方式添加。在graphscope.nx中,节点可以是可哈希的Python对象,包括int、str、float、tuple、bool对象。不过为了入门,我们先从简单的操作开始,从一个空图起步。您可以一次添加一个节点,
G.add_node(1)
或从任何可迭代容器中添加节点,例如list:
G.add_nodes_from([2, 3])
如果您的容器生成形式为(node, node_attribute_dict)的二元组,您也可以同时添加节点及其属性:
节点属性将在下文进一步讨论。
G.add_nodes_from(
[
(4, {"color": "red"}),
(5, {"color": "green"}),
]
)
一个图中的节点可以被整合到另一个图中:
H = nx.path_graph(10)
G.add_nodes_from(H)
之后,G 将包含 H 的节点作为 G 的节点。
list(G.nodes)
list(G.nodes.data()) # shows the node attributes
添加边¶
G 的边也可以通过一次添加一条边来扩展,
G.add_edge(1, 2)
e = (2, 3)
G.add_edge(*e) # unpack edge tuple*
list(G.edges)
或者通过添加边的列表,
G.add_edges_from([(1, 2), (1, 3)])
list(G.edges)
或者通过添加任意一组边来实现。边组可以是任何可迭代的边元组容器。边元组可以是一个包含2个节点的2元组,也可以是一个包含2个节点后跟边属性字典的3元组,例如(2, 3, {'weight': 3.1415})。边属性将在下文进一步讨论。
G.add_edges_from([(2, 3, {"weight": 3.1415})])
list(G.edges.data()) # shows the edge attributes
G.add_edges_from(H.edges)
list(G.edges)
或者通过一次性添加新节点和边,使用 .update(nodes, edges)
G.update(edges=[(10, 11), (11, 12)], nodes=[10, 11, 12])
list(G.nodes)
list(G.edges)
请注意,添加已存在的节点或边时不会产生报错。例如,在移除所有节点和边之后,
G.clear()
我们添加新的节点/边,graphscope.nx会自动忽略已存在的元素。
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()
G.number_of_edges()
检查图的元素¶
现在我们可以检查节点和边。四个基本的图属性有助于报告:G.nodes、G.edges、G.adj和G.degree。这些是图中节点、边、邻居(邻接关系)和节点度数的类集合视图。它们提供了持续更新的只读视图来展示图结构。这些视图还具有类似字典的特性,您可以通过视图查找节点和边的数据属性,并使用.items()和.data('span')方法迭代数据属性。如果您想要特定的容器类型而非视图,可以指定一个。这里我们使用列表,但在其他场景中集合、字典、元组或其他容器可能更合适。
list(G.nodes)
list(G.edges)
list(G.adj[1]) # or list(G.neighbors(1))
G.degree[1] # the number of edges incident to 1
可以通过指定nbunch来报告图中部分节点的边和度数。nbunch可以是以下任意一种:None(表示所有节点)、单个节点,或是图中节点组成的可迭代容器(但容器本身不是图中的节点)。
从图中移除元素¶
可以通过类似添加节点/边的方式从图中移除节点和边。可以使用方法 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)
list(G.edges)
G.remove_edge(1, 3)
G.remove_edges_from([(1, 2), (2, 3)])
list(G.edges)
使用图构造器¶
图对象无需逐步构建——可以直接将指定图结构的数据传递给各种图类的构造函数。通过实例化其中一个图类来创建图结构时,您可以用多种格式指定数据。
G.add_edge(1, 2)
H = nx.DiGraph(G) # create a DiGraph using the connections from G
list(H.edges())
edgelist = [(0, 1), (1, 2), (2, 3)]
H = nx.Graph(edgelist)
list(H.edges)
访问边与邻居节点¶
除了视图 Graph.edges 和 Graph.adj 之外,还可以使用下标表示法访问节点的边和邻居。
G = nx.Graph([(1, 2, {"color": "yellow"})])
G[1] # same as G.adj[1]
G[1][2]
G.edges[1, 2]
如果边已存在,可以使用下标表示法获取/设置边的属性。
G.add_edge(1, 3)
G[1][3]["color"] = "blue"
G.edges[1, 3]
G.edges[1, 2]["color"] = "red"
G.edges[1, 2]
快速检查所有(节点,邻接)对可通过使用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})")
通过以下方式访问所有边及其边属性:
for (u, v, wt) in FG.edges.data("weight"):
if wt < 0.5:
print(f"({u}, {v}, {wt:.3})")
为图、节点和边添加属性¶
诸如权重、标签、颜色等属性可以附加到图、节点或边上。
每个图、节点和边都可以存储键/值属性。默认情况下这些属性为空,但可以通过使用add_edge()、add_node()或直接操作名为G.graph、G.nodes和G.edges的属性字典来添加或修改属性,其中G表示一个图。
图属性¶
在创建新图时可以分配图属性
G = nx.Graph(day="Friday")
G.graph
并且可以稍后修改属性
G.graph["day"] = "Monday"
G.graph
节点属性¶
可以通过add_node()、add_nodes_from()或G.nodes添加节点属性
G.add_node(1, time="5pm")
G.add_nodes_from([3], time="2pm")
G.nodes[1]
G.nodes[1]["room"] = 714
G.nodes.data()
请注意,向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
G.edges.data()
特殊属性 weight 应为数值类型,因为需要加权边的算法会使用该属性。
诱导深拷贝 subgraph 和 edge_subgraph¶
graphscope.nx 支持通过给定节点集或边集生成一个 deepcopy 子图。
G = nx.path_graph(10)
# induce a subgraph by nodes
H = G.subgraph([0, 1, 2])
list(H.nodes)
list(H.edges)
# induce a edge subgraph by edges
K = G.edge_subgraph([(1, 2), (3, 4)])
list(K.nodes)
list(K.edges)
请注意,与NetworkX中返回视图的subgraph/edge_subgraph API不同,graphscope.nx返回的是subgraph/edge_subgraph的深拷贝。
制作副本¶
可以使用to_directed函数返回图的有向表示形式。
DG = G.to_directed() # here would return a "deepcopy" directed representation of G.
list(DG.edges)
# or with
DGv = G.to_directed(as_view=True) # return a view.
list(DGv.edges)
# or with
DG = nx.DiGraph(G) # return a "deepcopy" of directed representation of G.
list(DG.edges)
或获取图的副本。
H = G.copy() # return a view of copy
list(H.edges)
# or with
H = G.copy(as_view=False) # return a "deepcopy" copy
list(H.edges)
# or with
H = nx.Graph(G) # return a "deepcopy" copy
list(H.edges)
请注意,graphscope.nx不支持图的浅拷贝。
有向图¶
DiGraph类提供了针对有向边的额外方法和属性,例如DiGraph.out_edges、DiGraph.in_degree、DiGraph.predecessors()和DiGraph.successors()。为了让算法能同时轻松处理有向和无向图类,有向版本的neighbors()等同于successors(),而degree则报告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")
DG.degree(1, weight="weight")
list(DG.successors(1))
list(DG.neighbors(1))
list(DG.predecessors(1))
某些算法仅适用于有向图,而其他算法对于有向图则没有明确定义。事实上,将有向图和无向图混为一谈的做法是危险的。如果出于某些测量目的需要将有向图视为无向图,您应该使用Graph.to_undirected()方法进行转换。
H = DG.to_undirected() # return a "deepcopy" of undirected representation of DG.
list(H.edges)
# or with
H = nx.Graph(DG) # create an undirected graph H from a directed graph G
list(H.edges)
有向图还支持使用DiGraph.reverse()来反转边。
K = DG.reverse() # return a "deepcopy" of reversed copy.
list(K.edges)
# or with
K = DG.reverse(copy=False) # return a view of reversed copy.
list(K.edges)
图分析¶
可以使用各种图论函数分析G的结构,例如:
G = nx.Graph()
G.add_edges_from([(1, 2), (1, 3)])
G.add_node(4)
sorted(d for n, d in G.degree())
nx.builtin.clustering(G)
在graphscope.nx,中,我们支持一些内置算法用于图分析,有关支持的图算法详情请参阅builtin algorithm。
从GraphScope图对象创建图¶
此外,我们可以通过GraphScope的方式创建图,该图是从GraphScope图对象创建的。
# we load a GraphScope graph with load_ldbc
from graphscope.dataset import load_ldbc
graph = load_ldbc(directed=False)
# create graph with the GraphScope graph object
G = nx.Graph(graph)
转换为GraphScope图对象¶
由于graphscope.nx图可以从GraphScope图创建,graphscope.nx图也可以转换为GraphScope图,例如:
nodes = [(0, {"foo": 0}), (1, {"foo": 1}), (2, {"foo": 2})]
edges = [(0, 1, {"weight": 0}), (0, 2, {"weight": 1}), (1, 2, {"weight": 2})]
G = nx.Graph()
G.update(edges, nodes)
# transform to GraphScope graph
g = graphscope.g(G)