The graph object
In order to utilize most the functionality in GDS, you must first project a graph into the GDS Graph Catalog.
When projecting a graph with the Python client, a client-side reference to the projected graph is returned.
We call these references Graph objects.
Once created, the Graph objects can be passed as arguments to other methods in the Python client, for example for running algorithms or training machine learning models.
Additionally, the Graph objects have convenience methods allowing for inspection of the projected graph represented without explicitly involving the graph catalog.
In the examples below we assume that we have an instantiated GraphDataScience object called gds.
Read more about this in Getting started.
Projecting a graph object
There are several ways of projecting a graph object. The simplest way is to do a native projection:
# We put this simple graph in our database
gds.run_cypher(
"""
CREATE
(m: City {name: "Malmö"}),
(l: City {name: "London"}),
(s: City {name: "San Mateo"}),
(m)-[:FLY_TO]->(l),
(l)-[:FLY_TO]->(m),
(l)-[:FLY_TO]->(s),
(s)-[:FLY_TO]->(l)
"""
)
# We estimate required memory of the operation
res = gds.graph.project.estimate(
["City"], # Node projection
"FLY_TO", # Relationship projection
read_concurrency=4 # Configuration parameters
)
assert res.bytes_max < 1e12
G, result = gds.graph.project.native(
"offices", # Graph name
["City"], # Node projection
"FLY_TO", # Relationship projection
read_concurrency=4 # Configuration parameters
)
assert G.node_count() == result.node_count
where G is a Graph object, and result is a GraphProjectResult object containing metadata from the underlying procedure call.
Note that all projection syntax variants are supported by way of specifying a Python dict or list for the node and relationship projection arguments.
To specify configuration parameters corresponding to the keys of the procedure’s configuration map, we give named keyword arguments, like for read_concurrency=4 above.
Read more about the syntax in the GDS manual.
Similarly to Cypher there’s also a corresponding gds.graph.project.estimate method that can be called in an analogous way.
To get a graph object that represents a graph that has already been projected into the graph catalog, one can call the client-side only get method and passing it a name:
G = gds.graph.get("offices")
For users who are GDS admins, gds.graph.get will resolve graph names into Graph objects also when the provided name refers to another user’s graph projection.
In addition to those aforementioned there are more methods that create graph objects:
-
gds.graph.filter -
gds.graph.generate -
gds.graph.sample.rwr -
gds.graph.sample.cnarw
Their Cypher signatures map to Python in much the same way as gds.graph.project above.
See Projecting a graph using Cypher Projection for projecting a graph using a Cypher query.
Projecting a graph using Cypher Projection
The method gds.graph.project.cypher allows for projecting a graph using Cypher projection.
Cypher projection is not a dedicated procedure; rather it requires writing a Cypher query that calls the gds.graph.project aggregation function.
Read more about Cypher projection in the GDS manual.
The method gds.graph.project.cypher bridges the gap between gds.run_cypher and having to follow with gds.graph.get.
Syntax
| Name | Type | Default | Description |
|---|---|---|---|
|
|
|
The Cypher query to be executed. Must end with |
|
|
|
Overrides the target database. The default uses the database from the connection. |
|
|
|
The query parameters as keyword args. |
Unlike gds.run_cypher but very much like gds.graph.project.native, it returns a tuple of a Graph object and a GraphCypherProjectResult object containing metadata from the Cypher execution.
The method does not modify the Cypher query in any way, all projection configuration must be done in the query itself.
It expects the query to end with a single RETURN gds.graph.project(…) aggregation, and raises a ValueError if the query returns anything else.
If your query needs additional aggregations or a renamed result row, use gds.run_cypher together with gds.graph.get to achieve the same result.
# We put this simple graph in our database
gds.run_cypher(
"""
CREATE
(m: City {name: "Malmö"}),
(l: City {name: "London"}),
(s: City {name: "San Mateo"}),
(m)-[:FLY_TO]->(l),
(l)-[:FLY_TO]->(m),
(l)-[:FLY_TO]->(s),
(s)-[:FLY_TO]->(l)
"""
)
G, result = gds.graph.project.cypher(
"""
MATCH (n)-->(m)
RETURN gds.graph.project($graph_name, n, m, {
sourceNodeLabels: $label,
targetNodeLabels: $label,
relationshipType: $rel_type
})
""", # Cypher query
database="neo4j", # Target database
graph_name="offices", # Query parameter
label="City", # Query parameter
rel_type="FLY_TO" # Query parameter
)
assert G.node_count() == result.node_count
Inspecting a graph object
There are convenience methods on the graph object that let us extract information about our projected graph.
| Name | Arguments | Return type | Description |
|---|---|---|---|
|
|
|
The name of the projected graph. |
|
|
|
Name of the database in which the graph has been projected. |
|
|
|
The node count of the projected graph. |
|
|
|
The relationship count of the projected graph. |
|
|
|
A list of the node labels present in the graph. |
|
|
|
A list of the relationship types present in the graph. |
|
|
|
Returns a dictionary mapping every node label to a list of the properties present on nodes with that label. |
|
|
|
Returns a dictionary mapping every relationship type to a list of the properties present on relationships with that type. |
|
|
|
The average out-degree of generated nodes. |
|
|
|
Density of the graph. |
|
|
|
Number of bytes used in the Java heap to store the graph. |
|
|
|
Human-readable description of |
|
|
|
Returns |
|
|
|
Removes the graph from the GDS Graph Catalog. |
|
|
|
The configuration used to project the graph in memory. |
|
|
|
Time when the graph was projected. |
|
|
|
Time when the graph was last modified. |
For example, to get the node count and node properties of a graph G, we would do the following:
n = G.node_count()
props = G.node_properties()["City"]
Context management
The graph object also implement the context managment protocol, i.e., is usable inside with clauses.
On exiting the with block, the graph projection will be automatically dropped on the server side.
# We use the example graph from the `Projecting a graph object` section
with gds.graph.project.native(
"tmp_offices", # Graph name
["City"], # Node projection
"FLY_TO", # Relationship projection
read_concurrency=4 # Configuration parameters
)[0] as G_tmp:
assert G_tmp.exists()
# Outside of the with block the Graph does not exist
assert not gds.graph.exists("tmp_offices")
Using a graph object
The primary use case for a graph object is to pass it to algorithms, but it’s also the input to most methods of the GDS Graph Catalog.
Input to algorithms
The Python client syntax for using a Graph as input to an algorithm follows the GDS Cypher procedure API, where the graph is the first parameter passed to the algorithm.
# optional parameters are keyword arguments
df = gds.<algorithm>.stream(G, concurrency=4)
# required arguments are positional
mutate_result = gds.<algorithm>.mutate(G, "<mutateProperty>", concurrency=4)
# `estimate` is available for most algorithms (accepts a `Graph` or a graph-dimensions dict)
memory = gds.<algorithm>.estimate(G, concurrency=4)
In this example we run the degree centrality algorithm on a graph G:
result = gds.degree_centrality.mutate(G, mutate_property="degree")
assert result.centrality_distribution is not None
The graph catalog
All procedures of the GDS Graph Catalog have corresponding Python methods in the client.
Of those catalog procedures that take a graph name string as input, their Python client equivalents instead take a Graph object, with the exception of gds.graph.exists which still takes a graph name string.
Below are some examples of how the GDS Graph Catalog can be used via the client, assuming we inspect the graph G from the example above:
# List graphs in the catalog
list_result = gds.graph.list()
# Check for existence of a graph in the catalog
assert gds.graph.exists("offices")
# Stream the node property 'degree'
result = gds.graph.node_properties.stream(G, node_properties=["degree"])
# Drop a graph; same as G.drop()
gds.graph.drop(G)
Streaming properties
The client methods
-
gds.graph.nodeProperty.stream(previouslygds.graph.streamNodeProperty) -
gds.graph.nodeProperties.stream(previouslygds.graph.streamNodeProperties) -
gds.graph.relationshipProperty.stream(previouslygds.graph.streamRelationshipProperty) -
gds.graph.relationshipProperties.stream(previouslygds.graph.streamRelationshipProperties)
are greatly sped up if Apache Arrow Flight Server of GDS is enabled.
Additionally, the client only optional keyword parameter separate_property_columns=True (it defaults to True) for gds.graph.streamNodeProperties and gds.graph.streamRelationshipProperties returns a pandas DataFrame in which each property requested has its own column.
Note that this is different from the default behavior for which there would only be one column called propertyValue that contains all properties requested interleaved for each node or relationship.
Including node properties from Neo4j
Node properties such as names and descriptions are useful to understand the output of an algorithm, even if not needed to run the algorithm itself.
To fetch additional node properties directly from the Neo4j database, you can use the db_node_properties client-only parameter of the gds.graph.nodeProperty.stream and gds.graph.nodeProperties.stream methods.
In the following example, the City nodes have both a numeric and a String property.
The stream method retrieves the values of the database-only name property alongside the values of the projected population property.
gds.run_cypher(
"""
CREATE
(m: City {name: "Malmö", population: 360000}),
(l: City {name: "London", population: 8800000}),
(s: City {name: "San Mateo", population: 105000}),
(m)-[:FLY_TO]->(l),
(l)-[:FLY_TO]->(m),
(l)-[:FLY_TO]->(s),
(s)-[:FLY_TO]->(l)
"""
)
G, result = gds.graph.project.native(
"offices",
{
"City": {
"properties": ["population"]
}
},
"FLY_TO"
)
gds.graph.node_properties.stream(G, node_properties=["population"], db_node_properties=["name"])
Streaming topology by relationship type
The type returned from the Python client method corresponding to gds.graph.relationships.stream is called RelationshipsDataFrame and inherits from the standard pandas DataFrame.
TopologyDataFrame comes with an additional convenience method named by_rel_type which takes no arguments, and returns a dictionary of the form Dict[str, List[List[int]]].
This dictionary maps relationship types as strings to 2 x m matrices where m here represents the number of relationhips of the given type.
The first row of each such matrix are the source node ids of the relationships, and the second row are the corresponding target node ids.
We can illustrate this transformation with an example graph that is a simple four-node cycle:
gds.run_cypher(
"""
CREATE
(a:N)-[:REL]->(b:N),
(b)-[:REL]->(c:N),
(c)-[:REL]->(d:N),
(d)-[:REL]->(a)
"""
)
G, _ = gds.graph.project.native("cycle", "N", "REL")
topology_by_rel_type = gds.graph.relationships.stream(G).by_rel_type()
assert list(topology_by_rel_type.keys()) == ["REL"]
sources, targets = topology_by_rel_type["REL"]
# In a cycle every node appears once as a source and once as a target
assert len(sources) == 4
assert set(sources) == set(targets)
Like the Streaming properties methods, the gds.graph.relationships.stream is also accelerated if the GDS Apache Arrow Flight Server is enabled.