在SQL、Pandas、Cypher和GFQL之间进行翻译#
本指南提供了SQL、Pandas、Cypher和GFQL之间的比较,帮助您将熟悉的查询转换为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
解释:
GFQL: 通过许多算法进行增强,例如用于社区检测的GPU加速
graphistry.plugins.cugraph.compute_cugraph()。可以使用任何CPU和GPU库,顶级插件已经原生支持开箱即用。
—
时间窗口图分析#
目标: 查找在过去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
解释:
SQL 和 Pandas:这些版本错误地简化为两跳关系;对于多跳场景,请参考 所有路径和连通性。
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 操作符参考: 使用谓词来过滤节点和边的属性。
10 Minutes to PyGraphistry: 使用GPU加速工具可视化GFQL查询。
结论#
GFQL 填补了传统查询语言和图分析之间的空白。通过将 SQL、Pandas 和 Cypher 的查询转换为 GFQL,您可以在 Python 工作流中利用强大的图查询功能。
立即开始探索GFQL,从您的图数据中解锁新的见解!