在SQL、Pandas、Cypher和GFQL之间进行翻译#

本指南提供了SQLPandasCypherGFQL之间的比较,帮助您将熟悉的查询转换为GFQL。

介绍#

GFQL(GraphFrame查询语言)旨在让熟悉SQL、Cypher或类似Pandas和Spark的数据框架的用户感到直观。通过比较这些语言中的等效查询,您可以快速掌握GFQL的语法、优势,并开始在您的工作流程中利用其强大的图查询功能。

本指南适合谁?#

  • 数据科学家: 熟悉Pandas或SQL,探索图关系。

  • 工程师: 将图查询集成到应用程序中。

  • 数据库管理员: 了解GFQL如何补充SQL以处理图数据。

  • 图专家: 熟悉Cypher,能够将图查询集成到Python中。

常见的图和查询任务#

我们将涵盖一系列常见的图形和查询任务:

查找具有特定属性的节点#

目标: 找到所有type“person”的节点。

SQL

SELECT * FROM nodes
WHERE type = 'person';

Pandas

people_nodes_df = nodes_df[ nodes_df['type'] == 'person' ]

Cypher

MATCH (n {type: 'person'})
RETURN n;

GFQL

from graphistry import n

# df[['id', 'type', ...]]
g.chain([ n({"type": "person"}) ])._nodes

解释:

  • GFQL: n({“type”: “person”}) 过滤节点,其中 type“person”g.chain([…]) 将此过滤器应用于图 g,并且 ._nodes 检索结果节点。性能类似于 Pandas(CPU)或 cuDF(GPU)。

探索节点之间的关系#

目标: 找到所有连接类型为“person”的节点和类型为“company”的节点的边。

SQL

SELECT e.*
FROM edges e
JOIN nodes n1 ON e.src = n1.id
JOIN nodes n2 ON e.dst = n2.id
WHERE n1.type = 'person' AND n2.type = 'company';

Pandas

merged_df = edges_df.merge(
    nodes_df[['id', 'type']], left_on='src', right_on='id', suffixes=('', '_src')
).merge(
    nodes_df[['id', 'type']], left_on='dst', right_on='id', suffixes=('', '_dst')
)

result = merged_df[
    (merged_df['type_src'] == 'person') &
    (merged_df['type_dst'] == 'company')
]

Cypher

MATCH (n1 {type: 'person'})-[e]->(n2 {type: 'company'})
RETURN e;

GFQL

from graphistry import n, e_forward

# df[['src', 'dst', ...]]
chain([
    n({"type": "person"}), e_forward(), n({"type": "company"})
])._edges

解释:

  • GFQL: 从类型为“person”的节点开始,遍历前向边,到达类型为“company”的节点。结果边存储在edges_df中。此版本开始获得图查询语法在图任务中的可读性和可维护性优势,并保持自动向量化的pandas和GPU加速的cuDF的性能优势。

执行多跳遍历#

目标: 找到距离节点“Alice”两跳的节点。

SQL

WITH first_hop AS (
    SELECT e1.dst AS node_id
    FROM edges e1
    WHERE e1.src = 'Alice'
),
second_hop AS (
    SELECT e2.dst AS node_id
    FROM edges e2
    JOIN first_hop fh ON e2.src = fh.node_id
)
SELECT * FROM nodes
WHERE id IN (SELECT node_id FROM second_hop);

Pandas

first_hop = edges_df[ edges_df['src'] == 'Alice' ]['dst']
second_hop = edges_df[ edges_df['src'].isin(first_hop) ]['dst']
result_nodes_df = nodes_df[ nodes_df['id'].isin(second_hop) ]

Cypher

MATCH (n {id: 'Alice'})-->()-->(m)
RETURN m;

GFQL

from graphistry import n, e_forward

# df[['id', ...]]
g.chain([
    n({g._node: "Alice"}), e_forward(), e_forward(), n(name='m')
])._nodes.query('m')

解释:

  • GFQL: 从节点 “Alice” 开始,执行两次前向跳转,并获取两步之外的节点。结果在 nodes_df 中。基于之前1跳示例的表达性和性能优势,它开始展示GFQL在并行路径查找方面相对于Cypher的优势,这对CPU和GPU的使用都有好处。

使用条件过滤边和节点#

目标: 找到所有权重大于0.5的边。

SQL

SELECT * FROM edges
WHERE weight > 0.5;

Pandas

filtered_edges_df = edges_df[ edges_df['weight'] > 0.5 ]

Cypher

MATCH ()-[e]->()
WHERE e.weight > 0.5
RETURN e;

GFQL

from graphistry import e_forward

# df[['src', 'dst', 'weight', ...]]
g.chain([ e_forward(edge_query='weight > 0.5') ])._edges

解释:

  • GFQL: 使用 e_forward(edge_query=’weight > 0.5’) 来过滤 weight > 0.5 的边。此版本引入了方便的字符串查询形式。在底层,它仍然受益于 Pandas 和 cuDF 的向量化执行。

聚合与分组#

目标: 计算每个节点的出边数量。

SQL

SELECT src, COUNT(*) AS out_degree
FROM edges
GROUP BY src;

Pandas

out_degree = edges_df.groupby('src').size().reset_index(name='out_degree')

Cypher

MATCH (n)-[e]->()
RETURN n.id AS node_id, COUNT(e) AS out_degree;

GFQL

# df[['src', 'out_degree']]
g._edges.groupby('src').size().reset_index(name='out_degree')

解释:

  • GFQL: 直接使用标准的数据框操作在g._edges上执行聚合。或者更简洁地,调用g.get_degrees()来为每个节点添加入度、出度和总度。此版本受益于Pandas和cuDF的硬件加速列式分析执行,以及数据框操作的简单性。

所有路径和连通性#

目标: 找到所有通过友谊关系连接节点 "Alice""Bob" 的路径。

SQL

WITH RECURSIVE path AS (
    -- Base case: Start from "Alice" (no type or edge restrictions)
    SELECT e.src, e.dst, ARRAY[e.src, e.dst] AS full_path, 1 AS hop
    FROM edges e
    WHERE e.src = 'Alice'

    UNION ALL

    -- Recursive case: Expand path where intermediate src/dst are 'people' and edge is 'friend'
    SELECT e.src, e.dst, full_path || e.dst, p.hop + 1
    FROM edges e
    JOIN path p ON e.src = p.dst
    JOIN nodes n_src ON e.src = n_src.id  -- Check src type for intermediate nodes
    JOIN nodes n_dst ON e.dst = n_dst.id  -- Check dst type for intermediate nodes
    WHERE n_src.type = 'person' AND n_dst.type = 'person'  -- Intermediate nodes must be 'people'
    AND e.type = 'friend'  -- Intermediate edges must be 'friend'
    AND e.dst != ALL(full_path)  -- Avoid cycles (optional)
)
-- Final filter to ensure the path ends with "Bob"
SELECT *
FROM path
WHERE dst = 'Bob';

Pandas

def find_paths_fixed_point(edges_df, nodes_df, start_node, end_node):
    # Initialize paths with base case (start with 'Alice')
    paths = [{'path': [start_node], 'last_node': start_node}]
    all_paths = []
    expanded = True  # Continue loop as long as there are paths to expand

    while expanded:
        new_paths = []
        expanded = False

        # Expand each path
        for path in paths:
            last_node = path['last_node']

            # Find all outgoing 'friend' edges from the last node
            valid_edges = edges_df.merge(nodes_df, left_on='dst', right_on='id') \
                                .merge(nodes_df, left_on='src', right_on='id') \
                                [(edges_df['src'] == last_node) &
                                (edges_df['type'] == 'friend') &
                                (nodes_df['type_x'] == 'person') &  # src is 'person'
                                (nodes_df['type_y'] == 'person')]   # dst is 'person'

            for _, edge in valid_edges.iterrows():
                new_path = path['path'] + [edge['dst']]

                # If we reached 'Bob', add to all_paths
                if edge['dst'] == end_node:
                    all_paths.append(new_path)
                else:
                    # Otherwise, add to new paths to continue expanding
                    new_paths.append({'path': new_path, 'last_node': edge['dst']})
                    expanded = True  # Mark that we found new paths to expand

        # Stop if no new paths were found (fixed-point behavior)
        paths = new_paths

    return all_paths

# Run the pathfinding function to fixed point
paths = find_paths_fixed_point(edges_df, nodes_df, 'Alice', 'Bob')

Cypher

MATCH p = (n1 {id: 'Alice'})-[e:friend*]-(n2 {id: 'Bob'})
WHERE ALL(rel IN relationships(p) WHERE type(rel) = 'friend')
AND ALL(node IN NODES(p) WHERE node.type = 'person')
RETURN p;

GFQL

# g._edges: df[['src', 'dst', ...]]
# g._nodes: df[['id', ...]]
g.chain([
    n({"id": "Alice"}),
    e_forward(
        source_node_query='type == "person"',
        edge_query='type == "friend"',
        destination_node_query='type == "person"',
        to_fixed_point=True),
    n({"id": "Bob"})
])

解释:

  • GFQL: 使用 e(to_fixed_point=True) 来查找节点 “Alice”“Bob” 之间任意长度的边序列。在这个例子中,SQL 和 Pandas 版本在语法和语义上与图任务存在不匹配的问题。

社区检测与聚类#

目标: 使用Louvain算法识别图中的社区。

SQL 和 Pandas

  • 不适用于复杂的图算法,如社区检测。

Cypher

CALL algo.louvain.stream() YIELD nodeId, communityId

GFQL

# g._nodes: df[['id', 'louvain']]
g.compute_cugraph('louvain')._nodes

解释:

时间窗口图分析#

目标: 查找在过去7天内发生在节点“Alice”“Bob”之间的所有边。

SQL

SELECT * FROM edges
WHERE ((src = 'Alice' AND dst = 'Bob') OR (src = 'Bob' AND dst = 'Alice'))
  AND timestamp >= NOW() - INTERVAL '7 days';

警告

此版本错误地简化为两跳关系。对于多跳场景,请参考所有路径和连通性以获取更高级的技术。

Pandas

filtered_edges_df = edges_df[
    ((edges_df['src'] == 'Alice') & (edges_df['dst'] == 'Bob')) |
    ((edges_df['src'] == 'Bob') & (edges_df['dst'] == 'Alice')) &
    (edges_df['timestamp'] >= pd.Timestamp.now() - pd.Timedelta(days=7))
]

警告

此版本错误地简化为两跳关系。对于多跳场景,请参考所有路径和连通性以获取更高级的技术。

Cypher

MATCH path = (a {id: 'Alice'})-[e]-(b {id: 'Bob'})
WHERE e.timestamp >= datetime().subtract(duration({days: 7}))
RETURN e;

GFQL

past_week = pd.Timestamp.now() - pd.Timedelta(7)
g.chain([
    n({"id": {"$in": ["Alice", "Bob"]}}),
    e_forward(edge_query=f'timestamp >= "{past_week}"'),
    n({"id": {"$in": ["Alice", "Bob"]}})
])._edges

解释:

  • SQLPandas:这些版本错误地简化为两跳关系;对于多跳场景,请参考 所有路径和连通性

  • GFQL: 利用chain方法根据过去7天的时间戳过滤“Alice”“Bob”之间的边。这种方法允许利用图的结构进行多跳关系查询,并在可用时进一步使用cuDF进行GPU加速。

并行路径查找#

目标: 并行查找从“Alice”“Bob”“Charlie”的所有路径。并行路径查找特别有趣,因为它允许同时高效地查询多个目标节点,减少了计算多个独立路径所需的时间和复杂性,尤其是在大型图中。

SQL

  • 不适用: SQL 不适用于图上的路径查找。

Pandas

  • 不适用: Pandas 不是为跨图路径查找设计的。

Cypher

警告

Cypher是路径导向的,并不原生支持并行路径查找。每条路径必须单独处理,这可能导致大型图或多个目标的性能瓶颈。Neo4j用户可以利用APOC或GDS库来增加并行性,但这是一种有限的外部解决方案,而不是原生优势。

MATCH (a {id: 'Alice'}), (target)
WHERE target.id IN ['Bob', 'Charlie']
CALL algo.shortestPath.stream(a, target)
YIELD nodeId, cost
RETURN nodeId, cost;

GFQL

from graphistry import n, e_forward

# g._nodes: cudf.DataFrame[['src', 'dst', ...]]
g.chain([
    n({"id": "Alice"}),
    e_forward(to_fixed_point=False),
    n({"id": is_in(["Bob", "Charlie"])})
], engine='cudf')

解释:

  • Cypher: Cypher 单独处理路径,不支持原生并行性。像 APOC 或 GDS 这样的库提供了一种实现并行执行的方法,但这增加了复杂性。

  • GFQL: GFQL 原生支持使用批量波前算法进行并行路径查找,一次性处理所有路径,使其在GPU加速环境中非常高效。

GPU 执行#

目标*:在GPU上执行路径查找查询,同时计算从“Alice”“Bob”“Charlie”的所有路径,跨硬件资源。

SQL

  • 不适用: SQL 不适用于并行执行图查询。

Pandas

  • 不适用: Pandas 不是为跨图并行执行而设计的。

Cypher

  • 不适用:像Neo4j这样的流行Cypher引擎本身不支持GPU执行。

GFQL

from graphistry import n, e_forward

# Executing pathfinding queries in parallel
g.chain([
    n({"id": "Alice"}),
    e_forward(to_fixed_point=False),
    n({"id": is_in(["Bob", "Charlie"])})
], engine='cudf')

解释:

这个例子基于前一个例子,展示了GFQL如何原生处理并行执行。GFQL受益于批量向量处理,这提高了CPU和GPU模式下的性能:

  • 在CPU环境中,批量处理模型通过算法加速查询执行,并利用硬件并行性,提高效率。

  • 在GPU模式下,GFQL 原生并行化路径查找,进一步利用硬件加速同时快速处理多条路径,使其在大规模图遍历中非常高效。

GFQL 函数及其等效函数#

节点匹配

  • SQL: SELECT * FROM nodes WHERE ...

  • Pandas: nodes_df[ condition ]

  • Cypher: MATCH (n {property: value})

  • GFQL: n({ "property": value })

边缘匹配

  • SQL: SELECT * FROM edges WHERE ...

  • Pandas: edges_df[ condition ]

  • Cypher: MATCH ()-[e {property: value}]->()

  • GFQL: e_forward({ "property": value })e_reverse({ "property": value })e({ "property": value })

遍历

  • SQL: 复杂的连接或递归查询

  • Pandas: 多重合并;对于深度遍历不高效

  • Cypher: 用于遍历的模式,如 ()-[]->()

  • GFQL: n(), e_forward(), e_reverse(), 和 e() 函数的链式调用

用户提示#

  • 数据科学家和分析师:运用你的Pandas知识。GFQL在数据框上操作,允许熟悉的操作。

  • 工程师和开发者:无需额外基础设施即可将GFQL集成到Python应用程序中。

  • 数据库管理员:在不更改数据库的情况下,使用GFQL补充SQL查询以处理图数据。

  • 图爱好者:从简单的查询开始,探索复杂的分析。使用PyGraphistry可视化结果。

附加资源#

结论#

GFQL 填补了传统查询语言和图分析之间的空白。通过将 SQL、Pandas 和 Cypher 的查询转换为 GFQL,您可以在 Python 工作流中利用强大的图查询功能。

立即开始探索GFQL,从您的图数据中解锁新的见解!