原生投影

原生投影是从 Neo4j 数据库创建 GDS 图 的最简单方法。原生投影完全由配置参数描述。

节点投影关系投影描述了从数据库加载(投影)到内存中图的节点和关系的方式。节点投影基于节点标签,而关系投影基于关系类型。两者都可以包含属性。

注意事项

生命周期

投影图驻留在内存中(在图目录中),直到发生以下任何情况:

  • 使用 gds.graph.drop 过程删除该图。

  • 停止或删除投影图的 Neo4j 数据库。

  • 停止 Neo4j DBMS。

节点属性支持

原生投影只能从 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
表 1. 参数
名称 类型 可选 描述

graphName

字符串

图在目录中存储的名称。

nodeProjection

字符串、列表或映射

一个或多个 节点投影

relationshipProjection

字符串、列表或映射

一个或多个 关系投影

configuration

映射

其他参数 用于配置原生投影。

表 2. 配置
名称 类型 默认值 描述

readConcurrency

整数

4

创建图时使用的并发线程数。

nodeProperties

字符串、列表或映射

{}

要从与nodeProjection中指定的任何标签匹配的节点加载的节点属性。

relationshipProperties

字符串、列表或映射

{}

要从与relationshipProjection中指定的任何类型匹配的关系加载的关系属性。

validateRelationships

布尔值

false

如果relationshipProjection包含不在nodeProjection中的节点之间的关系,是否抛出错误。

jobId

字符串

内部生成

可以提供的 ID,以便更轻松地跟踪投影的进度。

表 3. 结果
名称 类型 描述

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(默认值)、SINGLECOUNTMINMAXSUM
5 关系属性投影,指定为单个 Neo4j 关系属性、Neo4j 关系属性列表或投影映射。默认值:空映射。
6 在投影图中创建的关系属性。它可以与相应的 Neo4j 关系属性相同。
7 将 Neo4j 关系属性作为字符串源。默认值:与投影属性相同(此处为<projected_prop_1>)。
8 如果关系未定义属性,则使用默认值。默认值:Double.NaN
9 处理与关系关联的特定关系属性的多个实例。允许的值:NONE(默认值)、SINGLECOUNTMINMAXSUM

注释

  • 当指定为字符串或列表时,投影不包含任何关系属性。

  • 通配符形式不会保留投影关系上的类型。关系类型对于完全支持异构关系的算法很有用。 包含所有节点标签的图示例展示了如何保留所有关系以应对这些情况。

  • 所有具有指定关系类型中任何一个类型的关系,以及其端点节点包含在节点投影中的关系,都将投影到 GDS 图中。validateRelationships 配置参数控制是失败还是静默丢弃端点节点未包含在节点投影中的关系。

  • 所有指定的节点标签和属性必须存在于数据库中。您可以使用db.createProperty()过程创建新的节点属性,而无需修改数据库。

示例

以下所有示例都应在空数据库中运行。

为了演示 GDS 图投影功能,我们将在 Neo4j 中创建一个小型社交网络图。示例图如下所示

Visualization of the example graph
以下 Cypher 语句将在 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的关系。
表 4. 结果
nodeProjection 节点 relationshipProjection 关系

"persons"

{Person={label="Person", properties={}}}

3

{KNOWS={aggregation="DEFAULT", indexInverse=false, orientation="NATURAL", properties={}, type="KNOWS"}}

2

在上面的示例中,我们使用了节点和关系投影的简写语法。使用的投影在内部扩展为完整的Map语法,如“结果”表中所示。此外,我们可以看到投影的内存中图包含三个Person节点和两个KNOWS关系。

多图

多图是一个具有多个节点标签和关系类型的图。

要投影多个节点标签和关系类型,我们可以调整投影如下

投影PersonBook节点以及KNOWSREAD关系
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 要投影的节点。在本例中,具有PersonBook标签的节点。
3 要投影的关系。在本例中,类型为KNOWSREAD的关系。
表 5. 结果
nodeProjection 节点 关系

"personsAndBooks"

{Book={label="Book", properties={}}, Person={label="Person", properties={}}}

5

6

在上面的示例中,我们使用了节点和关系投影的简写语法。使用的投影在内部扩展为完整的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
表 6. 结果
knowsProjection 节点 关系

"undirectedKnows"

{KNOWS={aggregation="DEFAULT", indexInverse=false, orientation="UNDIRECTED", properties={}, type="KNOWS"}}

3

4

要指定方向,我们需要使用扩展的 Map 语法编写relationshipProjection。以UNDIRECTED方式投影KNOWS关系会双向加载每个关系。因此,undirectedKnows图包含四个关系,是简单图persons图的两倍。

节点属性

要投影节点属性,我们可以使用nodeProperties配置参数来共享属性,或者扩展特定标签的单个nodeProjection

投影PersonBook节点以及KNOWSREAD关系
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 要投影的关系。在本例中,类型为KNOWSREAD的关系。
6 全局配置,在每个指定的标签上投影节点属性rating
表 7. 结果
graphName bookProjection 节点 关系

"graphWithProperties"

{label="Book", properties={price={defaultValue=5.0, property="price"}, ratings={defaultValue=null, property="ratings"}}}

5

6

投影的graphWithProperties图包含五个节点和六个关系。在返回的bookProjection中,我们可以观察到,Books加载了节点属性priceratings

GDS 目前仅支持加载数字属性。

此外,price属性的默认值为5.0。并非每个书籍在示例图中都指定了价格。在下面,我们将检查价格是否已正确投影

验证投影图中 Adam 的 ratings 属性
MATCH (n:Book)
RETURN n.name AS name, gds.util.nodeProperty('graphWithProperties', n, 'price') as price
ORDER BY price
表 8. 结果
名称 价格

"霍比特人"

5.0

"弗兰肯斯坦"

19.99

我们可以看到,价格已投影,霍比特人的价格为默认值 5.0。

关系属性

与节点属性类似,我们可以使用relationshipProperties配置参数或扩展特定类型的单个relationshipProjection

投影PersonBook节点以及具有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 要投影的节点。在本例中,具有PersonBook标签的节点。
3 使用扩展的关系投影语法。
4 投影类型为READ的关系及其numberOfPages属性。
表 9. 结果
readProjection 节点 关系

"readWithProperties"

{READ={aggregation="DEFAULT", indexInverse=false, orientation="NATURAL", properties={numberOfPages={aggregation="DEFAULT", defaultValue=null, property="numberOfPages"}}, type="READ"}}

5

4

接下来,我们将验证关系属性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
表 10. 结果
人员 书籍 页数

"亚当"

"霍比特人"

30.0

"弗洛伦丁"

"霍比特人"

42.0

"弗洛伦丁"

"霍比特人"

4.0

"维塞林"

"弗兰肯斯坦"

NaN

我们可以看到,已加载numberOfPages属性。默认属性值为Double.NaN,可以像节点属性中的 Map 语法一样更改。

并行关系

Neo4j 支持并行关系,即两个节点之间存在多条关系。默认情况下,GDS 会保留并行关系。对于某些算法,我们希望投影图在两个节点之间最多包含一条关系。

我们可以通过关系投影中的 `aggregation` 参数指定如何将并行关系聚合到单个关系中。

对于没有关系属性的图,我们可以使用 `COUNT` 聚合。如果我们不需要计数,可以使用 `SINGLE` 聚合。

投影 `Person` 和 `Book` 节点,以及 `COUNT` 聚合的 `READ` 关系
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 要投影的节点。在本例中,具有PersonBook标签的节点。
3 投影类型为 `READ` 的关系。
4 投影关系属性 `numberOfReads`。
5 一个占位符,表示关系属性的值是派生的,而不是基于 Neo4j 属性。
6 聚合类型。在此示例中,`COUNT` 会导致属性的值为并行关系的数量。
表 11. 结果
readProjection 节点 关系

"readCount"

{READ={aggregation="DEFAULT", indexInverse=false, orientation="NATURAL", properties={numberOfReads={aggregation="COUNT", defaultValue=null, property="*"}}, type="READ"}}

5

3

接下来,我们将验证 `READ` 关系是否已正确聚合。

流式传输投影图的关系属性 `numberOfReads`
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
表 12. 结果
人员 书籍 numberOfReads

"弗洛伦丁"

"霍比特人"

2.0

"亚当"

"霍比特人"

1.0

"维塞林"

"弗兰肯斯坦"

1.0

我们可以看到,Florentin 和霍比特人之间两条 READ 关系导致 `numberOfReads` 为 `2`。

带属性的并行关系

对于具有关系属性的图,我们也可以使用其他聚合。

投影 `Person` 和 `Book` 节点,并通过对 `numberOfPages` 求和来聚合 `READ` 关系
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 要投影的节点。在本例中,具有PersonBook标签的节点。
3 投影类型为 `READ` 的关系。聚合类型 `SUM` 会导致投影的 `numberOfPages` 属性的值为并行关系的 `numberOfPages` 属性的总和。
表 13. 结果
readProjection 节点 关系

"readSums"

{READ={aggregation="DEFAULT", indexInverse=false, orientation="NATURAL", properties={numberOfPages={aggregation="SUM", defaultValue=null, property="numberOfPages"}}, type="READ"}}

5

3

接下来,我们将验证关系属性 `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
表 14. 结果
人员 书籍 页数

"弗洛伦丁"

"霍比特人"

46.0

"亚当"

"霍比特人"

30.0

"维塞林"

"弗兰肯斯坦"

0.0

我们可以看到,Florentin 和霍比特人之间两条 READ 关系的 `numberOfReads` 总和为 `46`。

验证关系标志

语法部分 所述,`validateRelationships` 标志控制在尝试投影源节点或目标节点不存在于 节点投影 中的关系时是否会引发错误。请注意,即使该标志设置为 `false`,此类关系仍不会被投影,但加载过程不会中止。

我们可以使用 Neo4j 数据库中存在的图 模拟这种情况

投影 `READ` 和 `KNOWS` 关系,但仅投影 `Person` 节点,并将 `validateRelationships` 设置为 true
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 *
表 15. 结果
allLabels graphName nodeProjection 节点 关系

["Person", "Book"]

"allLabelsGraph"

{Book={label="Book", properties={}}, Person={label="Person", properties={}}}

5

6

类似地,您可以通过使用 `db.relationshipTypes()` 作为 `relationshipProjection` 参数的值(而不是 `['KNOWS', 'READ']`)来选择投影的所有关系类型。