加载图数据¶
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_field 和 dst_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_field 和 dst_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_label或dst_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 <- comment 和 person -> 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。可选的类型包括int、int64、float、double或str。
图的其他参数¶
类 Graph 有三个元选项,分别是:
oid_type,可以是int64_t或string。出于效率考虑默认使用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支持local、s3、oss和hdfs的加载器。
底层通过v6d实现分布式数据加载,v6d利用fsspec来解析特定方案和格式。
任何额外配置都可以通过Loader的kwargs参数传递,这些参数将由具体类直接解析。例如,hdfs的host和port参数,或者oss和s3的access-id、secret-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从数据中推断,在大多数情况下会更有优势。
根据业务场景需求过滤掉超级顶点。对于某些业务场景或算法,可能不需要高精度,尤其是在处理非常大的图数据时。