Bar Chart with multiple labels
Asked Answered
T

3

23

The following code only shows the main category ['one', 'two', 'three', 'four', 'five', 'six'] as the x axis labels. Is there a way show subcategory ['A', 'B', 'C', 'D'] as secondary x axis labels? enter image description here

df = pd.DataFrame(np.random.rand(6, 4),
                 index=['one', 'two', 'three', 'four', 'five', 'six'],
                 columns=pd.Index(['A', 'B', 'C', 'D'], 
                 name='Genus')).round(2)


df.plot(kind='bar',figsize=(10,4))
Tutor answered 21/4, 2017 at 14:56 Comment(1)
I can think of two options: 1. Create an ad-hoc secondary x-axis below the primary one (see this) ; 2. You start with df.unstack().plot.bar() and you change the figure attributes afterwards.Jacquejacquelin
I
11

Here a possible solution (I had quite a lot of fun!):

df = pd.DataFrame(np.random.rand(6, 4),
                 index=['one', 'two', 'three', 'four', 'five', 'six'],
                 columns=pd.Index(['A', 'B', 'C', 'D'],
                 name='Genus')).round(2)

ax = df.plot(kind='bar',figsize=(10,4), rot = 0)

# "Activate" minor ticks
ax.minorticks_on()

# Get location of the center of each rectangle
rects_locs = map(lambda x: x.get_x() +x.get_width()/2., ax.patches)
# Set minor ticks there
ax.set_xticks(rects_locs, minor = True)


# Labels for the rectangles
new_ticks = reduce(lambda x, y: x + y, map(lambda x: [x] * df.shape[0], df.columns.tolist()))
# Set the labels
from matplotlib import ticker
ax.xaxis.set_minor_formatter(ticker.FixedFormatter(new_ticks))  #add the custom ticks

# Move the category label further from x-axis
ax.tick_params(axis='x', which='major', pad=15)

# Remove minor ticks where not necessary
ax.tick_params(axis='x',which='both', top='off')
ax.tick_params(axis='y',which='both', left='off', right = 'off')

Here's what I get:

enter image description here

Inviolable answered 21/4, 2017 at 15:54 Comment(1)
Nice trick to use minor and major tick labels for this; it won't work, however, if there's an odd number of bars per group as you can't have both minor and major labels for the same tick mark. So in this case you'll need to add Texts for the group labels.Gelatinate
I
13

Here is a solution. You can get the positions of the bars and set some minor xticklabels accordingly.

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

df = pd.DataFrame(np.random.rand(6, 4),
                 index=['one', 'two', 'three', 'four', 'five', 'six'],
                 columns=pd.Index(['A', 'B', 'C', 'D'], 
                 name='Genus')).round(2)


df.plot(kind='bar',figsize=(10,4))

ax = plt.gca()
pos = []
for bar in ax.patches:
    pos.append(bar.get_x()+bar.get_width()/2.)


ax.set_xticks(pos,minor=True)
lab = []
for i in range(len(pos)):
    l = df.columns.values[i//len(df.index.values)]
    lab.append(l)

ax.set_xticklabels(lab,minor=True)
ax.tick_params(axis='x', which='major', pad=15, size=0)
plt.setp(ax.get_xticklabels(), rotation=0)

plt.show()

enter image description here

Ivar answered 21/4, 2017 at 16:5 Comment(1)
Nice trick to use minor and major tick labels for this; it won't work, however, if there's an odd number of bars per group as you can't have both minor and major labels for the same tick mark. So in this case you'll need to add Texts for the group labels.Gelatinate
I
11

Here a possible solution (I had quite a lot of fun!):

df = pd.DataFrame(np.random.rand(6, 4),
                 index=['one', 'two', 'three', 'four', 'five', 'six'],
                 columns=pd.Index(['A', 'B', 'C', 'D'],
                 name='Genus')).round(2)

ax = df.plot(kind='bar',figsize=(10,4), rot = 0)

# "Activate" minor ticks
ax.minorticks_on()

# Get location of the center of each rectangle
rects_locs = map(lambda x: x.get_x() +x.get_width()/2., ax.patches)
# Set minor ticks there
ax.set_xticks(rects_locs, minor = True)


# Labels for the rectangles
new_ticks = reduce(lambda x, y: x + y, map(lambda x: [x] * df.shape[0], df.columns.tolist()))
# Set the labels
from matplotlib import ticker
ax.xaxis.set_minor_formatter(ticker.FixedFormatter(new_ticks))  #add the custom ticks

# Move the category label further from x-axis
ax.tick_params(axis='x', which='major', pad=15)

# Remove minor ticks where not necessary
ax.tick_params(axis='x',which='both', top='off')
ax.tick_params(axis='y',which='both', left='off', right = 'off')

Here's what I get:

enter image description here

Inviolable answered 21/4, 2017 at 15:54 Comment(1)
Nice trick to use minor and major tick labels for this; it won't work, however, if there's an odd number of bars per group as you can't have both minor and major labels for the same tick mark. So in this case you'll need to add Texts for the group labels.Gelatinate
H
0
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

def subcategorybar(X, vals,als, width=0.8):
    n = len(vals)
    _X = np.arange(len(X))
    plt.figure(figsize=(14,9))
    for i in range(n):
        plt.bar(_X - width/2. + i/float(n)*width, vals[i], 
                width=width/float(n), align="edge")
        for j in _X:
            plt.text([_X - width/2. + i/float(n)*width][0][j],vals[i][j]+0.01*vals[i] 
                     [j],str(als[i][j]))
    plt.xticks(_X, X)

### data
X = ['a','b','c','d','f']
A1 = [1,2,3,4,5]
A2= [1,7,6,7,8]
A3 = [3,5,6,8,9]
A4= [4,5,6,7,3]
A5 = [5,6,7,8,5]

##labels
A1_al = ['da','dd',5,6,3]
A2_al = np.random.random_integers(20,size=5)
A3_al = np.random.random_integers(20,size=5)
A4_al = np.random.random_integers(20,size=5)
A5_al = np.random.random_integers(20,size=5)

subcategorybar(X, [A1,A2,A3,A4],[A1_al,A2_al,A3_al,A4_al],width=0.8)

plt.show()
Hackamore answered 18/9, 2018 at 10:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.