原生投影
原生投影是从 Neo4j 数据库创建 GDS 图 的最简单方法。原生投影完全由配置参数描述。
节点投影和关系投影描述了从数据库加载(投影)到内存中图的节点和关系的方式。节点投影基于节点标签,而关系投影基于关系类型。两者都可以包含属性。
注意事项
节点属性支持
原生投影只能从 Neo4j 数据库投影有限的节点属性类型集。节点属性页面详细说明了哪些节点属性类型受支持。为了能够使用原生投影进行投影,其他类型的节点属性必须转换为或编码为受支持的类型之一。
语法
CALL gds.graph.project(
graphName: String,
nodeProjection: String or List or Map,
relationshipProjection: String or List or Map,
configuration: Map
) YIELD
graphName: String,
nodeProjection: Map,
nodeCount: Integer,
relationshipProjection: Map,
relationshipCount: Integer,
projectMillis: Integer
名称 | 类型 | 可选 | 描述 |
---|---|---|---|
graphName |
字符串 |
否 |
图在目录中存储的名称。 |
nodeProjection |
字符串、列表或映射 |
否 |
一个或多个 节点投影。 |
relationshipProjection |
字符串、列表或映射 |
否 |
一个或多个 关系投影。 |
configuration |
映射 |
是 |
其他参数 用于配置原生投影。 |
名称 | 类型 | 默认值 | 描述 |
---|---|---|---|
readConcurrency |
整数 |
4 |
创建图时使用的并发线程数。 |
nodeProperties |
字符串、列表或映射 |
{} |
要从与 |
relationshipProperties |
字符串、列表或映射 |
{} |
要从与 |
validateRelationships |
布尔值 |
false |
如果 |
jobId |
字符串 |
内部生成 |
可以提供的 ID,以便更轻松地跟踪投影的进度。 |
名称 | 类型 | 描述 |
---|---|---|
graphName |
字符串 |
图在目录中存储的名称。 |
nodeProjection |
映射 |
用于投影图的 节点投影。 |
nodeCount |
整数 |
投影图中存储的节点数。 |
relationshipProjection |
映射 |
用于投影图的 关系投影。 |
relationshipCount |
整数 |
投影图中存储的关系数。 |
projectMillis |
整数 |
投影图所用的毫秒数。 |
节点投影
可以使用以下任何形式指定节点投影:
-
单个字符串(Neo4j 节点标签
<label>
或通配符*
) -
Neo4j 节点标签列表(
[<label_1>, <label_2>, <label_3>]
) -
投影映射,其中每个键是投影图中的节点标签,每个值本身都是一个映射
投影映射按如下方式指定:
{ <projected_label_1>: { (1) label: <label_1>, (2) properties: <prop_1> (3) }, <projected_label_2>: { label: <label_2>, properties: [<prop_1>, <prop_2>, ...] (3) }, <projected_label_3>: { label: <label_3>, properties: { (3) <projected_prop_1>: { (4) property: <prop_1>, (5) defaultValue: <default_1> (6) }, <projected_prop_2>: { property: <prop_2>, defaultValue: <default_2> }, ... } }, ... }
1 | 在投影图中创建的节点标签。它可以与相应的 Neo4j 节点标签相同。 |
2 | 作为字符串的源 Neo4j 节点标签。默认值:与投影标签相同(此处为<projected_label_1> )。 |
3 | 节点属性投影,指定为单个 Neo4j 节点属性、Neo4j 节点属性列表或投影映射。默认值:空映射。 |
4 | 在投影图中创建的节点属性。它可以与相应的 Neo4j 节点属性相同。 |
5 | 将 Neo4j 节点属性作为字符串源。默认值:与投影属性名称相同(此处为<projected_prop_1> )。 |
6 | 如果节点未定义属性,则使用默认值。默认值:根据属性类型使用回退值。 |
注释
-
当指定为字符串或列表时,投影不包含任何节点属性。
-
通配符形式不会保留投影节点上的标签。节点标签对于完全支持异构节点的算法很有用。 包含所有节点标签的图示例展示了如何保留所有标签以应对这些情况。
-
具有指定节点标签中任何一个标签的所有节点都将投影到 GDS 图中。
-
所有指定的节点标签和属性必须存在于数据库中。您可以使用
db.createProperty()
过程创建新的节点属性,而无需修改数据库。
关系投影
可以使用以下任何形式指定关系投影
-
单个字符串(Neo4j 关系类型
<type>
或通配符*
) -
Neo4j 关系类型的列表(
[<type_1>, <type_2>, <type_3>]
) -
投影映射,其中每个键都是投影图中的关系类型,每个值本身都是一个映射
投影映射按如下方式指定:
{ <projected_type_1>: { (1) type: <type_1>, (2) orientation: <orientation_1>, (3) aggregation: <aggregation_1>, (4) properties: <prop_1> (5) }, <projected_type_2>: { type: <type_2>, orientation: <orientation_2>, aggregation: <aggregation_2>, properties: [<prop_1>, <prop_2>, ...] (5) }, <projected_type_3>: { type: <type_3>, orientation: <orientation_3>, aggregation: <aggregation_3>, properties: { (5) <projected_prop_1>: { (6) property: <prop_1>, (7) defaultValue: <default_1>, (8) aggregation: <aggregation_1> (9) }, <projected_prop_2>: { property: <prop_2>, defaultValue: <default_2>, aggregation: <aggregation_2> }, ... } }, ... }
1 | 在投影图中创建的关系类型。它可以与相应的 Neo4j 关系类型相同。 |
2 | 将 Neo4j 关系类型作为字符串源。默认值:与投影类型相同(此处为<projected_type_1> )。 |
3 | 投影图中的关系方向。允许的值:NATURAL (默认值,与 Neo4j 图中的方向相同)、UNDIRECTED (使所有关系无向)、REVERSE (反转所有关系的方向)。 |
4 | 处理与关系关联的所有关系属性的多个实例。允许的值:NONE (默认值)、SINGLE 、COUNT 、MIN 、MAX 、SUM 。 |
5 | 关系属性投影,指定为单个 Neo4j 关系属性、Neo4j 关系属性列表或投影映射。默认值:空映射。 |
6 | 在投影图中创建的关系属性。它可以与相应的 Neo4j 关系属性相同。 |
7 | 将 Neo4j 关系属性作为字符串源。默认值:与投影属性相同(此处为<projected_prop_1> )。 |
8 | 如果关系未定义属性,则使用默认值。默认值:Double.NaN 。 |
9 | 处理与关系关联的特定关系属性的多个实例。允许的值:NONE (默认值)、SINGLE 、COUNT 、MIN 、MAX 、SUM 。 |
注释
-
当指定为字符串或列表时,投影不包含任何关系属性。
-
通配符形式不会保留投影关系上的类型。关系类型对于完全支持异构关系的算法很有用。 包含所有节点标签的图示例展示了如何保留所有关系以应对这些情况。
-
所有具有指定关系类型中任何一个类型的关系,以及其端点节点包含在节点投影中的关系,都将投影到 GDS 图中。
validateRelationships
配置参数控制是失败还是静默丢弃端点节点未包含在节点投影中的关系。 -
所有指定的节点标签和属性必须存在于数据库中。您可以使用
db.createProperty()
过程创建新的节点属性,而无需修改数据库。
示例
以下所有示例都应在空数据库中运行。 |
为了演示 GDS 图投影功能,我们将在 Neo4j 中创建一个小型社交网络图。示例图如下所示
CREATE
(florentin:Person { name: 'Florentin', age: 16 }),
(adam:Person { name: 'Adam', age: 18 }),
(veselin:Person { name: 'Veselin', age: 20, ratings: [5.0] }),
(hobbit:Book { name: 'The Hobbit', isbn: 1234, numberOfPages: 310, ratings: [1.0, 2.0, 3.0, 4.5] }),
(frankenstein:Book { name: 'Frankenstein', isbn: 4242, price: 19.99 }),
(florentin)-[:KNOWS { since: 2010 }]->(adam),
(florentin)-[:KNOWS { since: 2018 }]->(veselin),
(florentin)-[:READ { numberOfPages: 4 }]->(hobbit),
(florentin)-[:READ { numberOfPages: 42 }]->(hobbit),
(adam)-[:READ { numberOfPages: 30 }]->(hobbit),
(veselin)-[:READ]->(frankenstein)
简单图
简单图是一个只有一个节点标签和关系类型的图,即单部图。我们将从演示如何通过仅投影Person
节点标签和KNOWS
关系类型来加载简单图开始。
Person
节点和KNOWS
关系CALL gds.graph.project(
'persons', (1)
'Person', (2)
'KNOWS' (3)
)
YIELD
graphName AS graph, nodeProjection, nodeCount AS nodes, relationshipProjection, relationshipCount AS rels
1 | 图的名称。之后,可以使用persons 运行算法或管理图。 |
2 | 要投影的节点。在本例中,具有Person 标签的节点。 |
3 | 要投影的关系。在本例中,类型为KNOWS 的关系。 |
图 | nodeProjection | 节点 | relationshipProjection | 关系 |
---|---|---|---|---|
"persons" |
|
3 |
|
|
在上面的示例中,我们使用了节点和关系投影的简写语法。使用的投影在内部扩展为完整的Map
语法,如“结果”表中所示。此外,我们可以看到投影的内存中图包含三个Person
节点和两个KNOWS
关系。
多图
多图是一个具有多个节点标签和关系类型的图。
要投影多个节点标签和关系类型,我们可以调整投影如下
Person
和Book
节点以及KNOWS
和READ
关系CALL gds.graph.project(
'personsAndBooks', (1)
['Person', 'Book'], (2)
['KNOWS', 'READ'] (3)
)
YIELD
graphName AS graph, nodeProjection, nodeCount AS nodes, relationshipCount AS rels
1 | 以personsAndBooks 名称投影图。 |
2 | 要投影的节点。在本例中,具有Person 或Book 标签的节点。 |
3 | 要投影的关系。在本例中,类型为KNOWS 或READ 的关系。 |
图 | nodeProjection | 节点 | 关系 |
---|---|---|---|
"personsAndBooks" |
|
|
|
在上面的示例中,我们使用了节点和关系投影的简写语法。使用的投影在内部扩展为完整的Map
语法,如“结果”表中nodeProjection
所示。此外,我们可以看到投影的内存中图包含五个节点和两个关系。
关系方向
默认情况下,关系以与存储在 Neo4j 数据库中相同的方向加载。在 GDS 中,我们称之为NATURAL
方向。此外,我们提供了以REVERSE
甚至UNDIRECTED
方向加载关系的功能。
Person
节点和无向KNOWS
关系CALL gds.graph.project(
'undirectedKnows', (1)
'Person', (2)
{KNOWS: {orientation: 'UNDIRECTED'}} (3)
)
YIELD
graphName AS graph,
relationshipProjection AS knowsProjection,
nodeCount AS nodes,
relationshipCount AS rels
1 | 以undirectedKnows 名称投影图。 |
2 | 要投影的节点。在本例中,具有 Person 标签的节点。 |
3 | 投影类型为KNOWS 的关系,并使用orientation 参数指定它们应为UNDIRECTED 。 |
图 | knowsProjection | 节点 | 关系 |
---|---|---|---|
"undirectedKnows" |
|
|
|
要指定方向,我们需要使用扩展的 Map 语法编写relationshipProjection
。以UNDIRECTED
方式投影KNOWS
关系会双向加载每个关系。因此,undirectedKnows
图包含四个关系,是简单图中persons
图的两倍。
节点属性
要投影节点属性,我们可以使用nodeProperties
配置参数来共享属性,或者扩展特定标签的单个nodeProjection
。
Person
和Book
节点以及KNOWS
和READ
关系CALL gds.graph.project(
'graphWithProperties', (1)
{ (2)
Person: {properties: 'age'}, (3)
Book: {properties: {price: {defaultValue: 5.0}}} (4)
},
['KNOWS', 'READ'], (5)
{nodeProperties: 'ratings'} (6)
)
YIELD
graphName, nodeProjection, nodeCount AS nodes, relationshipCount AS rels
RETURN graphName, nodeProjection.Book AS bookProjection, nodes, rels
1 | 以graphWithProperties 名称投影图。 |
2 | 使用扩展的节点投影语法。 |
3 | 投影具有Person 标签的节点及其age 属性。 |
4 | 投影具有Book 标签的节点及其price 属性。每个没有price 属性的Book 都将获得defaultValue 5.0。 |
5 | 要投影的关系。在本例中,类型为KNOWS 或READ 的关系。 |
6 | 全局配置,在每个指定的标签上投影节点属性rating 。 |
graphName | bookProjection | 节点 | 关系 |
---|---|---|---|
"graphWithProperties" |
|
|
|
投影的graphWithProperties
图包含五个节点和六个关系。在返回的bookProjection
中,我们可以观察到,Books
加载了节点属性price
和ratings
。
GDS 目前仅支持加载数字属性。 |
此外,price
属性的默认值为5.0
。并非每个书籍在示例图中都指定了价格。在下面,我们将检查价格是否已正确投影
MATCH (n:Book)
RETURN n.name AS name, gds.util.nodeProperty('graphWithProperties', n, 'price') as price
ORDER BY price
名称 | 价格 |
---|---|
"霍比特人" |
5.0 |
"弗兰肯斯坦" |
19.99 |
我们可以看到,价格已投影,霍比特人的价格为默认值 5.0。
关系属性
与节点属性类似,我们可以使用relationshipProperties
配置参数或扩展特定类型的单个relationshipProjection
。
Person
和Book
节点以及具有numberOfPages
属性的READ
关系CALL gds.graph.project(
'readWithProperties', (1)
['Person', 'Book'], (2)
{ (3)
READ: { properties: "numberOfPages" } (4)
}
)
YIELD
graphName AS graph,
relationshipProjection AS readProjection,
nodeCount AS nodes,
relationshipCount AS rels
1 | 以readWithProperties 名称投影图。 |
2 | 要投影的节点。在本例中,具有Person 或Book 标签的节点。 |
3 | 使用扩展的关系投影语法。 |
4 | 投影类型为READ 的关系及其numberOfPages 属性。 |
图 | readProjection | 节点 | 关系 |
---|---|---|---|
"readWithProperties" |
|
|
|
接下来,我们将验证关系属性numberOfPages
是否已正确加载。
numberOfPages
CALL gds.graph.relationshipProperty.stream('readWithProperties', 'numberOfPages')
YIELD sourceNodeId, targetNodeId, propertyValue AS numberOfPages
RETURN
gds.util.asNode(sourceNodeId).name AS person,
gds.util.asNode(targetNodeId).name AS book,
numberOfPages
ORDER BY person ASC, numberOfPages DESC
人员 | 书籍 | 页数 |
---|---|---|
"亚当" |
"霍比特人" |
30.0 |
"弗洛伦丁" |
"霍比特人" |
42.0 |
"弗洛伦丁" |
"霍比特人" |
4.0 |
"维塞林" |
"弗兰肯斯坦" |
NaN |
我们可以看到,已加载numberOfPages
属性。默认属性值为Double.NaN
,可以像节点属性中的 Map 语法一样更改。
并行关系
Neo4j 支持并行关系,即两个节点之间存在多条关系。默认情况下,GDS 会保留并行关系。对于某些算法,我们希望投影图在两个节点之间最多包含一条关系。
我们可以通过关系投影中的 `aggregation` 参数指定如何将并行关系聚合到单个关系中。
对于没有关系属性的图,我们可以使用 `COUNT` 聚合。如果我们不需要计数,可以使用 `SINGLE` 聚合。
CALL gds.graph.project(
'readCount', (1)
['Person', 'Book'], (2)
{
READ: { (3)
properties: {
numberOfReads: { (4)
property: '*', (5)
aggregation: 'COUNT' (6)
}
}
}
}
)
YIELD
graphName AS graph,
relationshipProjection AS readProjection,
nodeCount AS nodes,
relationshipCount AS rels
1 | 以 `readCount` 为名称投影一个图。 |
2 | 要投影的节点。在本例中,具有Person 或Book 标签的节点。 |
3 | 投影类型为 `READ` 的关系。 |
4 | 投影关系属性 `numberOfReads`。 |
5 | 一个占位符,表示关系属性的值是派生的,而不是基于 Neo4j 属性。 |
6 | 聚合类型。在此示例中,`COUNT` 会导致属性的值为并行关系的数量。 |
图 | readProjection | 节点 | 关系 |
---|---|---|---|
"readCount" |
|
|
|
接下来,我们将验证 `READ` 关系是否已正确聚合。
CALL gds.graph.relationshipProperty.stream('readCount', 'numberOfReads')
YIELD sourceNodeId, targetNodeId, propertyValue AS numberOfReads
RETURN
gds.util.asNode(sourceNodeId).name AS person,
gds.util.asNode(targetNodeId).name AS book,
numberOfReads
ORDER BY numberOfReads DESC, person
人员 | 书籍 | numberOfReads |
---|---|---|
"弗洛伦丁" |
"霍比特人" |
2.0 |
"亚当" |
"霍比特人" |
1.0 |
"维塞林" |
"弗兰肯斯坦" |
1.0 |
我们可以看到,Florentin 和霍比特人之间两条 READ 关系导致 `numberOfReads` 为 `2`。
带属性的并行关系
对于具有关系属性的图,我们也可以使用其他聚合。
CALL gds.graph.project(
'readSums', (1)
['Person', 'Book'], (2)
{READ: {properties: {numberOfPages: {aggregation: 'SUM'}}}} (3)
)
YIELD
graphName AS graph,
relationshipProjection AS readProjection,
nodeCount AS nodes,
relationshipCount AS rels
1 | 以 `readSums` 为名称投影一个图。 |
2 | 要投影的节点。在本例中,具有Person 或Book 标签的节点。 |
3 | 投影类型为 `READ` 的关系。聚合类型 `SUM` 会导致投影的 `numberOfPages` 属性的值为并行关系的 `numberOfPages` 属性的总和。 |
图 | readProjection | 节点 | 关系 |
---|---|---|---|
"readSums" |
|
|
|
接下来,我们将验证关系属性 `numberOfPages` 是否已正确聚合。
numberOfPages
CALL gds.graph.relationshipProperty.stream('readSums', 'numberOfPages')
YIELD
sourceNodeId, targetNodeId, propertyValue AS numberOfPages
RETURN
gds.util.asNode(sourceNodeId).name AS person,
gds.util.asNode(targetNodeId).name AS book,
numberOfPages
ORDER BY numberOfPages DESC, person
人员 | 书籍 | 页数 |
---|---|---|
"弗洛伦丁" |
"霍比特人" |
46.0 |
"亚当" |
"霍比特人" |
30.0 |
"维塞林" |
"弗兰肯斯坦" |
0.0 |
我们可以看到,Florentin 和霍比特人之间两条 READ 关系的 `numberOfReads` 总和为 `46`。
验证关系标志
如 语法部分 所述,`validateRelationships` 标志控制在尝试投影源节点或目标节点不存在于 节点投影 中的关系时是否会引发错误。请注意,即使该标志设置为 `false`,此类关系仍不会被投影,但加载过程不会中止。
我们可以使用 Neo4j 数据库中存在的图 模拟这种情况
CALL gds.graph.project(
'danglingRelationships',
'Person',
['READ', 'KNOWS'],
{
validateRelationships: true
}
)
YIELD
graphName AS graph,
relationshipProjection AS readProjection,
nodeCount AS nodes,
relationshipCount AS rels
org.neo4j.graphdb.QueryExecutionException: Failed to invoke procedure `gds.graph.project`: Caused by: java.lang.IllegalArgumentException: Failed to load a relationship because its target-node with id 3 is not part of the node query or projection. To ignore the relationship, set the configuration parameter `validateRelationships` to false.
我们可以看到,以上查询导致抛出异常。异常消息将提供有关缺少的特定节点 ID 的信息,这将有助于调试潜在问题。
包含所有节点标签的图
您可以使用通配符运算符 `*` 选择投影的所有节点标签。但是,这不会保留投影节点上的标签信息。
请改用 `db.labels()` 检索所有标签的列表,并将其用作 `nodeProjection` 参数的值,如下例所示
CALL db.labels() YIELD label
WITH collect(label) AS allLabels
CALL gds.graph.project(
'allLabelsGraph',
allLabels,
['KNOWS', 'READ']
)
YIELD graphName, nodeProjection, nodeCount AS nodes, relationshipCount AS rels
RETURN *
allLabels | graphName | nodeProjection | 节点 | 关系 |
---|---|---|---|---|
["Person", "Book"] |
|
|
|
|
类似地,您可以通过使用 `db.relationshipTypes()` 作为 `relationshipProjection` 参数的值(而不是 `['KNOWS', 'READ']`)来选择投影的所有关系类型。