Remove empty sub plots in matplotlib figure - python

How can I determine whether a subplot (AxesSubplot) is empty or not? I would like to deactivate empty axes of empty subplots and remove completely empty rows.
For instance, in this figure only two subplots are filled and the remaining subplots are empty.
import matplotlib.pyplot as plt
# create figure wit 3 rows and 7 cols; don't squeeze is it one list
fig, axes = plt.subplots(3, 7, squeeze=False)
x = [1,2]
y = [3,4]
# plot stuff only in two SubAxes; other axes are empty
axes[0][1].plot(x, y)
axes[1][2].plot(x, y)
# save figure
plt.savefig('image.png')
Note: It is mandatory to set squeeze to False.
Basically I want a sparse figure. Some subplots in rows can be empty, but they should be deactivated (no axes must be visible). Completely empty rows must be removed and must not be set to invisible.

You can use the fig.delaxes() method:
import matplotlib.pyplot as plt
# create figure wit 3 rows and 7 cols; don't squeeze is it one list
fig, axes = plt.subplots(3, 7, squeeze=False)
x = [1,2]
y = [3,4]
# plot stuff only in two SubAxes; other axes are empty
axes[0][1].plot(x, y)
axes[1][2].plot(x, y)
# delete empty axes
for i in [0, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17,
18, 19, 20]:
fig.delaxes(axes.flatten()[i])
# save figure
plt.savefig('image.png')
plt.show(block=False)

One way of achieving what you require is to use matplotlibs subplot2grid feature. Using this you can set the total size of the grid (3,7 in your case) and choose to only plot data in certain subplots in this grid. I have adapted your code below to give an example:
import matplotlib.pyplot as plt
x = [1,2]
y = [3,4]
fig = plt.subplots(squeeze=False)
ax1 = plt.subplot2grid((3, 7), (0, 1))
ax2 = plt.subplot2grid((3, 7), (1, 2))
ax1.plot(x,y)
ax2.plot(x,y)
plt.show()
This gives the following graph:
EDIT:
Subplot2grid, in effect, does give you a list of axes. In your original question you use fig, axes = plt.subplots(3, 7, squeeze=False) and then use axes[0][1].plot(x, y) to specifiy which subplot your data will be plotted in. That is the same as what subplot2grid does, apart from it only shows the subplots with data in them which you have defined.
So take ax1 = plt.subplot2grid((3, 7), (0, 1)) in my answer above, here I have specified the shape of the 'grid' which is 3 by 7. That means I can have 21 subplots in that grid if I wanted, exactly like you original code. The difference is that your code displays all the subplots whereas subplot2grid does not. The (3,7) in ax1 = ... above specifies the shape of the whole grid and the (0,1) specifies where in that grid the subplot will be shown.
You can use any position the subplot wherever you like within that 3x7 grid. You can also fill all 21 spaces of that grid with subplots that have data in them if you require by going all the way up to ax21 = plt.subplot2grid(...).

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.

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 subplots: how to freeze x and y axis?

Good evening
matplotlib changes the scaling of the diagram when drawing with e.g. hist() or plot(), which is usually great.
Is it possible to freeze the x and y axes in a subplot after drawing, so that further drawing commands do not change them anymore? For example:
fig, (plt1, plt2) = plt.subplots(2, 1, figsize=(20, 10))
plt1.hist(…)
plt1.plot(…)
# How can this get done?:
plt1.Freeze X- and Y-Axis
# Those commands no longer changes the x- and y-axis
plt1.plot(…)
plt1.plot(…)
Thanks a lot, kind regards,
Thomas
Matplotlib has an autoscale() function that you can turn on or off for individual axis objects and their individual x- and y-axes:
from matplotlib import pyplot as plt
fig, (ax1, ax2) = plt.subplots(2)
#standard is that both x- and y-axis are autoscaled
ax1.plot([1, 3, 5], [2, 5, 1], label="autoscale on")
#rendering the current output
fig.draw_without_rendering()
#turning off autoscale for the x-axis of the upper panel
#the y-axis will still be autoscaled for all following artists
ax1.autoscale(False, axis="x")
ax1.plot([-1, 7], [-2, 4], label="autoscale off")
ax1.legend()
#other axis objects are not influenced
ax2.plot([-2, 4], [3, 1])
plt.show()
Sample output:
Use plt.xlim and plt.ylim to get the current limits after plotting the initial plots, then use those values to set the limits after plotting the additional plots:
import matplotlib.pyplot as plt
# initial data
x = [1, 2, 3, 4, 5]
y = [2, 4, 8, 16, 32]
plt.plot(x, y)
# Save the current limits here
xlims = plt.xlim()
ylims = plt.ylim()
# additional data (will change the limits)
new_x = [-10, 100]
new_y = [2, 2]
plt.plot(new_x, new_y)
# Then set the old limits as the current limits here
plt.xlim(xlims)
plt.ylim(ylims)
plt.show()
Output figure (note how the x-axis limits are ~ [1, 5] even though the orange line is defined in the range [-10, 100]) :
To freeze x-axis specify the domain on the plot function:
import matplotlib.pyplot as plt
fig, (plt1, plt2) = plt.subplots(2, 1, figsize=(20, 10))
# range(min, max, step)
n = range(0, 10, 1) # domain [min, max] = [0, 9]
# make sure your functions has equal length
f = [i * 2 for i in n]
g = [i ** 2 for i in n]
# keep x-axis scale the same by specifying x-axis on the plot function.
plt1.plot(n, f) # funtion (f) range depends on it's value [min, max]
plt1.plot(n, g) # funtion (g) range depends on it's value [min, max]
# range of (f) and (g) impacts the scaling of y-axis
See matplotlib.pyplot for hist function parameters.
The answer of #jfaccioni is almost perfect (thanks a lot!), but it does not work with matplotlib subplots (as asked) because Python, as unfortunately so often, does not have uniform attributes and methods (not even in the same module), and so the matplotlib interface to a plot and a subplot is different.
In this example, this code works with a plot but not with a subplot:
# this works for plots:
xlims = plt.xlim()
# and this must be used for subplots :-(
xlims = plt1.get_xlim()
therefore, this code works with subplots:
import matplotlib.pyplot as plt
fig, (plt1, plt2) = plt.subplots(2, 1, figsize=(20, 10))
# initial data
x = [1, 2, 3, 4, 5]
y = [2, 4, 8, 16, 32]
plt1.plot(x, y)
# Save the current limits here
xlims = plt1.get_xlim()
ylims = plt1.get_ylim()
# additional data (will change the limits)
new_x = [-10, 100]
new_y = [2, 2]
plt1.plot(new_x, new_y)
# Then set the old limits as the current limits here
plt1.set_xlim(xlims)
plt1.set_ylim(ylims)
plt.show()
btw: Freezing the x- and y axes can even be done by 2 lines because once again, python unfortunately has inconsistent attributes:
# Freeze the x- and y axes:
plt1.set_xlim(plt1.get_xlim())
plt1.set_ylim(plt1.get_ylim())
It does not make sense at all to set xlim to the value it already has.
But because Python matplotlib misuses the xlim/ylim attribute and sets the current plot size (and not the limits!), therefore this code works not as expected.
It helps to solve the task in question, but those concepts makes using matplotlib hard and reading matplotlib code is annoying because one must know hidden / unexpected internal behaviors.

pandas histogram with by: possible to make axes uniform?

I am using the option to generate a separate histogram of a value for each group in a data frame like so (example code from documentation)
data = pd.Series(np.random.randn(1000))
data.hist(by=np.random.randint(0, 4, 1000), figsize=(6, 4))
This is great, but what I am not seeing is a way to set and standardize the axes. Is this possible?
To be specific, I would like to specify the x and y axes of the plots so that the y axis in particular has the same range for all plots. Otherwise it can be hard to compare distributions to one another.
you can pass kwds to hist and it will pass them along to appropriate sub processes. The relevant ones here are sharex and sharey
data = pd.Series(np.random.randn(1000))
data.hist(by=np.random.randint(0, 4, 1000), figsize=(6, 4),
sharex=True, sharey=True)

How to set the axis scale and ticklabels using matplotlib object oriented API

I would need some help with plotting in Matplotlib.pyplot under Python2.7!
I want to generate a plot with the following x-axis:
x-axis as it should be
I got so far by using myaxis.set_xticks([0,0.5,1,2,4,6,8]) and it looks good, but if I want to create **an logarithmic x-axis* **, then my axis labels look like this!
wrong x-axis labels
What can I do to have both a log-scaled x-axis and integer formated labels (not logarithmic values as labels either!). Please read the note regarding to the log-scale!!!
While browsing Stackoverflow I found the following similar question, but nothing of the suggestions worked for me and I do not know what I did wrong.
Matplotlib: show labels for minor ticks also
Thanks!
Note: This plot is called Madau-Plot (see:adsabs[dot]harvard[dot]edu Madau (1998) DOI=10.1086/305523). It is common to plot it log-scales and show the z=0.0 value although the axis is log-scaled axis and log10(0)=Error. I definitely want to point out here that this is common use in my field but should not be applied one to one to any other plots. So actually the plot is made with a trick! You plot (1+z) [1,1.5,2,3,5,7,9]] and then translate the x-axis to the pure z-values 0.0 < z 8.0! So what I need to find is how to set xticks to the "translated" values ([0,0.5,1,2,4,6,8])
What if you plotted your datapoint corresponding to x=0 somewhere else, like at x=0.25, then relabel it. For example,
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
plot_vals = [0.25, 0.5, 1, 2, 4, 6, 8]
label_vals = [0, 0.5, 1, 2, 4, 6, 8]
ax.plot(plot_vals, plot_vals, 'k-o')
ax.set_xscale('log')
ax.set_xticks(plot_vals)
ax.set_xticklabels(label_vals) # relabeling the ticklabels
This yields what I think is what you want.
You can turn off minor ticks by doing something like:
ax.tick_params(axis='x', which='minor', bottom='off', top='off')
Edit: Given the edit to the op, this can be done easily by:
import matplotlib.pyplot as plt
original_values = [0, 0.5, 1, 2, 4, 6, 8]
# if using numpy:
# import numpy as np
# plot_values = np.array(original_values) + 1
# if using pure python
plot_values = [i + 1 for i in original_values]
fig, ax = plt.subplots()
ax.plot(plot_values, plot_values, 'k-o') #substitute actual plotting here
ax.set_xscale('log')
ax.set_xticks(plot_values)
ax.set_xticklabels(original_values)
which yields:

Categories