import time
import logging
from graphit import Graph
logging.basicConfig(level=logging.WARN)
# Build tutorial graph
g = Graph()
g.add_nodes('GRAPH', data='word')
g.add_edges([(1, 2), (2, 3), (2, 4), (4, 5)], label='link')
g.auto_nid = False
g.add_node('final', label='yellow')
g.add_edge(4, 'final')
print('Tutorial example graph:', g)
Tutorial 2: Working with nodes and edges¶
After the first tutorial you should be familiar with building graphs
graphit. In this tutorial you will learn about Graph
class
methods that allow you to retrieve specific nodes and edges from a graph
based on their ID or query for them based on the attributes they
contain. In addition this tutorial discussed how to best work with the
data that is stored as node and edge attributes.
Retrieving or query for specific nodes and edges always returns a new
Graph
object with the nodes and edges represented as a subgraph. The
next tutorial (3) will explain subgraphs and their properties in more
detail.
2.1 Getting nodes and edges from a graph using their ID¶
The primary method for retrieving nodes and edges based on their ID
(nid, eid) is getnodes
and getedges
respectivly. Nearly all
other class methods including magic methods that query for nodes and
edges based on ID use getnodes
and getedges
internally.
# Get single or multiple nodes
print(g.getnodes(2))
print(g.getnodes((2, 4, 5)))
# Equivalent for edges
print(g.getedges((2, 3)))
print(g.getedges([(2, 3), (2, 4)]))
# `getnodes` and `getedges` are also used in Graph magic methods such as dictionary like 'key' access
# using the __getitem__ magic method.
print(g[2])
print(g['final'])
print(g[(2, 3)])
# __getitem__ also accepts a 'slice' object for integer based node IDs
print(g[1:3])
Iterating over nodes and edges
Iterate over nodes and edges using the iternodes
and iteredges
methods respectivly. iternodes
is also used in the default Graph
class iterator (*__iter__* class method).
Both methods sort the node and edge ID’s before iteration using Pythons
build in sorted
function. Sorting behaviour can be modified using
the reverse
and sort_key
arguments to respectivly reverse the
sorting order or use a different key-based sorting function. More
information about this functionality is available as part of the
sorted
documentation.
for node in g.iternodes():
print(node)
for node in g:
print(node)
for edge in g.iteredges():
print(edge)
Query a graph for nodes and edges
Retrieving specific nodes and edges based on their attributes is
possible using the query_nodes
and query_edges
methods.
print(g.query_nodes(key='P'))
print(g.query_nodes(attr='word'))
print(g.query_edges(label='link'))
2.2 Working with node and edge data stores¶
Node and edge data (attributes) are stored in a dictionary-like fasion
as explained in tutorial 1. The DictStorage
class handling storage
has an API mimicking that of the familiar Python dict
class. The
node or edge ID is the primary key and the attributes as value stored as
additional dictionary as explained by technical node 1 in the first
tutorial.
Attributes can thus be accessed and updated using a familiar dict-like
API as demonstrated for nodes below. However, there is good reason to
use the dedicated API methods for that are a part of the Graph
class. More on that later on in this tutorial.
Working with node and edge IDs
Pythons dictionary
methods
supported by DictStorage
provide a fast and easy means of inspecting
the nodes and edges in graph or subgraph.
node = g['final']
print('current node ID:', node.nid)
print(node.nodes())
print(node.nodes.keys())
print(node.nodes.values())
print(node.nodes.items())
print(len(node.nodes))
In additon, the DictStorage
class provides set-like
comparison
methods to directly compare nodes or edges from two subgraphs in a
number of ways.
sel1 = g.getnodes([1, 2, 3])
sel2 = g.getnodes([3, 4, 5])
print('difference: {0}'.format(sel1.nodes.difference(sel2.nodes)))
print('intersection: {0}'.format(sel1.nodes.intersection(sel2.nodes)))
print('union: {0}'.format(sel1.nodes.union(sel2.nodes)))
print('symmetric_difference: {0}'.format(sel1.nodes.symmetric_difference(sel2.nodes)))
print('\nissubset: {0}'.format(sel1.nodes.issubset(g.nodes)))
print('issuperset: {0}'.format(sel1.nodes.issuperset(g.nodes)))
print('isdisjoint: {0}'.format(sel1.nodes.isdisjoint(sel2.nodes)))
Important note 2: Be carefull with editing on ``DictStorage`` objects directly
With the familiarity of the dict-like API it may be tempting to edit
node and edge ID’s (primary keys) directly using the respective node and
edge DictStorage
objects, one could even add or remove nodes and
edges this way. It is however strongly advised NOT to do this as it can
leave the graph in a funny state. Use the dedicated method to add or
remove nodes and edges instead.
Working with node and edge attributes
Node and edge attribute stores are also dict-like objects and thus share the same API. Use the node or edge ID to access the attribute store followed by the familiar dict-like API to edit the data.
# Add new data attribute and set its value using __setitem__ method
node.nodes[node.nid]['extra'] = 4.33
# Retrieve attributes using __getitem__ or get methods
print(node.nodes[node.nid]['extra'])
print(node.nodes[node.nid].get('extra', None))
# Update attributes from other dictionary
node.nodes[node.nid].update({'update': True, 'extra': 3.44})
print(node.nodes[node.nid])
Using the dedicated node and edge data API
Although the node and edge data can be directly and conveniently modified as shown above, there is a dedicated API available for this. Every single node or edge Graph has additional method added to it (using the ORM further explained in the moderate1_graph_orm.ipynb tutorial) for convenient attribute access.
The benefit of this API is that custom Graph
classes (using the ORM)
can overload the methods changing the behaviour of attribute access. An
example application would be the pre- or post-processing of the data.
node = g['final']
# Dictionary 'get' and 'set' are the core methods
print(node.get('label', default=None))
node.set('set', [1,2,3])
# Direct attribute or item based access
print(node.label)
print(node['label'])
# Including attribute or item setters
node.label = 'green'
node['extra'] = 'red'
# Magic methods and other methods
node.update({'first': 1, 'second': 2})
print('extra' in node, 'void' in node)
print(node.nodes())