[Matplotlib-users] Remove Axes, Rescale Others to Fill Figure

All-

I frequently find myself in a position where I am programmatically generating figures with many subplots. For example, I might have data from 8 channels for 10 experimental runs and want to compare the different runs for each channel, ending up with 8 different figures (a figure for each channel), each of which has 10 stacked subplots (a subplot for each run).

I look at the data I’ve plotted, and discover that, whoops, channel 0 had an error and didn’t produce any data during run number 3, and now I have an empty axis in the middle of my figure. I’d like to be able to delete that empty axes and have the other axes grow to fill the now empty space.

The Figure class provides the delaxes method, which removes the axes from the figure, but doesn’t change the size of any of the other axes. So I still end up with a hole in my figure. I was hoping that tight_layout might take care of it, but it assumes the axes are still there.

Is there an easy way to do this? Failing that, what’s the hard way (I assume it exists)?

Thank you,
–Chad

generate some data (only 2 channels/5 runs in this example)

nc, nr, nd = 2, 5, 12

data = {c: {r: [random() for x in range(nd)] for r in range(nr)} for c in range(nc)}
data[0][3] = # whoops, no data here.

save the handles here

figaxlist =

for each channel

for chan in data:
nplots = len(data[chan].keys())

create a figure with many subplots

fig, axs = plt.subplots(nplots, sharex=True)
figaxlist.append((fig, axs))
for run, ax in zip(data[chan].keys(), axs):

plot the data

ax.plot(data[chan][run])

fig, axs = figaxlist[0]

removes axes, but leaves hole.

fig.delaxes(fig.axes[3])

Hi Chad,

I am no expert on the topic, but I found the `ax.update_geometry` method that may allow to solve your problem. See at the end of the following example adapted from your own code.

import matplotlib.pyplot as plt
from numpy.random import random
plt.ion()

# generate some data (only 2 channels/5 runs in this example)
nc, nr, nd = 2, 5, 12
data = {c: {r: [random() for x in range(nd)] for r in range(nr)} for c in range(nc)}
data[0][3] = [] # whoops, no data here.

# save the handles here
figaxlist = []
# for each channel
for chan in data:
  nplots = len(data[chan].keys())
  # create a figure with many subplots
  fig, axs = plt.subplots(nplots, sharex=True)
  figaxlist.append((fig, axs))
  for run, ax in zip(data[chan].keys(), axs):
    # plot the data
    ax.plot(data[chan][run], color=f"C{run}")

fig, axs = figaxlist[0]

# removes axes, but leaves hole.
fig.delaxes(fig.axes[3])

# update the axes geometry information
n_nonempty = len(fig.axes)  # amount of non empty plots
for idx, ax in enumerate(fig.axes):
    ax.change_geometry(n_nonempty, 1, idx+1)  # assuming single column layout

Hopefully this is more or less what you are looking for.

Regards,
Adrien

···

Le 04/05/2021 à 23:41, Chad Parker a écrit :

All-

I frequently find myself in a position where I am programmatically generating figures with many subplots. For example, I might have data from 8 channels for 10 experimental runs and want to compare the different runs for each channel, ending up with 8 different figures (a figure for each channel), each of which has 10 stacked subplots (a subplot for each run).

I look at the data I've plotted, and discover that, whoops, channel 0 had an error and didn't produce any data during run number 3, and now I have an empty axis in the middle of my figure. I'd like to be able to delete that empty axes and have the other axes grow to fill the now empty space.

The Figure class provides the delaxes method, which removes the axes from the figure, but doesn't change the size of any of the other axes. So I still end up with a hole in my figure. I was hoping that tight_layout might take care of it, but it assumes the axes are still there.

Is there an easy way to do this? Failing that, what's the hard way (I assume it exists)?

Thank you,
--Chad

# generate some data (only 2 channels/5 runs in this example)
nc, nr, nd = 2, 5, 12
data = {c: {r: [random() for x in range(nd)] for r in range(nr)} for c in range(nc)}
data[0][3] = # whoops, no data here.

# save the handles here
figaxlist =
# for each channel
for chan in data:
nplots = len(data[chan].keys())
# create a figure with many subplots
fig, axs = plt.subplots(nplots, sharex=True)
figaxlist.append((fig, axs))
for run, ax in zip(data[chan].keys(), axs):
# plot the data
ax.plot(data[chan][run])

fig, axs = figaxlist[0]

# removes axes, but leaves hole.
fig.delaxes(fig.axes[3])

_______________________________________________
Matplotlib-users mailing list
Matplotlib-users@python.org
Matplotlib-users Info Page

_______________________________________________
Matplotlib-users mailing list
Matplotlib-users@python.org
https://mail.python.org/mailman/listinfo/matplotlib-users

Hi Adrien-

This is exactly the effect I’m looking to achieve. Thank you.

The only problem is that it appears to be a solution for only the near term:

MatplotlibDeprecationWarning:
The change_geometry function was deprecated in Matplotlib 3.4 and will be removed two minor releases later. Use set_subplotspec instead.
ax.change_geometry(n_nonempty, 1, idx+1)
MatplotlibDeprecationWarning:
The update_params function was deprecated in Matplotlib 3.4 and will be removed two minor releases later.
ax.change_geometry(n_nonempty, 1, idx+1)
MatplotlibDeprecationWarning:
The figbox attribute was deprecated in Matplotlib 3.4 and will be removed two minor releases later. Use get_subplotspec().get_position(self.figure) instead.
ax.change_geometry(n_nonempty, 1, idx+1)

These messages suggest that there must be a way to accomplish this with subplotspecs. A little further digging and I came up with a very similar alternative:

gs = gridspec.GridSpec(len(fig.axes), 1)
for i, ax in enumerate(ig.axes):
ax.set_subplotspec(gs[i])

Thanks for your solution!
–Chad

···

On Wed, May 5, 2021 at 3:09 AM vincent.adrien@gmail.com <vincent.adrien@gmail.com> wrote:

Hi Chad,

I am no expert on the topic, but I found the ax.update_geometry method that may allow to solve your problem. See at the end of the following example adapted from your own code.

import matplotlib.pyplot as plt
from numpy.random import random
plt.ion()

# generate some data (only 2 channels/5 runs in this example)
nc, nr, nd = 2, 5, 12
data = {c: {r: [random() for x in range(nd)] for r in range(nr)} for c in range(nc)}
data[0][3] = [] # whoops, no data here.

# save the handles here
figaxlist = []
# for each channel
for chan in data:
nplots = len(data[chan].keys())
# create a figure with many subplots
fig, axs = plt.subplots(nplots, sharex=True)
figaxlist.append((fig, axs))
for run, ax in zip(data[chan].keys(), axs):
# plot the data
ax.plot(data[chan][run], color=f"C{run}")

fig, axs = figaxlist[0]

# removes axes, but leaves hole.
fig.delaxes(fig.axes[3])

# update the axes geometry information
n_nonempty = len(fig.axes) # amount of non empty plots
for idx, ax in enumerate(fig.axes):
ax.change_geometry(n_nonempty, 1, idx+1) # assuming single column layout

Hopefully this is more or less what you are looking for.

Regards,
Adrien

Le 04/05/2021 à 23:41, Chad Parker a écrit :

All-

I frequently find myself in a position where I am programmatically generating figures with many subplots. For example, I might have data from 8 channels for 10 experimental runs and want to compare the different runs for each channel, ending up with 8 different figures (a figure for each channel), each of which has 10 stacked subplots (a subplot for each run).

I look at the data I’ve plotted, and discover that, whoops, channel 0 had an error and didn’t produce any data during run number 3, and now I have an empty axis in the middle of my figure. I’d like to be able to delete that empty axes and have the other axes grow to fill the now empty space.

The Figure class provides the delaxes method, which removes the axes from the figure, but doesn’t change the size of any of the other axes. So I still end up with a hole in my figure. I was hoping that tight_layout might take care of it, but it assumes the axes are still there.

Is there an easy way to do this? Failing that, what’s the hard way (I assume it exists)?

Thank you,
–Chad

generate some data (only 2 channels/5 runs in this example)

nc, nr, nd = 2, 5, 12
data = {c: {r: [random() for x in range(nd)] for r in range(nr)} for c in range(nc)}
data[0][3] = # whoops, no data here.

save the handles here

figaxlist =

for each channel

for chan in data:
nplots = len(data[chan].keys())

create a figure with many subplots

fig, axs = plt.subplots(nplots, sharex=True)
figaxlist.append((fig, axs))
for run, ax in zip(data[chan].keys(), axs):

plot the data

ax.plot(data[chan][run])

fig, axs = figaxlist[0]

removes axes, but leaves hole.

fig.delaxes(fig.axes[3])


Matplotlib-users mailing list
Matplotlib-users@python.org
https://mail.python.org/mailman/listinfo/matplotlib-users


Matplotlib-users mailing list
Matplotlib-users@python.org
https://mail.python.org/mailman/listinfo/matplotlib-users