How to remove the space between subplots in matplotlib.pyplot? - python

I am working on a project in which I need to put together a plot grid of 10 rows and 3 columns. Although I have been able to make the plots and arrange the subplots, I was not able to produce a nice plot without white space such as this one below from gridspec documentatation..
I tried the following posts, but still not able to completely remove the white space as in the example image. Can someone please give me some guidance? Thanks!
Matplotlib different size subplots
how to remove “empty” space
between subplots?
Here's my image:
Below is my code. The full script is here on GitHub.
Note: images_2 and images_fool are both numpy arrays of flattened images with shape (1032, 10), while delta is an image array of shape (28, 28).
def plot_im(array=None, ind=0):
"""A function to plot the image given a images matrix, type of the matrix: \
either original or fool, and the order of images in the matrix"""
img_reshaped = array[ind, :].reshape((28, 28))
imgplot = plt.imshow(img_reshaped)
# Output as a grid of 10 rows and 3 cols with first column being original, second being
# delta and third column being adversaril
nrow = 10
ncol = 3
n = 0
from matplotlib import gridspec
fig = plt.figure(figsize=(30, 30))
gs = gridspec.GridSpec(nrow, ncol, width_ratios=[1, 1, 1])
for row in range(nrow):
for col in range(ncol):
plt.subplot(gs[n])
if col == 0:
#plt.subplot(nrow, ncol, n)
plot_im(array=images_2, ind=row)
elif col == 1:
#plt.subplot(nrow, ncol, n)
plt.imshow(w_delta)
else:
#plt.subplot(nrow, ncol, n)
plot_im(array=images_fool, ind=row)
n += 1
plt.tight_layout()
#plt.show()
plt.savefig('grid_figure.pdf')

A note at the beginning: If you want to have full control over spacing, avoid using plt.tight_layout() as it will try to arange the plots in your figure to be equally and nicely distributed. This is mostly fine and produces pleasant results, but adjusts the spacing at its will.
The reason the GridSpec example you're quoting from the Matplotlib example gallery works so well is because the subplots' aspect is not predefined. That is, the subplots will simply expand on the grid and leave the set spacing (in this case wspace=0.0, hspace=0.0) independent of the figure size.
In contrast to that you are plotting images with imshow and the image's aspect is set equal by default (equivalent to ax.set_aspect("equal")). That said, you could of course put set_aspect("auto") to every plot (and additionally add wspace=0.0, hspace=0.0 as arguments to GridSpec as in the gallery example), which would produce a plot without spacings.
However when using images it makes a lot of sense to keep an equal aspect ratio such that every pixel is as wide as high and a square array is shown as a square image.
What you will need to do then is to play with the image size and the figure margins to obtain the expected result. The figsize argument to figure is the figure (width, height) in inch and here the ratio of the two numbers can be played with. And the subplot parameters wspace, hspace, top, bottom, left can be manually adjusted to give the desired result.
Below is an example:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import gridspec
nrow = 10
ncol = 3
fig = plt.figure(figsize=(4, 10))
gs = gridspec.GridSpec(nrow, ncol, width_ratios=[1, 1, 1],
wspace=0.0, hspace=0.0, top=0.95, bottom=0.05, left=0.17, right=0.845)
for i in range(10):
for j in range(3):
im = np.random.rand(28,28)
ax= plt.subplot(gs[i,j])
ax.imshow(im)
ax.set_xticklabels([])
ax.set_yticklabels([])
#plt.tight_layout() # do not use this!!
plt.show()
Edit:
It is of course desireable not having to tweak the parameters manually. So one could calculate some optimal ones according to the number of rows and columns.
nrow = 7
ncol = 7
fig = plt.figure(figsize=(ncol+1, nrow+1))
gs = gridspec.GridSpec(nrow, ncol,
wspace=0.0, hspace=0.0,
top=1.-0.5/(nrow+1), bottom=0.5/(nrow+1),
left=0.5/(ncol+1), right=1-0.5/(ncol+1))
for i in range(nrow):
for j in range(ncol):
im = np.random.rand(28,28)
ax= plt.subplot(gs[i,j])
ax.imshow(im)
ax.set_xticklabels([])
ax.set_yticklabels([])
plt.show()

Try to add to your code this line:
fig.subplots_adjust(wspace=0, hspace=0)
And for every an axis object set:
ax.set_xticklabels([])
ax.set_yticklabels([])

Following the answer by ImportanceOfBeingErnest, but if you want to use plt.subplots and its features:
fig, axes = plt.subplots(
nrow, ncol,
gridspec_kw=dict(wspace=0.0, hspace=0.0,
top=1. - 0.5 / (nrow + 1), bottom=0.5 / (nrow + 1),
left=0.5 / (ncol + 1), right=1 - 0.5 / (ncol + 1)),
figsize=(ncol + 1, nrow + 1),
sharey='row', sharex='col', # optionally
)

If you are using matplotlib.pyplot.subplots you can display as many images as you want using Axes arrays. You can remove the spaces between images by making some adjustments to the matplotlib.pyplot.subplots configuration.
import matplotlib.pyplot as plt
def show_dataset_overview(self, img_list):
"""show each image in img_list without space"""
img_number = len(img_list)
img_number_at_a_row = 3
row_number = int(img_number /img_number_at_a_row)
fig_size = (15*(img_number_at_a_row/row_number), 15)
_, axs = plt.subplots(row_number,
img_number_at_a_row,
figsize=fig_size ,
gridspec_kw=dict(
top = 1, bottom = 0, right = 1, left = 0,
hspace = 0, wspace = 0
)
)
axs = axs.flatten()
for i in range(img_number):
axs[i].imshow(img_list[i])
axs[i].set_xticks([])
axs[i].set_yticks([])
Since we create subplots here first, we can give some parameters for grid_spec using the gridspec_kw parameter(source).
Among these parameters are the "top = 1, bottom = 0, right = 1, left = 0, hspace = 0, wspace = 0" parameters that will prevent inter-image spacing. To see other parameters, please visit here.
I usually use a figure size like (30,15) when setting the figure_size above. I generalized this a bit and added it to the code. If you wish, you can enter a manual size here.

Here's another simple approach using the ImageGrid class (adapted from this answer).
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import ImageGrid
nrow = 5
ncol = 3
fig = plt.figure(figsize=(4, 10))
grid = ImageGrid(fig,
111, # as in plt.subplot(111)
nrows_ncols=(nrow,ncol),
axes_pad=0,
share_all=True,)
for row in grid.axes_column:
for ax in row:
im = np.random.rand(28,28)
ax.imshow(im)
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)

Related

Add a single colorbar after generating subplots in a loop

My problem lies within the colorbar after generating subplots based on a data within certain gridpoints.
If I generate a single Snapshot, the colorbar will appear:
fig1, ax = plt.subplots()
im = ax.imshow(data[0])
fig1.colorbar(im, ax=ax,label='blublub')
ax.set_title = ('blabla')
But when I generate a loop for several subplots like, I cannot implement showing the colorbar at least once at the bottom of the figure.
fig = plt.figure(figsize = (15, 15))
for t in range(17):
plt.subplot(5, 5, t + 1)
plt.pcolor(x1grid, x2grid, data[t])
txt = "t = {t:.1f}"
plt.title(txt.format(t = time[t]))
I reviewed all questions on this platform, but could not implement a single one within my code. If my question is covered by already existing one, pls excuse me. I will review it and delete my post.
Thanks in advance
Maria
If you want to add a colorbar to each subplot in your loop, you can use the plt.colorbar() function in the same way as you did for the single subplot. However, instead of passing the im object as the first argument, you should pass the mappable object returned by plt.pcolor(). For example:
fig = plt.figure(figsize = (15, 15))
for t in range(17):
plt.subplot(5, 5, t + 1)
pc = plt.pcolor(x1grid, x2grid, data[t])
plt.colorbar(pc, label='blublub')
txt = "t = {t:.1f}"
plt.title(txt.format(t = time[t]))
I hope that you understand that, if you want to use a single colormap, you should use a single normalization for all of your plots. That said, here it is your figure with your colormap
And here it's the code
import numpy as np
import matplotlib.pyplot as plt
# Let's fake the data…
Nt, Nxy = 17, 201
x = y = np.linspace(0, 10, Nxy)
x, y = np.meshgrid(x,y)
data = np.empty((Nt, Nxy, Nxy))
for t in range(Nt):
t4 = 1+t/4
data[t] = t4*(1+np.sin( t4*x+y/t4))
# as I said, we need a single normalize object for all the data
norm = plt.Normalize(round(data.min()), round(data.max()))
# now we plot the data's elements
fig = plt.figure(figsize = (8, 8), layout='constrained')
# because we'll later need a list of all axes …
axes = []
for t in range(17):
axes.append(plt.subplot(5, 5, t + 1))
axes[-1].pcolor(x, y, data[t], norm=norm)
axes[-1].set_title('t='+str(t))
# decorate the figure and place the colormap
fig.suptitle('A single colormap, a single normalization scale\n',
size='xx-large')
fig.colorbar(plt.cm.ScalarMappable(norm=norm),
orientation='horizontal',
# ax = axes instructs the colormap to extend over all axes
ax=axes,
# but it's to much, so we shrink it to 75%
shrink=0.75,
# and make it a little slimmer
aspect=30,
)
# I'm satisfied, hence
plt.show()
ps if you want squarish plots, you could a) reduce the figure width or b) play with the aspect of the plots.

Set arrow size based on figure units instead of axis data units?

In matplotlib, is there a way to specify arrow head sizes in figure units rather than in data units?
The use case is: I am making a multi-panel figure in which each panel has a different axis size (e.g., one goes from 0 to 1 on the X-axis, and the next goes from 0 to 10). I'd like the arrows to appear the same in each panel. I'd also like the arrows to appear the same independent of direction.
For axes with an aspect ratio not equal to 1, the width of the tail (and therefore the size of the head) varies with direction.
The closest I've come is, after drawing on the canvas:
dx = ax.get_xlim()[1] - ax.get_xlim()[0]
for arrow in ax.patches:
arrow.set_data(width=dx/50)
but this does not work; it results in images like this:
Just use ax.annotate() instead of ax.arrow():
import matplotlib.pyplot as plt
import numpy as np
xlim, ylim = (-.3, .8), (0, 5.8)
arrow_start, arrow_end = np.asarray([.1, 3]), np.asarray([.5, 5])
fig = plt.figure(figsize=(3, 2))
ax = plt.gca()
ax.set_title('using ax.arrow()')
ax.set_xlim(xlim)
ax.set_ylim(ylim)
ax.arrow(*arrow_start, *(arrow_end - arrow_start), width=1/50)
fig = plt.figure(figsize=(3, 2))
ax = plt.gca()
ax.set_title('using ax.annotate()')
ax.set_xlim(xlim)
ax.set_ylim(ylim)
ax.annotate('', arrow_end, arrow_start, arrowprops=dict(width=5, headwidth=10, headlength=5))

matplotlib combining subplots into a single plot with no axis and no gaps

I have an image which looks like so:
It was generated using matplotlib using:
for slice_idx in range(mandrill_t.highpasses[1].shape[2]):
print(slice_idx)
subplot(2, 3, slice_idx+1)
imshow(np.abs(mandrill_t.highpasses[1][:,:,slice_idx]), cmap='Spectral', clim=(0, 1))
However, for my use case, I would like all these 6 images in a single image with no gaps or axis - I do not have an example output image to show, but essentially, I would like them stacked horizontally (3 of them) and vertically (2 of them) so that the 6 images are a single image.
I tried looking around for similar problems to draw inspiration from, but no luck so far :(
Any pointers would be great.
You have to specify the grid parameters:
2 rows
3 columns
0 width space
0 height space
with matplotlib.pyplot.subplots:
fig, axes = plt.subplots(nrows = 2, ncols = 3, gridspec_kw = {'wspace': 0, 'hspace': 0})
Then you can loop over created axes and, for each one of them, you have to show the image and set axis to 'tight' firtsly and 'off' secondly:
for ax in axes.flatten():
ax.imshow(img)
ax.axis('tight')
ax.axis('off')
Your code would be slighlty different, since you are plotting different images for each ax.
Complete Code
import matplotlib.pyplot as plt
img = plt.imread('img.jpeg')
fig, axes = plt.subplots(nrows = 2, ncols = 3, gridspec_kw = {'wspace': 0, 'hspace': 0})
for ax in axes.flatten():
ax.imshow(img)
ax.axis('tight')
ax.axis('off')
plt.show()
That's what GridSpec is for (see plt.subplots docs):
Just add the following line at the start:
subplots(2, 3, gridspec_kw={"wspace": 0, "hspace": 0})
You might also have to set some plot elements to invisible but it's hard to figure out exactly which without an MCVE.

Matplotlib - two different colormaps with different ranges

I'm trying to combine two figures that were separate before.
One is a 3 panel figure (ax1,ax2,ax3) (all generated with imshow), where I was using one single colormap on the side. Now, I want to add another figure (ax0) that I load from a png file, using imread and get_sample_data (from matplotlib.cbook).
The problem is that this new figure takes the same colormap as the one from the 3 panels, thus yielding one simple uniform color in the newly added panel. The 3 other panels still have the right color.
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import numpy as np
g1 = gridspec.GridSpec(4, 1, height_ratios=[4,1,1,1])
g1.update(wspace=0.05, hspace=0.2) # set the spacing between axes.
f, ((ax0), (ax1), (ax2), (ax3)) = plt.subplots(4, 1, sharex='col', sharey='row')
ax0 = subplot(g1[0])
ax1 = subplot(g1[1])
ax2 = subplot(g1[2])
ax3 = subplot(g1[3])
from matplotlib.cbook import get_sample_data
im2 = plt.imread(get_sample_data('PathToFig/Figure.png'))
ax0.imshow(im2, vmax=1)
ziAA = np.random.rand(4, 4, 4)
ziAB = np.random.rand(4, 4, 4)
ziBA = np.random.rand(4, 4, 4)
ax1.imshow(ziAA,origin="lower",vmin=0,vmax=0.03,aspect="auto",extent=[-0.15,0.15,0,4])
ax2.imshow(ziAB,origin="lower",vmin=0,vmax=0.03,aspect="auto",extent=[-0.15,0.15,0,4])
im = ax3.imshow(ziBA,origin="lower",vmin=0,vmax=0.03,aspect="auto",extent=[-0.15,0.15,0,4])
from matplotlib import ticker
tick_locator = ticker.MaxNLocator(nbins=5)
f.subplots_adjust(right=0.85)
cbar_ax = f.add_axes([1.0, 0.15, 0.01, 0.7])
cbar = f.colorbar(im, cax=cbar_ax)
cbar.locator = tick_locator
cbar.update_ticks()
cbar.solids.set_rasterized(True)
cbar.solids.set_edgecolor("face")
ziAB, ziBA and ziAA were generated in a previous griddata interpolation call.
I tried specifying two different colormaps inside each imshow call, I tried changing the values of vmax. But to no avail...
If I put ax0 after ax1-3, then, it's ax0 who gets the right colors, and not ax1-3.
I've looked at the other Similar Questions (Two different color colormaps in the same imshow matplotlib) that talk about creating masked arrays or my own colormap, but because of the png file origin for ax0, I don't really see how I should proceed.
EDIT :
Toy data:
ziAA = np.random.rand(4, 4, 4)
ziAB = np.random.rand(4, 4, 4)
ziBA = np.random.rand(4, 4, 4)
Toy png figure:
With this figure, the uniform color turns out to be white. My actual figure gives uniform red. This suggests that by properly tuning vmin, vmax and maybe other control parameters, one might get the png figure to display properly. However, I'd be interested in something that would work for any png figure...
With:
im0 = ax0.imshow(im2, aspect='auto',extent=[-0.15,0.15,0,4])
which produces the following result:

plotting coordinate as a matrix matplotlib python

I have a set of coordinates, say [(2,3),(45,4),(3,65)]
I need to plot them as a matrix is there anyway I can do this in matplotlib so I want it to have this sort of look http://imgur.com/Q6LLhmk
Edit: My original answer used ax.scatter. There is a problem with this: If two points are side-by-side, ax.scatter may draw them with a bit of space in between, depending on the scale:
For example, with
data = np.array([(2,3),(3,3)])
Here is a zoomed-in detail:
So here is a alternative solution that fixes this problem:
import matplotlib.pyplot as plt
import numpy as np
data = np.array([(2,3),(3,3),(45,4),(3,65)])
N = data.max() + 5
# color the background white (1 is white)
arr = np.ones((N,N), dtype = 'bool')
# color the dots black (0)
arr[data[:,1], data[:,0]] = 0
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.imshow(arr, interpolation='nearest', cmap = 'gray')
ax.invert_yaxis()
# ax.axis('off')
plt.show()
No matter how much you zoom in, the adjacent squares at (2,3) and (3,3) will remain side-by-side.
Unfortunately, unlike ax.scatter, using ax.imshow requires building an N x N array, so it could be more memory-intensive than using ax.scatter. That should not be a problem unless data contains very large numbers, however.

Categories