图对象

为了利用 GDS 中的大部分功能,您必须首先将图投影到 GDS 图目录中。当使用 Python 客户端投影图时,会返回对投影图的客户端引用。我们将这些引用称为 Graph 对象。

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

在下面的示例中,我们假设我们有一个已实例化的 GraphDataScience 对象,名为 gds。请在 入门 中阅读更多内容。

1. 投影图对象

投影图对象有几种方法。最简单的方法是进行 原生投影

# 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_cyphergds.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

-

要构建的图的名称。

nodes

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 服务器支持

construct 方法如果启用,可以利用 GDS 的 Apache Arrow Flight 服务器。这尤其意味着

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

  • 可以提供多个数据帧,包括节点和关系的数据帧。如果使用多个节点数据帧,它们需要包含所有节点数据帧中不同的节点 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

NetworkX 格式的图

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]]

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

relationship_properties

type: Optional[str]

Union[Series, list[str]]

如果提供了类型参数,则返回具有给定关系类型的关系上存在的属性列表。否则,返回一个 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 图目录中移除图

configuration

-

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. 算法输入

Python 客户端使用 Graph 作为算法输入的语法遵循 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 数据库获取额外的节点属性,可以使用 db_node_properties 客户端专用参数 gds.graph.nodeProperty.streamgds.graph.nodeProperties.stream 方法。

在以下示例中,City 节点同时具有数字属性和 String 属性。stream 方法检索仅数据库的 name 属性的值以及投影的 population 属性的值。

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. 按关系类型流式传输拓扑

Python 客户端方法 gds.beta.graph.relationships.stream 返回的类型名为 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 也会加速。