Customising Matplotlib Subplots - python

I've always found matplotlib subplots confusing, and so I was wondering whether somebody could show how to generate the following arrangement of subplots:
Subplot Image
Thanks in advance.

You can use plt.GridSpec to easily create differently sized subplots.
cols = 5 #number of columns in the grid
s = 2 #width of top right subplot in #cells
w = 1 #spacing between subplot columns
fig = plt.figure()
gs = plt.GridSpec(2, cols, figure=fig, wspace=w)
ax1 = fig.add_subplot(gs[0, :-s])
ax2 = fig.add_subplot(gs[0, -s:])
ax3 = fig.add_subplot(gs[1, :-s])

#Marc 's answer is fine, but perhaps easier and more flexible is:
fig, axs = plt.subplots(2, 2, gridspec_kw={'width_ratios':[2, 1]})
# if you really don't want 4 axes:
fig.delaxes(axs[1, 1])

Related

Remove separation between subplots in horizontal direction

I have four 2D arrays I want to plot in four subplots using imshow. I want the separation between these subplots to be removed, making the subplots touch each other, similar to the Matplotlib Documentation (second to last example). My attempt is
fig, axs = plt.subplots(2, 2, sharex='col', sharey='row', gridspec_kw={'hspace': 0, 'wspace': 0})
(ax1, ax2), (ax3, ax4) = axs
ax1.imshow(im1)
ax2.imshow(im2)
ax3.imshow(im3)
ax4.imshow(im4)
for ax in fig.get_axes():
ax.label_outer()
plt.show()
This produces
The separation in the vertical direction seems to be correctly removed, but I still have a separation in the horizontal direction. Does anyone know how I can get rid of that as well here?
You can try something along the lines of this answer of ImportanceOfBeingErnest. I have prepared the following pseudo code based on it for your question. You can try it and see if it works for you.
from matplotlib import gridspec
nrow, ncol = 2, 2
fig = plt.figure(figsize=(6,6))
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))
ims = [im1, im2, im3, im4]
c = 0 # Counter for the ims array
for i in range(nrow):
for j in range(ncol):
ax= plt.subplot(gs[i,j])
ax.imshow(ims[c])
ax.set_xticklabels([])
ax.set_yticklabels([])
c += 1
for ax in fig.get_axes():
ax.label_outer()

Add multiple suptitles [duplicate]

In matplotlib, Is it possible to set a a separate title for each row of subplots in addition to the title set for the entire figure and the title set for each individual plot? This would correspond to the orange text in the figure below.
If not, how would you get around this problem? Create a separate column of empty subplots to the left and fill them with the orange text?
I am aware that it is possible to manually position each single title using text() or annotate(), but that usually requires a lot of tweaking and I have many subplots. Is there a smoother solution?
New in matplotlib 3.4.0
Row titles can now be implemented as subfigure suptitles:
The new subfigure feature allows creating virtual figures within figures with localized artists (e.g., colorbars and suptitles) that only pertain to each subfigure.
See how to plot subfigures for further details.
How to reproduce OP's reference figure:
Either Figure.subfigures (most straightforward)
Create 3x1 fig.subfigures where each subfig gets its own 1x3 subfig.subplots and subfig.suptitle:
fig = plt.figure(constrained_layout=True)
fig.suptitle('Figure title')
# create 3x1 subfigs
subfigs = fig.subfigures(nrows=3, ncols=1)
for row, subfig in enumerate(subfigs):
subfig.suptitle(f'Subfigure title {row}')
# create 1x3 subplots per subfig
axs = subfig.subplots(nrows=1, ncols=3)
for col, ax in enumerate(axs):
ax.plot()
ax.set_title(f'Plot title {col}')
Or Figure.add_subfigure (onto existing subplots)
If you already have 3x1 plt.subplots, then add_subfigure into the underlying gridspec. Again each subfig will get its own 1x3 subfig.subplots and subfig.suptitle:
# create 3x1 subplots
fig, axs = plt.subplots(nrows=3, ncols=1, constrained_layout=True)
fig.suptitle('Figure title')
# clear subplots
for ax in axs:
ax.remove()
# add subfigure per subplot
gridspec = axs[0].get_subplotspec().get_gridspec()
subfigs = [fig.add_subfigure(gs) for gs in gridspec]
for row, subfig in enumerate(subfigs):
subfig.suptitle(f'Subfigure title {row}')
# create 1x3 subplots per subfig
axs = subfig.subplots(nrows=1, ncols=3)
for col, ax in enumerate(axs):
ax.plot()
ax.set_title(f'Plot title {col}')
Output of either example (after some styling):
An idea is to create three "big subplots", to give each of them a title, and make them invisible. On the top of that you can create your matrix of smaller subplots.
This solution is entirely based on this post, except that more attention has been paid to actually removing the background subplot.
import matplotlib.pyplot as plt
fig, big_axes = plt.subplots( figsize=(15.0, 15.0) , nrows=3, ncols=1, sharey=True)
for row, big_ax in enumerate(big_axes, start=1):
big_ax.set_title("Subplot row %s \n" % row, fontsize=16)
# Turn off axis lines and ticks of the big subplot
# obs alpha is 0 in RGBA string!
big_ax.tick_params(labelcolor=(1.,1.,1., 0.0), top='off', bottom='off', left='off', right='off')
# removes the white frame
big_ax._frameon = False
for i in range(1,10):
ax = fig.add_subplot(3,3,i)
ax.set_title('Plot title ' + str(i))
fig.set_facecolor('w')
plt.tight_layout()
plt.show()
Another easy cheat is to give the title of the middle column as subplot row XX\n\nPlot title No.YY
It is better to firstly plot your real subplots and then plot empty subplots above them, thus you will have a more precise title align. And to do it precisely we need plt.GridSpec() (link).
It is best seen in columns subtitles:
# modified code of #snake_chrmer
fig, big_axes = plt.subplots(figsize=(9, 3) , nrows=1, ncols=3, sharey=True)
for title, big_ax in zip(['First', 'Second', 'Third'], big_axes):
big_ax.set_title(f'{title}\n', fontweight='semibold')
big_ax.set_frame_on(False)
big_ax.axis('off')
for i in range(1, 7):
ax = fig.add_subplot(1,6,i)
ax.set_title('Plot title ' + str(i))
fig.set_facecolor('w')
plt.tight_layout()
plt.show()
# my solition
import matplotlib.pyplot as plt
from matplotlib.gridspec import SubplotSpec
def create_subtitle(fig: plt.Figure, grid: SubplotSpec, title: str):
"Sign sets of subplots with title"
row = fig.add_subplot(grid)
# the '\n' is important
row.set_title(f'{title}\n', fontweight='semibold')
# hide subplot
row.set_frame_on(False)
row.axis('off')
rows = 1
cols = 6
fig, axs = plt.subplots(rows, cols, figsize=(9, 3))
for i, ax in enumerate(axs.flatten()):
ax.set_title(f'Plot title {i}')
grid = plt.GridSpec(rows, cols)
create_subtitle(fig, grid[0, 0:2], 'First')
create_subtitle(fig, grid[0, 2:4], 'Second')
create_subtitle(fig, grid[0, 4:6], 'Third')
fig.tight_layout()
fig.set_facecolor('w')
# original problem
rows = 3
cols = 3
fig, axs = plt.subplots(rows, cols, figsize=(9, 9))
for i, ax in enumerate(axs.flatten()):
ax.set_title(f'Plot title {i}')
grid = plt.GridSpec(rows, cols)
create_subtitle(fig, grid[0, ::], 'First')
create_subtitle(fig, grid[1, ::], 'Second')
create_subtitle(fig, grid[2, ::], 'Third')
fig.tight_layout()
fig.set_facecolor('w')
UPD
It is more logical and comprehensible to create subgrid for a set of subplots just to title them. The subgrig gives a wast space for modifications:
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
rows = 1
cols = 3
fig = plt.figure(figsize=(9, 3))
# grid for pairs of subplots
grid = plt.GridSpec(rows, cols)
for i in range(rows * cols):
# create fake subplot just to title pair of subplots
fake = fig.add_subplot(grid[i])
# '\n' is important
fake.set_title(f'Fake #{i}\n', fontweight='semibold', size=14)
fake.set_axis_off()
# create subgrid for two subplots without space between them
# <https://matplotlib.org/2.0.2/users/gridspec.html>
gs = gridspec.GridSpecFromSubplotSpec(1, 2, subplot_spec=grid[i], wspace=0)
# real subplot #1
ax = fig.add_subplot(gs[0])
ax.set_title(f'Real {i}1')
# hide ticks and labels
ax.tick_params(left=False, labelleft=False, labelbottom=False, bottom=False)
# real subplot #2
ax = fig.add_subplot(gs[1], sharey=ax)
ax.set_title(f'Real {i}2')
# hide ticks and labels
ax.tick_params(left=False, labelleft=False, labelbottom=False, bottom=False)
fig.patch.set_facecolor('white')
fig.suptitle('SUPERTITLE', fontweight='bold', size=16)
fig.tight_layout()
Original problem:
rows = 3
cols = 1
fig = plt.figure(figsize=(9, 9))
# grid for pairs of subplots
grid = plt.GridSpec(rows, cols)
for i in range(rows * cols):
# create fake subplot just to title set of subplots
fake = fig.add_subplot(grid[i])
# '\n' is important
fake.set_title(f'Fake #{i}\n', fontweight='semibold', size=14)
fake.set_axis_off()
# create subgrid for two subplots without space between them
# <https://matplotlib.org/2.0.2/users/gridspec.html>
gs = gridspec.GridSpecFromSubplotSpec(1, 3, subplot_spec=grid[i])
# real subplot #1
ax = fig.add_subplot(gs[0])
ax.set_title(f'Real {i}1')
# real subplot #2
ax = fig.add_subplot(gs[1], sharey=ax)
ax.set_title(f'Real {i}2')
# real subplot #3
ax = fig.add_subplot(gs[2], sharey=ax)
ax.set_title(f'Real {i}3')
fig.patch.set_facecolor('white')
fig.suptitle('SUPERTITLE', fontweight='bold', size=16)
fig.tight_layout()

How to adjust the plot size in Matplotlib?

I'm trying to remove the white space from the plot that I created:
As it is possible to see, there a big white spot on the right and also on the bottom, how to fix it? Here is my script:
fig = plt.figure(figsize=(7,7))
ax1 = plt.subplot2grid((4,3), (0,0),)
ax2 = plt.subplot2grid((4,3), (1,0),)
ax3 = plt.subplot2grid((4,3), (0,1),)
ax4 = plt.subplot2grid((4,3), (1,1),)
data = self.dframe[i]
tes = print_data(data, self.issues, self.color, self.type_user)
tes.print_top(data=data, top=10, ax=ax1, typegraph="hbar", problem=self.issues[i], tone=self.color[i])
tes.print_top(data=data, top=10, ax=ax2, typegraph="prod_bar", problem=self.issues[i], tone=self.color[i])
tes.print_top(data=data, top=10, ax=ax3, typegraph="reg_hbar", problem=self.issues[i], tone=self.color[i])
tes.print_top(data=data, top=10, ax=ax4, typegraph=self.type_user, problem=self.issues[i], tone=self.color[i])
problem = self.issues[i]
plt.tight_layout()
name = problem + str('.PNG')
plt.close(fig)
fig.savefig(name)
You are creating too many subplots!
If we look at this line:
ax1 = plt.subplot2grid((4,3), (0,0),)
We can see the first argument given to subplot2grid are the dimensions of the subplot grid to be made, in this case 4 rows, and 3 columns. You are then plotting in the subplots in the top left of your figure (the second argument given) which leaves a lot of space that's not used.
So to solve this, reduce the number of subplots by using:
ax1 = plt.subplot2grid((2,2), (0,0),)
Full example:
import numpy as np
import matplotlib.pyplot as plt
data = np.random.randn(25)
fig = plt.figure(figsize=(7,7))
ax1 = plt.subplot2grid((2,2), (0,0),)
ax2 = plt.subplot2grid((2,2), (1,0),)
ax3 = plt.subplot2grid((2,2), (0,1),)
ax4 = plt.subplot2grid((2,2), (1,1),)
ax1.plot(data)
ax2.plot(data)
ax3.plot(data)
ax4.plot(data)
plt.show()
Giving:
you can use
plt.subplots_adjust(left=0.09, bottom=0.07, right=0.98, top=0.97, wspace=0.2 , hspace=0.17 ) to adjust the window.
But the issue is that a lot of the space in your plot is empty
maybe you should change
plt.subplot2grid((4,3)... to plt.subplot2grid((2,2)

Share axes in matplotlib for only part of the subplots

I am having a big plot where I initiated with:
import numpy as np
import matplotlib.pyplot as plt
fig, axs = plt.subplots(5, 4)
And I want to do share-x-axis between column 1 and 2; and do the same between column 3 and 4. However, column 1 and 2 does not share the same axis with column 3 and 4.
I was wondering that would there be anyway to do this, and not sharex=True and sharey=True across all figures?
PS: This tutorial does not help too much, because it is only about sharing x/y within each row/column; they cannot do axis sharing between different rows/columns (unless share them across all axes).
I'm not exactly sure what you want to achieve from your question. However, you can specify per subplot which axis it should share with which subplot when adding a subplot to your figure.
This can be done via:
import matplotlib.pylab as plt
fig = plt.figure()
ax1 = fig.add_subplot(5, 4, 1)
ax2 = fig.add_subplot(5, 4, 2, sharex = ax1)
ax3 = fig.add_subplot(5, 4, 3, sharex = ax1, sharey = ax1)
A slightly limited but much simpler option is available for subplots. The limitation is there for a complete row or column of subplots.
For example, if one wants to have common y axis for all the subplots but common x axis only for individual columns in a 3x2 subplot, one could specify it as:
import matplotlib.pyplot as plt
fig, ax = plt.subplots(3, 2, sharey=True, sharex='col')
One can manually manage axes sharing using a Grouper object, which can be accessed via ax._shared_x_axes and ax._shared_y_axes. For example,
import matplotlib.pyplot as plt
def set_share_axes(axs, target=None, sharex=False, sharey=False):
if target is None:
target = axs.flat[0]
# Manage share using grouper objects
for ax in axs.flat:
if sharex:
target._shared_x_axes.join(target, ax)
if sharey:
target._shared_y_axes.join(target, ax)
# Turn off x tick labels and offset text for all but the bottom row
if sharex and axs.ndim > 1:
for ax in axs[:-1,:].flat:
ax.xaxis.set_tick_params(which='both', labelbottom=False, labeltop=False)
ax.xaxis.offsetText.set_visible(False)
# Turn off y tick labels and offset text for all but the left most column
if sharey and axs.ndim > 1:
for ax in axs[:,1:].flat:
ax.yaxis.set_tick_params(which='both', labelleft=False, labelright=False)
ax.yaxis.offsetText.set_visible(False)
fig, axs = plt.subplots(5, 4)
set_share_axes(axs[:,:2], sharex=True)
set_share_axes(axs[:,2:], sharex=True)
To adjust the spacing between subplots in a grouped manner, please refer to this question.
I used Axes.sharex /sharey in a similar setting
https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.sharex.html#matplotlib.axes.Axes.sharex
import matplotlib.pyplot as plt
fig, axd = plt.subplot_mosaic([list(range(3))] +[['A']*3, ['B']*3])
axd[0].plot([0,0.2])
axd['A'].plot([1,2,3])
axd['B'].plot([1,2,3,4,5])
axd['B'].sharex(axd['A'])
for i in [1,2]:
axd[i].sharey(axd[0])
plt.show()

Issues with python matplotlib and subplot sizes

I am trying to create a figure with 6 sub-plots in python but I am having a problem. Here is a simplified version of my code:
import matplotlib.pyplot as plt
import numpy
g_width = 200
g_height = 200
data = numpy.zeros(g_width*g_height).reshape(g_height,g_width)
ax1 = plt.subplot(231)
im1 = ax1.imshow(data)
ax2 = plt.subplot(232)
im2 = ax2.imshow(data)
ax3 = plt.subplot(233)
im3 = ax3.imshow(data)
ax0 = plt.subplot(234)
im0 = ax0.imshow(data)
ax4 = plt.subplot(235)
im4 = ax4.imshow(data)
ax5 = plt.subplot(236)
ax5.plot([1,2], [1,2])
plt.show()
The above figure has 5 "imshow-based" sub-plots and one simple-data-based sub-plot. Can someone explain to me why the box of the last sub-plot does not have the same size with the other sub-plots? If I replace the last sub-plot with an "imshow-based" sub-plot the problem disappears. Why is this happening? How can I fix it?
The aspect ratio is set to "equal" for the 5imshow()calls (check by callingax1.get_aspect()) while forax5it is set toautowhich gives you the non-square shape you observe. I'm guessingimshow()` defaults to equal while plot does not.
To fix this set all the axis aspect ratios manually e.g when creating the plot ax5 = plt.subplot(236, aspect="equal")
On a side node if your creating many axis like this you may find this useful:
fig, ax = plt.subplots(ncols=3, nrows=2, subplot_kw={'aspect':'equal'})
Then ax is a tuple (in this case ax = ((ax1, ax2, ax3), (ax4, ax5, ax6))) so to plot in the i, j plot just call
ax[i,j].plot(..)

Categories

Resources