14

Class point is defined as (there are also some methods, atributes, and stuff in it, but this is minimal part):

class point():
    def ___init___(self, x, y):
        self.x = x
        self.y = y

So, I saw this question, but when I tried applying it, it returns an error:

G = nx.Graph()
p = point(0,0)
G.add_node(0, p)

NetworkXError: The attr_dict argument must be a dictionary.

If i use

G = nx.Graph()
p = point(0,0)
G.add_node(0, data = p)

I don't get an error, but when i try to access the x-coordinate, it turns out it didn't save it as a point.

G[0].x

returns: AttributeError: 'dict' object has no attribute 'x'

doing

G = nx.Graph()
G.add_node(0, data = point(0,0))
G[0]

returns: {}

which means it still saves it as a dictionary.

I saw I can make my points hashable, and use these objects as nodes, so i added atribute id, since points are going to move. I added this to the class, and __repr__ for nice drawing of the graphs:

def __hash__(self):
    return self.id_n
def __cmp__(self, p):
    if self.id_n < p.id_n: return -1
    elif self.id_n == p.id_n: return 0
    else: return 1
def __eq__(self, p):
    if p.id_n == self.id_n: return True
    else: return False
def __repr__(self):
    return str(self.id_n) 

but that is a bit wierd, since I don't understand how to select a node then, by

G[<what should i put here?>]

So, question is, what is a proper way to do this?

I hoped to be able to use something like

G[node_id].some_method(some_args)
Luka Petrović
  • 329
  • 3
  • 9

3 Answers3

11

edit - in the below, replace G.node[] with G.nodes[] - in version 2.4 G.node was deprecated.

You're looking at G[0]. But that's not what you want. G[0] contains the information about neighbors of node 0 and the attributes of the edges, but nothing about the attributes of node 0.

class point():
    def __init__(self, x, y):
        self.x = x
        self.y = y

import networkx as nx
G = nx.Graph()
p0 = point(0,0)
p1 = point(1,1)

G.add_node(0, data=p0)
G.add_node(1, data=p1)
G.add_edge(0,1, weight=4)
G[0]
> AtlasView({1: {'weight': 4}})  #in networkx 1.x this is actually a dict. In 2.x it is an "AtlasView"

For networkx there is an expectation that a node may have a lot of data associated with it. In your case, you only have a single piece of data, namely the point. But you could have also assigned a color, a weight, a time, an age, etc. So networkx is going to store all the attributes in another dictionary, but that dictionary is accessed through G.node[0] rather than G[0].

G.node[0]
> {'data': <__main__.point at 0x11056da20>}
G.node[0]['data'].x
> 0

Notice that data in your input becomes the string 'data'.

It might be better to enter the nodes like G.add_node(0, x=0, y=0) and then you can access the entries as G.node[0]['x'].

Joel
  • 22,598
  • 6
  • 69
  • 93
0

You have added a node and as such, you can examine the nodes which is a set-like view. Quoting from the docs:

These are set-like views of the nodes, edges, neighbors (adjacencies), and degrees of nodes in a graph. They offer a continually updated read-only view into the graph structure.

Do for example:

mynodes = list(G.nodes())
print(mynodes)

You should be now able to also do:

mynode = mynodes[0]  # because we have converted the set-like view to a list

See the tutorial: https://networkx.github.io/documentation/stable/tutorial.html

mementum
  • 3,153
  • 13
  • 20
  • Q: Does changing mynode change the G[0]? E: also, it feels a bit dirty. – Luka Petrović Jan 31 '18 at 14:10
  • `G[0]` as indicated above, doesn't give you access to the node. In any case `mynodes` is now a list which is not bound to your graph, so changing `mynodes[0]` won't change anything in the graph. It has been pulled from a `read-only` view. – mementum Jan 31 '18 at 14:48
  • So this is basically the same as having an index-to-object dictionary on the side. Do you think this is the best way to have objects as nodes? – Luka Petrović Jan 31 '18 at 16:24
0

I think I understand your problem.

You want to use instances of a class you have defined as nodes, and be able to easily access instance attributes or methods.

When you create a graph in networkx with something like:

G = nx.DiGraph() # create graph
G.add_nodes_from([node0, node1, node2]) # add 3 nodes
G.nodes[node0]["node_attribute0"] = "value0"
G.add_edges_from([(node0, node1), (node0, node2)]) # add 2 edges
G[node0][node1]["edge_attribute0"] = "value1" # add one attribute to edge
G[node0][node1]["edge_attribute1"] = 10 # add another attribute to same edge

networkx creates nested python dictionaries, in the case of a DiGraph for example, 3 nested dictionaries like this:

'''
   G = {
        node0 : {
                 node1 : {
                          "edge_attribute0" : "value1",
                          "edge_attribute1" : 10
                         },
                 node2 : {}
                 },
        node1 : {},
        node2 : {}
        }
'''

That is why you access each with successive [key]

G[node0]                    # = output dict of dict, with neighbours on top layer
G[node0][node1]             # = output dict with edge attributes
G[node0][node1][attribute0] # = output value

Now, node attributes are not stored anywhere on those nested dicts. They are instead on a separate nested dict which you access via G.nodes:

'''
    G.nodes = {
               node0 : {
                        "node_attribute0" : "value0"
                       }
               }
'''

You can access this other dictionary of dictionaries by using G.nodes like this:

G.nodes[node0]["node_attribure0"]  # = value assigned to key

As you can see, there is no way en either of the 2 nests of dictionaries to access the nodes as objects themselves, since in all cases they are just dictionary "keys".

The workaround, is to store the node object itself as a node attribute, for which you could use "self" as convenient key:

for node in G: # iterate over all nodes in G
    G.nodes[node]["self"] = node # create new node attribute = node

Now, if node0, node1, node2 where all instances of a class you defined before, with, lets say a variable called "variable" and a method called "method", you can now access those by using:

G.nodes[node0]["self"].variable
G.nodes[node0]["self"].method()
Dharman
  • 30,962
  • 25
  • 85
  • 135
Random
  • 1