Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

matplotlib 'bar' vs 'barh' plots. Problem with barh

I've just noticed something strange and was wondering if someone could explain what's going on?

I have a dictionary i'm plotting a horizontal and vertical bar charts from.

plt.bar(food.keys(), food.values()) #works fine, but:
plt.barh(food.keys(), food.values() #gives "unhashable type: 'dict_keys'" error.

if dictionary is unhashable, why does it let me plot a normal bar graph? Is this just a quirk of the barh function or am I doing something wrong?

Here's my test dataset:

food = {'blueberries':2, 'pizza':3.50, 'apples':0.50}

Thanks.

like image 258
Noobcoder Avatar asked May 04 '20 09:05

Noobcoder


People also ask

What is the difference between bar and Barh?

What is difference between bar and barh graph? BAR Graph - plots vertical rectangles with constant width. BARH Graph- plots horizontal rectangles with constant heights.

What is Matplotlib Barh?

barh() Function. The Axes. barh() function in axes module of matplotlib library is used to make a horizontal bar plot. Syntax: Axes.barh(self, y, width, height=0.8, left=None, *, align='center', **kwargs)

How do I invert a bar graph in Matplotlib?

By using ylim() method ylim() method is also used to invert axes of a plot in Matplotlib. Generally, this method is used to set limits for the axes.


2 Answers

The other answer is of course correct in how to solve the problem - simply convert to a list. The reason why it doesn't work in the first place involves digging into the matplotlib source code.

bar and barh do both call matplotlib.axes.Axes.bar under the hood. When you plot a bar, x is the x position of the bars and height is the y value.

However, calling a barh, the y values (i.e. ['blueberries', 'pizza', 'apples']) is actually set to the argument bottom and the length of the bars is given by width.

The following line is called within the source code

func(ax, *map(sanitize_sequence, args), **kwargs)

Where sanitize_sequence is:

def sanitize_sequence(data):
    """
    Convert dictview objects to list. Other inputs are returned unchanged.
    """
    return (list(data) if isinstance(data, collections.abc.MappingView)
            else data)

The problem is that the argument bottom is a kwarg, and is therefore not passed to sanitize_sequence and is never converted to a list. Whereas in a normal bar, x is converted to a list by sanitize_sequence

So the issue is with how barh is implemented under the hood.

like image 131
DavidG Avatar answered Nov 11 '22 19:11

DavidG


Nice MWE. Looks like you ran into a subtle python 2 to python 3 conversion bug.

Under python 2, dict.keys() (etc.) used to return lists of values. Under python 3, dict.keys() returns a view of the data corresponding to the keys. This view is not hashable (I think in principle, it would be; however, that behaviour is not implemented (yet)). Coercing your key view to a list circumvents the issue:

plt.barh(list(food.keys()), food.values())

Edit

The real question is why plt.bar works, as both, plt.bar and plt.barh call matplotlib.axes.Axes.bar under the hood. The definition of barh is literally:

def barh(self, y, width, height=0.8, left=None, *, align="center",
         **kwargs):
    kwargs.setdefault('orientation', 'horizontal')
    patches = self.bar(x=left, height=height, width=width, bottom=y,
                       align=align, **kwargs)
    return patches
like image 33
Paul Brodersen Avatar answered Nov 11 '22 18:11

Paul Brodersen