diff --git a/CHANGELOG.md b/CHANGELOG.md index 31af122f83..f628f670c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Development] +### Docs +- **GFQL**: Show Cypher string syntax above the fold in "10 Minutes to GFQL" and "Overview" pages so the first code a reader sees is familiar Cypher, not the native chain API. + ## [0.53.7 - 2026-03-29] ### Fixed diff --git a/docs/source/gfql/about.rst b/docs/source/gfql/about.rst index b868846022..fa57ea92e7 100644 --- a/docs/source/gfql/about.rst +++ b/docs/source/gfql/about.rst @@ -75,14 +75,43 @@ GFQL is part of the open-source ``graphistry`` library. Install it using pip: Ensure you have ``pandas`` or ``cudf`` installed, depending on whether you want to run on CPU or GPU. -Basic Concepts --------------- +Two Syntax Styles +------------------ + +GFQL supports two syntax styles through the same ``g.gfql(...)`` entrypoint: + +**Cypher strings** — familiar if you know SQL or Cypher: + +.. code-block:: python + + # Filter nodes — returns a DataFrame + nodes_df = g.gfql("MATCH (n {type: 'person'}) RETURN n")._nodes + +.. doc-test: skip + +.. code-block:: python + + # Extract a subgraph — returns a graph with ._nodes and ._edges + g2 = g.gfql("GRAPH { MATCH (a)-[e]->(b) WHERE e.interesting = true }") + +**Native chain syntax** — composable Python objects: + +.. code-block:: python + + from graphistry import n, e_forward -Before we begin with examples, let's understand some basic concepts: + # Same node filter, chain form + nodes_df = g.gfql([ n({"type": "person"}) ])._nodes -- **Nodes and Edges:** In GFQL, graphs are represented using dataframes for nodes and edges. -- **Chaining:** GFQL queries are constructed by chaining operations that filter and traverse the graph. -- **Predicates:** Conditions applied to nodes or edges to filter them based on properties. +.. doc-test: skip + +.. code-block:: python + + # Same subgraph extraction, chain form + g2 = g.gfql([ e_forward({"interesting": True}) ]) + +Both styles run on the same vectorized engine, with the same CPU/GPU +acceleration. Use whichever you prefer — or mix them. Examples -------- @@ -90,22 +119,15 @@ Examples 1. Find Nodes of a Certain Type ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -You can filter nodes based on their properties using the ``n()`` function. +.. code-block:: python -**Example: Find all nodes of type "person"** - -:: + # Cypher style — returns a DataFrame of matching nodes + nodes_df = g.gfql("MATCH (n {type: 'person'}) RETURN n")._nodes + # Equivalent chain style from graphistry import n - - people_nodes_df = g.gfql([ n({"type": "person"}) ])._nodes - # people_nodes_df: DataFrame with 'a' and 'b' (the person nodes) - -**Explanation:** - -- ``n({"type": "person"})`` filters nodes where the ``type`` property is ``"person"``. -- ``g.gfql([...])`` applies the chain of operations to the graph ``g``. -- ``._nodes`` retrieves the resulting nodes dataframe. + nodes_df = g.gfql([ n({"type": "person"}) ])._nodes + # nodes_df: DataFrame with 'a' and 'b' (the person nodes) .. graphviz:: @@ -129,17 +151,18 @@ You can filter nodes based on their properties using the ``n()`` function. 2. Find 2-Hop Edge Sequences with an Attribute ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Traverse multiple hops and filter edges based on attributes using ``e_forward()``. +Traverse multiple hops and filter edges based on attributes. -**Example: Find 2-hop paths where edges are marked as "interesting"** +.. code-block:: python -:: + # Cypher style — GRAPH { } returns a subgraph with ._nodes and ._edges + g2 = g.gfql("GRAPH { MATCH (a)-[e]->(b) WHERE e.interesting = true }") + # Equivalent chain style from graphistry import e_forward - - g_2_hops = g.gfql([ e_forward({"interesting": True}, hops=2) ]) - # g_2_hops._edges: edges a->b->c (both marked interesting) - g_2_hops.plot() + g2 = g.gfql([ e_forward({"interesting": True}, hops=2) ]) + # g2._edges: edges a->b->c (both marked interesting) + g2.plot() **Explanation:** diff --git a/docs/source/gfql/overview.rst b/docs/source/gfql/overview.rst index 570fc2fedd..698a0b65ea 100644 --- a/docs/source/gfql/overview.rst +++ b/docs/source/gfql/overview.rst @@ -52,8 +52,8 @@ Key GFQL Concepts GFQL works on the same graphs as the rest of the PyGraphistry library. The operations run on top of the dataframe engine of your choice, with initial support for Pandas dataframes (CPU) and cuDF dataframes (GPU). - **Nodes and Edges**: Represented using dataframes, making integration with Pandas and cuDF seamless -- **Functional**: Build queries by layering operations, similar to functional method chaining in Pandas -- **Query**: Run graph pattern matching using method `chain()` in a style similar to the popoular OpenCypher graph query language +- **Cypher strings**: Write queries as Cypher strings — ``g.gfql("MATCH (n) WHERE n.score > 5 RETURN n")`` +- **Native chains**: Or compose queries as Python objects — ``g.gfql([n({"score": gt(5)})])`` - **Predicates**: Apply conditions to filter nodes and edges based on their properties, reusing the optimized native operations of the underlying dataframe engine - **Same-path constraints (WHERE)**: Relate attributes across steps in a chain using `where` - **Row pipelines (`MATCH ... RETURN` style)**: Move from graph pattern matches to tabular results with `rows()`, `where_rows()`, `return_()`, `order_by()`, `group_by()`, `skip()`, and `limit()` @@ -85,27 +85,35 @@ If you need to enrich a graph and keep matching locally, use graph-preserving `c Quick Examples ~~~~~~~~~~~~~~~ -**Find Nodes of a Certain Type** +GFQL supports Cypher strings and native Python chains through the same ``g.gfql(...)`` entrypoint: -Example: Find all nodes where the `type` is `"person"`. +**Find Nodes of a Certain Type** .. code-block:: python - from graphistry import n - - people_nodes_df = g.gfql([ n({"type": "person"}) ])._nodes - print('Number of person nodes:', len(people_nodes_df)) + # Cypher string — returns a DataFrame of matching nodes + nodes_df = g.gfql("MATCH (n {type: 'person'}) RETURN n")._nodes -**Visualize 2-Hop Edge Sequences with an Attribute** + # Equivalent native chain + from graphistry import n + nodes_df = g.gfql([ n({"type": "person"}) ])._nodes -Example: Find 2-hop paths where edges have `"interesting": True`. +**Extract a Subgraph** .. code-block:: python - from graphistry import n, e_forward + # Cypher string — GRAPH { } returns a subgraph with ._nodes and ._edges + g2 = g.gfql( + "GRAPH { " + "MATCH (a)-[e]->(b) " + "WHERE e.interesting = true " + "}" + ) - g_2_hops = g.gfql([n(), e_forward({"interesting": True}, hops=2) ]) - g_2_hops.plot() + # Equivalent native chain + from graphistry import n, e_forward + g2 = g.gfql([n(), e_forward({"interesting": True}, hops=2) ]) + g2.plot() **Same-Path Constraints (WHERE)**