Chord Diagram TypeError [SOLVED]

I am proceeding through the Chord docs and have successfully reproduced the example on the page. I am now trying to use my own data. If I look at the data, everything seems correct. When I run the code I get a Type Error as shown below. I think the problem is the my ‘source’ and ‘target’ are string encoded integers eg. ‘111000’ with one exception ‘unk’. Is it possible that the two columns are being interpreted as mixed types? If not, has anyone else had this issue and resolved it?

[SOLVED] So in the example in the docs you have this line:

chord = hv.Chord((links,nodes))

Take a look at your ‘nodes’ variable because if it’s dimensions are incorrect, as in my case, it will throw the error below. What I had done was inadvertently create a node_list as long as the number of rows in my ‘links’ variable but because of the error message I spent a long time looking at everything except the node list which I was certain was correct. Embarrassing.

TypeError                                 Traceback (most recent call last)
~/miniconda3/lib/python3.8/site-packages/numpy/core/fromnumeric.py in _wrapfunc(obj, method, *args, **kwds)
     60     try:
---> 61         return bound(*args, **kwds)
     62     except TypeError:

TypeError: '<' not supported between instances of 'int' and 'str'

During handling of the above exception, another exception occurred:

TypeError                                 Traceback (most recent call last)
<ipython-input-245-6b5eb876f215> in <module>
      5 display(links.head(),nodes.data.head())
      6 
----> 7 hv.Chord((links, nodes))

~/miniconda3/lib/python3.8/site-packages/holoviews/element/graphs.py in __init__(self, data, kdims, vdims, compute, **params)
    784         if compute:
    785             self._nodes = nodes
--> 786             chord = layout_chords(self)
    787             self._nodes = chord.nodes
    788             self._edgepaths = chord.edgepaths

~/miniconda3/lib/python3.8/site-packages/param/parameterized.py in __new__(class_, *args, **params)
   2810         inst = class_.instance()
   2811         inst.param._set_name(class_.__name__)
-> 2812         return inst.__call__(*args,**params)
   2813 
   2814     def __call__(self,*args,**kw):

~/miniconda3/lib/python3.8/site-packages/holoviews/core/operation.py in __call__(self, element, **kwargs)
    194             kwargs['streams'] = self.p.streams
    195         kwargs['per_element'] = self._per_element
--> 196         return element.apply(self, **kwargs)
    197 
    198 

~/miniconda3/lib/python3.8/site-packages/holoviews/core/accessors.py in pipelined_call(*args, **kwargs)
     43 
     44             try:
---> 45                 result = __call__(*args, **kwargs)
     46 
     47                 if not in_method:

~/miniconda3/lib/python3.8/site-packages/holoviews/core/accessors.py in __call__(self, apply_function, streams, link_inputs, dynamic, per_element, **kwargs)
    196             if hasattr(apply_function, 'dynamic'):
    197                 inner_kwargs['dynamic'] = False
--> 198             return apply_function(self._obj, **inner_kwargs)
    199         elif self._obj._deep_indexable:
    200             mapped = []

~/miniconda3/lib/python3.8/site-packages/holoviews/core/operation.py in __call__(self, element, **kwargs)
    190             elif ((self._per_element and isinstance(element, Element)) or
    191                   (not self._per_element and isinstance(element, ViewableElement))):
--> 192                 return self._apply(element)
    193         elif 'streams' not in kwargs:
    194             kwargs['streams'] = self.p.streams

~/miniconda3/lib/python3.8/site-packages/holoviews/core/operation.py in _apply(self, element, key)
    130         element_pipeline = getattr(element, '_pipeline', None)
    131 
--> 132         ret = self._process(element, key)
    133         for hook in self._postprocess_hooks:
    134             ret = hook(self, ret, **kwargs)

~/miniconda3/lib/python3.8/site-packages/holoviews/element/graphs.py in _process(self, element, key)
    654         max_chords = self.p.max_chords
    655         src, tgt = (element.dimension_values(i) for i in range(2))
--> 656         src_idx = search_indices(src, nodes)
    657         tgt_idx = search_indices(tgt, nodes)
    658         if element.vdims:

~/miniconda3/lib/python3.8/site-packages/holoviews/core/util.py in search_indices(values, source)
   2102     """
   2103     orig_indices = source.argsort()
-> 2104     return orig_indices[np.searchsorted(source[orig_indices], values)]
   2105 
   2106 

<__array_function__ internals> in searchsorted(*args, **kwargs)

~/miniconda3/lib/python3.8/site-packages/numpy/core/fromnumeric.py in searchsorted(a, v, side, sorter)
   1339 
   1340     """
-> 1341     return _wrapfunc(a, 'searchsorted', v, side=side, sorter=sorter)
   1342 
   1343 

~/miniconda3/lib/python3.8/site-packages/numpy/core/fromnumeric.py in _wrapfunc(obj, method, *args, **kwds)
     68         # Call _wrapit from within the except clause to ensure a potential
     69         # exception has a traceback chain.
---> 70         return _wrapit(obj, method, *args, **kwds)
     71 
     72 

~/miniconda3/lib/python3.8/site-packages/numpy/core/fromnumeric.py in _wrapit(obj, method, *args, **kwds)
     45     except AttributeError:
     46         wrap = None
---> 47     result = getattr(asarray(obj), method)(*args, **kwds)
     48     if wrap:
     49         if not isinstance(result, mu.ndarray):

TypeError: '<' not supported between instances of 'int' and 'str'

The nodes you have provided are of categorical(str) type. you need to map each categorical node to a unique integer value for the edge data-frame(i.e links in your case). Then the comparison issue will be resoloved.

See my example code for a chord diagram of categorical node values using some sample data source.

source_data = [{"from": "Cronje", "to": "Lord Kitchener", "value": 1}, {"from": "Cronje", "to": "Lord Selborne", "value": 1}, {"from": "Cronje", "to": "George Godfrey", "value": 1}, {"from": "Cronje", "to": "Lord Milner", "value": 1}, {"from": "Cronje", "to": "Booker T. Washington", "value": 1}, {"from": "Cronje", "to": "Dada Abdulla", "value": 1}, {"from": "Cronje", "to": "Lord Lansdowne", "value": 1}, {"from": "Cronje", "to": "Escombe", "value": 1}, {"from": "Cronje", "to": "Messrs", "value": 1}, {"from": "Cronje", "to": "Lawley", "value": 1}, {"from": "Cronje", "to": "Sheth Haji Adam", "value": 1}, {"from": "Cronje", "to": "Lord Ripon", "value": 1}, {"from": "Cronje", "to": "Lord Elgin", "value": 1}, {"from": "Sheth Haji Adam", "to": "Lord Kitchener", "value": 1}, {"from": "Sheth Haji Adam", "to": "Lord Selborne", "value": 1}, {"from": "Sheth Haji Adam", "to": "George Godfrey", "value": 1}, {"from": "Sheth Haji Adam", "to": "Dada Abdulla", "value": 1}, {"from": "Sheth Haji Adam", "to": "Booker T. Washington", "value": 1}, {"from": "Sheth Haji Adam", "to": "Lord Milner", "value": 1}, {"from": "Sheth Haji Adam", "to": "Lord Lansdowne", "value": 1}, {"from": "Sheth Haji Adam", "to": "Escombe", "value": 1}, {"from": "Sheth Haji Adam", "to": "Messrs", "value": 1}, {"from": "Sheth Haji Adam", "to": "Lawley", "value": 1}, {"from": "Sheth Haji Adam", "to": "Lord Elgin", "value": 1}, {"from": "Sheth Haji Adam", "to": "Lord Ripon", "value": 1}, {"from": "Lord Selborne", "to": "Lord Elgin", "value": 1}, {"from": "Lord Selborne", "to": "George Godfrey", "value": 1}, {"from": "Lord Selborne", "to": "Merriman", "value": 1}, {"from": "Lord Selborne", "to": "Olive Schreiner", "value": 1}, {"from": "Lord Selborne", "to": "Dada Abdulla", "value": 1}, {"from": "Lord Selborne", "to": "Booker T. Washington", "value": 1}, {"from": "Lord Selborne", "to": "Lord Milner", "value": 1}, {"from": "Lord Selborne", "to": "Lord Kitchener", "value": 1}, {"from": "Lord Selborne", "to": "Lord Lansdowne", "value": 1}, {"from": "Lord Selborne", "to": "Escombe", "value": 1}, {"from": "Lord Selborne", "to": "Messrs", "value": 1}, {"from": "Lord Selborne", "to": "Lawley", "value": 1}, {"from": "Lord Selborne", "to": "John Molteno", "value": 1}, {"from": "Lord Selborne", "to": "Lord Ripon", "value": 1}, {"from": "George Godfrey", "to": "Dada Abdulla", "value": 1}, {"from": "George Godfrey", "to": "Booker T. Washington", "value": 1}, {"from": "George Godfrey", "to": "Lord Milner", "value": 1}, {"from": "George Godfrey", "to": "Lord Kitchener", "value": 1}, {"from": "George Godfrey", "to": "Lord Lansdowne", "value": 1}, {"from": "George Godfrey", "to": "Escombe", "value": 1}, {"from": "George Godfrey", "to": "Messrs", "value": 1}, {"from": "George Godfrey", "to": "Lawley", "value": 1}, {"from": "George Godfrey", "to": "Lord Elgin", "value": 1}, {"from": "George Godfrey", "to": "Lord Ripon", "value": 1}, {"from": "Merriman", "to": "Lord Lansdowne", "value": 1}, {"from": "Merriman", "to": "John Molteno", "value": 1}, {"from": "Merriman", "to": "Olive Schreiner", "value": 1}, {"from": "Olive Schreiner", "to": "Lord Lansdowne", "value": 1}, {"from": "Olive Schreiner", "to": "John Molteno", "value": 1}, {"from": "Dada Abdulla", "to": "Lord Kitchener", "value": 1}, {"from": "Dada Abdulla", "to": "Booker T. Washington", "value": 1}, {"from": "Dada Abdulla", "to": "Lord Milner", "value": 1}, {"from": "Dada Abdulla", "to": "Lord Lansdowne", "value": 1}, {"from": "Dada Abdulla", "to": "Escombe", "value": 1}, {"from": "Dada Abdulla", "to": "Messrs", "value": 1}, {"from": "Dada Abdulla", "to": "Lawley", "value": 1}, {"from": "Dada Abdulla", "to": "Lord Elgin", "value": 1}, {"from": "Dada Abdulla", "to": "Lord Ripon", "value": 1}, {"from": "Booker T. Washington", "to": "Lord Milner", "value": 1}, {"from": "Booker T. Washington", "to": "Lord Kitchener", "value": 1}, {"from": "Booker T. Washington", "to": "Lord Lansdowne", "value": 1}, {"from": "Booker T. Washington", "to": "Escombe", "value": 1}, {"from": "Booker T. Washington", "to": "Messrs", "value": 1}, {"from": "Booker T. Washington", "to": "Lawley", "value": 1}, {"from": "Booker T. Washington", "to": "Lord Elgin", "value": 1}, {"from": "Booker T. Washington", "to": "Lord Ripon", "value": 1}, {"from": "Lord Milner", "to": "Lord Kitchener", "value": 1}, {"from": "Lord Milner", "to": "Lord Lansdowne", "value": 1}, {"from": "Lord Milner", "to": "Escombe", "value": 1}, {"from": "Lord Milner", "to": "Messrs", "value": 1}, {"from": "Lord Milner", "to": "Lawley", "value": 1}, {"from": "Lord Milner", "to": "Lord Elgin", "value": 1}, {"from": "Lord Milner", "to": "Lord Ripon", "value": 1}, {"from": "Lord Kitchener", "to": "Lord Lansdowne", "value": 1}, {"from": "Lord Kitchener", "to": "Escombe", "value": 1}, {"from": "Lord Kitchener", "to": "Messrs", "value": 1}, {"from": "Lord Kitchener", "to": "Lawley", "value": 1}, {"from": "Lord Kitchener", "to": "Lord Elgin", "value": 1}, {"from": "Lord Kitchener", "to": "Lord Ripon", "value": 1}, {"from": "Lord Lansdowne", "to": "Escombe", "value": 1}, {"from": "Lord Lansdowne", "to": "Messrs", "value": 1}, {"from": "Lord Lansdowne", "to": "Lawley", "value": 1}, {"from": "Lord Lansdowne", "to": "John Molteno", "value": 1}, {"from": "Lord Lansdowne", "to": "Lord Elgin", "value": 1}, {"from": "Lord Lansdowne", "to": "Lord Ripon", "value": 1}, {"from": "Escombe", "to": "Messrs", "value": 1}, {"from": "Escombe", "to": "Lawley", "value": 1}, {"from": "Escombe", "to": "Lord Elgin", "value": 1}, {"from": "Escombe", "to": "Lord Ripon", "value": 1}, {"from": "Messrs", "to": "Lawley", "value": 1}, {"from": "Messrs", "to": "Lord Elgin", "value": 1}, {"from": "Messrs", "to": "Lord Ripon", "value": 1}, {"from": "Lawley", "to": "Lord Elgin", "value": 1}, {"from": "Lawley", "to": "Lord Ripon", "value": 1}, {"from": "Lord Elgin", "to": "Lord Ripon", "value": 1}]
            
            
import pandas as pd
import holoviews as hv
from holoviews import opts, dim
import numpy as np
hv.extension('bokeh')
hv.output(size=300)
            
            
df_json = pd.DataFrame(source_data)
            
df_links = pd.DataFrame(columns = ['source', 'target', 'value'])
            
df_links['source'] = df_json['from'].values.tolist()
df_links['target'] = df_json['to'].values.tolist()
df_links['value'] = df_json['value'].values.tolist()
            
unique_names = set(df_json['from'].unique().tolist() + df_json['to'].unique().tolist())
            
df_nodes = pd.DataFrame(columns=['name', 'group'])
df_nodes['name'] = list(unique_names)
            
## Grouping the nodes
df_nodes['group'][0:5] = 0
df_nodes['group'][5:10] = 1
df_nodes['group'][10:] = 2
            
## mapping to numerical value for the source and target node
mapper = {key: i for i, key in enumerate(list(unique_names))}
df_links['source'] = df_links['source'].map(mapper)
df_links['target'] = df_links['target'].map(mapper)
            
links = df_links
nodes = hv.Dataset(df_nodes, 'index')
            
chord = hv.Chord((links, nodes)).select(value=(1, None))
chord.opts(
          opts.Chord(cmap='Category10', edge_cmap='Category10', edge_color=dim('source').str(), 
                           labels='name', node_color=dim('group').str()))