Search code examples
pythonnetworkxedgesgraphml

Networkx edge not adding attributes correctly


I've pulled up some code I used to play with in 1.6.1 of networkx. On 1.8.1 it doesn't work when writing to gml or graphml.

The problem boils down to being unable to write edge attributes inside the data dict like so:

BasicGraph = nx.read_graphml("KeggCompleteEng.graphml")

for e,v in BasicGraph.edges_iter():
    BasicGraph[e][v]['test'] = 'test'

nx.write_graphml(BasicGraph, "edgeTester.graphml")

Causes the error:

AttributeError: 'str' object has no attribute 'items'

When I use: for e,v,data in BasicGraph.edges_iter(data=True): the data prints out like so:

{'root_index': -3233, 'label': u'unspecified'}
test

AKA the new attribute is outside the dictionary.

The documentation says I should be able to do it like above. However, I imagine I've made a silly mistake and would appreciate being put back on the right path!

EDIT:

So I ran the program with a graph generated inside the program: BasicGraph = nx.complete_graph(100) and it ran fine.

I then ran it with an example graphml file from the primer: BasicGraph = nx.read_graphml("graphmltest.graphml") and that too worked. (I even imported into and out of Cytoscape to check that wasn't the issue)

So obviously it's the file I'm using. Here's a link to it, can anyone see what's wrong with it?


Solution

  • The issue is that your graph is has parallel edges so NetworkX is loading it as a MultiGraph object:

    In [1]: import networkx as nx
    
    In [2]: G = nx.read_graphml('KeggCompleteEng.graphml')
    
    In [3]: type(G)
    Out[3]: networkx.classes.multigraph.MultiGraph
    
    In [4]: G.number_of_edges()
    Out[4]: 7123
    
    In [5]: H = nx.Graph(G) # convert to graph, remove parallel edges
    
    In [6]: H.number_of_edges()
    Out[6]: 6160
    

    Because of that the internal structure of the graph object storage for an edge is G[node][node][key][attribute]=value (note the extra key dictionary level for multigraphs).

    You are explicitly modifying the structure by

    for e,v in BasicGraph.edges_iter():
        BasicGraph[e][v]['test'] = 'test'
    

    which breaks it.

    It is allowed to modify the data structure that way but it is safer to use the NetworkX API

    In [7]: G = nx.MultiGraph()
    
    In [8]: G.add_edge(1,2,key='one')
    
    In [9]: G.add_edge(1,2,key='two')
    
    In [10]: G.edges(keys=True)
    Out[10]: [(1, 2, 'two'), (1, 2, 'one')]
    
    In [11]: G.add_edge(1,2,key='one',color='red')
    
    In [12]: G.add_edge(1,2,key='two',color='blue')
    
    In [13]: G.edges(keys=True,data=True)
    Out[13]: [(1, 2, 'two', {'color': 'blue'}), (1, 2, 'one', {'color': 'red'})]