4

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:

enter image description here

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.

smci
  • 32,567
  • 20
  • 113
  • 146
Vincent
  • 219
  • 2
  • 5
  • 13

3 Answers3

7
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

enter image description here

  • 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)

enter image description here

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

enter image description here

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

enter image description here

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

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()

enter image description here

Trenton McKinney
  • 56,955
  • 33
  • 144
  • 158
gepcel
  • 1,326
  • 11
  • 21
3

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()

min max bar

Sadman Sakib
  • 360
  • 2
  • 8