Search code examples
pythonnodesnetworkx

How to set node names in networkxgraph from 0 to len(G.nodes)


Hope you can help me with the following, proably very simple but I just can't seem to get it.

I want to set nodesnames to my nodes in my graph, so I can use them later for dijkstra's algorithm from the networkx package. The shapefile has nodes based on long lat, as shown below:

crossings = list(G.nodes(data = True))
print(crossings)

Gives:

[((4.8703865, 52.364651), {}), ((4.8719547, 52.3651758), {}), ((4.9264105, 52.3695602), {}), ((4.9289823, 52.3711744), {}), ((4.9259642, 52.3692824), {}), ((4.877608, 52.3752153), {}), ((4.8847629, 52.3765112), {}), ((4.8701251, 52.3757447), {}), ((4.8738594, 52.3804434), {}), ((4.8866343, 52.3574955), {}), ((4.8873865, 52.3602753), {}), ((4.8792688, 52.3649914), {}), ((4.8775365, 52.366768), {}), ((4.879805, 52.3667268), {}), ((4.8824711, 52.3674209), {}), ((4.8790738, 52.3677393), {}), ((4.8771909, 52.3704447), {}) .....

Now I want to set nodenames, as input for the following code:

print(nx.dijkstra_path(weighted_G, "1" , "20", weight='cost'))

But I can't seem to figure out how to set a name to each node as a number, so node 1 gets name "1", node "2" gets name "2", till node n gets "n".

 G=nx.read_shp('shapefile.shp', simplify=True) # shapefile from OSM
 crossings = list(G.nodes(data = True))
 weighted_G = nx.Graph()
 j = 0
 for i in crossings:
   nx.set_node_attributes(weighted_G, values = "none" ,name = j)
   j = j + 1
 print(crossings)

But this output gives:

[((4.8703865, 52.364651), {}), ((4.8719547, 52.3651758), {}), ((4.9264105, 52.3695602), {}), ((4.9289823, 52.3711744), {}), ((4.9259642, 52.3692824), {}), ((4.877608, 52.3752153),.......

As you can see, no number has been added as nodename, does anybody know how to fix this?

Thanks in advance

EDIT and update--------------------------------------------------------------

So I changed my code last night into:

weighted_G=nx.read_shp('shapefile.shp', simplify=True) # shapefile from OSM
     c = list(weighted_G.nodes(data=True))
    j = 0 
    for i in weighted_G.nodes:
        weighted_G.node[i]['name']= j
        j = j + 1 
    print (c[0])

Which got the same output as the code @zohar.kom provided. Each node now contains the following:

((4.8703865, 52.364651), {'name': 0})

But Dijkstra can't read this input if I say:

print(nx.dijkstra_path(weighted_G, {'name': 1} , {'name': 20}, weight='cost'))

But as @zohar.kom said, which gave me more insight, it changes the attribute, and not the name of the node itself; which is needed for the dijkstra algorithm in networkx.

Any thoughts on how I can change/add a name or a label to each node? So that my input for Dijkstra is

print(nx.dijkstra_path(weighted_G, 'node 1' , 'node 20', weight='cost'))

and each node will look something like this:

(node 1, (4.8719547, 52.3651758))

instead of:

((4.8703865, 52.364651), {'name': 0})

Hope you can help me with this!

UPDATE 2------------------------------------------------------------------

With the help of @zohar.kom I managed to finally solve it. When I directly used the code from @zohar.kom, I still got an error in

for edge in G.edges():
    w_G.add_edge(lat_lon_to_index[edge[0]], lat_lon_to_index[edge[1]], cost=edge[2]['cost_property_name'])

saying:

IndexError: tuple index out of range

But I solved that last part with the following by adding data = True:

for edge in G.edges(data=True):
        w_G.add_edge(lat_lon_to_index[edge[0]], lat_lon_to_index[edge[1]], cost=edge[2]['cost_property_name'])

Which solved it completely!! So here is the final code for anyone!

    G=nx.read_shp('path/shapefile.shp', simplify=False) # use simplify is false otherwise chart get shifted
w_G = nx.DiGraph()
lat_lon_to_index = {}
for i, node in enumerate(G.nodes()):
    w_G.add_node(i, lat_lon= node)
    lat_lon_to_index[node] = i

for edge in G.edges(data=True):
    w_G.add_edge(lat_lon_to_index[edge[0]], lat_lon_to_index[edge[1]], weight=edge[2]['cost'])
c =list(w_G.nodes(data = True))
j = list(w_G.edges(data = True))
print(c[1])
print (j[2])

gives:

    (1, {'lat_lon': (4.8716933, 52.3650215)})
(1, 2, {'weight': 122.826431079961})

Because, like @zohar.kom said, I already have attributes for each edge in the shapefile. So all I had to do was add them. Thanks a lot!!


Solution

  • The right way to use set_node_attributes for your purpose is:

    G=nx.read_shp('shapefile.shp', simplify=True)
    crossings = list(G.nodes(data=True))
    names_dict = {val[0]: {"name":i} for i, val in enumerate(crossings)}
    nx.set_node_attributes(G, values=names_dict)
    

    In this implementation names_dict is a dictionary that maps each lat-long pair (i.e., each node) to a dictionary with a single property, with a key "name" and a value equals to the appropriate index.

    Read the documentation for in-depth explanation on how to use set_node_attributes properly.

    You have 2 mistakes in your implementation:

    1. weighted_G is a new empty directed graph, instead of the directed graph returned by nx.read_shp
    2. Your usage of set_node_attributes arguments is wrong. More specifically, name is an optional argument that refers to the name of a node's attribute and not the name of the node itself.

    Edit:

    First, for building the graph such that the lat_lon are a property and the nodes are defined with indices you can write:

    w_G = nx.DiGraph()
    lat_lon_to_index =  {}
    for i, node in enumerate(G.nodes()):
        w_G.add_node(i, lat_lon=node)
        lat_lon_to_index[node] = i
    
    for edge in G.edges():
        w_G.add_edge(lat_lon_to_index[edge[0]], lat_lon_to_index[edge[1]], cost=edge[2]['cost_property_name'])
    

    Note that here I defined the edges according to the edges as read from the input file.

    For using nx.dijkstra_path, you need to specify the weights of the graph's edges (which are defined according to the input file). Basically, there are 2 options for defining the weights:

    1. Using an edge property - this can be done by calling something like nx.dijkstra_path(G, 1, 20, weight='cost') where 1 and 20 are nodes and 'cost' is a name of a property of the edges.
    2. Another option is to define the distance as a function. Say you want the euclidian distance you can define a distance function:

      def distance_func(u, v, d): return math.sqrt( ((w_G.nodes[u]['lat_lon'][0] - w_G.nodes[v ['lat_lon'][0]) ** 2) + ((w_G.nodes[u]['lat_lon'][1] - w_G.nodes[v]['lat_lon'][1]) ** 2))

      and finally use it as follows: path = nx.dijkstra_path(w_G, 1, 20, distance_func)

    Again, for more details simply look at the documentation and examples here.