How to fill a figure with a certain number of subplots?

Viewed 465

Hello fellow programmers!

I am trying to write code that creates a matplotlib.pyplot.figure and fills it with subplots. The only input shall be the number of subplots. In this case every white box represents one subplot.

Following example works for subplot numbers that are divisible by 100:

import matplotlib.pyplot as plt

subplots = 200
cols = int(round(subplots/10))
rows = int(round(subplots/20))

print(f'subplots: {cols*rows}')

fig, axes = plt.subplots(nrows=rows, ncols=cols)
_axes = []
for ax_array in axes:
    for ax in ax_array:
        _axes.append(ax)

for ax in _axes:
    ax.set_xticklabels([])
    ax.set_yticklabels([])
    ax.tick_params(axis='both', which='both', length=0)
    ax.plot()

plt.tight_layout()
plt.subplots_adjust(wspace=0, hspace=0)
plt.show()

Output:

subplots: 200

enter image description here

This figure is depicted correctly with 200 subplots.

But when I make the change subplots = 150, the amount of subplots is calculated and depicted wrong:

subplots: 120

enter image description here

How do I compute the correct amount of nrows and ncols required for depicting the subplots? I want the figure to be able to depict 149 subplots if required.

Is there a better way for doing this?

Thank you in advance.

3 Answers

Try replacing this:

rows = int(round(subplots/20))

with this:

rows = int(round(subplots/cols))

That will fix the the calculated amount of subplots.


Since plt.subplots() define a rectangular grid of subplots, the total amount of them should not be a prime number, because it must be divisible by the number of columns and the number of rows. If you enter a prime number as subplots, you code will find the nearest not-prime number. For example, if you enter:

subplots = 149

your code will give you 150 subplots, because it does not exist a combination of cols * rows which gives 149 as a product.

In order to manage this, use this code:

import matplotlib.pyplot as plt

subplots = 149
cols = int(round(subplots/10))
rows = int(round(subplots/cols))

print(f'subplots: {cols*rows}')

if subplots > cols * rows:
    fig, axes = plt.subplots(nrows = rows + 1, ncols = cols)
else:
    fig, axes = plt.subplots(nrows = rows, ncols = cols)

_axes = []
for ax_array in axes:
    for ax in ax_array:
        _axes.append(ax)

for ax in _axes:
    ax.set_xticklabels([])
    ax.set_yticklabels([])
    ax.tick_params(axis='both', which='both', length=0)
    ax.plot()

if subplots > cols * rows:
    for idx in range(1, cols):
        fig.delaxes(axes[rows, idx])
elif subplots < cols * rows:
    fig.delaxes(axes[rows - 1, cols - 1])

plt.tight_layout()
plt.subplots_adjust(wspace=0, hspace=0)
plt.show()
  • in the case when subplots > rows * cols (for example subplots = 31) it will add a subplot at the bottom:

enter image description here

  • in the case when subplots < rows * cols (for example subplots = 23) it will delete the last subplot:

enter image description here

So you can manage all cases.

I found a workaround using the modulo operator and removing excessive subplots afterwards. In this example 149 subplots are to be depicted in a figure with space for 150 subplots. The figure is expanded to the next number divisible by 10 so in this case to 150.

import matplotlib.pyplot as plt


features = 149
if features % 10 != 0:
    subplots = features + (10-(features % 10))
else:
    subplots = features
print(f'features: {features}')

cols = int(round(subplots/10))
rows = int(round(subplots/cols))
print(f'subplots: {cols*rows}')

fig, axes = plt.subplots(nrows=rows, ncols=cols)
_axes = []
for ax_array in axes:
    for ax in ax_array:
        _axes.append(ax)

# axes to be plotted
for ax in _axes:
    ax.set_xticklabels([])
    ax.set_yticklabels([])
    ax.tick_params(axis='both', which='both', length=0)
    ax.plot()

# axes to be removed
for ax in range(10-(features % 10)):
    fig.delaxes(_axes[-(ax+1)])

plt.tight_layout()
plt.subplots_adjust(wspace=0, hspace=0)
plt.show()

Output:

features: 149
subplots: 150

enter image description here

If you really need to deal with prime numbers and if having a non-squared grid is an option, you could just define a reference square grid and "turn off" the remaining plots:

import matplotlib.pyplot as plt

subplots = 149
cols, rows = 10, 15

nullplots = cols * rows - subplots

print(f'subplots: {subplots}')

fig, axes = plt.subplots(nrows=rows, ncols=cols)

# Supress the nullplots
for idx in range(nullplots):
    axes[14, -(idx + 1)].axis('off')

_axes = []
for ax_array in axes:
    for ax in ax_array:
        _axes.append(ax)

for ax in _axes:
    ax.set_xticklabels([])
    ax.set_yticklabels([])
    ax.tick_params(axis='both', which='both', length=0)
    ax.plot()

plt.tight_layout()
plt.subplots_adjust(wspace=0, hspace=0)
plt.show()

In this slight modification of your code I have defined a 10 x 15 square grid that will host 149 plots. Then I remove the 150 - 149 = 1 remaining plots in the right-bottom corner for example. The resulting plot: enter image description here

I hope this helps you.

Related