Pandas, Bar Chart Annotations - pandas

How to properly give Annotations to Pandas Bar Charts?
I'm following Bar Chart Annotations with Pandas and MPL, but somehow I can't make it into my own code -- this is as far as I can go. What's wrong?
I've also found the following code from here:
def autolabel(rects):
# attach some text labels
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)
But I don't how to apply that to my code either. Please help.
UPDATE:
Thank you #CT Zhu, for the answer. However, in your horizontal bars, you are still placing the text on top of bars, but I need the text show up within or along them, like this from my referenced article,
where s/he says,
"I am very parital to horizontal bar charts, as I really think they are easier to read, however, I understand that a lot of people would rather see this chart implemented in a regular bar chart. So, here is the code to do that; you will notice that a few things have changed in order to create the annotation"*

It appears your autolabel function is expecting a list of patches, sssuming your plot only those bars as its patches, we could do:
df = pd.DataFrame({'score':np.random.randn(6),
'person':[x*3 for x in list('ABCDEF')]})
def autolabel(rects):
x_pos = [rect.get_x() + rect.get_width()/2. for rect in rects]
y_pos = [rect.get_y() + 1.05*rect.get_height() for rect in rects]
#if height constant: hbars, vbars otherwise
if (np.diff([plt.getp(item, 'width') for item in rects])==0).all():
scores = [plt.getp(item, 'height') for item in rects]
else:
scores = [plt.getp(item, 'width') for item in rects]
# attach some text labels
for rect, x, y, s in zip(rects, x_pos, y_pos, scores):
ax.text(x,
y,
'%s'%s,
ha='center', va='bottom')
ax = df.set_index(['person']).plot(kind='barh', figsize=(10,7),
color=['dodgerblue', 'slategray'], fontsize=13)
ax.set_alpha(0.8)
ax.set_title("BarH")#,fontsize=18)
autolabel(ax.patches)
ax = df.set_index(['person']).plot(kind='bar', figsize=(10,7),
color=['dodgerblue', 'slategray'], fontsize=13)
ax.set_alpha(0.8)
ax.set_title("Bar")#,fontsize=18)
autolabel(ax.patches)

Related

Some matplotlib colorbars disappear when colorbar axes are moved

I am using the following lines of python code to create a figure with multiple subplots in a Jupiter notebook and attempting to add colorbars to some of the plots. The following lines are 1 of 7 sections copied and pasted with adjustments to GridSpec, variables, labels and axes handles made for each:
fig = plt.figure(figsize=(20,20))
gs = gridspec.GridSpec(21, 13)
...
if i >= 1:
ax3 = plt.subplot(gs[6:9, 3*i+1:3*i+4],projection=ccrs.Robinson())
else:
ax3 = plt.subplot(gs[6:9, 3*i:3*i+3],projection=ccrs.Robinson())
if i == 0:
cs3 = ax3.contourf(Lon,lat,cldhgh.squeeze(),12,transform=ccrs.PlateCarree(),cmap='gist_gray',vmin=0,vmax=1)
ax3.coastlines()
Cticks=np.around(np.linspace(0,1,6),decimals=1)
Cbar_ax3 = fig.add_axes([0.3,0.58,0.01,0.10])
cb3 = fig.colorbar(cs3, spacing='proportional',orientation='vertical',cax=Cbar_ax3,ticks=Cticks)
#cb2.set_ticklabels(Cticks.astype(int).astype(str),fontsize=7)
cb3.set_ticklabels(Cticks.astype(str),fontsize=12)
cb3.set_label('High Cloud Fraction',fontsize=10)
else:
cs3 = ax3.contourf(Lon,lat,delta_cldhgh,61,transform=ccrs.PlateCarree(),cmap='BrBG',vmin=-0.2,vmax=0.2)
c3 = ax3.contour(Lon,lat,cldhgh.squeeze(),12,vmin=0,vmax=1,colors='black',linewidths=0.5)
ax3.coastlines()
if i == 1:
cticks=np.around(np.linspace(-0.2,0.2,5),decimals=1)
cbar_ax = fig.add_axes([1.02,0.58,0.01,0.10])
ax3.set_ylabel('Hybrid Sigma-Pressure level (mb)',fontsize=12)
#cb = fig.colorbar(cs, spacing='proportional',orientation='vertical',cax=cbar_ax,ticks=cticks)
cb3 = fig.colorbar(mappable=None, norm=Normalize(vmin=-0.2,vmax=0.2), cmap='BrBG',spacing='proportional',orientation='vertical',cax=cbar_ax,ticks=cticks)
cb3.set_ticklabels(cticks.astype(str),fontsize=12)
#cb2.set_ticklabels(cticks.astype(int).astype(str),fontsize=10)
cb3.set_label('Cloud Fraction Difference',fontsize=10)
...
plt.suptitle('Comparison of mappables of Background Climate States',fontsize=24,y=1.01)
#fig.text(-0.04, 0.5, 'Sigma Pressure Level (mb)', va='center', rotation='vertical')
fig.tight_layout(pad=0.2)
plt.show()
fig.savefig(figure_path+'Reference_Climate_Comparison_of_Mappables.pdf',bbox_inches='tight')
I am able to almost do this successfully, except the original guess I made for the x displacement of my colorbars on the left side of the figure was too large:
To fix this I simply adjusted the first index of each subplot's "Cbar_ax" variable to be slightly smaller (e.g. from 0.3 to 0.25):
Cbar_ax3 = fig.add_axes([0.25,0.58,0.01,0.10])
The adjustment works for some subplots, but for others the colorbars all but vanish:
I have no idea how to solve this problem. I can make the colorbars appear using plt.colorbar() instead of fig.colorbar() without an colorbar axes designation, but the subplots themselves are not a consistent size with the rest of the figure (since plt.colorbar steals axes space from it's parent axes by default). What am I not seeing here? Why do some of these colorbars disappear when I move them?

Matplotlib: Multiple plots with same layout (no automatic layout)

I am trying to make several pie charts that I can then transition between in a presentation. For this, it would be very useful for the automatic layouting to... get out of the way. The problem is that whenever I change a label, the whole plot moves around on the canvas so that it fits perfectly. I'd like the plot to stay centered, so it occupies the same area every time. I have tried adding center=(0,0) to ax.pie(), but to no avail.
Two examples:
Image smaller, left
Image larger, right
Instead of that effect, I'd like the pie chart to be in the middle of the canvas and have the same size in both cases (and I'd then manually make sure that the labels are on canvas by setting large margins).
The code I use to generate these two images is:
import matplotlib.pyplot as plt
import numpy as np
# Draw labels, from
# https://matplotlib.org/3.2.2/gallery/pie_and_polar_charts/pie_and_donut_labels.html#sphx-glr-gallery-pie-and-polar-charts-pie-and-donut-labels-py
def make_labels(ax, wedges, labs):
bbox_props = dict(boxstyle="square,pad=0.3", fc="w", ec="k", lw=0.72)
kw = dict(arrowprops=dict(arrowstyle="-"),
bbox=bbox_props,
zorder=0, va="center")
for i, p in enumerate(wedges):
if p.theta2-p.theta1 < 5:
continue
ang = (p.theta2 - p.theta1) / 2. + p.theta1
y = np.sin(np.deg2rad(ang))
x = np.cos(np.deg2rad(ang))
horizontalalignment = {-1: "right", 1: "left"}[int(np.sign(x))]
connectionstyle = "angle,angleA=0,angleB={}".format(ang)
kw["arrowprops"].update({"connectionstyle": connectionstyle})
ax.annotate(labs[i], xy=(x, y),
xytext=(1.1*x,1.1*y),
horizontalalignment=horizontalalignment, **kw)
kw=dict(autoscale_on=False, in_layout=False, xmargin=1, ymargin=1)
fig, ax = plt.subplots(figsize=(3, 3), dpi=100, subplot_kw=kw)
wedges, texts = ax.pie(x=[1,2,3], radius=1,
wedgeprops=dict(width=1),
pctdistance=0.7,
startangle=90,
textprops=dict(fontsize=8),
center=(0, 0))
make_labels(ax, wedges, ["long text", "b", "c"])
#make_labels(ax, wedges, ["a", "b", "long text"])
plt.show()
Thanks a lot in advance!
How are you saving your figures? It looks like you may be using savefig(..., bbox_inches='tight') which automatically resized the figure to include all the artists.
If I run your code with fig.savefig(..., bbox_inches=None), I get the following output

Horizontal Alignment of Ipywidgets / Interactive plotting

The goal ist to:
plot data based on a dropdown value
have multiple plots based on the same value
align them in a row (horizontal).
In my case the interactive_plot is the box with all plots in it, therefore I can't style them how I want it. How do I plot the two plots in one line?
def showMADetails(column=filter_unique):
plt.figure(1)
filtered_ma = ma_data[(ma_data['2'] == column)]
plt.bar(column, filtered_ma['GrossTurnoverBudget'], align='center', alpha=0.5)
plt.figure(2)
filtered_ma = ma_data[(ma_data['2'] == column)]
plt.bar(column, filtered_ma['Productive billableDays'], align='center', alpha=0.5)
interactive_plot = interactive(showMADetails)
output = interactive_plot.children[1]
output.layout.height = '400px'
output.layout.width = '200px'
interactive_plot
figured out that I was wrong. The plots aren't widgets, so I followed a wrong approach.
The interactive_plot controls the UI of the widgets.
To control the plots itself I need to control the layout via subplots. That is the solution.

Graphics issues when combining matplotlib widgets: Spanselector, cursor, fill_between:

I have found minor graphical issues while using the spanselector, cursor and fill_between widgets, which I would like to share with you.
All of them, can be experienced in this code (which I took from the matplolib example)
"""
The SpanSelector is a mouse widget to select a xmin/xmax range and plot the
detail view of the selected region in the lower axes
"""
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import SpanSelector
import matplotlib.widgets as widgets
Fig = plt.figure(figsize=(8,6))
Fig.set_facecolor('w')
Fig.set
Ax = Fig.add_subplot(211)
x = np.arange(0.0, 5.0, 0.01)
y = np.sin(2*np.pi*x) + 0.5*np.random.randn(len(x))
Ax.plot(x, y, '-')
Ax.set_ylim(-2,2)
Ax.set_title('Press left mouse button and drag to test')
RegionIndices = []
ax2 = Fig.add_subplot(212)
line2, = ax2.plot(x, y, '-')
def onselect(xmin, xmax):
if len(RegionIndices) == 2:
Ax.fill_between(x[:], 0.0, y[:],facecolor='White',alpha=1)
del RegionIndices[:]
indmin, indmax = np.searchsorted(x, (xmin, xmax))
indmax = min(len(x)-1, indmax)
Ax.fill_between(x[indmin:indmax], 0.0, y[indmin:indmax],facecolor='Blue',alpha=0.30)
thisx = x[indmin:indmax]
thisy = y[indmin:indmax]
line2.set_data(thisx, thisy)
ax2.set_xlim(thisx[0], thisx[-1])
ax2.set_ylim(thisy.min(), thisy.max())
Fig.canvas.draw()
RegionIndices.append(xmin)
RegionIndices.append(xmax)
# set useblit True on gtkagg for enhanced performance
span = SpanSelector(Ax, onselect, 'horizontal', useblit = True,rectprops=dict(alpha=0.5, facecolor='purple') )
cursor = widgets.Cursor(Ax, color="red", linewidth = 1, useblit = True)
plt.show()
I wonder if there is some way to avoid these two small issues:
1) You can see that when you select a region the spanselector box (purple) glitches. In this code the effect is barely noticeable but on plots with many lines is quite annoying (I have tried all the trueblit combinations to not effect)
2) In this code when you select a region, the area in the upper plot between the line and the horizontal axis is filled in blue. When you select a new region the old area is filled in white (to clear it) and the new one is filled with blue again. However, when I do that the line plotted, as well as, the horizontal axis, become thicker... Is there a way to clear such a region (generated with fill_between) without this happening... Or is it necessary to replot the graph? Initially, I am against doing this since I have a well structured code and importing all the data again into the spanselector method seems a bit messy... Which is the right way in python to delete selected regions of a plot?
Any advice would be most welcome

small scatter plot markers in matplotlib are always black

I'm trying to use matplotlib to make a scatter plot with very small gray points. Because of the point density, the points need to be small. The problem is that the scatter() function's markers seem to have both a line and a fill. When the markers are small, only the line is visible, not the fill, and the line isn't the right colour (it's always black).
I can get exactly what I want using gnuplot: plot 'nodes' with points pt 0 lc rgb 'gray'
How can I make very small gray points using matplotlib scatterplot()?
scatter([1,2,3], [2,4,5], s=1, facecolor='0.5', lw = 0)
This sets the markersize to 1 (s=1), the facecolor to gray (facecolor='0.5'), and the linewidth to 0 (lw=0).
If the marker has no face (cannot be filled, e.g. '+','x'), then the edgecolor has to be set instead of c, and lw should not be 0:
scatter([1,2,3], [2,4,5], marker='+', edgecolor='r')
The following will no work
scatter([1,2,3], [2,4,5], s=1, marker='+', facecolor='0.5', lw = 0)
because the edge/line will not be displayed, so nothing will be displayed.
The absolute simplest answer to your question is: use the color parameter instead of the c parameter to set the color of the whole marker.
It's easy to see the difference when you compare the results:
from matplotlib import pyplot as plt
plt.scatter([1,2,3], [3,1,2], c='0.8') # marker not all gray
plt.scatter([1,2,3], [3,1,2], color='0.8') # marker all gray
Details:
For your simple use case where you just want to make your whole marker be the same shade of gray color, you really shouldn't have to worry about things like face color vs edge color, and whether your marker is defined as all edges or some edges and some fill. Instead, just use the color parameter and know that your whole marker will be set to the single color that you specify!
In response to zwol's question in comment - my reputation is not high enough to leave comments, so this will have to do: In the event that your colors come from a colormap (i.e., are from a "sequence of values to be mapped") you can use color = as demonstrated in the following:
from matplotlib import pyplot
x = [1,5,8,9,5]
y = [4,2,4,7,9]
numSides = [2,3,1,1,5]
cmap = pyplot.cm.get_cmap("copper_r")
min, max = min(numSides), max(numSides)
for i in range(len(x)):
if numSides[i] >= 2:
cax = pyplot.scatter(x[i], y[i], marker = '+', s = 100, c = numSides[i], cmap = cmap)
cax.set_clim(min, max)
elif numSides[i] == 1:
pyplot.scatter(x[i], y[i], marker = '.', s = 40, color = cmap(numSides[i]))
fig = pyplot.gcf()
fig.set_size_inches(8.4, 6)
fig.savefig('figure_test.png', dpi = 200)
pyplot.show()