教程:使用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对象,包括intstrfloattuplebool对象。不过为了入门,我们先从简单的操作开始,从一个空图起步。您可以一次添加一个节点,

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.nodesG.edgesG.adjG.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.edgesGraph.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.graphG.nodesG.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 应为数值类型,因为需要加权边的算法会使用该属性。

诱导深拷贝 subgraphedge_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_edgesDiGraph.in_degreeDiGraph.predecessors()DiGraph.successors()。为了让算法能同时轻松处理有向和无向图类,有向版本的neighbors()等同于successors(),而degree则报告in_degreeout_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)