图对象

为了使用 GDS 中的大多数功能,您必须首先将图投影到 GDS 图目录 (Graph Catalog) 中。当使用 Python 客户端投影图时,会返回一个指向已投影图的客户端引用。我们将这些引用称为 Graph 对象。

一旦创建,Graph 对象就可以作为参数传递给 Python 客户端中的其他方法,例如用于运行算法或训练机器学习模型。此外,Graph 对象还具有便捷方法,允许在不显式涉及图目录的情况下检查所代表的投影图。

在下面的示例中,我们假设已经实例化了一个名为 gdsGraphDataScience 对象。请在 入门指南 中阅读更多相关信息。

1. 投影图对象

投影图对象有多种方法。最简单的方法是执行 原生投影 (native projection)

# We put this simple graph in our database
gds.run_cypher(
  """
  CREATE
    (m: City {name: "Malmö"}),
    (l: City {name: "London"}),
    (s: City {name: "San Mateo"}),

    (m)-[:FLY_TO]->(l),
    (l)-[:FLY_TO]->(m),
    (l)-[:FLY_TO]->(s),
    (s)-[:FLY_TO]->(l)
  """
)

# We estimate required memory of the operation
res = gds.graph.project.estimate(
    ["City"],                   #  Node projection
    "FLY_TO",                   #  Relationship projection
    readConcurrency=4           #  Configuration parameters
)
assert res["bytesMax"] < 1e12

G, result = gds.graph.project(
    "offices",                  #  Graph name
    ["City"],                   #  Node projection
    "FLY_TO",                   #  Relationship projection
    readConcurrency=4           #  Configuration parameters
)

assert G.node_count() == result["nodeCount"]

其中 G 是一个 Graph 对象,result 是一个包含底层过程调用元数据的 pandas Series

请注意,通过为节点和关系投影参数指定 Python dictlist,可以支持所有投影语法变体。要指定对应于过程 configuration 映射键的配置参数,我们使用命名关键字参数,例如上面的 readConcurrency=4。在 GDS 手册 中阅读有关语法的更多信息。

与 Cypher 类似,也有一个相应的 gds.graph.project.estimate 方法,可以以类似的方式调用。

要获取一个表示已投影到图目录中的图的图对象,可以调用仅限客户端的 get 方法并传递一个名称

G = gds.graph.get("offices")

对于身为 GDS 管理员 的用户,即使提供的名称是指其他用户的图投影,gds.graph.get 也会将图名称解析为 Graph 对象。

除了上述方法外,还有五种方法可以创建图对象

  • gds.graph.project.cypher (这是旧版 Cypher 投影,请参阅 使用 Cypher 投影投影图 以获取新的 Cypher 投影)

  • gds.graph.filter

  • gds.graph.generate

  • gds.graph.sample.rwr

  • gds.graph.sample.cnarw

它们的 Cypher 签名映射到 Python 的方式与上面的 gds.graph.project 大致相同。

2. 使用 Cypher 投影投影图

方法 gds.graph.cypher.project 允许使用 Cypher 投影来投影图。Cypher 投影不是一个专用的过程;相反,它需要编写一个调用 gds.graph.project 聚合函数的 Cypher 查询。

GDS 手册 中阅读有关 Cypher 投影的更多信息。

方法 gds.graph.cypher.project 弥合了 gds.run_cypher 与后续必须使用 gds.graph.get 之间的差距。

2.1. 语法

表 1. Cypher 投影签名
名称 类型 默认 描述

query

str

-

要执行的 Cypher 查询。必须以 RETURN gds.graph.project(…​) 结尾。

database

Optional[str]

None

覆盖目标数据库。默认使用连接中的数据库。

**params

Any

\{\}

作为关键字参数的查询参数。

gds.run_cypher 不同,但与 gds.graph.project 非常相似,它返回一个 Graph 对象和包含 Cypher 执行元数据的 pandas Series 的元组。

该方法不会以任何方式修改 Cypher 查询,所有投影配置都必须在查询本身中完成。但是,该方法会验证查询是否仅包含一个 RETURN gds.graph.project(…​) 子句,并且该子句出现在查询末尾。这意味着此方法不能用于将图与其他 Cypher 聚合一起投影,也不能重命名结果行。如果提供的查询验证失败,则回退方案是使用 gds.run_cypher 结合 gds.graph.get 来实现相同的结果。

# We put this simple graph in our database
gds.run_cypher(
  """
  CREATE
    (m: City {name: "Malmö"}),
    (l: City {name: "London"}),
    (s: City {name: "San Mateo"}),

    (m)-[:FLY_TO]->(l),
    (l)-[:FLY_TO]->(m),
    (l)-[:FLY_TO]->(s),
    (s)-[:FLY_TO]->(l)
  """
)

G, result = gds.graph.cypher.project(
    """
    MATCH (n)-->(m)
    RETURN gds.graph.project($graph_name, n, m, {
        sourceNodeLabels: $label,
        targetNodeLabels: $label,
        relationshipType: $rel_type
    })
    """,                   #  Cypher query
    database="neo4j",      #  Target database
    graph_name="offices",  #  Query parameter
    label="City",          #  Query parameter
    rel_type="FLY_TO"      #  Query parameter
)

assert G.node_count() == result["nodeCount"]

3. 从 DataFrames 构建图

除了从 Neo4j 数据库投影图外,还可以直接从 pandas DataFrame 对象创建图。

3.1. 语法

表 2. 图构建签名
名称 类型 默认 描述

graph_name

str

-

要构建的图的名称。

节点

Union[DataFrame, List[DataFrame]]

-

一个或多个包含节点数据的数据框。

relationships

Union[DataFrame, List[DataFrame]]

-

一个或多个包含关系数据的数据框。

concurrency

int

4

用于构建图的线程数。

undirected_relationship_types

Optional[List[str]]

None

要作为无向投影的关系类型列表。

3.2. 示例

nodes = pandas.DataFrame(
    {
        "nodeId": [0, 1, 2, 3],
        "labels":  ["A", "B", "C", "A"],
        "prop1": [42, 1337, 8, 0],
        "otherProperty": [0.1, 0.2, 0.3, 0.4]
    }
)

relationships = pandas.DataFrame(
    {
        "sourceNodeId": [0, 1, 2, 3],
        "targetNodeId": [1, 2, 3, 0],
        "relationshipType": ["REL", "REL", "REL", "REL"],
        "weight": [0.0, 0.0, 0.1, 42.0]
    }
)

G = gds.graph.construct(
    "my-graph",      # Graph name
    nodes,           # One or more dataframes containing node data
    relationships    # One or more dataframes containing relationship data
)

assert "REL" in G.relationship_types()

上面的示例从两个 DataFrame 对象创建一个图,一个用于节点,一个用于关系。投影的图等同于以下 Cypher 查询在 Neo4j 数据库中创建的图

CREATE
    (a:A {prop1: 42,    otherProperty: 0.1),
    (b:B {prop1: 1337,  otherProperty: 0.2),
    (c:C {prop1: 8,     otherProperty: 0.3),
    (d:A {prop1: 0,     otherProperty: 0.4),

    (a)-[:REL {weight: 0.0}]->(b),
    (b)-[:REL {weight: 0.0}]->(c),
    (c)-[:REL {weight: 0.1}]->(d),
    (d)-[:REL {weight: 42.0}]->(a),

节点数据框支持的格式在 Arrow 节点模式 中描述,关系数据框的格式在 Arrow 关系模式 中描述。

3.3. Apache Arrow Flight 服务器支持

如果启用了 GDS 的 Apache Arrow Flight 服务器construct 方法可以利用它。这特别意味着

  • 图的构建速度大大加快,

  • 可以提供多个数据框,无论是节点还是关系。如果使用多个节点数据框,它们需要在所有节点数据框中包含不同的节点 ID。

  • 在调用 construct 之前,必须先调用 GraphDataScience.set_database 以明确指定应针对哪个 Neo4j 数据库。

3.4. 社区版限制

对于 GDS 社区版用户,大型图的性能可能会受到影响。数据库的套接字连接可能会超时。如果发生这种情况,一种可能的解决方法是修改服务器配置 server.bolt.connection_keep_aliveserver.bolt.connection_keep_alive_probes。但是,请注意副作用,例如真正的连接问题现在需要更长时间才能被检测到。

4. 加载 NetworkX 图

从客户端数据构建图的另一种方法是使用该库方便的 NetworkX 加载方法。为了使用此方法,必须为 graphdatascience 库安装 NetworkX 支持

pip install graphdatascience[networkx]

公开 NetworkX 数据集加载功能的方法称为 gds.graph.networkx.load。它返回 Graph 对象,并接受三个参数

表 3. NetworkX 图加载方法参数
名称 类型

nx_G

networkx.Graph

NetworX 格式的图

graph_name

str

创建的 GDS 图的名称

concurrency

int = 4

可选使用的线程数

networkx.Graph nx_G 如何映射到 GDS Graph 投影的具体细节将在下文中概述。

4.1. 示例

让我们看一个加载最小异构玩具 NetworkX 图的示例。

加载有向 NetworkX 图的示例
import networkx as nx

# Construct a directed NetworkX graph
nx_G = nx.DiGraph()
nx_G.add_node(1, labels=["Person"], age=52)
nx_G.add_node(42, labels=["Product", "Item"], cost=17.2)
nx_G.add_edge(1, 42, relationshipType="BUYS", quantity=4)

# Load the graph into GDS
G = gds.graph.networkx.load(nx_G, "purchases")

# Verify that the projection is what we expect
assert G.name() == "purchases"
assert G.node_count() == 2
assert set(G.node_labels()) == {"Person", "Product", "Item"}
assert G.node_properties("Person") == ["age"]
assert G.node_properties("Product") == ["cost"]
# Count rel not being = 2 indicates the graph is indeed directed
assert G.relationship_count() == 1
assert G.relationship_types() == ["BUYS"]
assert G.relationship_properties("BUYS") == ["quantity"]

结合 NetworkX 读取各种图格式的功能,可以轻松地将边缘列表和 GML 等流行图格式加载到 GDS 中。

结合 NetworkX 生成各种图的功能,可以轻松地将文献中流行的图类型加载到 GDS 中,例如扩展图、棒棒糖图、完全图等。

4.2. NetworkX 模式到 GDS 模式

关于 NetworkX 图如何映射到 GDS 中投影的 Graph,有一些规则。它们遵循与 从 DataFrames 构建图 类似的原则,并在此部分中有详细概述。

4.2.1. 节点标签

投影的 GDS 图的节点标签取自 networkx.Graph 节点的属性。节点属性键 labels 的值将决定节点在投影中被赋予什么标签。这些值应该是字符串或字符串列表。

networkx.Graph 的所有节点必须具有有效的 labels 属性,或者可以从图中完全省略它们。也就是说,也允许没有任何 labels 节点属性的 networx.Graph。在后一种情况下,投影的 Graph 中的节点都将具有节点标签 N

4.2.2. 节点属性

投影的 GDS 图的节点属性取自 networkx.Graph 节点的属性。属性的键将映射到属性名称,允许的值必须遵循 GDS 中节点属性值的常规准则。但请注意,节点属性键 labels 保留用于节点标签(上一节),不会转换为投影中的节点属性。

4.2.3. 关系类型

投影的 GDS 图的关系类型取自 networkx.Graph 边的属性。边属性键 relationshipType 的值将决定关系在投影中被赋予什么类型。这些值应该是字符串或被省略。

networkx.Graph 的所有边必须具有有效的 relationshipType 属性,或者可以从图中完全省略它们。也就是说,也允许没有任何 relationshipType 边属性的 networx.Graph。在后一种情况下,投影的 Graph 中的关系都将具有关系类型 R

4.2.4. 关系属性

投影的 GDS 图的关系属性取自 networkx.Graph 边的属性。属性的键将映射到属性名称,允许的值必须遵循 GDS 中关系属性值的常规准则。但请注意,边属性键 relationshipType 保留用于关系类型(上一节),不会转换为投影中的关系属性。

4.2.5. 关系方向

投影的 GDS 图中关系的方向(DIRECTEDUNDIRECTED)是从所使用的 networkx.Graph 的类型推断出来的。如果给定的 NetworkX 图是有向的,即 networkx.DiGraphnetworkx.MultiDiGraph 的子类,则投影中的关系将是 DIRECTED。否则,它们将是 UNDIRECTED

4.3. 社区版限制

对于 GDS 社区版用户,大型图的性能可能会受到影响。数据库的套接字连接可能会超时。如果发生这种情况,一种可能的解决方法是修改服务器配置 server.bolt.connection_keep_aliveserver.bolt.connection_keep_alive_probes。但是,请注意副作用,例如真正的连接问题现在需要更长时间才能被检测到。

5. 检查图对象

图对象上有便捷方法,让我们能够提取有关我们投影的图的信息。

表 4. 图对象方法
名称 参数 返回类型 描述

名称 (name)

-

str

投影图的名称。

database

-

str

图已投影到的数据库名称。

node_count

-

int

投影图的节点数。

relationship_count

-

int

投影图的关系数。

node_labels

-

list[str]

图中存在的节点标签列表。

relationship_types

-

list[str]

图中存在的关系类型列表。

node_properties

label: Optional[str]

Union[Series, list[str]]

如果给定了 label 参数,则返回具有提供的节点标签的节点上存在的属性列表。否则,返回一个 Series,将每个节点标签映射到具有该标签的节点上存在的属性列表。

relationship_properties

type: Optional[str]

Union[Series, list[str]]

如果给定了 type 参数,则返回具有提供的关系类型的关系上存在的属性列表。否则,返回一个 Series,将每个关系类型映射到具有该类型的关系上存在的属性列表。

degree_distribution

-

Series

生成节点的平均出度。

density

-

float

图的密度。

size_in_bytes

-

int

Java 堆中用于存储图的字节数。

memory_usage

-

str

size_in_bytes 的人类可读描述。

exists

-

bool

如果图存在于 GDS 图目录中,则返回 True,否则返回 False

drop

failIfMissing: Optional[bool]

Series

从 GDS 图目录中删除图

配置

-

Series

用于在内存中投影图的配置。

creation_time

-

neo4j.time.Datetime

图投影的时间。

modification_time

-

neo4j.time.Datetime

图最后一次被修改的时间。

例如,要获取图 G 的节点数和节点属性,我们可以执行以下操作

n = G.node_count()
props = G.node_properties("City")

6. 上下文管理

图对象还实现了上下文管理协议,即可以在 with 子句中使用。在退出 with 块时,图投影将自动在服务器端被删除。

# We use the example graph from the `Projecting a graph object` section
with gds.graph.project(
    "tmp_offices",              #  Graph name
    ["City"],                   #  Node projection
    "FLY_TO",                   #  Relationship projection
    readConcurrency=4           #  Configuration parameters
)[0] as G_tmp:
    assert G_tmp.exists()

# Outside of the with block the Graph does not exist
assert not gds.graph.exists("tmp_offices")["exists"]

7. 使用图对象

图对象的主要用例是将它传递给算法,但它也是 GDS 图目录 大多数方法的输入。

7.1. 算法的输入

使用 Graph 作为算法输入的 Python 客户端语法遵循 GDS Cypher 过程 API,其中图是传递给算法的第一个参数。

语法组合
result = gds[.<tier>].<algorithm>.<execution-mode>[.<estimate>](
  G: Graph,
  **configuration: dict[str, any]
)

在此示例中,我们在图 G 上运行度中心性算法

result = gds.degree.mutate(G, mutateProperty="degree")
assert "centralityDistribution" in result

7.2. 图目录

GDS 图目录 的所有过程在客户端中都有相应的 Python 方法。在那些接受图名称字符串作为输入的过程目录中,它们的 Python 客户端对应项改为接受 Graph 对象,但 gds.graph.exists 除外,它仍然接受图名称字符串。

以下是一些如何通过客户端使用 GDS 图目录的示例,假设我们从上面的示例中检查图 G

# List graphs in the catalog
list_result = gds.graph.list()

# Check for existence of a graph in the catalog
exists_result = gds.graph.exists("offices")
assert exists_result["exists"]

# Stream the node property 'degree'
result = gds.graph.nodeProperty.stream(G, node_property="degree")

# Drop a graph; same as G.drop()
gds.graph.drop(G)

7.2.1. 流式传输属性

客户端方法

如果启用了 GDS 的 Apache Arrow Flight 服务器,这些方法的速度将大大加快。

此外,为 gds.graph.streamNodePropertiesgds.graph.streamRelationshipProperties 设置仅客户端的可选关键字参数 separate_property_columns=True(默认为 False),将返回一个 pandas DataFrame,其中请求的每个属性都有自己的列。请注意,这不同于默认行为,默认行为中只有一个名为 propertyValue 的列,其中包含为每个节点或关系交错请求的所有属性。

7.2.2. 包含来自 Neo4j 的节点属性

诸如名称和描述之类的节点属性对于理解算法的输出非常有用,即使在运行算法本身时不需要它们。要直接从 Neo4j 数据库获取额外的节点属性,您可以使用 gds.graph.nodeProperty.streamgds.graph.nodeProperties.stream 方法的 db_node_properties 仅客户端参数。

在以下示例中,City 节点既有数值属性,也有 String 属性。stream 方法在检索投影的 population 属性的值的同时,检索数据库专属 name 属性的值。

gds.run_cypher(
  """
  CREATE
    (m: City {name: "Malmö", population: 360000}),
    (l: City {name: "London", population: 8800000}),
    (s: City {name: "San Mateo", population: 105000}),

    (m)-[:FLY_TO]->(l),
    (l)-[:FLY_TO]->(m),
    (l)-[:FLY_TO]->(s),
    (s)-[:FLY_TO]->(l)
  """
)

G, result = gds.graph.project(
    "offices",
    {
        "City": {
            "properties": ["population"]
        }
    },
    "FLY_TO"
)

gds.graph.nodeProperties.stream(G, node_properties=["population"], db_node_properties=["name"])

7.2.3. 按关系类型流式传输拓扑

从对应于 gds.beta.graph.relationships.stream 的 Python 客户端方法返回的类型称为 TopologyDataFrame,它继承自标准 pandas DataFrameTopologyDataFrame 带有一个名为 by_rel_type 的附加便捷方法,该方法不接受任何参数,并返回 Dict[str, List[List[int]]] 形式的字典。该字典将关系类型作为字符串映射到 2 x m 矩阵,其中 m 在此处表示给定类型的关系数。每个此类矩阵的第一行是关系的源节点 ID,第二行是相应的目标节点 ID。

我们可以使用来自上面的构建示例的图 G 的示例来说明这种转换

topology_by_rel_type = gds.beta.graph.relationships.stream(G).by_rel_type()

assert list(topology_by_rel_type.keys()) == ["REL"]
assert topology_by_rel_type["REL"][0] == [0, 1, 2, 3]
assert topology_by_rel_type["REL"][1] == [1, 2, 3, 0]

流式传输属性 方法一样,如果启用了 GDS Apache Arrow Flight 服务器,gds.beta.graph.relationships.stream 也会得到加速。