1

Good afternoon,

I am trying to plot this sample data in a way that grouping is maintained on the actual graph, but I couldn't find a way/method to get these labels added this way with matplotlib (the provided example is done using Excel). Please refer to the first graph provided below and pay attention to the nicely nested grouping "London -> Switch 1, Switch3 -> Interface"

>>> data = {
   'Location': ['NYC', 'NYC', 'NYC', 'NYC', 'LON', 'LON', 'LON', 'TKY', 'TKY', 'TKY'], 
   'Device': ['Device1', 'Device2', 'Device3', 'Device1', 'Switch1', 'Switch1', 'Switch3', 'Router1', 
   'Router2', 'Router2'], 
   'Interface': ['Et1', 'Et2', 'Et3', 'Et2', 'Et1', 'Et3', 'Et22', 'Gi1', 'Gi1', 'Gi2'], 
   'Errors': [69, 58, 16, 83, 58, 78, 16, 30, 33, 64]}

>>> df = pd.DataFrame(data)

I would like to keep this structure when plotting with matplotlib too, but all my attempts were not very successful so far ...

Is there an easy way to achieve this?

Graph using Excel

And here is what the matplotlib graph looks like:

enter image description here

SOLUTION

I really wanted to post this solution so it can stay here for completeness. I had to add on to the code I found within the duplicated thread and here it is.

from matplotlib import pyplot as plt
from collections import Counter

def add_line(ax, xpos, ypos):
    line = plt.Line2D([xpos, xpos], [ypos + .1, ypos], 
        transform=ax.transAxes, color='gray')
    line.set_clip_on(False)
    ax.add_line(line)

def my_label(my_index, level):
    labels = my_index.get_level_values(level)

    if level == df.index.nlevels-1:
        return [(x, 1) for x in labels]
    else:
        x = Counter()

        x.update(labels)

        return x.items()
    
def label_group_bar_table(ax,df):
    ypos = -.1
    scale = 1./df.index.size
    for level in range(df.index.nlevels)[::-1]:
        pos = 0
        for label, rpos in my_label(df.index, level):
            lxpos = (pos + .5 * rpos) * scale
            ax.text(lxpos, ypos, label, ha='center', 
                transform=ax.transAxes, size=12, color='gray')   
            add_line(ax, pos*scale, ypos)
            pos += rpos
        add_line(ax, pos*scale, ypos)
        ypos -= .05

def autonotate(df): 
    for num, entry in enumerate(df.reset_index().iterrows()):
        num, row = entry
        location, device, interface, errors = row
        ax.text(num, errors*1.02, errors, ha='center', size=12, color='black')

>>> df = rdf.groupby(['Location','Device','Interface']).sum()
>>> fig = plt.figure()
>>> ax = fig.add_subplot()
>>> df.plot(kind='bar', stacked=False, ax=fig.gca(), figsize=(20,7), color='orange')
>>> labels = ['' for item in ax.get_xticklabels()]
>>> ax.set_xticklabels(labels)
>>> ax.set_xlabel('')
>>> label_group_bar_table(ax, df)
>>> fig.subplots_adjust(bottom=.1*df.index.nlevels)
>>> autonotate(df)
>>> plt.tight_layout()
>>> plt.show()

This way I was able to produce the following diagram, which is pretty close to what I needed:

enter image description here

Enthusiast
  • 53
  • 5

0 Answers0