Search code examples
pythonpandasmatplotlibnetworkx

TypeError due to float() argument when I create a network with nodes colored using a colormap


I have the following two datasets

Node     Edge
A         B
A         D
B         N
B         A
B         X
S         C

and

Nodes    Attribute
A           -9
B           
C           -1.5
D           1.0
...
N           1.0
... 
X           0.0
Y           -1.5
W          -1.5
Z           1.0

where Attribute type is float64. For replication, you can use the following (unique Attribute elements, i.e., uni_val): array([ 9. , 0. , 1. , 0.5, -0.5, -1. , -1.5, -2. ])

I want to create a network using a color map for coloring nodes. I did as follows:

# Create map of color

uni_val=df2.Attribute.unique()
colors = plt.cm.jet(np.linspace(0,1,len(df2.Attribute.unique())))

n=0
val=[]
col=[]

for i in uni_val:
    val.append(i)
    col.append(colors[n])
    n=n+1

mapper=dict(zip(val,col))
colour_map = df2.set_index('Nodes')['Attribute'].map(mapper)

# Create the network

G = nx.from_pandas_edgelist(df1, source='Node', target='Edge')
# Add Attribute to each node
nx.set_node_attributes(G, colour_map, name="colour")

# Then draw with colours based on attribute values:
nx.draw(G, 
        node_color=nx.get_node_attributes(G, 'colour').values(),
        with_labels=True)

I am getting the following error:

TypeError: float() argument must be a string or a number, not 'dict_values'

but I do not understand what is causing it and how I can fix it. It would be great if you could help me to better understand it and show me a way to fix it.


Solution

  • The current mapping is incorrect. A valid colour code format is needed not arrays:

    From the docs networkx.drawing.nx_pylab.draw_networkx

    Node color. Can be a single color or a sequence of colors with the same length as nodelist. Color can be string or rgb (or rgba) tuple of floats from 0-1. If numeric values are specified they will be mapped to colors using the cmap and vmin,vmax parameters.

    The following mapping is created with the current approach:

    uni_val = np.array([9., 0., 1., 0.5, -0.5, -1., -1.5, -2.])
    colors = plt.cm.jet(np.linspace(0, 1, len(uni_val)))
    
    print(colors)
    n = 0
    val = []
    col = []
    
    for i in uni_val:
        val.append(i)
        col.append(colors[n])
        n = n + 1
    
    mapper = dict(zip(val, col))
    

    mapper:

    {-2.0: array([0.5, 0. , 0. , 1. ]),
     -1.5: array([1.        , 0.18954248, 0.        , 1.        ]),
     -1.0: array([1.        , 0.72694263, 0.        , 1.        ]),
     -0.5: array([0.71790006, 1.        , 0.24984187, 1.        ]),
     0.0: array([0.        , 0.06470588, 1.        , 1.        ]),
     0.5: array([0.24984187, 1.        , 0.71790006, 1.        ]),
     1.0: array([0.        , 0.64509804, 1.        , 1.        ]),
     9.0: array([0. , 0. , 0.5, 1. ])}
    

    These arrays are invalid inputs for colours.


    These arrays need to be converted into some valid form. A simple fix would be to use matplotlib.colors.to_hex

    uni_val = np.array([9., 0., 1., 0.5, -0.5, -1., -1.5, -2.])
    colors = plt.cm.jet(np.linspace(0, 1, len(uni_val)))
    mapper = dict(zip(uni_val, map(mcolor.to_hex, colors)))
    

    Which produces:

    {-2.0: '#800000',
     -1.5: '#ff3000',
     -1.0: '#ffb900',
     -0.5: '#b7ff40',
     0.0: '#0010ff',
     0.5: '#40ffb7',
     1.0: '#00a4ff',
     9.0: '#000080'}
    

    All together it can look like:

    import networkx as nx
    import numpy as np
    import pandas as pd
    from matplotlib import pyplot as plt, colors as mcolor
    
    # Sample DataFrames
    df1 = pd.DataFrame({
        'Node': ['A', 'A', 'B', 'B', 'B', 'S'],
        'Edge': ['B', 'D', 'N', 'A', 'X', 'C']
    })
    df2 = pd.DataFrame({
        'Nodes': ['A', 'B', 'C', 'D', 'N', 'S', 'X'],
        'Attribute': [-1, 0, -1.5, 1, 1, 9, 0]
    })
    
    # Simplified construction of `colour_map`
    uni_val = df2['Attribute'].unique()
    colors = plt.cm.jet(np.linspace(0, 1, len(uni_val)))
    # Map colours to_hex then zip with
    mapper = dict(zip(uni_val, map(mcolor.to_hex, colors)))
    colour_map = df2.set_index('Nodes')['Attribute'].map(mapper)
    
    G = nx.from_pandas_edgelist(df1, source='Node', target='Edge')
    # Add Attribute to each node
    nx.set_node_attributes(G, colour_map, name="colour")
    
    # Then draw with colours based on attribute values:
    nx.draw(G,
            node_color=nx.get_node_attributes(G, 'colour').values(),
            with_labels=True)
    
    plt.show()
    

    Producing a graph like:

    coloured graph