原生投影

在 Aura 图分析中,此功能仅可通过 Python 客户端使用。它目前无法作为 Cypher 过程或函数使用。

原生投影是从 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

字符串、列表或映射

一个或多个关系投影

配置

Map

附加参数用于配置原生投影。

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

readConcurrency

整数

4

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

nodeProperties

字符串、列表或映射

{}

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

relationshipProperties

字符串、列表或映射

{}

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

validateRelationships

布尔值

false

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

jobId

字符串

内部生成

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

logProgress

布尔值

true

如果禁用,进度百分比将不会被记录。

表 3. 结果
名称 类型 描述

graphName

字符串

图在目录中存储时所使用的名称。

nodeProjection

Map

用于投影图的节点投影

nodeCount

整数

投影图中存储的节点数。

relationshipProjection

Map

用于投影图的关系投影

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. 结果
graph(图) nodeProjection 节点 relationshipProjection rels

"persons"

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

3

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

2

在上面的示例中,我们使用了节点和关系投影的简写语法。所使用的投影在内部被展开为完整的 Map 语法,如 Results 表所示。此外,我们可以看到投影后的内存图包含三个 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. 结果
graph(图) nodeProjection 节点 rels

"personsAndBooks"

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

5

6

在上面的示例中,我们使用了节点和关系投影的简写语法。所使用的投影在内部被展开为完整的 Map 语法(如结果表中的 nodeProjection 所示)。此外,我们可以看到投影后的内存图包含五个节点和两个关系。

关系方向

默认情况下,关系按照它们在 Neo4j 数据库中存储的方向加载。在 GDS 中,我们称之为 NATURAL 方向。此外,我们提供了以 REVERSEUNDIRECTED 方向加载关系的功能。

投影 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. 结果
graph(图) knowsProjection 节点 rels

"undirectedKnows"

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

3

4

要指定方向,我们需要使用扩展的 Map 语法编写 relationshipProjection。将 KNOWS 关系以 UNDIRECTED 方式投影,会加载两个方向上的每个关系。因此,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 都将获得 5.0defaultValue
5 要投影的关系。在此示例中,是类型为 KNOWSREAD 的关系。
6 全局配置,在每个指定的标签上投影节点属性 rating
表 7. 结果
graphName bookProjection 节点 rels

"graphWithProperties"

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

5

6

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

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. 结果
名称 (name) 价格

"The Hobbit"

5.0

"Frankenstein"

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. 结果
graph(图) readProjection 节点 rels

"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. 结果
person book numberOfPages

"Adam"

"The Hobbit"

30.0

"Florentin"

"The Hobbit"

42.0

"Florentin"

"The Hobbit"

4.0

"Veselin"

"Frankenstein"

NaN

我们可以看到,numberOfPages 属性已加载。默认属性值为 Double.NaN,可以使用与 节点属性 中相同的 Map 语法进行更改。

平行关系

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

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

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

投影 PersonBook 节点以及 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. 结果
graph(图) readProjection 节点 rels

"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. 结果
person book numberOfReads

"Florentin"

"The Hobbit"

2.0

"Adam"

"The Hobbit"

1.0

"Veselin"

"Frankenstein"

1.0

我们可以看到,Florentin 和《霍比特人》之间的两个 READ 关系导致 numberOfReads 为 2

带属性的平行关系

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

投影 PersonBook 节点,并通过对 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. 结果
graph(图) readProjection 节点 rels

"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. 结果
person book numberOfPages

"Florentin"

"The Hobbit"

46.0

"Adam"

"The Hobbit"

30.0

"Veselin"

"Frankenstein"

0.0

我们可以看到,Florentin 和《霍比特人》之间的两个 READ 关系的 numberOfPages 总和为 46

验证关系标志

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

我们可以使用 Neo4j 数据库中现有的图来模拟这种情况。

投影 READKNOWS 关系,但仅投影 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 节点 rels

["Person", "Book"]

"allLabelsGraph"

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

5

6

同样,您可以对 relationshipProjection 参数使用 db.relationshipTypes(),而不是 ['KNOWS', 'READ'],来为投影选择所有关系类型。