Skip to content

GFQL Query Patterns for Cypher

Overview

This document analyzes GFQL (GraphFrame Query Language) patterns from PyGraphistry and identifies equivalent Cypher patterns for the graph database. GFQL provides a declarative, chain-based approach to graph traversal that can inspire more expressive and maintainable Cypher queries in Graffold.

Key Insight: GFQL's chain operations model is conceptually similar to Cypher's pattern matching, but emphasizes progressive filtering and tagging through sequential operations.

Note: All Cypher examples in this document work on both Neo4j and Memgraph backends.


Core GFQL Concepts

1. Chain Operations

GFQL queries are built as chains of operations that progressively filter and traverse the graph:

# GFQL chain example
chain_operations = graphistry.Chain([
    n(name="seed_nodes"),                    # Tag starting nodes
    e_forward(hops=1, edge_match={...}),     # Traverse edges
    n(name="destination_nodes")              # Tag destination nodes
])

Cypher Equivalent: Pattern matching with WHERE clauses and variable binding

MATCH (seed_nodes:Entity)
WHERE seed_nodes.property = value
MATCH (seed_nodes)-[r:RELATIONSHIP]->(destination_nodes:Entity)
WHERE r.property = value
RETURN seed_nodes, r, destination_nodes

Pattern Mappings

Pattern 1: Node Filtering with Tagging

GFQL Pattern:

n({"point_of_interest": "Grocery store"}, name="grocery_stores")

Neo4j/Memgraph Cypher Equivalent:

MATCH (grocery_stores:Location {point_of_interest: "Grocery store"})
RETURN grocery_stores

Use Case in Graffold: Filter proteins by specific properties (e.g., subcellular location, gene symbol)

MATCH (proteins:Protein)
WHERE proteins.subcellular_location = "Nucleus"
  AND proteins.gene_symbol IS NOT NULL
RETURN proteins

Pattern 2: Undirected Edge Traversal

GFQL Pattern:

e_forward(hops=1, direction="undirected", edge_match={...})

Cypher Equivalent:

MATCH (n)-[r]-(m)  // Undirected relationship
WHERE r.property = value
RETURN n, r, m

Use Case in Graffold: Find all proteins associated with a disease (bidirectional)

MATCH (p:Protein)-[r:ASSOCIATES_WITH]-(d:Disease)
WHERE d.name = "Alzheimer's disease"
RETURN p, r, d

Pattern 3: Multi-Hop Traversal with Fixed Depth

GFQL Pattern:

e_forward(hops=2, to_fixed_point=False)

Cypher Equivalent:

MATCH path = (n)-[*2]-(m)  // Exactly 2 hops
RETURN path

Use Case in Graffold: Multi-hop protein-disease relationships (already implemented in US-001)

MATCH path = (p:Protein)-[*1..3]-(d:Disease)
WHERE p.uniprot_id = "P04637"  // TP53
RETURN path, 
       [rel in relationships(path) | type(rel)] as relationship_chain,
       [node in nodes(path) | node.name] as entity_chain

Pattern 4: Greedy Traversal to Fixed Point

GFQL Pattern:

e_forward(direction="undirected", to_fixed_point=True, name="connected_edges")

Cypher Equivalent:

MATCH path = (start)-[*]-(end)  // Variable-length path (greedy)
WHERE start.id = "seed_id"
RETURN path

Use Case in Graffold: Find all entities reachable from a seed protein (community detection)

MATCH path = (seed:Protein {uniprot_id: "P38398"})-[*]-(connected:Entity)
RETURN connected, length(path) as distance
ORDER BY distance
LIMIT 100

⚠️ Performance Warning: Greedy traversal can be expensive. Use LIMIT and depth constraints.


Pattern 5: Conditional Edge Filtering

GFQL Pattern:

e_forward(edge_match={
    "originator_bank_country": "Latvia",
    "beneficiary_bank_country": "Russia",
    "amount_transactions": between(15800000, 16000000)
})

Cypher Equivalent:

MATCH (n)-[r:TRANSACTION]->(m)
WHERE r.originator_country = "Latvia"
  AND r.beneficiary_country = "Russia"
  AND r.amount >= 15800000 AND r.amount <= 16000000
RETURN n, r, m

Use Case in Graffold: Filter protein-disease associations by confidence score and evidence count

MATCH (p:Protein)-[r:ASSOCIATES_WITH]->(d:Disease)
WHERE r.avg_confidence >= 0.8
  AND r.evidence_sources >= 5
  AND r.last_seen >= datetime() - duration({days: 30})
RETURN p, r, d
ORDER BY r.avg_confidence DESC

Pattern 6: Progressive Filtering with Intermediate Tagging

GFQL Pattern:

chain_operations = graphistry.Chain([
    n({"town": "Oakville"}, name="oakville_locations"),
    e_forward(hops=1, name="direct_roads"),
    n({"point_of_interest": "Movie theatre"}, name="theatres")
])

Cypher Equivalent:

MATCH (oakville_locations:Location {town: "Oakville"})
MATCH (oakville_locations)-[direct_roads:ROAD]-(theatres:Location)
WHERE theatres.point_of_interest = "Movie theatre"
RETURN oakville_locations, direct_roads, theatres

Use Case in Graffold: Find proteins in specific pathways that associate with cardiovascular diseases

MATCH (p:Protein)-[:PARTICIPATES_IN]->(pathway:Pathway)
WHERE pathway.name CONTAINS "cardiovascular"
MATCH (p)-[r:ASSOCIATES_WITH]->(d:Disease)
WHERE d.mondo_id STARTS WITH "MONDO:0005267"  // Cardiovascular disease hierarchy
RETURN p, pathway, r, d

Pattern 7: Subgraph Extraction with Property Matching

GFQL Pattern:

chain_operations = graphistry.Chain([
    n({"nodeId": contains(pat="expo")}),
    e_forward(edge_match={"amount_transactions": between(15800000, 16000000)}),
    n({"nodeId": contains(pat="soyuz")}, name="target_nodes")
])

Cypher Equivalent:

MATCH (source:Entity)
WHERE source.id CONTAINS "expo"
MATCH (source)-[r:TRANSACTION]->(target:Entity)
WHERE r.amount >= 15800000 AND r.amount <= 16000000
  AND target.id CONTAINS "soyuz"
RETURN source, r, target

Use Case in Graffold: Find proteins with specific gene symbols that interact with diseases containing keywords

MATCH (p:Protein)
WHERE p.gene_symbol =~ ".*BRCA.*"  // Regex match for BRCA genes
MATCH (p)-[r:ASSOCIATES_WITH]->(d:Disease)
WHERE d.name =~ "(?i).*cancer.*"  // Case-insensitive cancer match
RETURN p, r, d
ORDER BY r.avg_confidence DESC

Adopted Patterns for Graffold

Pattern 1: Multi-Hop Traversal with Path Context ✅ IMPLEMENTED (US-001)

Rationale: Enables discovery of indirect protein-disease relationships through intermediate entities.

Implementation: src/agents/neo4j_query_agent.py

MATCH path = (seed:Entity)-[*1..{hop_depth}]-(target:Entity)
WHERE seed.id IN $seed_ids
WITH path, 
     [rel in relationships(path) | type(rel)] as rel_chain,
     [node in nodes(path) | {name: node.name, type: labels(node)[0]}] as node_chain
RETURN path, rel_chain, node_chain
LIMIT 50

Benefits: - Discovers hidden connections (e.g., Protein → Pathway → Disease) - Provides full path context for explainability - Configurable depth (1-3 hops) for performance control


Pattern 2: Conditional Relationship Filtering

Rationale: Filter relationships by evidence quality (confidence, recency, source count).

Prototype:

MATCH (p:Protein)-[r:ASSOCIATES_WITH]->(d:Disease)
WHERE r.avg_confidence >= $min_confidence
  AND r.evidence_sources >= $min_sources
  AND r.last_seen >= datetime() - duration({days: $max_age_days})
RETURN p, r, d
ORDER BY r.avg_confidence DESC, r.evidence_sources DESC
LIMIT 20

Integration Point: Neo4jQueryAgent._retrieve_neighborhood() - add confidence filtering parameter


Pattern 3: Hierarchical Disease Traversal

Rationale: Leverage MONDO disease hierarchies for broader or narrower disease queries.

Prototype:

// Find all diseases under "Cardiovascular disease" hierarchy
MATCH (parent:Disease {mondo_id: "MONDO:0005267"})-[:IS_A*]->(child:Disease)
MATCH (p:Protein)-[r:ASSOCIATES_WITH]->(child)
RETURN p, r, child, parent

Integration Point: New method _expand_disease_hierarchy() in Neo4jQueryAgent


Pattern 4: Protein Pathway Co-occurrence

Rationale: Find proteins that share pathways with a seed protein (functional similarity).

Prototype:

MATCH (seed:Protein {uniprot_id: $seed_id})-[:PARTICIPATES_IN]->(pathway:Pathway)
MATCH (related:Protein)-[:PARTICIPATES_IN]->(pathway)
WHERE related.uniprot_id <> $seed_id
WITH related, collect(DISTINCT pathway.name) as shared_pathways, count(pathway) as pathway_count
WHERE pathway_count >= 2  // At least 2 shared pathways
RETURN related, shared_pathways, pathway_count
ORDER BY pathway_count DESC
LIMIT 10

Integration Point: New retrieval mode pathway_similarity in QueryModeRouter


Pattern 5: Temporal Relationship Analysis

Rationale: Track when relationships were first discovered and last updated (incremental updates).

Prototype:

MATCH (p:Protein)-[r:ASSOCIATES_WITH]->(d:Disease)
WHERE r.first_seen >= datetime() - duration({days: 30})  // New relationships
RETURN p, r, d, r.first_seen as discovered_date, r.evidence_sources
ORDER BY r.first_seen DESC

Integration Point: New query mode recent_discoveries for tracking new findings


Pattern 6: Evidence Provenance Tracking

Rationale: Trace relationships back to source PMIDs for citation and validation.

Prototype:

MATCH (p:Protein {uniprot_id: "P04637"})-[r:ASSOCIATES_WITH]->(d:Disease {name: "Cancer"})
UNWIND r.pmids as pmid
MATCH (abstract:Abstract {pmid: pmid})
RETURN p, d, pmid, abstract.title, abstract.publication_date
ORDER BY abstract.publication_date DESC

Integration Point: Add include_provenance=True parameter to query methods


Pattern 7: Subgraph Extraction by Community

Rationale: Extract subgraphs for specific communities (global query mode).

Prototype:

MATCH (n:Entity)
WHERE n.community_id = $community_id
MATCH path = (n)-[r]-(m:Entity)
WHERE m.community_id = $community_id
RETURN path
LIMIT 100

Integration Point: _global_query() in QueryModeRouter - add subgraph extraction option


Performance Considerations

GFQL vs Cypher Performance Patterns

Pattern GFQL Approach Cypher Optimization (Neo4j & Memgraph)
Greedy traversal to_fixed_point=True Use LIMIT and depth constraints [*1..5]
Large result sets Client-side filtering Use WHERE clauses early in query
Multi-hop Progressive filtering Use CYPHER runtime=slotted for timeout handling
Tagging name parameter Use WITH clauses for intermediate results

Timeout Protection (from US-020)

-- Works on both Neo4j and Memgraph
MATCH path = (n)-[*1..{hop_depth}]-(m)
WHERE n.id IN $seed_ids
RETURN path
LIMIT 50

Timeout Handling: 5s primary query, 2s fallback with reduced seed set


Implementation Roadmap

Phase 1: Core Patterns (Completed)

  • ✅ Multi-hop traversal with path context (US-001)
  • ✅ Timeout protection for expensive queries (US-020)
  • [ ] Add min_confidence and min_sources parameters to _retrieve_neighborhood()
  • [ ] Integrate into QueryRequest model in src/api/app.py
  • [ ] Update documentation with confidence filtering examples

Phase 3: Hierarchical Queries (Future)

  • [ ] Implement _expand_disease_hierarchy() method
  • [ ] Add include_hierarchy=True parameter to disease queries
  • [ ] Create validation script for MONDO hierarchy coverage

Phase 4: Temporal and Provenance (Future)

  • [ ] Add recent_discoveries query mode
  • [ ] Implement include_provenance=True parameter
  • [ ] Create API endpoint for PMID-based relationship lookup

Key Takeaways

  1. Declarative Patterns: GFQL's chain operations map cleanly to Cypher's pattern matching
  2. Progressive Filtering: Use multiple MATCH clauses with WHERE for step-by-step filtering
  3. Tagging via Variables: Cypher variable names serve the same purpose as GFQL's name parameter
  4. Performance: Always use LIMIT, depth constraints, and early filtering for production queries
  5. Path Context: Extract relationship chains and node sequences for explainability

References