Matplotlib, horizontal bar chart (barh) is upside-down


TL’DR, the vertical bar charts are shown in a conventional way — things line up from left to right. However, when it is converted to horizontal bar chart (from bar to barh), everything is upside-down. I.e., for a grouped bar chart, not only the order of the grouped bar is wrong, the order of the each group is wrong as well.

For e.g., the graph from

enter image description here

If you look closely, you will find that the the bar and legend are in reverse order — Beef shows on top in legend but on bottom in the graph.

As the simplest demo, I changed kind='bar', to kind='barh',
from this graph
and the result looks like this:

I.e., the bars in the horizontal grouped bar chart is ordered upside-down.

How to fix it?

EDIT: @Ajean, it is actually not only the order of the grouped bar is wrong, the order of the each group is wrong as well. The graph from Simple customization of matplotlib/pandas bar chart (labels, ticks, etc.) shows it clearly:

the order of the each group is wrong

We can see that the order is unconventional too, because people would expect the graph to be top-down, with “AAA” at the top, not the bottom.

If you search for “Excel upside-down”, you will find people are complaining about this in Excel all over the places. The Microsoft Excel has a fix for it, do Matplotlib/Panda/Searborn/Ploty/etc has a fix for it?

Asked By: xpt



I believe the joint wrong order of groups and subgroups boils down to a single feature: that the y axis increases upwards, as in a usual plot. Try reversing the y axis of your axes as in this pandas-less example:

import numpy as np
import matplotlib.pyplot as plt


#plot1: bar

#plot2: barh, wrong order

#plot3: barh with correct order: top-down y axis

Specifically for pandas, pandas.DataFrame.plot and its various plotting submethods return a matplotlib axes object, so you can invert its y axis directly:

ax = df.plot.barh()  # or df.plot(), or similar

I will consider this to be a bug, i.e., the y position of the bars are not assigned correctly. The patch is however relatively simple:

This is only one right order of bars, and that is called…, the right order. Anything that is not the right order, is thus a buggy order. :p

In [63]:

print df
      Total_beef_cattle  Total_dairy_cattle  Total_sheep  Total_deer  
1994           0.000000            0.000000     0.000000    0.000000   
2002         -11.025827           34.444950   -20.002034   33.858009   
2003          -8.344764           32.882482   -20.041908   37.229441   
2004         -11.895128           34.207998   -20.609926   42.707754   
2005         -12.366101           32.506699   -19.379727   38.499840   

      Total_pigs  Total_horses  
1994    0.000000      0.000000  
2002  -19.100637     11.811093  
2003  -10.766476     18.504488  
2004   -8.072078     13.376472  
2005  -19.230733   -100.000000  
In [64]:

ax = df.plot(kind='barh', sort_columns=True)

#Get the actual bars
bars = [item for item in ax.get_children() if isinstance(item, matplotlib.patches.Rectangle)]
bars = bars[:df.size]

#Reset the y positions for each bar
bars_y = [plt.getp(item, 'y') for item in bars]
for B, Y in zip(bars, np.flipud(np.array(bars_y).reshape(df.shape[::-1])).ravel()):

enter image description here

Answered By: CT Zhu

General fix is simple:

handles, labels = axis.get_legend_handles_labels()
# reverse to keep order consistent
axis.legend(reversed(handles), reversed(labels), loc='upper left')
Answered By: alexsalo

I believe the simplest solution for this problem is to reverse the pandas dataframe before plotting. For example:

df = df.iloc[::-1]

In my opinion that is a bug in the pandas barh function. At least users should be able to pass an argument like reverse_order = True etc.

Answered By: Philipp Schwarz