Running algorithms

In the examples below we assume that we have an instantiated GraphDataScience object called gds. Read more about this in Getting started.

1. Introduction

Running most algorithms with the Python client is structurally similar to using the Cypher API:

Syntax composition:
result = gds[.<tier>].<algorithm>.<execution-mode>[.<estimate>](
  G: Graph,
  **configuration: dict[str, any]
)

Here we can note a few key differences:

  • Instead of a graph name string as first argument, we have a graph object as first positional argument.

  • Instead of a configuration map, we have named keyword arguments.

The result of running a procedure is returned as either a pandas DataFrame or a pandas Series depending on the execution mode.

To exemplify this, we introduce a small road-network graph:

gds.run_cypher(
  """
  CREATE
    (a:City {name: "New York City", settled: 1624}),
    (b:City {name: "Philadelphia", settled: 1682}),
    (c:City:Capital {name: "Washington D.C.", settled: 1790}),
    (d:City {name: "Baltimore"}),
    (e:City {name: "Atlantic City"}),
    (f:City {name: "Boston"}),

    (a)-[:ROAD {cost: 50}]->(b),
    (a)-[:ROAD {cost: 50}]->(c),
    (a)-[:ROAD {cost: 100}]->(d),
    (b)-[:ROAD {cost: 40}]->(d),
    (c)-[:ROAD {cost: 40}]->(d),
    (c)-[:ROAD {cost: 80}]->(e),
    (d)-[:ROAD {cost: 30}]->(e),
    (d)-[:ROAD {cost: 80}]->(f),
    (e)-[:ROAD {cost: 40}]->(f);
  """
)
G, project_result = gds.graph.project("road_graph", "City", {"ROAD": {"properties": ["cost"]}})

assert G.relationship_count() == 9

Now we are ready to run algorithms on our graph G.

louvain_result = gds.louvain.mutate(
    G,                            #  Graph object
    maxIterations=5,              #  Configuration parameters
    mutateProperty="community"
)
assert louvain_result["communityCount"] > 0

fastrp_result = gds.fastRP.write(
    G,                                #  Graph object
    featureProperties=["community"],  #  Configuration parameters
    embeddingDimension=256,
    propertyRatio=0.3,
    relationshipWeightProperty="cost",
    writeProperty="embedding"
)
assert fastrp_result["nodePropertiesWritten"] == G.node_count()

Some algorithms deviate from the standard syntactic structure. We describe how to use them in the Python client in the sections below.

2. Execution modes

Algorithms return results in a format that is controlled by its execution mode. These modes are explained in some detail in Running algorithms. In the Python client, the stats, mutate and write modes return a pandas Series containing the summary result of running the algorithm. The same applies to estimate procedures.

2.1. Stream

The stream mode is a bit different as this mode does not retain the result in any form on the server side. Instead, the result is streamed back to the Python client, as a pandas DataFrame. The result is materialized on the client side immediately once the computation is finished. Streaming results back in this way can be resource-intensive, as the result can be large. Typically, the result size will be in the same order of magnitude as the graph. Some algorithms produce particularly sizeable results, for example node embeddings.

2.2. Train

The train mode is used for algorithms that produce a machine learning model into the GDS Model Catalog. The Python client has special support for working with such models, which we describe in The model object.

3. Algorithms that require node matching

Some algorithms take (database) node ids as inputs. These node ids must be matched directly from the Neo4j database. This is straight-forward when working in Cypher. In the Python client there is a convenience method gds.find_node_id to retrieve a node id based on node labels and property key-value pairs.

For example, to find a source and target node of a graph G with cities to run Dijkstra Source-Target Shortest Path on, we could do the following:

source_id = gds.find_node_id(["City"], {"name": "New York City"})
target_id = gds.find_node_id(["City"], {"name": "Boston"})

result = gds.shortestPath.dijkstra.stream(
    G,
    sourceNode=source_id,
    targetNode=target_id,
    relationshipWeightProperty="cost"
)
assert result["totalCost"][0] == 160

gds.find_node_id takes a list of node labels and a dictionary of node property key-value pairs. The nodes found are those that have all labels specified and fully match all property key-value pairs given. Note that exactly one node per method call must be matched, otherwise an error will be raised.

3.1. Cypher mapping

The Python call:

gds.find_node_id(["City", "Capital"], {"settled": 1790, "name": "Washington D.C."})

is exactly equivalent to the Cypher statement:

MATCH (n:City:Capital {settled: 1790, name: 'Washington D.C.'})
RETURN id(n) AS id

To do more advanced matching beyond the capabilities of find_node_id() we recommend using Cypher’s MATCH via gds.run_cypher.

The methods for doing Topological link prediction are a bit different. Just like in the GDS procedure API they do not take a graph as an argument, but rather two node references as positional arguments. And they simply return the similarity score of the prediction just made as a float - not any kind of pandas data structure.

For example, to run the Adamic Adar algorithm, we can use the following:

node1 = gds.find_node_id(["City"], {"name": "Boston"})
node2 = gds.find_node_id(["City"], {"name": "Atlantic City"})

score = gds.alpha.linkprediction.adamicAdar(node1, node2)
assert round(score, 2) == 0.62