How to Plot Min/Max Bars with a Bar Plot

I'd like to adapt my plotting code in order to show min/max bar as depicted in the figure below:

My code is:

from datetime import datetime, timedelta
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_style("white")
sns.set_style('darkgrid',{"axes.facecolor": ".92"}) # (1)
sns.set_context('notebook')

Delay = ['S1', 'S2', 'S3', 'S4']

Time = [87, 66, 90, 55]

df = pd.DataFrame({'Delay':Delay,'Time':Time})
print("Accuracy")

display(df) # in jupyter

fig, ax = plt.subplots(figsize = (8,6))

x = Delay
y = Time

plt.xlabel("Delay", size=14)
plt.ylim(-0.3, 100)
width = 0.1

for i, j in zip(x,y): 
    ax.bar(i,j, edgecolor = "black",
        error_kw=dict(lw=1, capsize=1, capthick=1))  
    ax.set(ylabel = 'Accuracy')

from matplotlib import ticker
ax.yaxis.set_major_locator(ticker.MultipleLocator(10)) 
plt.savefig("Try.png", dpi=300, bbox_inches='tight')

The code produce this figure:

The min/max I want to add is for:

87 (60-90)
66 (40-70)
90 (80-93)
55 (23-60)

Thanks in advance for help.

0

3 Answers

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

# set edgecolor param (this is a global setting, so only set it once)
plt.rcParams["patch.force_edgecolor"] = True

# setup the dataframe
Delay = ['S1', 'S2', 'S3', 'S4']

Time = [87, 66, 90, 55]

df = pd.DataFrame({'Delay':Delay,'Time':Time})

# create a dict for the errors
error = {87: {'max': 90,'min': 60}, 66: {'max': 70,'min': 40}, 90: {'max': 93,'min': 80}, 55: {'max': 60,'min': 23}}

seaborn.barplot

  • seaborn.barplot will add error bars automatically, as shown in the examples at the link. However, this is specific to using many data points. In this case, a value is being specified as the error, the error is not being determined from the data.
    • When error bars are added in this way, the capsize parameter can be specified, to add horizontal lines at the top and bottom of the error bar.
# plot the figure
fig, ax = plt.subplots(figsize=(8, 6))
sns.barplot(x='Delay', y='Time', data=df, ax=ax)

# add the lines for the errors 
for p in ax.patches:
    x = p.get_x()  # get the bottom left x corner of the bar
    w = p.get_width()  # get width of bar
    h = p.get_height()  # get height of bar
    min_y = error[h]['min']  # use h to get min from dict z
    max_y = error[h]['max']  # use h to get max from dict z
    plt.vlines(x+w/2, min_y, max_y, color='k')  # draw a vertical line
  • As noted in the answer from gepcel, the yerr parameter can be used to explicitly provide errors to the API.
    • However, the format of your errors is not correct for the parameter. yerr expects the values to be in relation to the top of the bar
      • S1 is 87, with min of 60, and max of 90. Therefore, ymin is 27, (87-60), and ymax is 3, (90-87).
  • The seaborn.barplot capsize parameter doesn't seem to work with yerr, so you must set the matplotlib 'errorbar.capsize' rcParmas. See Matplotlib Errorbar Caps Missing
# set capsize param (this is a global setting, so only set it once)
plt.rcParams['errorbar.capsize'] = 10

# create dataframe as shown by gepcel
Delay = ['S1', 'S2', 'S3', 'S4']

Time = [87, 66, 90, 55]
_min = [60, 40, 80, 23]
_max = [90, 70, 93, 60]
df = pd.DataFrame({'Delay':Delay,'Time':Time, 'Min': _min, 'Max': _max})

# create ymin and ymax
df['ymin'] = df.Time - df.Min
df['ymax'] = df.Max - df.Time

# extract ymin and ymax into a (2, N) array as required by the yerr parameter
yerr = df[['ymin', 'ymax']].T.to_numpy()

# plot with error bars
fig, ax = plt.subplots(figsize=(8, 6))
sns.barplot(x='Delay', y='Time', data=df, yerr=yerr, ax=ax)

pandas.DataFrame.plot.bar

fig, ax = plt.subplots(figsize=(8, 6))

df.plot.bar(x='Delay', y='Time', ax=ax)

for p in ax.patches:
    x = p.get_x()  # get the bottom left x corner of the bar
    w = p.get_width()  # get width of bar
    h = p.get_height()  # get height of bar
    min_y = error[h]['min']  # use h to get min from dict z
    max_y = error[h]['max']  # use h to get max from dict z
    plt.vlines(x+w/2, min_y, max_y, color='k')  # draw a vertical line

ax.bar

fig, ax = plt.subplots(figsize=(8, 6))

ax.bar(x='Delay', height='Time', data=df)

for p in ax.patches:
    x = p.get_x()  # get the bottom left x corner of the bar
    w = p.get_width()  # get width of bar
    h = p.get_height()  # get height of bar
    min_y = error[h]['min']  # use h to get min from dict z
    max_y = error[h]['max']  # use h to get max from dict z
    plt.vlines(x+w/2, min_y, max_y, color='k')  # draw a vertical line
0

You can use yerr arg of plt.bar directly. Using @Trenton McKinney's code for an example:

import pandas as pd
import matplotlib.pyplot as plt

# setup the dataframe
Delay = ['S1', 'S2', 'S3', 'S4']

Time = [87, 66, 90, 55]
_min = [60, 40, 80, 23]
_max = [90, 70, 93, 60]
df = pd.DataFrame({'Delay':Delay,'Time':Time, 'Min': _min, 'Max': _max})
df = (df.assign(yerr_min = df.Time-df.Min)
        .assign(yerr_max=df.Max-df.Time))

plt.figure(figsize=(8, 6))
plt.bar(x='Delay', height='Time', yerr=df[['yerr_min', 'yerr_max']].T.values, capsize=10, data=df)

plt.show()
0

Here's a solution using yerr and numpy. It has less boilerplate code than @gepcel's.

import matplotlib.pyplot as plt
import numpy as np

Delay = ['S1', 'S2', 'S3', 'S4']   # Categories

Time = [87, 66, 90, 55]
_min = [60, 40, 80, 23]
_max = [90, 70, 93, 60]

plt.figure(figsize=(8, 6))

yerr = [np.subtract(Time, _min), np.subtract(_max, Time)]
plt.bar(Delay, Time, yerr=yerr, capsize=10)

plt.show()

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

David Miller

David Miller

Executive Financial & Market Analyst

David Miller brings 15 years of experience in global economics, personal finance strategy, and market dynamics. He specializes in turning complex economic trends into actionable insights for everyday readers.

Share this article
Twitter Facebook Pinterest