加载图数据

GraphScope 将图数据建模为 Property Graph, 其中边/顶点带有标签,每个标签可以拥有多个属性。

加载内置数据集

GraphScope 提供了一系列流行的数据集和实用函数,可以轻松将它们加载到内存中,方便用户快速上手。以下是一个示例:

from graphscope.dataset import load_ogbn_mag
graph = load_ogbn_mag()

在独立模式下,系统会自动将数据下载到~/.graphscope/dataset目录,这些数据会保留在该目录供后续使用。

然而,在Kubernetes模式下,将数据下载到Pod的本地存储并非易事,因此我们提供了挂载OSS存储桶的选项,该存储桶包含所有可用数据集。例如,我们加载与上述相同的图,但这次graphscope运行在Kubernetes集群中:

import graphscope
from graphscope.dataset import load_ogbn_mag
sess = graphscope.session(cluster_type='k8s', mount_dataset='/dataset')
graph = load_ogbn_mag(sess, '/dataset/ogbn_mag_small')

在这里,我们首先在Kubernetes集群中创建了一个Session,并将数据集存储桶挂载到/dataset,该路径相对于Pods。然后我们将该会话作为第一个参数传递,/dataset/ogbn_mag_small作为第二个参数。/dataset是我们通过mount_dataset指定的数据集根路径,ogbn_mag_small是该数据集的子文件夹名称。

您可以在此这里查看所有可用数据集,并在这些源文件中获取详细的描述和使用说明。

加载自定义数据集

然而,更常见的情况是用户需要加载自己的数据并进行一些分析。 要将属性图加载到GraphScope中,我们提供了在Session中定义的g()方法。

要在GraphScope上构建属性图,我们首先使用g()创建一个空图。

import graphscope
sess = graphscope.session()
# Use `sess = graphscope.session(cluster_type='hosts')` if you are in standalone mode.
graph = sess.g()

Graph类包含以下方法:

def add_vertices(self, vertices, label="_", properties=None, vid_field=0):
    pass

def add_edges(self, edges, label="_e", properties=None, src_label=None, dst_label=None, src_field=0, dst_field=1):
    pass

这些方法帮助用户逐步构建属性图的模式结构。

在本教程中,我们将使用ldbc_sample目录下的文件。您可以通过此处获取这些文件。您可以使用print(graph.schema)来查看图模式。

添加顶点

我们可以向图中添加一类顶点,该方法具有以下参数:

vertices: 顶点数据源的位置,可以是文件路径或numpy数组等。更多详情请参阅Loader对象

一个简单的示例:

graph = sess.g()
graph = graph.add_vertices('/home/ldbc_sample/person_0_0.csv')

它将从路径 /home/ldbc_sample/person_0_0.csv 读取数据。由于我们没有提供额外参数,这些顶点默认会被标记为 _,使用文件第一列作为ID,其他列作为属性。属性的名称和数据类型都将被自动推断。

另一个常用的参数是label:

label: 顶点的标签名称,默认为 _

由于属性图允许存在多种类型的顶点,建议用户为每种顶点赋予一个有意义的标签名称。例如:

graph = sess.g()
graph = graph.add_vertices('/home/ldbc_sample/person_0_0.csv', label='person')

然后我们有一个包含一种顶点的图,其标签名称为person。

此外,每种带标签的顶点都有各自的属性。以下是第三个参数:

properties: 属性列表,可选参数,默认为 None

该参数从源数据文件或pandas DataFrame中选择对应的列作为属性。请注意,此参数的值必须存在于文件/DataFrame中。默认情况下(值为None),除vid_field列外的所有列都将被添加为属性。如果等于空列表[],则不会添加任何属性。

例如:

# properties will be firstName,lastName,gender,birthday,creationDate,locationIP,browserUsed
graph = sess.g()
graph = graph.add_vertices('/home/ldbc_sample/person_0_0.csv', label='person', properties=None)

# properties will be firstName, lastName
graph = sess.g()
graph = graph.add_vertices('/home/ldbc_sample/person_0_0.csv', label='person', properties=['firstName', 'lastName'])

# no properties
graph = sess.g()
graph = graph.add_vertices('/home/ldbc_sample/person_0_0.csv', label='person', properties=[])

vid_field 决定使用哪一列作为顶点ID。(在加载边时也作为源ID或目标ID。)

可以是str类型的列名,也可以是int类型的列索引。

默认情况下,该值为0,因此第一列将用作顶点ID。

graph = sess.g()
graph = graph.add_vertices('/home/ldbc_sample/person_0_0.csv', vid_field='firstName')

graph = sess.g()
graph = graph.add_vertices('/home/ldbc_sample/person_0_0.csv', vid_field=0)

添加边

接下来,我们来看一下加载边的参数。

edges: 指示数据读取位置的路径。例如,

graph = sess.g()
graph = graph.add_vertices('/home/ldbc_sample/person_0_0.csv', label='person')
# Note we already added a vertex label named 'person'.
graph = graph.add_edges('/home/ldbc_sample/person_knows_person_0_0.csv', src_label='person', dst_label='person')

这将加载一条边,其标签为_e(默认值),其源顶点和目标顶点将为person,使用第一列作为源顶点ID,第二列作为目标顶点ID,其余列作为属性。

与顶点类似,我们可以使用参数label来指定标签名称,并使用properties来选择属性。

label: 边的标签名称,默认为 _e。(建议使用有意义的标签名称。) properties: 属性列表,默认为 None(将所有列添加为属性)。

graph = sess.g()
graph = graph.add_vertices('/home/ldbc_sample/person_0_0.csv', label='person')
graph = graph.add_edges('/home/ldbc_sample/person_knows_person_0_0.csv', label='knows', src_label='person', dst_label='person')

与顶点不同,边具有一些额外的参数。

src_label: 源顶点的标签名称。 dst_label: 目标顶点的标签名称,可以与src_label不同, src_fielddst_field: 用于源(目标)顶点id的列。默认为0和1。

例如,

graph = sess.g()
graph = graph.add_vertices('/home/ldbc_sample/person_0_0.csv', label='person')
graph = graph.add_vertices('/home/ldbc_sample/comment_0_0.csv', label='comment')
# Note we already added a vertex label named 'person'.
graph = graph.add_edges('/home/ldbc_sample/person_likes_comment_0_0.csv', label='likes', src_label='person', dst_label='comment')

该值和行为与Vertex中的vid_field类似,不同之处在于它需要两列作为边,因为边由源顶点ID和目标顶点ID构成。以下是一个示例:

src_fielddst_field 的示例:

# Steps to init a graph and add vertices are omitted
graph = graph.add_edges('/home/ldbc_sample/person_likes_comment_0_0.csv', label='likes', src_label='person', dst_label='comment', src_field='Person.id', dst_field='Comment.id')
# Or use the index.
graph = graph.add_edges('/home/ldbc_sample/person_likes_comment_0_0.csv', label='likes', src_label='person', dst_label='comment', src_field=0, dst_field=1)

高级用法

以下是一些处理同构图或非常复杂图的高级用法。

在无歧义时推断顶点标签

如果图中只有一种顶点类型,则可以省略顶点标签。 GraphScope 会自动将源顶点和目标顶点的标签推断为该唯一标签。

graph = sess.g()
graph = graph.add_vertices('/home/ldbc_sample/person_0_0.csv', label='person')
# GraphScope will assign `src_label` and `dst_label` to `person` automatically.
graph = graph.add_edges('/home/ldbc_sample/person_knows_person_0_0.csv')

从边推导顶点

如果用户添加了带有未识别的src_labeldst_label的边,graphscope会从边数据中根据给定标签提取顶点表。

graph = sess.g()
# Deduce vertex label `person` from the source and destination endpoints of edges.
graph = graph.add_edges('/home/ldbc_sample/person_knows_person_0_0.csv', src_label='person', dst_label='person')

graph = sess.g()
# Deduce the vertex label `person` from the source endpoint,
# and vertex label `comment` from the destination endpoint of edges.
graph = graph.add_edges('/home/ldbc_sample/person_likes_comment_0_0.csv', label='likes', src_label='person', dst_label='comment')

多重关系

在某些情况下,一条边标签可能连接两种类型的顶点。例如,在一个图中,两种边都被标记为likes,但表示两种不同的关系。即person -> likes <- commentperson -> likes <- post

在这种情况下,我们可以简单地再次添加具有相同边标签的关系,但使用不同的源标签和目标标签。

# Steps to init a graph and add vertices are omitted
graph = graph.add_edges('/home/ldbc_sample/person_likes_comment_0_0.csv',
        label="likes",
        src_label="person", dst_label="comment",
    )

graph = graph.add_edges('/home/ldbc_sample/person_likes_post_0_0.csv',
        label="likes",
        src_label="person", dst_label="post",
    )

手动指定属性的数据类型

GraphScope 会从输入文件中推断数据类型,在大多数情况下都能按预期工作。 然而,有时用户可能希望自行确定数据类型,例如:

graph = sess.g()
graph = graph.add_vertices('/home/ldbc_sample/post_0_0.csv', label='post', properties=['content', ('length', 'int'), ])

该参数强制属性按照指定的数据类型进行(转换并)加载。参数格式为由名称和类型组成的元组。 例如,在此情况下,属性length将被指定为int类型,而非默认的int64_t。可选的类型包括intint64floatdoublestr

图的其他参数

Graph 有三个元选项,分别是:

  • oid_type,可以是int64_tstring。出于效率考虑默认使用int64_t。但如果ID列无法用int64_t表示,则应使用string

  • directed, 布尔值,默认为 True。控制加载有向图还是无向图。

  • generate_eid, bool, 默认为 True, 是否自动为所有边生成唯一ID。

整合应用

让我们完善这个示例。

graph = sess.g(oid_type='int64_t', directed=True, generate_eid=True)
graph = graph.add_vertices('/home/ldbc_sample/person_0_0.csv', label='person')
graph = graph.add_vertices('/home/ldbc_sample/comment_0_0.csv', label='comment')
graph = graph.add_vertices('/home/ldbc_sample/post_0_0.csv', label='post')

graph = graph.add_edges('/home/ldbc_sample/person_knows_person_0_0.csv', label='knows', src_label='person', dst_label='person')
graph = graph.add_edges('/home/ldbc_sample/person_likes_comment_0_0.csv', label='likes', src_label='person', dst_label='comment')
graph = graph.add_edges('/home/ldbc_sample/person_likes_post_0_0.csv', label='likes', src_label='person', dst_label='post')

print(graph.schema)

加载LDBC snb图的更复杂示例可以在这里找到。

从Pandas或Numpy加载数据

上述数据源是一个Loader对象。加载器封装了数据位置或数据本身。 GraphScope支持从pandas数据框或numpy ndarrays加载图,使得在python控制台中直接构建图变得非常便捷。

除了加载器之外,其他字段如属性、标签等与上述示例相同。

从Pandas转换

import pandas as pd

df_v = pd.read_csv('/home/ldbc_sample/comment_0_0.csv', sep='|')
df_e = pd.read_csv('/home/ldbc_sample/comment_replyOf_comment_0_0.csv', sep='|')

# use a dataframe as datasource, properties omitted,
# for edges, 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_v).add_edges(df_e)

从Numpy开始

请注意,每个数组代表一列,我们以COO矩阵格式传递给加载器。

import numpy

array_v = [df_v[col].values for col in df_v.columns.values]
array_e = [df_e[col].values for col in df_e.columns.values]

graph = sess.g().add_vertices(array_v).add_edges(array_e)

加载器变体

当加载器包装一个位置时,它可能只包含一个字符串。该字符串遵循URI标准。当接收到从某个位置加载图的请求时,graphscope会解析URI并根据协议调用相应的加载器。

目前,graphscope支持locals3osshdfs的加载器。 底层通过v6d实现分布式数据加载,v6d利用fsspec来解析特定方案和格式。 任何额外配置都可以通过Loader的kwargs参数传递,这些参数将由具体类直接解析。例如,hdfshostport参数,或者osss3access-idsecret-access-key参数。

from graphscope.framework.loader import Loader

ds1 = Loader("file:///var/datafiles/group.e")
ds2 = Loader("oss://graphscope_bucket/datafiles/group.e", key='access-id', secret='secret-access-key', endpoint='oss-cn-hangzhou.aliyuncs.com')
ds3 = Loader("hdfs:///datafiles/group.e", host='localhost', port='9000', extra_conf={'conf1': 'value1'})
d34 = Loader("s3://datafiles/group.e", key='access-id', secret='secret-access-key', client_kwargs={'region_name': 'us-east-1'})

用户可以自定义驱动程序来支持额外的数据源。以ossfs为例,用户需要继承AbstractFileSystem类(用于解析特定协议方案)和AbstractBufferFile类(用于读写操作)。用户只需重写_upload_chunk_initiate_upload_fetch_range这三个方法。最后需要使用fsspec.register_implementation('protocol_name', 'protocol_file_system')来注册对应的解析器。

面向大规模图的技术

降低图内存消耗的小技巧

  • Tune the parameter of graph constructor sess.g()
    • 如果不需要边的方向性,可将directed=False设置为使用无向图。相同数据量下,无向图比有向图占用更少内存,因为我们无需存储边的方向信息。

    • 如果不需要为交互式引擎(GIE)操作生成边ID,请设置generate_eid=False

    • 如果不需要将ID列作为交互式引擎(GIE)操作的属性,请设置retain_oid=False

    • 当ID不超过2^31 - 1时,设置oid_type='int32_t'

    • 提供一个完整的模式来指定每个属性的数据类型,而不是让GraphScope从数据中推断,在大多数情况下会更有优势。

  • 根据业务场景需求过滤掉超级顶点。对于某些业务场景或算法,可能不需要高精度,尤其是在处理非常大的图数据时。