图分组

大型图通常难以理解或可视化。

表格结果可以进行聚合以生成概览,例如在包含总和、计数等的图表中。

将节点按属性值分组为虚拟节点,有助于在图可视化中实现同样的效果。

在此过程中,这些组之间的关系也会被聚合,因此您只会看到汇总信息。

此功能灵感来源于 Martin Junghanns 在 Gradoop 图处理系统的 分组演示 (Grouping Demo) 中的工作。

基本上,您可以使用任何 (entity)<-->(entity) 图进行分组,对图投影 (graph projections) 的支持正在规划中。

这是一个使用 :play movies 数据集的示例。

电影图示例
MATCH (n)
SET n.century = toInteger(coalesce(n.born,n.released)/100) * 100;

CALL apoc.nodes.group(['Person','Movie'],['century']);
apoc.nodes.group

有时,UI 处理分组的返回值(节点列表和关系列表)时可能会出现问题,此时运行以下代码可能会有所帮助:

CALL apoc.nodes.group(['Person','Movie'],['century'])
YIELD nodes, relationships
UNWIND nodes as node
UNWIND relationships as rel
RETURN node, rel;

使用

CALL apoc.nodes.group(labels,properties, [grouping], [config])

唯一必需的参数是 label-list(也可以是 ['*'])和用于分组的 属性名称列表(适用于关系和节点)。

您还可以选择提供按字段的分组运算符以及一系列配置选项。

分组运算符

对于分组运算符,您需要按以下格式为每个字段提供一个 MAP 操作:{fieldName: [operators]}

节点一个 map,关系一个 map:[{nodeOperators},{relOperators}]

可选运算符

  • count_*

  • count

  • sum

  • min/max

  • avg

  • collect

默认值为:[{`*`:"count"},{`*`:"count"}],即仅对节点和关系进行计数。

配置

配置中还有更多选项

选项 默认 description(描述)

selfRels

true

在结果图中显示自引用关系

orphans

true

在结果图中显示孤立节点

limitNodes

-1

限制节点的最大数量

limitRels

-1

限制关系的最大数量

relsPerNode

-1

限制每个节点的关系数量

filter

null

按属性值进行最小/最大过滤,例如 {User.count_*.min:2},见下文

includeRels(包含关系)

[]

包含的关系类型。默认为包含所有关系类型。可以是类型列表或单个类型。

excludeRels(排除关系)

[]

排除的关系类型。默认为不排除任何关系类型。可以是类型列表或单个类型。

filter 配置选项是一个 {Label/TYPE.operator_property.min/max: number} 格式的 MAP,其中 Label/TYPE. 前缀是可选的。

因此,例如,您可以仅过滤分组中最小年龄为 21 岁的人:Person.min_age.min: 21,或者过滤拥有最多 10 个 KNOWS 共同关系的人:KNOWS.count_*.max:10

示例

图设置
CREATE
 (alice:Person {name:'Alice', gender:'female', age:32, kids:1}),
 (bob:Person   {name:'Bob',   gender:'male',   age:42, kids:3}),
 (eve:Person   {name:'Eve',   gender:'female', age:28, kids:2}),
 (graphs:Forum {name:'Graphs',    members:23}),
 (dbs:Forum    {name:'Databases', members:42}),
 (alice)-[:KNOWS {since:2017}]->(bob),
 (eve)-[:KNOWS   {since:2018}]->(bob),
 (alice)-[:MEMBER_OF]->(graphs),
 (alice)-[:MEMBER_OF]->(dbs),
 (bob)-[:MEMBER_OF]->(dbs),
 (eve)-[:MEMBER_OF]->(graphs)
查询
CALL apoc.nodes.group(['*'],['gender'],
  [{`*`:'count', age:'min'}, {`*`:'count'} ])
表 1. 结果
节点 relationships 节点 关系 (relationship)

[(:Person {gender: "female",min_age: 28,count_*: 2})]

[[:MEMBER_OF {count_*: 3}], [:KNOWS {count_*: 2}]]

(:Person {gender: "female",min_age: 28,count_*: 2})

[:MEMBER_OF {count_*: 3}]

[(:Person {gender: "female",min_age: 28,count_*: 2})]

[[:KNOWS {count_*: 2}]]

(:Person {gender: "female",min_age: 28,count_*: 2})

[:KNOWS {count_*: 2}]

[(:Person {gender: "male",min_age: 42,count_*: 1})]

[[:MEMBER_OF {count_*: 1}]]

(:Person {gender: "male",min_age: 42,count_*: 1})

[:MEMBER_OF {count_*: 1}]

[(:Forum {gender: null,count_*: 2})]

[]

(:Forum {gender: null,count_*: 2})

null

请注意,此查询在 Neo4j Browser 的“图形”模式下无法运行,仅在“表格”模式(或 cypher-shell)下有效。这是因为 Forum 没有 gender 属性,在 node 结果中会出现 "gender": null 属性,这不被支持并会返回 TypeError。相反,下面的查询在“图形”模式下也可以正常工作。

CALL apoc.nodes.group(
        ['Person'],['gender'],
        [{`*`:'count', kids:'sum', age:['min', 'max', 'avg'], gender:'collect'},
         {`*`:'count', since:['min', 'max']}]);

大型示例

图设置
WITH ["US","DE","UK","FR","CA","BR","SE"] AS tld
UNWIND range(1,1000) AS id
CREATE (u:User {id:id, age : id % 100, female: rand() < 0.5, name: "Name "+id, country:tld[toInteger(rand()*size(tld))]})
WITH collect(u) AS users
UNWIND users AS u
WITH u, users[toInteger(rand()*size(users))] AS u2
WHERE u <> u2
MERGE (u)-[:KNOWS]-(u2);
CALL apoc.nodes.group(['*'], ['country'])
YIELD node, relationship return *
grouping country all
查询
CALL apoc.nodes.group(['*'], ['country'], null,
    {selfRels:false, orphans:false,
     filter:{`User.count_*.min`:130,`KNOWS.count_*.max`:200}})
YIELD node, relationship return *
grouping country filter

要在 Neo4j Browser 中可视化此结果,使用自定义图形样式表 (GRASS) 非常有用,它可以用一些聚合数据来渲染分组后的属性。

node {
  diameter: 50px;
  color: #A5ABB6;
  border-color: #9AA1AC;
  border-width: 2px;
  text-color-internal: #FFFFFF;
  font-size: 10px;
}

relationship {
  color: #A5ABB6;
  shaft-width: 3px;
  font-size: 8px;
  padding: 3px;
  text-color-external: #000000;
  text-color-internal: #FFFFFF;
  caption: '{count_*}';
}

node.Country {
  color: #68BDF6;
  diameter: 80px;
  border-color: #5CA8DB;
  text-color-internal: #FFFFFF;
  caption: '{country} ({count_*})';
}