GraphScope 以 属性图 建模图数据。 图上的点/边都带有标签(label),每个标签都可能带有许多属性(property)。
在这个教程中,我们将展示 GraphScope 如何载入一张图,包括
首先,创建会话并导入相关的包
import os
import graphscope
from graphscope.framework.graph import Graph
from graphscope.framework.loader import Loader
import vineyard
k8s_volumes = {
"data": {
"type": "hostPath",
"field": {
"path": "/testingdata", # Path in host
"type": "Directory"
},
"mounts": {
"mountPath": "/home/jovyan/datasets", # Path in pods
"readOnly": True
}
}
}
graphscope.set_option(show_log=True) # enable logging
graphscope.set_option(initializing_interactive_engine=False)
sess = graphscope.session(k8s_volumes=k8s_volumes, k8s_etcd_mem='512Mi') # create a session
我们使用类 Graph
来载入图。
在载图过程中,Graph
相当于一个builder, 可以是用户以迭代的方式来建立图,具体为,用户可以先加入一些点,再加入一些边,如此反复。
这是我们接下来将要用到的建图方法。
def add_vertices(self, vertices, label="_", properties=None, vid_field=0):
pass
def add_edges(self, edges, label="_", properties=None, src_label=None, dst_label=None, src_field=0, dst_field=1):
pass
接下来,我们将介绍这些方法,并且展示其用法。
我们使用定义在 Session
内的函数 g()
来初始化一张图。
graph = sess.g()
我们首先向图中添加一种点。参数分别是:
Loader object,代表数据源,指示 graphscope 可以在哪里找到源数据,可以为文件路径,或者 numpy 数组等;
点标签的名字;
一组属性名字。属性名应当与数据中的首行表头中的名字相一致。可选项,如果省略或为空,除ID列之外的所有列都将会作为属性载入;
作为ID列的列名,此列将会载入边时被用来做起始点ID或目标点ID。
这里是一个点配置的例子:
graph = sess.g()
graph = graph.add_vertices(
# source file for vertices labeled as person;
Loader("/home/jovyan/datasets/ldbc_sample/person_0_0.csv", delimiter="|"),
"person",
# columns loaded as property
["firstName", "lastName"],
# The column used as vertex ID
"id"
)
注意到这里有个名为 person
的域,我们将在下一节详细介绍。
在这里 Loader
是一个封装了如何载入数据的类,包含数据路径(包括 本地, HDFS, 亚马逊 S3,或阿里云 OSS),列分隔符以及一些其他元信息。
在这里,Loader
指示一个挂载在 Pod 里的文件路径。
我们可以省略部分参数:
Loader
只包含一个 URL,我们可以省略 Loader
,只写入 URL。graph = sess.g()
graph = graph.add_vertices(Loader("/home/jovyan/datasets/ldbc_sample/person_0_0.csv", delimiter="|"), "person")
在最简单的情况下,点的配置可以只包含一个 URL。此时,第0列将作为 ID, 其他所有列作为属性,而标签名为 '_'。
graph = sess.g()
graph = graph.add_vertices(Loader("/home/jovyan/datasets/ldbc_sample/person_0_0.csv", delimiter="|"))
现在我们向图中添加边。其参数分别为:
Loader
对象,代表数据源,告知 graphscope 可以在哪里找到源数据,可以是文件路径,或者 numpy 数组等;
点标签名字;
一组属性名字。属性名应当与数据中的首行表头中的名字相一致。可选项,如果省略或为空,除起始点列和目标点列之外的所有列都将会作为属性载入;
定义边的起始点的元组,格式为(起点列名,起点标签名);
定义边的目标点的元组,格式为(目标列名,目标标签名);
让我们来看一个例子:
# a kind of edge with label "knows"
graph = sess.g()
graph = graph.add_vertices(Loader("/home/jovyan/datasets/ldbc_sample/person_0_0.csv", delimiter="|"), label="person")
graph = graph.add_edges(
# the data source, in this case, is a file location.
Loader("/home/jovyan/datasets/ldbc_sample/person_knows_person_0_0.csv", delimiter="|"),
# Label name
label="knows",
# selected column names that would be load as properties
properties=["creationDate"],
# Label name of the source vertex
src_label="person",
# Label name of the destination vertex
dst_label="person",
# Column name, which is the source vertex ID
src_field="Person.id",
# Column name, which is the destination vertex ID
dst_field="Person.id.1"
)
对于边来说,一些域可以被省略。
Loader
只包含一个 URL,那么可以省略 Loader
这个类,只写 URL。graph = sess.g()
graph = graph.add_vertices(Loader("/home/jovyan/datasets/ldbc_sample/person_0_0.csv", delimiter="|"), label="person")
graph = graph.add_edges(
Loader("/home/jovyan/datasets/ldbc_sample/person_knows_person_0_0.csv", delimiter="|"),
"knows",
src_label="person",
dst_label="person",
src_field="Person.id",
dst_field="Person.id.1"
)
另外,所有的列名都可以用其在数据中的索引值来表示。 比如,在表示源点和终点的元组中的数字表示将第0列作为源点,将第1列作为终点。
graph = sess.g()
graph = graph.add_vertices(Loader("/home/jovyan/datasets/ldbc_sample/person_0_0.csv", delimiter="|"), label="person")
graph = graph.add_edges(
Loader("/home/jovyan/datasets/ldbc_sample/person_knows_person_0_0.csv", delimiter="|"),
"knows",
src_label="person",
dst_label="person"
)
进一步的,当图中只有一个点的标签时,边的源点和终点的元组也可以被省略,此时 GraphScope 认为所有边的源点和终点都为这个唯一的无歧义的点标签。
graph = sess.g()
graph = graph.add_vertices(Loader("/home/jovyan/datasets/ldbc_sample/person_0_0.csv", delimiter="|"), label="person")
graph = graph.add_edges(
Loader("/home/jovyan/datasets/ldbc_sample/person_knows_person_0_0.csv", delimiter="|"),
"knows"
)
最后,我们可以省略掉点。 GraphScope 将会从边的端点中推理出点ID,并将 _
作为点的标签。
最简单的形式可以只包含一个路径,默认情况下,第0列将作为源点 ID,第1列将作为终点 ID,所有其他列都将作为属性载入。
graph = sess.g()
graph = graph.add_edges(
Loader("/home/jovyan/datasets/ldbc_sample/person_knows_person_0_0.csv", delimiter="|"),
"knows"
)
在一些情况下,一种边标签也许连接着多种点标签。在 LDBC 图中,likes 的边标签包含两个关系,比如在论坛中,人们(Person)可以 like 帖子(Post),也可以 立刻 帖子下的评论(Comment),这两种关系可以被表示为 person likes post, 以及 person likes comments。在这种情况下,标签 likes 的值可以是一个列表。
graph = sess.g()
graph = graph.add_vertices(Loader("/home/jovyan/datasets/ldbc_sample/person_0_0.csv", delimiter="|"), label="person")
graph = graph.add_vertices(Loader("/home/jovyan/datasets/ldbc_sample/post_0_0.csv", delimiter="|"), label="post")
graph = graph.add_vertices(Loader("/home/jovyan/datasets/ldbc_sample/comment_0_0.csv", delimiter="|"), label="comment")
graph = graph.add_edges(
Loader("/home/jovyan/datasets/ldbc_sample/person_likes_comment_0_0.csv", delimiter="|"),
"comment",
["creationDate"],
src_label="person",
dst_label="comment"
)
graph = graph.add_edges(
Loader("/home/jovyan/datasets/ldbc_sample/person_likes_post_0_0.csv", delimiter="|"),
"likes",
["creationDate"],
src_label="person",
dst_label="post"
)
现在我们在GraphScope 里载入了一行图,包括两个名为 person
和 post
的点标签,以及一个名为 knows
的边标签,其包含两种关系。让我们检查一下图的模型定义。
print(graph.schema)
图的构造函数也包括一些元信息的控制,如下所示:
oid_type
, 可选项,默认为 int64_t
。 参数 oid_type
指示图上的 ID 的数据类型。可选的选项为 string
或 int64_t
。我们推荐尽量使用 int64_t
因为相比于 string
可以节省大量内存,并且在速度上也有提升。
directed
, 可选项,默认为 True
。 参数 directed
代表以有向还是无向的方式载入图。
generate_eid
, 可选项,默认为 True
。 将 generate_eid
置为 True
将为每条边自动生成一个 ID。
现在,我们来载入一张复杂一些的图。
graph = (
sess.g()
.add_vertices(Loader("/home/jovyan/datasets/ldbc_sample/person_0_0.csv", delimiter="|"), "person")
.add_vertices(Loader("/home/jovyan/datasets/ldbc_sample/comment_0_0.csv", delimiter="|"), "comment")
.add_vertices(Loader("/home/jovyan/datasets/ldbc_sample/post_0_0.csv", delimiter="|"), "post")
.add_edges(Loader("/home/jovyan/datasets/ldbc_sample/person_knows_person_0_0.csv", delimiter="|"),
"knows", src_label="person", dst_label="person")
.add_edges(Loader("/home/jovyan/datasets/ldbc_sample/person_likes_comment_0_0.csv", delimiter="|"),
"likes", src_label="person", dst_label="comment")
.add_edges(Loader("/home/jovyan/datasets/ldbc_sample/person_likes_post_0_0.csv", delimiter="|"),
"likes", src_label="person", dst_label="post")
)
print(graph.schema)
当图的规模很大时,可能要花大量时间载入(可能多达几小时)。
GraphScope 提供了序列化与反序列化图数据的功能,可以将载入的图以二进制的形式序列化到磁盘上,以及从这些文件反序列化为一张图。
graph.serialize
需要一个 path
的参数,代表写入二进制文件的路径。
graph.save_to('/tmp/seri')
graph.load_from
的参数类似 graph.save_to
. 但是,其 path
参数必须和序列化时为 graph.save_to
提供的 path
参数完全一致,因为 GraphScope 依赖命名规则去找到所有文件,注意在序列化时,所有的工作者都将其自己所持有的图数据写到一个以自己的工作者ID结尾的文件中,所以在反序列化时的工作者数目也必须和序列化时的工作者数目 完全一致。
graph.load_from
额外需要一个 sess
的参数,代表将反序列化后的图载入到此会话。
deserialized_graph = Graph.load_from('/tmp/seri', sess)
Loader
定义了如何载入数据,包含路径,元信息以及其他配置。GraphScope 支持将路径将路径指定为一个 str
。当接到 Loader
的请求时, GraphScope 解析这个字符串并调用 Vineyard
中相应协议的 driver。目前,支持的存储包括本地,亚马逊 S3,阿里云OSS, HDFS 以及网络URL。
额外的,GraphScope 也支持从符合特定格式的 pandas.DataFrame
或 numpy.ndarray
中载图。
载入数据的部分通过 Vineyard
来完成,Vineyard
利用 fsspec
来解析协议,并处理不同的格式。额外的配置参数可以通过关键字参数传递给 Loader
, 比如为 HDFS 指定 host
和 port
, 为 OSS 和 S3 指定 access-id
和 secret-access-key
.
Loader
可以只包含一个字符串。 字符串格式遵循标准的 URI 标准。
ds1 = Loader("file:///var/datafiles/edgefile.e")
用户需要提供 key
和 secret
以从 S3 中载图。额外的参数可以通过 client_kwargs
来传递, 比如桶的区域 region_name
.
ds2 = Loader("s3://bucket/datafiles/edgefile.e", key='access-id', secret='secret-access-key', client_kwargs={'region_name': 'us-east-1'})
用户需要提供 key
,secret
以及桶所在的的 endpoint
以从 OSS 中载图。
ds3 = Loader("oss://bucket/datafiles/edgefile.e", key='access-id', secret='secret-access-key', endpoint='oss-cn-hangzhou.aliyuncs.com')
用户需要提供 host
和 port
以从 HDFS 中载图,额外的参数可以通过 extra_conf
传递。
ds4 = Loader("hdfs:///var/datafiles/edgefile.e", host='localhost', port='9000', extra_conf={'conf1': 'value1'})
我们来看一个从 S3 中载图的例子。
graph = sess.g()
graph = graph.add_vertices(
Loader("s3://datasets/ldbc_sample/person_0_0.csv", delimiter="|", key='testing', secret='testing', client_kwargs={
"endpoint_url": "http://192.168.0.222:5000"
}),
"person"
)
graph = graph.add_edges(
Loader("s3://datasets/ldbc_sample/person_knows_person_0_0.csv", delimiter="|", key='testing', secret='testing', client_kwargs={
"endpoint_url": "http://192.168.0.222:5000"
}),
"knows"
)
print(graph)
print(graph.schema)
对于 pandas
来说,其 DataFrame
的格式应和 CSV 文件中数据的格式一致。
当前支持的数据类型只包括 int64
和 float64
。
import numpy as np
import pandas as pd
leader_id = np.array([0, 0, 0, 1, 1, 3, 3, 6, 6, 6, 7, 7, 8])
member_id = np.array([2, 3, 4, 5, 6, 6, 8, 0, 2, 8, 8, 9, 9])
group_size = np.array([4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2])
e_data = np.transpose(np.vstack([leader_id, member_id, group_size]))
df_group = pd.DataFrame(e_data, columns=['leader_id', 'member_id', 'group_size'])
student_id = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
avg_score = np.array([490.33, 164.5 , 190.25, 762. , 434.2, 513. , 569. , 25. , 308. , 87. ])
v_data = np.transpose(np.vstack([student_id, avg_score]))
df_student = pd.DataFrame(v_data, columns=['student_id', 'avg_score']).astype({'student_id': np.int64})
# use a dataframe as datasource, properties omitted, col_0/col_1 will be used as src/dst by default.
# (for vertices, col_0 will be used as vertex_id by default)
graph = sess.g().add_vertices(df_student).add_edges(df_group)
对于 numpy
来说,可以传入一组COO格式的数组。
array_group = [df_group[col].values for col in ['leader_id', 'member_id', 'group_size']]
array_student = [df_student[col].values for col in ['student_id', 'avg_score']]
graph = sess.g().add_vertices(array_student).add_edges(array_group)
最后,关闭会话以释放所有资源。
sess.close()