How can I fix the size of the last subplot? - matplotlib

I want to have 5x4 subplots, one for each group. I wrote the following code:
axeng = []
for i in range(5):
for ii in range(4):
axeng.append([i,ii])`
yy = (0.5, 4.5, 9.5, 14.5, 19.5, 24.5)
xx=np.arange(0.5,10)
f,axes = plt.subplots(5,4,figsize=(50,50), sharex=True, sharey=True)
cbar_ax = f.add_axes([.92, .3, .03, .4])
for i in range(20):
paxesrow = tuple(axeng[i])[0]
paxescol = tuple(axeng[i])[1]
# gnuplot, jet, YlGnBu, GnBu_r
g=sns.heatmap(heat[i],cmap="viridis",vmin=0.1,vmax=1,
ax=axes[paxesrow,paxescol],linewidth=.1,
cbar=True if i==3 else False,
cbar_ax=cbar_ax if i==3 else None,
square=False)
g.set_yticks(yy)
g.set_xticks(xx)
g.set_yticklabels([' ','25',' ','15',' ','5'],fontsize=33)
g.set_xticklabels([' ','2',' ','4',' ','6',' ','8',' ','10'],fontsize=33,rotation=0)
f.tight_layout(rect=[1, 1, 1, 1])
f.suptitle('Behavior of all subgroups',fontsize=70,y=.93)
cbar=axes[tuple(axeng[3])[0],tuple(axeng[3])[1]].collections[0].colorbar
cbar.ax.tick_params(labelsize=35)
plt.show()
As you can see in the image, the last subplot is scaled, but I have no idea why that would be the case.
Thanks in advance.

Related

probelm with subplots in matplotlib

I have the following code which works just fine:
plt.rcParams["figure.figsize"] = (5,5) # V1.0b
fig, axes = plt.subplots(ncols = 2, nrows = 2) # V1.0b
ax1, ax2, ax3, ax4 = axes.flatten()
plt.subplot(2, 2, 1)
ax1.plot(x1, y1)
ax1.plot(x2, y2)
(etc)
Exactly as expected, I get 2 plots in row 1, 2 plots in row 2.
Now, I want 2 rows by 3 cols and 4 plots (from exactly the same data):
plt.rcParams["figure.figsize"] = (6,4)
fig, axes = plt.subplots(ncols = 3, nrows = 2)
ax1, ax2, ax3, ax4 = axes.flatten()
plt.subplot(2, 3, 1)
ax1.plot(x1, y1)
(etc)
And I get an error from the line:
---> 12 ax1, ax2, ax3, ax4 = axes.flatten()
The error message is:
ValueError: too many values to unpack (expected 4)
Surely ax1, ax2, ax3, ax4 are the 4 values? But, evidently not; what's going wrong here?
I've found this works. As you say, no need for subplots:
figure, axis = plt.subplots(3, 3)
axis[0, 0]
axis[0, 0].set_title("NGC0628")
axis[0, 0].plot(x0,y0)
axis[0, 1]
axis[0, 0].plot(x1,y1)
axis[0, 2]
axis[0, 0].plot(x2,y2)
(etc)
BTW I need control over each plot, i.e. as in
axis[0, 0].set_title("NGC0628")
Thanks for the steer

Grid lines not aligned with cells when defining minor ticks in Matplotlib Heatmap

I am using the code below to generate this heatmap:
dim = np.arange(1, 32, 1)
fig, ax = plt.subplots(figsize=(7,9))
heatmap = ax.imshow(h.T, cmap=plt.cm.get_cmap('Blues', 4), clim=[1,144])
cbaxes = fig.add_axes([.8, .35, .04, .3])
cbar = fig.colorbar(heatmap, ticks = [1, 36, 72, 108, 144], label = 'Number of valid records per day', cax = cbaxes)
ax.set_ylabel("Days", fontsize=15)
ax.set_xlabel("Months", fontsize=15)
ax.set_title("Number of valid records per day", fontsize=20)
ax.set_yticks(range(0,31))
ax.set_yticklabels(dim, ha='center', minor=False, fontsize=12)
ax.set_xticks(range(0,13,1))
ax.set_xticklabels(ylabel, rotation = 45, ha = 'right')
ax.set_facecolor('gray')
cbar.set_label('Number of valid records')
ax.xaxis.set_minor_locator(MultipleLocator(0.5))
ax.yaxis.set_minor_locator(MultipleLocator(0.5))
ax.tick_params(axis='y', which='major', pad=10)
ax.grid(which = 'minor', color = 'w')
fig.show()
As you can see there is a slight offset of the gridlines with respect to the heat map cells. Why is that? How can I fix it?
Thanks to the comment left by Jody Klymak, I added the following line of code at the beginning of my notebook and it solved the problem:
matplotlib.rcParams['figure.dpi'] = 300

Matplotlib subplots how to align colorbars with other legends, or how to justify subplots to left

How can I add a colorbar scale to the 2nd & 3rd subplots, such that it is inline with my legends in the 1st and 4th subplots? Or, another way to say the question: how can I add a colorbar scale without changing the alignment/justification of the 2nd & 3rd subplots?
There are good examples available on setting colorbar locations (e.g., here on stackoverflow and in the matplotlib docs), but I still haven't been able to solve this.
Below is a reproducible example. The real data are more complicated, and this is part of a loop to produce many figures, so the "extra" stuff about setting axis limits and subplot aspect ratios is needed and will change with different datasets.
Using Python 3.8.
Reproducible example without colorbar
## Specify axes limits, tick intervals, and aspect ratio
xl, yl, xytick, ar = [-40000,120000], [-30000,10000], 20000, 0.8
## Global plot layout stuff
fig = plt.figure(figsize=(10, 7.5), constrained_layout=True)
gs = fig.add_gridspec(4, 1)
ax1 = fig.add_subplot(gs[0, 0])
ax2 = fig.add_subplot(gs[1, 0], sharex = ax1, sharey = ax1)
ax3 = fig.add_subplot(gs[2, 0], sharex = ax1)
ax4 = fig.add_subplot(gs[3, 0], sharex = ax1, sharey = ax3)
fig.execute_constrained_layout()
fig.suptitle('Suptitle')
## First Plot
ax1.plot([-30000, 500], [-2000, -21000], c='red', label='A')
ax1.plot([80000, 110000], [-9000, 800], c='blue', label='B')
ax1.set_title('ax1', style='italic');
ax1.set_xlabel('x');
ax1.set_ylabel('beta');
ax1.set_xlim(xl)
ax1.set_ylim(yl)
ax1.xaxis.set_major_locator(ticker.MultipleLocator(xytick))
ax1.yaxis.set_major_locator(ticker.MultipleLocator(xytick))
ax1.legend(handles=leg, bbox_to_anchor=(1.05, 1), loc='upper left')
ax1.set_aspect(aspect=ar)
## Dummy data for plots 2/3/4
x = [-15000, -2000, 0, 5000, 6000, 11000, 18000, 21000, 25000, 36000, 62000]
beta = [1000, 200, -800, 100, 1000, -2000, -5000, -5000, -15000, -21000, -1500]
y = [0.01, 0.2, 1.3, 0.35, 0.88, 2.2, 2.5, 1.25, 3.4, 4.1, 2.1]
## Second Plot
vals = ax2.scatter(x, beta, c=y, norm=mcolors.LogNorm(), cmap='rainbow')
ax2.set_title('ax2', style='italic');
ax2.set_xlabel('x');
ax2.set_ylabel('beta');
ax2.set_aspect(aspect=ar)
## Attempt to add colorbar
#cbar = fig.colorbar(vals, ax=ax2, format = '%1.2g', location='right', aspect=25)
#cbar.ax.set_ylabel('y')
#cbar.ax.yaxis.set_label_position('left')
#cbar_range = [min(y), max(y)]
#ticklabels = cbar.ax.get_ymajorticklabels()
#cbarticks = list(cbar.get_ticks())
#cbar.set_ticks(cbar_range + cbarticks)
## Third Plot
ax3.scatter(x, y, c=y, norm=mcolors.LogNorm(), cmap='rainbow')
ax3.set_title('ax3', style='italic');
ax3.set_xlabel('x');
ax3.set_ylabel('y');
ax3.yaxis.set_major_formatter(FormatStrFormatter('%1.2g'))
## Fourth Plot
ax4.scatter(x, y, c='black', label='Dots')
ax4.set_title('ax4', style='italic');
ax4.set_xlabel('x');
ax4.set_ylabel('y');
ax4.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
## Clean-up, set aspect ratios
figW, figH = ax1.get_figure().get_size_inches()
_, _, w, h = ax1.get_position().bounds
disp_ratio = (figH * h) / (figW * w)
data_ratio = sub(*ax3.get_ylim()) / sub(*ax3.get_xlim())
ax3.set_aspect(aspect=disp_ratio / data_ratio )
ax4.set_aspect(aspect=disp_ratio / data_ratio)
## Clean-up, turn axis ticks back on after messing with cbar
#ax1.tick_params(axis='both', which='both', labelbottom='on')
#ax2.tick_params(axis='both', which='both', labelbottom='on')
#ax3.tick_params(axis='both', which='both', labelbottom='on')
Result when trying colorbar, note misalignment of second plot
Suggest you simplify your code and make sure it all works; for instance I have no idea what sub does.
A partial solution to your problem could be panchor=False, which is a bit of an obscure kwarg, but...
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
## Specify axes limits, tick intervals, and aspect ratio
ar = 1.2
## Global plot layout stuff
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 4), constrained_layout=True, sharex=True, sharey=True)
## First Plot
ax1.plot([-20_000, 20_000], [-20_000, 20_000] )
ax1.set_aspect(aspect=ar)
## Dummy data for plots 2/3/4
x = [-15000, -2000, 0, 5000, 6000, 11000, 18000, 21000, 25000, 36000, 62000]
beta = [1000, 200, -800, 100, 1000, -2000, -5000, -5000, -15000, -21000, -1500]
y = [0.01, 0.2, 1.3, 0.35, 0.88, 2.2, 2.5, 1.25, 3.4, 4.1, 2.1]
## Second Plot
vals = ax2.scatter(x, beta, c=y, norm=mcolors.LogNorm(), cmap='rainbow')
ax2.set_aspect(aspect=ar)
cbar = fig.colorbar(vals, ax=ax2, format = '%1.2g', location='right',
aspect=25, panchor=False)
plt.show()
Depending on the size of the figure, this could comically place the colorbar far to the right. The problem here is the aspect ratio of your plots, which makes the actual axes more narrow than the figure. But the colorbar doesn't really know about that, and places itself on the outside of the space allocated for the axes.
If this is displeasing, then you can also specify an inset axes for the colorbar.
cbax = ax2.inset_axes([1.05, 0.2, 0.05, 0.6], transform=ax2.transAxes)
cbar = fig.colorbar(vals, cax=cbax, format = '%1.2g', orientation='vertical')
Using inset_axes() solves this, as suggested in the other answer, but the parameters relative to the transform were not explained in the example, but I was able to figure it out with some research.
The parameters in inset_axes are [x-corner, y-corner, width, height] and the transform is like a local reference. So, using [1,0,0.5,0.75] means: x = 100% or end of parent ax; y = 0% or bottom of parent ax; width = 50% of parent ax; and height = 75% of parent ax.
Here I wanted the colorbar to be the same height as the parent ax (ax2 and ax3), very thin, and offset a little bit to be more in line with the other legends. Using cbax = ax2.inset_axes([1.1, 0, 0.03, 1], transform=ax2.transAxes) achieves this.
This code works for any aspect ratio ar.
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import matplotlib.colors as mcolors
from operator import sub
%matplotlib inline
plt.style.use('seaborn-whitegrid')
## Specify axes limits, tick intervals, and aspect ratio
xl, yl, ar = [-40000,120000], [-30000,10000], .5
## Global plot layout stuff
fig = plt.figure(figsize=(10, 7.5), constrained_layout=True)
gs = fig.add_gridspec(4, 1)
ax1 = fig.add_subplot(gs[0, 0])
ax2 = fig.add_subplot(gs[1, 0], sharex = ax1, sharey = ax1)
ax3 = fig.add_subplot(gs[2, 0], sharex = ax1)
ax4 = fig.add_subplot(gs[3, 0], sharex = ax1, sharey = ax3)
fig.execute_constrained_layout()
fig.suptitle('Suptitle')
## First Plot
ax1.plot([-30000, 500], [-2000, -21000], c='red', label='A')
ax1.plot([80000, 110000], [-9000, 800], c='blue', label='B')
ax1.set_title('ax1', style='italic');
ax1.set_xlim(xl)
ax1.set_ylim(yl)
ax1.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
ax1.set_aspect(aspect=ar)
## Dummy data for plots 2/3/4
x = [-15000, -2000, 0, 5000, 6000, 11000, 18000, 21000, 25000, 36000, 62000]
beta = [1000, 200, -800, 100, 1000, -2000, -5000, -5000, -15000, -21000, -1500]
y = [0.01, 0.2, 1.3, 0.35, 0.88, 2.2, 2.5, 1.25, 3.4, 4.1, 2.1]
## Second Plot
vals = ax2.scatter(x, beta, c=y, norm=mcolors.LogNorm(), cmap='rainbow')
ax2.set_title('ax2', style='italic');
ax2.set_aspect(aspect=ar)
cbax = ax2.inset_axes([1.1, 0, 0.03, 1], transform=ax2.transAxes)
cbar2 = fig.colorbar(vals, cax=cbax, format = '%1.2g', orientation='vertical')
## Third Plot
ax3.scatter(x, y, c=y, norm=mcolors.LogNorm(), cmap='rainbow')
ax3.set_title('ax3', style='italic');
cbax = ax3.inset_axes([1.1, 0, 0.03, 1], transform=ax3.transAxes)
cbar3 = fig.colorbar(vals, cax=cbax, format = '%1.2g', orientation='vertical')
## Fourth Plot
ax4.scatter(x, y, c='black', label='Dots')
ax4.set_title('ax4', style='italic');
ax4.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
## Clean-up, set aspect ratios
figW, figH = ax1.get_figure().get_size_inches()
_, _, w, h = ax1.get_position().bounds
disp_ratio = (figH * h) / (figW * w)
data_ratio = sub(*ax3.get_ylim()) / sub(*ax3.get_xlim())
ax3.set_aspect(aspect=disp_ratio / data_ratio )
ax4.set_aspect(aspect=disp_ratio / data_ratio)
## Colorbars
cbar2.ax.set_ylabel('y')
cbar2.ax.yaxis.set_label_position('left')
cbar3.ax.set_ylabel('y')
cbar3.ax.yaxis.set_label_position('left')
Result with aspect ratio = 0.5 for top 2 plots
Result with aspect ratio = 2 for top 2 plots

Text in matplotlib subplots in wrong positions

I'm trying to get some text into a matplotlib subplot, but I get it into weird locations.
The code I use is like the following:
limSX_list = [0, 0, 0]
limDX_list = [10, 10, 10]
fig, axs = plt.subplots(3, 3)
for xxx in range(3):
for yyy in range(3):
if xxx==yyy:
#axs[xxx,yyy].hist( Deltas_list[xxx], range=[limSX_list[xxx], limDX_list[xxx]], bins=100, color='red' )
axs[xxx,yyy].hist( Deltas_list[xxx], bins=100, color='red' )
else:
#axs[xxx,yyy].hist2d( Deltas_list[xxx], Deltas_list[yyy], bins=(100, 100), cmap=plt.cm.viridis, range=[[limSX_list[xxx],limDX_list[xxx]],[limSX_list[yyy],limDX_list[yyy]]], norm=LogNorm() )
axs[xxx,yyy].hist2d( Deltas_list[xxx], Deltas_list[yyy], bins=(100, 100), cmap=plt.cm.viridis, norm=LogNorm() )
for row in range(3):
for col in range(3):
axs[row, col].text(0.5, 0.5, str((row, col)),color='blue', fontsize=18, ha='center')
plt.show()
And I get this weird output:
And if I set a x/y range (i.e. I uncomment the commented lines, and comment the ones below), I get the text in a different, but still wrong, position.
If I try with an even more minimal code, like the following, instead, everything goes fine:
fig, ax = plt.subplots(rows, cols)
for row in range(3):
for col in range(3):
ax[row, col].text(0.5, 0.5, str((row, col)), color='blue', fontsize=18, ha='center')
plt.show()
Any guess why? Thanks! :)

matplotlib long legend name

I have a following code which produces a graph -
# imports specific to the plots in this example
import sys
import numpy as np
from matplotlib import cm
import matplotlib.pyplot as plt
resultsDirectory='results'
outputFile=resultsDirectory+".pdf"
axisLabelFontSize=16
borderWidth=0.0
# Twice as wide as it is tall.
fig = plt.figure(figsize=plt.figaspect(0.5))
ax = fig.add_subplot(111)
# Set up the Grid
[i.set_linewidth(borderWidth) for i in ax.spines.itervalues()]
unsatX=[680,2775,3821,680,4073,941,1202,1463]
unsatY=[1,1,1,4,1,2,2,2]
paretoX=[680, 1203, 1726, 4870]
paretoY=[10,7, 4,1]
satX=[4870,680,1727,1726,1203,680]
satY=[1,13,7,4,7,10]
typeX=[680, 1727]
typeY=[13, 7]
leftX=[680]
leftY=[12]
c = np.rec.fromarrays([paretoX, paretoY], names='x,y')
c.sort()
paretoX=c.x
paretoY=c.y
markrsz=8
l4, = plt.plot(paretoX, paretoY, '#000000', lw=2, label='Pareto Curve(unfolding, period locality)',markersize=markrsz,zorder = 10)
l1, = plt.plot(satX, satY, 'bo', label='Sat Points',markersize=markrsz,zorder = 10)
l2, = plt.plot(unsatX, unsatY, 'ro',marker='s',label='Unsat Points',markersize=markrsz,zorder = 10)
l5, = plt.plot(leftX, leftY, 'gp',label='Proc. count pareto points',markersize=markrsz)
l6, = plt.plot(typeX, typeY, 'w*',label='Modulo pareto points',markersize=markrsz,zorder=10)
leg=plt.legend(bbox_to_anchor=(0.,-0.200, 1., 1.102), loc=3, numpoints=1,
ncol=3, mode="expand", borderaxespad=0., fancybox=True, shadow=True,prop={'size':axisLabelFontSize})
rect = leg.get_frame()
rect.set_facecolor('#cccccc') # a grayscale intensity
#leg.set_frame_on(False)
latency=[680,2775,4870, 680,3821,4868, 680,1727,4341,4864, 680,1203,1726,1203, 680,4073,4334,4595,4856, 941,1202,1463,1724]
processor=[1, 1, 1,13, 1, 1, 7, 7, 1, 1, 4, 4, 4, 7,10,1, 1, 1, 1, 2, 2, 2, 2]
ax.set_xlabel('Period',size=axisLabelFontSize,labelpad=10)
ax.set_ylabel('Processors',size=axisLabelFontSize,labelpad=10)
ax.set_xlim(0, max(latency)+100)
ax.set_ylim(0, max(processor)+1)
# Set Border width zero
[i.set_linewidth(0) for i in ax.spines.itervalues()]
gridLineWidth=0.1
ax.set_axisbelow(False)
gridlines = ax.get_xgridlines()+ax.get_ygridlines()
#ax.set_axisbelow(True)
plt.setp(gridlines, 'zorder', 5)
ax.yaxis.grid(True, linewidth=gridLineWidth, linestyle='-', color='0.6',alpha='0.3')
ax.xaxis.grid(False)
ax.xaxis.set_ticks_position('none')
ax.yaxis.set_ticks_position('none')
fig.savefig(outputFile, format="pdf", bbox_inches='tight')
The legends in the plot get messed up.
Could someone tell me how do i fix a long legend entry which overwrites into area of other entry? What would be ideal if, I could do 3 legend entries in first row and two legend entries in the second row.
Besides the workaround found by the question author, a possibility is to add new lines for long labels:
...
l4, = plt.plot(paretoX, paretoY, '#000000', lw=2,
label='Pareto Curve \n(unfolding, period locality)',markersize=markrsz,zorder = 10)
...
The following modification to the code also solved my problem -
leg=plt.legend(bbox_to_anchor=(0.,-0.350, 1., 1.102), loc=3, numpoints=1, ncol=2 , borderaxespad=0., fancybox=True, shadow=True,prop={'size':axisLabelFontSize})