How to remove the space between subplots
Asked Answered
L

5

24

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.image w/o white space.

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!

Here's my image: 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')
Lobotomy answered 10/12, 2016 at 3:45 Comment(0)
C
42

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

enter image description here

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()
Currajong answered 10/12, 2016 at 11:58 Comment(7)
Works like a magic, thanks @Currajong ! Just wondering why you use figsize=(4, 10) instead of figsize=(10, 10)...the latter one brings back the space immediately.Lobotomy
Why would you need a square figure size if you have 3 times more rows than columns? You can of course set it to (10,10) and then adapt the left and right parameters again. My choice of figsize=(4, 10) is dictated more by the idea that having n rows and m columns, a figsize of (m+1, n) might be appropriate; the rest is then done by finetuning the subplot parameters.Currajong
I see. So "figsize" really refers to the overall image size instead of the subplots. I confused myself.Lobotomy
figsize is the size of the figure in inch. I have edited the answer to include an example where the parameters are calculated from the number of columns and rows.Currajong
For those using an array of images, put gen = iter(image_array) outside the for loop and ax.imshow(next(gen, np.full((1, 1, 3), 255))) inside. When gen runs out of values, it'll render a white image of size 1x1Tague
Since the question's goal image didn't have tick marks, I'd suggest using ax.axis('off')Tague
I love u Ser. This is the only best answer compared to other not-working ones.Rivy
F
15

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([])
Furr answered 10/12, 2016 at 6:53 Comment(2)
This solution will work fine in case of subplots with aspect set to auto. For images plotted with imshow as in the usage case here, it will fail. See my solution.Currajong
Thanks for your reply. This does remove the vertical space, but the horizontal space is still there...Lobotomy
E
5

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
)
Erupt answered 8/12, 2020 at 15:42 Comment(0)
M
0

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.

Maryrose answered 10/12, 2016 at 3:46 Comment(0)
T
0

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)

enter image description here

Teplitz answered 2/12, 2022 at 11:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.