I am working with networkx and cant find a list of available attributes for edges or nodes anywhere. I am not interested in what attributes are assigned already, but what I can set/change when i create or edit the node or edge.
Can someone point me to where this is documented?
Thanks!
If you want to query a graph for all the possible attributes that might have been applied across the various nodes (and this, for communally created graphs, or ones that have been edited over time, is more common than you might imagine), then the following does the trick for me:
set(np.array([list(self.graph.node[n].keys()) for n in self.graph.nodes()]).flatten())
This returns all possible attribute names for which there are values attributed to graph-nodes. I've imported numpy as np here in order to use np.flatten for (relative) performance, but I'm sure there are various vanilla python alternatives (e.g. try the following itertools.chain method, if you need to avoid numpy )
from itertools import chain
set(chain(*[(ubrg.graph.node[n].keys()) for n in ubrg.graph.nodes()]))
You can assign a lot of edge or node attributes when you create them. It's up to you to decide what their names will be.
import networkx as nx
G=nx.Graph()
G.add_edge(1,2,weight=5) #G now has nodes 1 and 2 with an edge
G.edges()
#[(1, 2)]
G.get_edge_data(2,1) #note standard graphs don't care about order
#{'weight': 5}
G.get_edge_data(2,1)['weight']
#5
G.add_node('extranode',color='yellow', age = 17, qwerty='dvorak', asdfasdf='lkjhlkjh') #nodes are now 1, 2, and 'extranode'
G.node['extranode']
{'age': 17, 'color': 'yellow', 'qwerty': 'dvorak', 'asdfasdf': 'lkjhlkjh'}
G.node['extranode']['qwerty']
#'dvorak'
Or you can use a dict to define some of the attributes with nx.set_node_attributes and create a dict for all nodes for which a particular attribute is defined with nx.get_node_attributes
tmpdict = {1:'green', 2:'blue'}
nx.set_node_attributes(G,'color', tmpdict)
colorDict = nx.get_node_attributes(G,'color')
colorDict
#{1: 'green', 2: 'blue', 'extranode': 'yellow'}
colorDict[2]
#'blue'
Similarly there is a nx.get_edge_attributes and nx.set_edge_attributes.
More information is here in the networkx tutorial. About halfway down this page under the headings "Node Attributes" and "Edge Attributes". Specific documentation for the set...attributes and get...attributes can be found here under "Attributes".
Related
I have a MultiDiGraph with all my data in it, now I want to do some math on a filtered view of it that has only single directed edges between nodes.
>>> filtered_view[0][1]
Out[23]: AtlasView(FilterAtlas({0: {'d': 0.038, 'l': 2, 'showfl': True, 'type': 'pipe', 'q': 0.0001}}, <function FilterMultiInner.__getitem__.<locals>.new_node_ok at 0x7fa0987b55a0>))
I already have a lot of code that was working on a DiGraph, so a lot of it would not work anymore because of the differences in accessing and storing information. So thus my question:
Is there a way to have the view behave like a DiGraph?
Alternatively, I can do: ndg = nx.DiGraph(filtered_view)to get a DiGraph, but is there a smart (simple, clear, error free) way of merging it back into the main graph?
This is the implementation I came up with, it allows to either merge only data on existing nodes and edges (allnodes=False) or to merge the entire results_graph which is a DiGraph (allnodes=True). Condition is that the MultiDiGraph has not changed since the filtered view was created.
def merge_results_back(results_graph, multidigraph, allnodes=False):
for n in results_graph.nodes:
if n not in multidigraph.nodes and allnodes:
multidigraph.add_node(n)
if n in multidigraph.nodes:
nx.set_node_attributes(multidigraph, {n : results_graph.nodes[n]})
for e in results_graph.edges:
if e in multidigraph.edges:
for ed1, ed2, key, data in multidigraph.edges(e[0], keys=True, data=True):
if data['type'] == results_graph.edges[e]['type']:
nx.set_edge_attributes(multidigraph, {(e[0], e[1], key) : results_graph.edges[e]})
else:
nx.set_edge_attributes(multidigraph, {(e[0], e[1], 0): results_graph.edges[e]})
Offering a couple of suggestions for improvement here based on the code that you posted. It's unclear under what circumstances a node would be added (if the DiGraph is based on the MultiDiGraph, how is a new node possible?), so I'll leave that part alone.
In the loop for modifying edges, you end up looping through multidigraph every time a common edge is found. As an improvement, I'd suggest the following (assuming the type attribute differs based on the edge index, which wasn't clear in your question):
for u, v, data in results_graph.edges(data = True):
#only loops through each edge in the multidigraph one time
for i in range(multidigraph.number_of_edges(u, v)):
if multidigraph.edges[u, v, i]['type'] == data['type']:
multidigraph.edges[u, v, i].update(data)
If the type doesn't change based on the index, just eliminate that if statement line.
I think you can also get rid of the else block:
else:
nx.set_edge_attributes(multidigraph, {(e[0], e[1], 0): results_graph.edges[e]})
If edge e from results_graph isn't in multidigraph, then setting the edge attributes won't create edge e and it will be silently ignored. If you have a new edge and attributes though (again, unclear how this is possible if results_graph was created from multidigraph), you can add the following directly under the for u, v, data... line:
if (u, v) not in multidigraph.edges:
multidigraph.add_edge(u, v, **data)
Here is a MWE to create the "issue"
>>> A = nx.complete_bipartite_graph(2, 3)
>>> same_neighbors = lambda u, v: (u not in A[v] and v not in A[u] and A[u] == A[v])
>>> B = nx.quotient_graph(A, same_neighbors)
>>> B.nodes()
NodeView((frozenset({0, 1}), frozenset({2, 3, 4})))
>>> B[frozenset({0, 1})]
AtlasView({frozenset({2, 3, 4}): {'weight': 6}})
>>> B.nodes[frozenset({0, 1})]
{'graph': <networkx.classes.graph.Graph object at 0x12b066e80>, 'nnodes': 2, 'nedges': 0, 'density': 0}
I believe this graph attribute on the node is specifying the subgraph which this node comes from in the original graph, but I'm not sure. If someone could verify that would be nice.
Either way though, this graph attribute prevents me from using the nx.write_graphml function because subgraphs cannot be used as data formats. In particular it raises
networkx.exception.NetworkXError: GraphML writer does not support <class 'networkx.classes.graph.Graph'> as data values.
Now I don't actually need the subgraph in the graphml file, so just dropping that data is probably the best way for me to get the graph written to a file. What is the best way to do this?
I believe this graph attribute on the node is specifying the subgraph which this node comes from in the original graph, but I'm not sure. If someone could verify that would be nice.
Yes, you are right in assuming that the graph attribute is actually specifying the subgraph based on the original graph. It is mentioned in the documentation for quotient graph . Check out the description of node_data, you will find that:
node_data (function) – This function takes one argument, B, a set of
nodes in G, and must return a dictionary representing the node data
attributes to set on the node representing B in the quotient graph. If
None, the following node attributes will be set:
- graph, the subgraph of the graph G that this block represents,
- nnodes, the number of nodes in this block,
- nedges, the number of edges within this block,
- density, the density of the subgraph of G that this block represents.
You can also check out the subgraph yourself, by typing
for node in B.nodes():
print("Information for subgraph ", node)
print(B.nodes[node]['graph'].nodes())
# Output:
# Information for subgraph frozenset({0, 1})
# Nodes are [0, 1]
# Information for subgraph frozenset({2, 3, 4})
# Nodes are [2, 3, 4]
Now I don't actually need the subgraph in the graphml file, so just
dropping that data is probably the best way for me to get the graph
written to a file. What is the best way to do this?
You can simply create a new graph and add only the nodes and edges, and discard any other data. (Again, you are right in assuming that this information is irrelevant for writing data in GraphML format as you only need the nodes and edges).
Let's check the EdgeView of the Quotient graph, before going further:
B.edges(data=True)
# EdgeDataView([(frozenset({0, 1}), frozenset({2, 3, 4}), {'weight': 6})])
Notice, how the data for the edge is dictionary with weight as key. This info. will be useful in the subsequent code.
Now, create a new graph, and add the edges directly from the quotient graph.
H = nx.Graph()
# Here B is the quotient graph
for u,v,d in B.edges(data=True):
# Notice how the weight is assigned to the new graph
H.add_edge(u, v, weight=d['weight'])
If you want to verify that the new graph has the same structure, you can check it out using nx.is_isomorphic
nx.is_isomorphic(H, B)
# True
Now you can simply write your graph into GraphML format
nx.readwrite.graphml.write_graphml(H, "my_graph.graphml")
For more information, you can check out this Google Colab Notebook, with the above working code.
References:
NetworkX - Quotient Graph
write_graphml
NetworkX - is_isomorphic
I want to individually update nodes for a certain new custom attribute called 'journeys', but am having severe difficulties with it.
Say I have some graph:
import pandas as pd
import networkx as nx
cols = ['node_a','node_b','travel_time','attribute']
data = [['A','B',3,'attribute1'],
['B','C',1,'attribute1'],
[ 'C','D',7,'attribute1'],
['D','E',3,'attribute1'],
['E','F',2,'attribute1'],
['F','G',4,'attribute1'],
['A','L',4,'attribute2'],
['L','D',3,'attribute2']
]
edges = pd.DataFrame(data)
edges.columns = cols
G=nx.convert_matrix.from_pandas_edgelist(edges,'node_a','node_b', ['travel_time','attribute'])
For each node, I want to add an attribute in the form of {direction: [[id,timestamp, set_of_carrying_items]]} where the inner one is a list of lists as i want to add more lists of the form [id,timestamp, carrying_items] to it.
Example: Update a particular node A with
new_attribute = {'A':{'up': [[0, Timestamp('1900-01-01 05:31:00'), set()]]}}}
However, no matter what I try, the node doesnt get updated correctly. nx.get_node_attributes(G, 'A') returns an empty dictionary. But nx.get_node_attributes(G,'up') returns the attribute!!
It seems i'm setting it wrongly but I cant figure out how. Anyone know the proper way?
Using networkx 2.4
Solved it.
nx.get_node_attributes(G,X) returns attributes called X for all nodes. You're looking for G.node['nodeA']
if your attribute is of the form {'nodeA':{'attribute_name': value}}, you'll set value to attribute_name for node nodeA.
For example, in my case:
att = {'nodeA':{'up': [[1, Timestamp('1900-01-01 22:31:00'), set()]]}}}
nx.set_node_attributes(G,att)
works
I want every node to have a ipaddress string, latitude and longitude values. Also how do I get a pointer to such object on looking up the graph created by networkx?
NetworkX is based on the idea that graphs can act like dictionaries. You don't need a custom object to act as nodes, as the nodes can have arbitrary properties added to their "dictionaries".
Consider the following interactive session:
>>> import networkx as nx
>>> G = nx.Graph()
>>> G.add_node(1)
>>> G.node[1]['ipaddress'] = '8.8.8.8'
>>> G.node[1]['longitude'] = 37
>>> G.node[1]['latitude'] = 50
>>> G.node[1]
{'latitude': 50, 'ipaddress': '8.8.8.8', 'longitude': 37}
>>> G.node[1]['ipaddress']
'8.8.8.8'
Here, a graph with a single node 1 is created, to which ipaddress, longitude, and latitude are associated. You access that node directly in constant time by asking the graph for the node, and get its properties in much the same way.
To refer to specific nodes, you have a couple of possibilities. You could, e.g., use a dictionary to store a list or set of nodes for any desired properties. The second possibility - which is only useful for a single property that is unique to each node - is to use the property as the node directly. E.g., for IP addresses:
>>> H = nx.Graph()
>>> H.add_node('8.8.8.8', longitude=37, latitude=50)
>>> H.node['8.8.8.8']
{'latitude': 50, 'longitude': 37}
Here, I've also taken advantage of a convenience provided by NetworkX to specify the properties as the node is created.
pydot has a huge number of bound methods for getting and setting every little thing in a dot graph, reading and writing, you-name-it, but I can't seem to find a simple membership test.
>>> d = pydot.Dot()
>>> n = pydot.Node('foobar')
>>> d.add_node(n)
>>> n in d.get_nodes()
False
is just one of many things that didn't work. It appears that nodes, once added to a graph, acquire a new identity
>>> d.get_nodes()[0]
<pydot.Node object at 0x171d6b0>
>>> n
<pydot.Node object at 0x1534650>
Can anyone suggest a way to create a node and test to see if it's in a graph before adding it so you could do something like this:
d = pydot.Dot()
n = pydot.Node('foobar')
if n not in d:
d.add_node(n)
Looking through the source code, http://code.google.com/p/pydot/source/browse/trunk/pydot.py, it seems that node names are unique values, used as the keys to locate the nodes within a graph's node dictionary (though, interestingly, rather than return an error for an existing node, it simply adds the attributes of the new node to those of the existing one).
So unless you want to add an implementation of __contains__() to one of the classes in the pydot.py file that does the following, you can just do the following in your code:
if n.get_name() not in d.obj_dict['nodes'].keys():
d.add_node(n)