1

I have a dataframe dictionary like this

{'region': {0: 'R0',1: 'R1',2: 'R2',3: 'R3',4: 'R4',5: 'R5',6: 'R6'},
 'DT': {0: 0.765, 1: 0.694, 2: 0.778, 3: 0.694, 4: 0.629, 5: 0.67, 6: 0.668},
 'GB': {0: 0.714, 1: 0.741, 2: 0.752, 3: 0.741, 4: 0.683, 5: 0.706, 6: 0.656},
 'KNN': {0: 0.625, 1: 0.641, 2: 0.628, 3: 0.641, 4: 0.552, 5: 0.544, 6: 0.578},
 'LR': {0: 0.624, 1: 0.662, 2: 0.634, 3: 0.662, 4: 0.581, 5: 0.629, 6: 0.649},
 'lstm': {0: 0.803,1: 0.633,2: 0.845,3: 0.668,4: 0.717,5: 0.726,6: 0.674}}

In neat format

    region DT   GB      KNN      LR     lstm
0   R0  0.765   0.714   0.625   0.624   0.803
1   R1  0.694   0.741   0.641   0.662   0.633
2   R2  0.778   0.752   0.628   0.634   0.845
3   R3  0.694   0.741   0.641   0.662   0.668
4   R4  0.629   0.683   0.552   0.581   0.717
5   R5  0.67    0.706   0.544   0.629   0.726
6   R6  0.668   0.656   0.578   0.649   0.674

I want to plot stacked bar graph with error bar. This dataframe dont have information about standard deviation, but i have another dataframe of standard deviation.

Suppose there are two dataframe mean, and std

I tried this code

fig, ax = plt.subplots()
width=0.5
clfs=['DT', 'KNN', 'LR', 'GB', 'lstm']
ax.bar(mean_df['region'], mean_df[clfs[0]], width,yerr=std_df[clfs[0]], label=clfs[0])
for i in range(1,5):
    ax.bar(mean_df['region'], mean_df[clfs[i]], width,yerr=std_df[clfs[i]], label=clfs[i],bottom=mean_df[clfs[i-1]])

plt.xticks(rotation=90)
plt.legend()
plt.show()

but the bars are not being stacked properly. I am also looking a way to write value on each bar segment to increase the readability of plot enter image description here

EDIT: Solution is to add first two list in bottom while plotting third one.

fig, ax = plt.subplots()
ax.bar(mean_df['region'], mean_df[clfs[0]], width,yerr=std_df[clfs[0]], label=clfs[0])
ax.bar(mean_df['region'], mean_df[clfs[1]], width,yerr=std_df[clfs[1]], label=clfs[1],bottom=mean_df[clfs[0]])
ax.bar(mean_df['region'], mean_df[clfs[2]], width,yerr=std_df[clfs[2]], label=clfs[2],
       bottom=mean_df[clfs[0]]+mean_df[clfs[1]])

But i am looking for an elegant way to do this and also how to write values on segment of bar

EDIT 2: I came to this

ax = mean_df.plot(kind='bar', stacked=True, figsize=(8, 6),yerr=std_df, rot=0, xlabel='region', ylabel='DT')

But now i am looking way to write text. I tried this

for c in ax.containers:
    ax.bar_label(c, label_type='center')

but i got this error

AttributeError: 'ErrorbarContainer' object has no attribute 'patches'

EDIT 3
This error is because of yerr=std_df, but i also want to keep error bars

Trenton McKinney
  • 56,955
  • 33
  • 144
  • 158
Talha Anwar
  • 2,699
  • 4
  • 23
  • 62

1 Answers1

4
  • Stacked bars are not an ideal way to present the data. With error bars, stacked bars are even more difficult to read, may overlap with the error bar within a given stack, and with the annotations, which can lead to a confusing visualization.
  • The issue will occur for stacked=True or stacked=False, and it applies to using matplotlib.axes.Axes.bar followed by matplotlib.axes.Axes.errorbar.
    • This answer also applies to horizontal bars.
    • This does not apply to seaborn barplots with ci=True
  • pandas.DataFrame.plot returns an Axes, from which containers can be extracted.
    • Adding yerr results in the containers containing ErrorbarContainer object and BarContainer object
  • See this answer for a thorough explanation of using matplotlib.pyplot.bar_label with additional examples.
  • Tested in python 3.10, pandas 1.3.4, matplotlib 3.5.0, seaborn 0.11.2

ax.containers

[<ErrorbarContainer object of 3 artists>,
 <BarContainer object of 2 artists>,
 <ErrorbarContainer object of 3 artists>,
 <BarContainer object of 2 artists>,
 <ErrorbarContainer object of 3 artists>,
 <BarContainer object of 2 artists>]
  • .bar_label will annotate with the patch value when using label_type='center', and the cumsum of the patches when using label_type='edge'

pandas.DataFrame.plot with yerr

  • The BarContainer objects are at the odd indices, which can be extracted with ax.containers[1::2]
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np

# load same dataframe
pen = sns.load_dataset('penguins')

# create the aggregated dataframe (mean)
pen_mean = pen.pivot_table(index='sex', columns='species', values='bill_depth_mm', aggfunc='mean')

# create the dataframe for the error bars with (std)
pen_std = pen.pivot_table(index='sex', columns='species', values='bill_depth_mm', aggfunc='std')

# plot the dataframe and add yerr
ax = pen_mean.plot(kind='bar', stacked=True, figsize=(9, 6), rot=0, yerr=pen_std)

# move the legend
ax.legend(bbox_to_anchor=(1, 1.02), loc='upper left')

# iterate through every other container; the even containers are ErrorbarContainer
for c in ax.containers[1::2]:

    # add the annotation
    ax.bar_label(c, label_type='center')

enter image description here

Horizontal Bars

# plot the dataframe and add yerr
ax = pen_mean.plot(kind='barh', stacked=True, figsize=(9, 6), rot=0, xerr=pen_std)

# move the legend
ax.legend(bbox_to_anchor=(1, 1.02), loc='upper left')

# iterate through every other container; the even containers are ErrorbarContainer
for c in ax.containers[1::2]:

    # add the annotation
    ax.bar_label(c, label_type='center')

enter image description here


Axes.bar with Axes.errorbar

  • The BarContainer objects are at the even indices, which can be extracted with ax.containers[0::2]
data = pen_mean

cols = pen_mean.columns
rows = pen_mean.index

# Get some pastel shades for the colors
colors = ['tab:blue', 'tab:green']
n_rows = len(data)

index = np.arange(len(cols))
bar_width = 0.4

# Initialize the vertical-offset for the stacked bar chart.
y_offset = np.zeros(len(cols))

# Plot bars and create text labels for the table
fig, ax = plt.subplots(figsize=(8, 5))

for i, row in enumerate(rows):
    ax.bar(cols, data.loc[row], bar_width, bottom=y_offset, color=colors[i])
    ax.errorbar(cols, y_offset+data.loc[row], pen_std.loc[row], color='k', ls='none')
    y_offset = y_offset + data.loc[row]
    
# note the order of the container objects is different
for c in ax.containers[0::2]:
    ax.bar_label(c, label_type='center')

plt.show()

enter image description here


seaborn bars

  • seaborn bar plots with the default ci=True do not return ErrorbarContainer objects in containers.

sns.catplot with kind='bar'

  • See this answer for an additional example of annotating a seaborn figure-level bar plot.
p = sns.catplot(kind='bar', data=pen, x='sex', y='bill_depth_mm', hue='species', height=4.5, aspect=1.5)

# since this is a single subplot of a figure
ax = p.axes.flat[0]

for c in ax.containers:

    # add the annotation
    ax.bar_label(c, label_type='center')

enter image description here

sns.barplot

fig = plt.figure(figsize=(9, 6))
p = sns.barplot(data=pen, x='sex', y='bill_depth_mm', hue='species')

p.legend(bbox_to_anchor=(1, 1.02), loc='upper left')

for c in p.containers:

    # add the annotation
    p.bar_label(c, label_type='center')

enter image description here

Trenton McKinney
  • 56,955
  • 33
  • 144
  • 158