Based on the link @Stef provided, here's a working solution.
The patches
attribute of your ax
object contains the bars themselves, which are matplotlib.patches.Rectangle
objects. If you loop through them, you can then use the get_x()
and get_width()
functions to get the x positions and bar widths, respectively. The x positions of each bar are actually the left side, so you need to add half the bar width to get the center.
sublabel_positions = []
# get_x() returns x position of bar's left side
# add 1/2 the bar width via get_width() to get the center
for bar in ax.patches:
bar_center = (bar.get_x() + bar.get_width() / 2)
sublabel_positions.append(bar_center)
As for the labels, since you defined rects1
before rects2
(the men values, then the women values), you want the list of labels to be 5 Men
strings, followed by 5 Women
strings:
sublabels = ['Men', 'Men', 'Men', 'Men', 'Men', 'Women', 'Women', 'Women', 'Women', 'Women']
# or you could do:
# sublabels = ['Men'] * 5 + ['Women'] * 5
Then you could add the extra x-ticks and labels with a single line shown below. The major x-ticks are your G1 thru G5, so you want to set minor=True
here.
ax.set_xticks(sublabel_positions, sublabels, minor=True)
To make it look better, I ended up specifying a larger figure size, padding the major x-tick labels so they don't overlap, and turning off the major x-ticks, and I get this.
x = np.arange(len(labels)) # the label locations
width = 0.35 # the width of the bars
fig, ax = plt.subplots(figsize=(8, 6))
rects1 = ax.bar(x - width/2, men_means, width, label='Men')
rects2 = ax.bar(x + width/2, women_means, width, label='Women')
# Add some text for labels, title and custom x-axis tick labels, etc.
ax.set_ylabel('Scores')
ax.set_title('Scores by group and gender')
ax.set_xticks(x, labels)
ax.legend()
ax.bar_label(rects1, padding=3)
ax.bar_label(rects2, padding=3)
ax.set_xticks(sublabel_positions, sublabels, minor=True)
ax.tick_params(axis='x', which='major', pad=15)
ax.tick_params(axis='x', which='major', bottom=False)
fig.tight_layout()
plt.show()
(I'm sorry I don't have enough reputation to paste images yet)
Finished barplot with 2 series and stacked major/minor x-tick labels
As an aside, if you run ax.get_xticklabels(minor=True)
, you'll see what I mean about the order of bars being plotted—men first, then women:
>>> ax.get_xticklabels(minor=True)
[Text(-0.175, 0, 'Men'),
Text(0.825, 0, 'Men'),
Text(1.825, 0, 'Men'),
Text(2.825, 0, 'Men'),
Text(3.825, 0, 'Men'),
Text(0.175, 0, 'Women'),
Text(1.175, 0, 'Women'),
Text(2.175, 0, 'Women'),
Text(3.175, 0, 'Women'),
Text(4.175, 0, 'Women')]