I wanted to display only half error bars, as they are symetric ; as I had no clue how to do this with a "clean way", I chose to use asymetric errors with 0 on the bottom side ; but then, when I displayed caps, I realised this was not the best way to do this.
Here's the code :
import numpy as np
import matplotlib.pyplot as plt
N = 5
men_means = (20, 35, 30, 35, 27)
men_std = (2, 3, 4, 1, 2)
ind = np.arange(N)
width = 0.35
fig, ax = plt.subplots()
rects1 = ax.bar(ind, men_means, width, color='r',yerr=[np.zeros(len(men_std)),men_std],capsize = 5)
women_means = (25, 32, 34, 20, 25)
women_std = (3, 5, 2, 3, 3)
rects2 = ax.bar(ind + width, women_means, width, color='y',yerr=[np.zeros(len(women_std)),women_std],capsize = 5)
plt.show()
And this is the plot I get :. As you can see, my way of plotting half error bars is probably not what should be done.
So is there any way to hide the bottom cap line or a better way to plot half error bars ?
ax.errorbar has the option to set uplims=True or lolims=True to signify that the means repesent the upper or lower limits, respectively. Unfortunately, it doesn't seem like you can use these options directly with ax.bar, so we have to plot the errorbar and the bar plot separately.
The documentation for the uplims/lolims options in ax.errorbar:
lolims / uplims / xlolims / xuplims : bool, optional, default:None
These arguments can be used to indicate that a value gives only upper/lower limits. In that case a caret symbol is used to indicate this. lims-arguments may be of the same type as xerr and yerr. To use limits with inverted axes, set_xlim() or set_ylim() must be called before errorbar().
Note that using this option changes your caps to arrows. See below for an example of how to change them back to caps, if you need flat caps instead of arrows.
You can see these options in action in this example on the matplotlib website.
Now, here's your example, modified:
import numpy as np
import matplotlib.pyplot as plt
N = 5
men_means = (20, 35, 30, 35, 27)
men_std = (2, 3, 4, 1, 2)
ind = np.arange(N)
width = 0.35
fig, ax = plt.subplots()
rects1 = ax.bar(ind, men_means, width, color='r')
err1 = ax.errorbar(ind, men_means, yerr=men_std, lolims=True, capsize = 0, ls='None', color='k')
women_means = (25, 32, 34, 20, 25)
women_std = (3, 5, 2, 3, 3)
rects2 = ax.bar(ind + width, women_means, width, color='y')
err2 = ax.errorbar(ind + width, women_means, yerr=women_std, lolims=True, capsize = 0, ls='None', color='k')
plt.show()
If you don't like the arrows, you change them to flat caps, by changing the marker of the caplines that are returned (as the second item) from ax.errorbar. We can change them from the arrows to the marker style _, and then control their size with .set_markersize:
import numpy as np
import matplotlib.pyplot as plt
N = 5
men_means = (20, 35, 30, 35, 27)
men_std = (2, 3, 4, 1, 2)
ind = np.arange(N)
width = 0.35
fig, ax = plt.subplots()
rects1 = ax.bar(ind, men_means, width, color='r')
plotline1, caplines1, barlinecols1 = ax.errorbar(
ind, men_means, yerr=men_std, lolims=True,
capsize = 0, ls='None', color='k')
caplines1[0].set_marker('_')
caplines1[0].set_markersize(20)
women_means = (25, 32, 34, 20, 25)
women_std = (3, 5, 2, 3, 3)
rects2 = ax.bar(ind + width, women_means, width, color='y')
plotline2, caplines2, barlinecols2 = ax.errorbar(
ind + width, women_means, yerr=women_std,
lolims=True, capsize = 0, ls='None', color='k')
caplines2[0].set_marker('_')
caplines2[0].set_markersize(10)
plt.show()
A simpler solution is to use zorder. The grid has zorder=0. Setting the errorbar to zorder=1 and the bar to zorder=2and lowering the bottom error a bit will hide the lower error bar with little effort. This also allows to use bar_label. The only downside is if alpha is used for the bars.
I also changed to use np.zeros_like(std) instead of np.zeros(len(std)) and use error_kw to style the errorbar.
import numpy as np
import matplotlib.pyplot as plt
N = 5
ind = np.arange(N)
width = 0.8
fig, ax = plt.subplots()
women_means = (25, 32, 34, 20, 25)
women_std = (3, 5, 2, 3, 3)
rects2 = ax.bar(ind + width, women_means, width, color='y',yerr=[np.zeros_like(women_std)+0.5,women_std],zorder=2,error_kw=dict(capsize = 10, capthick=1,zorder=1))
ax.bar_label(rects2,labels=[f'{v:.2f} ± {e:.2f}' for v,e in zip(women_means, women_std)], padding=10, fontsize=14, label_type='edge')
fig.tight_layout()
plt.show()
Many thanks,
based on your answer, method below is my solution:
def process_error_bar(ax, x, y, y_err, marker_size):
"""
hide half error_bar
:param ax: plt.subplots()
:param x: x position
:param y: y position
:param y_err: y errors
:param marker_size: size
"""
lolims = []
uplims = []
for y_value in y:
if y_value < 0:
lolims.append(False)
uplims.append(True)
else:
lolims.append(True)
uplims.append(False)
plotline, caplines, barlinecols = ax.errorbar(
x, y, yerr=y_err, lolims=lolims, uplims=uplims,
capsize=0, ls='None', color='k')
# [arrow] -> [-]
for capline in caplines:
capline.set_marker('_')
capline.set_markersize(marker_size)
Related
Why doesn't zorder work in this case? I've tried using it but the text still ends up being covered by the bar plot towers.
import numpy as np
from matplotlib import pyplot as plt
Percentage_Differences_1 = np.array([ [7.94*(10**-10),7.94*(10**-9),7.94*(10**-8),7.94*(10**-7),7.94*(10**-6),7.94*(10**-5)],
[7.92*(10**-12),7.92*(10**-11),7.92*(10**-10),7.92*(10**-9),7.92*(10**-8),7.92*(10**-7)],
[7.72*(10**-14),7.72*(10**-13),7.72*(10**-12),7.72*(10**-11),7.72*(10**-10),7.72*(10**-9)],
[5.66*(10**-16),5.66*(10**-15),5.66*(10**-14),5.66*(10**-13),5.66*(10**-12),5.66*(10**-11)],
[1.49*(10**-17),1.49*(10**-16),1.49*(10**-15),1.49*(10**-14),1.49*(10**-13),1.49*(10**-12)],
[2.21*(10**-18),2.21*(10**-17),2.21*(10**-16),2.21*(10**-15),2.21*(10**-14),2.21*(10**-13)] ]) # Layer 1, 12
fig1 = plt.figure(dpi = 120, tight_layout = True)
fig1.set_size_inches(10, 7)
ax1 = fig1.add_subplot(111, projection='3d')
width = depth = 0.3
column_names = ['$10^{-6} m$','$10^{-5} m$','$10^{-4} m$','$10^{-3} m$','$10^{-2} m$','$10^{-1} m$']
row_names = ['$10^{-6} g$','$10^{-5} g$','$10^{-4} g$','$10^{-3} g$','$10^{-2} g$','$10^{-1} g$']
height_names = ['$10^{-2}$','$10^{-4}$','$10^{-6}$','$10^{-8}$','$10^{-10}$','$10^{-12}$','$10^{-14}$','$10^{-16}$','$10^{-18}$']
for x in range(0,6):
for y in range(0,6):
plot1 = ax1.bar3d(x, y, 0, width, depth, np.log10(Percentage_Differences_1[x][y]), color = "#0040bf", alpha=0.3, zorder = 1)
txt1 = ax1.text(x,y,1.15*np.log10(Percentage_Differences_1[x][y]),'{:.2e}'.format(Percentage_Differences_1[y][x]), verticalalignment='top', bbox=dict(facecolor='grey', alpha=0.5), zorder = 2)
ax1.view_init(-140, -30)
ax1.set_xticks(np.linspace(0, 6, num = 6))
ax1.set_yticks(np.linspace(0, 6, num = 6))
ax1.set_xticklabels(column_names)
ax1.set_yticklabels(row_names)
ax1.set_zticklabels(height_names)
ax1.set_xlabel("Mass", labelpad = 13, rotation = 45)
ax1.set_ylabel("Radius", labelpad = 10, rotation = 45)
ax1.set_zlabel("Deviation $\Delta$")
ax1.set_title("1st Initial Condition: $r(0)$ and $r'(0)$ of $\Theta(12) = 2.18 \\times 10^{7} m$", pad = 40)
plt.show()
I've tried using both set_zorder and zorder but the plot still ends up covering the majority of the text labels.
Change your zorder for a number larger than the number of bar objects, 100 for example:
Edit:
I tried the following.
auto_y_ticks=list(axes2.get_yticklabels())
But still the output is not a list of tick values. It shows as Matplotlib text.
The following code produces bar and line plot in the same graph.
In the secondary y axis, the ytick values range from -1 to +1.
My question is, how do I store these values in a list?
from matplotlib import pyplot as plt
import numpy as np
plt.figure()
N = 5
menMeans = (20, 35, 30, 35, 27)
menStd = (2, 3, 4, 1, 2)
width = 0.35 # the width of the bars
womenMeans = (25, 32, 34, 20, 25)
womenStd = (3, 5, 2, 3, 3)
ind = np.arange(N)
plt.ylim(0.0, 65.0)
plt.bar(ind, menMeans, width, color='r', yerr=menStd, label='Men means')
plt.bar(ind+width, womenMeans, width, color='y', yerr=womenStd, label='Women means')
plt.ylabel('Bar plot')
x = np.linspace(0, N)
y = np.sin(x)
axes2 = plt.twinx()
axes2.plot(x, y, color='k', label='Sine')
axes2.set_ylim(-1, 1)
axes2.set_ylabel('Line plot')
plt.show()
auto_y_ticks=list(What's the logic)
Starting from your code,
axes2.get_yticks()
gives
array([-1. , -0.75, -0.5 , -0.25, 0. , 0.25, 0.5 , 0.75, 1. ])
Which is what you're after, right?
This question already has answers here:
Annotate bars with values on Pandas bar plots
(4 answers)
Closed 2 years ago.
I am new in Jupyer Notebook/Python. Can i ask what script should I add if I want my numbers, to be also placed on top of the bars?
please see image below.
If you are looking for something like this...
here is a sample code taken from this site
import numpy as np
import matplotlib.pyplot as plt
N = 5
men_means = (20, 35, 30, 35, 27)
men_std = (2, 3, 4, 1, 2)
ind = np.arange(N) # the x locations for the groups
width = 0.35 # the width of the bars
fig, ax = plt.subplots()
rects1 = ax.bar(ind, men_means, width, color='r', yerr=men_std)
women_means = (25, 32, 34, 20, 25)
women_std = (3, 5, 2, 3, 3)
rects2 = ax.bar(ind + width, women_means, width, color='y', yerr=women_std)
# add some text for labels, title and axes ticks
ax.set_ylabel('Scores')
ax.set_title('Scores by group and gender')
ax.set_xticks(ind + width / 2)
ax.set_xticklabels(('G1', 'G2', 'G3', 'G4', 'G5'))
ax.legend((rects1[0], rects2[0]), ('Men', 'Women'))
def autolabel(rects):
"""
Attach a text label above each bar displaying its height
"""
for rect in rects:
height = rect.get_height()
ax.text(rect.get_x() + rect.get_width()/2., 1.05*height,
'%d' % int(height),
ha='center', va='bottom')
autolabel(rects1)
autolabel(rects2)
plt.show()
I am following the example code here to produce a bar chart. I just want to add a superscript \textdagger(†) to the last group name "G5". Here is what I tried (check the line ax.set_xticklabels):
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
N = 5
men_means = (20, 35, 30, 35, 27)
men_std = (2, 3, 4, 1, 2)
ind = np.arange(N) # the x locations for the groups
width = 0.35 # the width of the bars
matplotlib.rc('text', usetex = True)
fig, ax = plt.subplots()
rects1 = ax.bar(ind, men_means, width, color='r', yerr=men_std)
women_means = (25, 32, 34, 20, 25)
women_std = (3, 5, 2, 3, 3)
rects2 = ax.bar(ind + width, women_means, width, color='y', yerr=women_std)
# add some text for labels, title and axes ticks
ax.set_ylabel('Scores')
ax.set_title('Scores by group and gender')
ax.set_xticks(ind + width)
ax.set_xticklabels(('G1', 'G2', 'G3', 'G4', 'G5\textsuperscript{\textdagger}'))
ax.legend((rects1[0], rects2[0]), ('Men', 'Women'))
def autolabel(rects):
"""
Attach a text label above each bar displaying its height
"""
for rect in rects:
height = rect.get_height()
ax.text(rect.get_x() + rect.get_width()/2., 1.05*height,
'%d' % int(height),
ha='center', va='bottom')
autolabel(rects1)
autolabel(rects2)
plt.show()
But the output is totally messed up.
How can I get a proper superscript † for "G5" then?
Many tex commands can be used within matplotlib without the need to use tex itself; this is called MathText. Like tex commands, you would enclose MathText command with two dollar signs.
'G5$^\dagger$'
Now, even if you use tex (plt.rc('text', usetex = True)) the command stays the same:
'G5$^\dagger$'
Please refer the minimum working example here.
The bar chart is plotted as shown below but I can't find a way to increase the thickness of the error lines. The elinewidth option is not available in ax.bar
> rects1 = ax.bar(..., elinewidth=3)
AttributeError: Unknown property elinewidth
The following links require the use of ax.errorbar()
Change errorbar size
How to set the line width of error bar caps, in matplotlib?
But is there an option that can be supplied directly to ax.bar()?
You can use the error_kw parameters as follows:
error_kw=dict(lw=5, capsize=5, capthick=3)
So in the example, it would be:
import numpy as np
import matplotlib.pyplot as plt
N = 5
men_means = (20, 35, 30, 35, 27)
men_std = (2, 3, 4, 1, 2)
ind = np.arange(N) # the x locations for the groups
width = 0.35 # the width of the bars
fig, ax = plt.subplots()
rects1 = ax.bar(ind, men_means, width, color='r', yerr=men_std, error_kw=dict(lw=5, capsize=5, capthick=3))
women_means = (25, 32, 34, 20, 25)
women_std = (3, 5, 2, 3, 3)
rects2 = ax.bar(ind + width, women_means, width, color='y', yerr=women_std, error_kw=dict(lw=5, capsize=5, capthick=3))
# add some text for labels, title and axes ticks
ax.set_ylabel('Scores')
ax.set_title('Scores by group and gender')
ax.set_xticks(ind + width / 2)
ax.set_xticklabels(('G1', 'G2', 'G3', 'G4', 'G5'))
ax.legend((rects1[0], rects2[0]), ('Men', 'Women'))
def autolabel(rects):
"""
Attach a text label above each bar displaying its height
"""
for rect in rects:
height = rect.get_height()
ax.text(rect.get_x() + rect.get_width() / 2,
1.05 * height,
f'{height:.0f}',
ha='center', va='bottom')
autolabel(rects1)
autolabel(rects2)
plt.show()
Giving you:
An improvement to this would be to display the values above each of the error bars. This can be done by first obtaining the error bar heights as follows:
import numpy as np
import matplotlib.pyplot as plt
N = 5
men_means = (20, 35, 30, 35, 27)
men_std = (2, 3, 4, 1, 2)
ind = np.arange(N) # the x locations for the groups
width = 0.35 # the width of the bars
fig, ax = plt.subplots()
rects1 = ax.bar(ind, men_means, width, color='r', yerr=men_std, error_kw=dict(lw=5, capsize=5, capthick=3))
women_means = (25, 32, 34, 20, 25)
women_std = (3, 5, 2, 3, 3)
rects2 = ax.bar(ind + width, women_means, width, color='y', yerr=women_std, error_kw=dict(lw=5, capsize=5, capthick=3))
# add some text for labels, title and axes ticks
ax.set_ylabel('Scores')
ax.set_title('Scores by group and gender')
ax.set_xticks(ind + width / 2)
ax.set_xticklabels(('G1', 'G2', 'G3', 'G4', 'G5'))
ax.set_ylim(0, 45) # Add space for errorbar height
ax.legend((rects1[0], rects2[0]), ('Men', 'Women'))
def autolabel(rects):
"""
Attach a text label above each bar displaying its height
"""
data_line, capline, barlinecols = rects.errorbar
for err_segment, rect in zip(barlinecols[0].get_segments(), rects):
height = err_segment[1][1] # Use height of error bar
ax.text(rect.get_x() + rect.get_width() / 2,
1.05 * height,
f'{height:.0f}',
ha='center', va='bottom')
autolabel(rects1)
autolabel(rects2)
plt.show()
Giving you:
Note: The y axis limit would need to be calculated.