I read the instruction in Customizing Location of Subplot Using GridSpec and try out the following codes and got the plot layout:
import matplotlib.gridspec as gridspec
gs = gridspec.GridSpec(3, 3)
ax1 = plt.subplot(gs[0, :])
ax2 = plt.subplot(gs[1, :-1])
ax3 = plt.subplot(gs[1:, -1])
ax4 = plt.subplot(gs[-1, 0])
ax5 = plt.subplot(gs[-1, -2])
I understand that gridspec.GridSpec(3, 3) will give a 3*3 layout, but what it means for gs[0, :] gs[1, :-1] gs[1:, -1] gs[-1, 0] gs[-1, -2]? I look up online but not found a detailed expanation, and I also try to change the index but not found a regular pattern. Could anyone give me some explanation or throw me a link about this?
Using gs = gridspec.GridSpec(3, 3), you have created essentially a 3 by 3 "grid" for your plots. From there, you can use gs[...,...] to specify the location and size of each subplot, by the number of rows and columns each subplot fills in that 3x3 grid. Looking in more detail:
gs[1, :-1] specifies where on the gridspace your subplot will be. For instance ax2 = plt.subplot(gs[1, :-1]) says: put the axis called ax2 on the first row (denoted by [1,...) (remember that in python, there is zero indexing, so this essentially means "second row down from the top"), stretching from the 0th column up until the last column (denoted by ...,:-1]). Because our gridspace is 3 columns wide, this means it will stretch 2 columns.
Perhaps it's better to show this by annotating each axis in your example:
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
gs = gridspec.GridSpec(3, 3)
ax1 = plt.subplot(gs[0, :])
ax2 = plt.subplot(gs[1, :-1])
ax3 = plt.subplot(gs[1:, -1])
ax4 = plt.subplot(gs[-1, 0])
ax5 = plt.subplot(gs[-1, -2])
ax1.annotate('ax1, gs[0,:] \ni.e. row 0, all columns',xy=(0.5,0.5),color='blue', ha='center')
ax2.annotate('ax2, gs[1, :-1]\ni.e. row 1, all columns except last', xy=(0.5,0.5),color='red', ha='center')
ax3.annotate('ax3, gs[1:, -1]\ni.e. row 1 until last row,\n last column', xy=(0.5,0.5),color='green', ha='center')
ax4.annotate('ax4, gs[-1, 0]\ni.e. last row, \n0th column', xy=(0.5,0.5),color='purple', ha='center')
ax5.annotate('ax5, gs[-1, -2]\ni.e. last row, \n2nd to last column', xy=(0.5,0.5), ha='center')
plt.show()
Related
I would like to plot a composite figure over two rows with one entry on the top row and two on the bottom row, with each subplot having the same aspect ratio. How can this be done?
I think you could follow this pattern
from matplotlib.gridspec import GridSpec
# do not forget constrained_layout=True to have some space between axes
fig = plt.figure(constrained_layout=True)
gs = GridSpec(2, 2, figure=fig)
ax1 = fig.add_subplot(gs[0, :])
# identical to ax1 = plt.subplot(gs.new_subplotspec((0, 0), colspan=2))
ax2 = fig.add_subplot(gs[1, 0])
ax3 = fig.add_subplot(gs[1, 1])
plt.show()
I apologise for the titlegore, but I could not figure out how to phrase it in a different way. The problem is best illustrated by the picture below. As you can see, I made figure consisting of 5 subplots using matplotlibs gridspec, which are fit into 4 square panels. The three empty panels have their own sets of x coordinates, and require their own label. However, the data from the first two panels shares the X axis, and (given that the actual label will be lengthy) I'd rather have only a single label and a single set of ticks for both, as shown here.
But as you can see, this leaves a rather large gap of whitespace between the two panels where the label would have gone. And this is what I'd like to solve; I'd like to stretch the two panels in equal amounts to fill up this white space. At the same time the top of the top panel and the bottom of the bottom panel should still align with the subplot to the right, and the bottom of the two panels shouldn't interfere with the position of the row that comes below either. I looked into the documentation on adjusting the panels in the documentation but I couldn't figure it out.
As an aside I'd also like to have a single y-axis label for the two panels, but I think I can fudge that in with fig.text().
The code that generates the above plot:
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import gridspec
xs = np.linspace(0,8*np.pi,101)
ys = np.cos(xs)
fig = plt.figure(figsize=(7.2,4.45*1.5))
gs1 = gridspec.GridSpec(4, 2, figure=fig)
#gs1.update(hspace=0.1)
ax1 = plt.subplot(gs1[0, 0])
ax1.plot(xs, ys)
#ax1.set_xlabel('X')
ax1.set_ylabel('Y1')
ax1.set_xticks([])
ax2 = plt.subplot(gs1[1, 0])
ax2.plot(xs, 0.5*ys)
ax2.set_xlabel('X')
ax2.set_ylabel('Y2')
ax2.set_ylim(-1,1)
gs2 = gridspec.GridSpec(4, 2)
ax3 = plt.subplot(gs2[0:2, 1])
ax3.set_xlabel('X3')
ax3.set_ylabel('Y3')
ax4 = plt.subplot(gs2[2:, 0])
ax4.set_xlabel('X4')
ax4.set_ylabel('Y4')
ax5 = plt.subplot(gs2[2:, 1])
ax5.set_xlabel('X5')
ax5.set_ylabel('Y5')
plt.tight_layout()
You can use a SubplotSpec in one of the quadrants of a 2x2 gridspec.
An example is found int gridspec-using-subplotspec.
Here it would look like
import numpy as np
from matplotlib import pyplot as plt
xs = np.linspace(0,8*np.pi,101)
ys = np.cos(xs)
fig = plt.figure(figsize=(7.2,4.45*1.5))
# 2x2 "outer" GridSpec
gs = fig.add_gridspec(2, 2)
# 2x1 "inner" GridSpec to be used
# in one cell of the outer grid
gs00 = gs[0,0].subgridspec(2, 1)
ax1 = fig.add_subplot(gs00[0])
ax1.plot(xs, ys)
ax1.set_ylabel('Y1')
ax1.set_xticks([])
ax2 = fig.add_subplot(gs00[1])
ax2.plot(xs, 0.5*ys)
ax2.set_xlabel('X')
ax2.set_ylabel('Y2')
ax2.set_ylim(-1,1)
ax3 = fig.add_subplot(gs[0,1])
ax3.set_xlabel('X3')
ax3.set_ylabel('Y3')
ax4 = fig.add_subplot(gs[1,0])
ax4.set_xlabel('X4')
ax4.set_ylabel('Y4')
ax5 = fig.add_subplot(gs[1,1])
ax5.set_xlabel('X5')
ax5.set_ylabel('Y5')
fig.tight_layout()
plt.show()
Using matplotlib, I'd like to display multiple subplots on a grid that has a different number of columns per row, where each subplot has roughly the same size, and the subplots are arranged such that they are more or less centered, like this:
It's a fairly simple matter to create a grid that has the 2, 3, 2 pattern with gridspec, but the problem there is that gridspec, unsurprisingly, aligns them to a grid, so the plots in the rows with 2 plots in them are wider:
Here's the code to generate that:
from matplotlib import gridspec
from matplotlib import pyplot as plt
fig = plt.figure()
arrangement = (2, 3, 2)
nrows = len(arrangement)
gs = gridspec.GridSpec(nrows, 1)
ax_specs = []
for r, ncols in enumerate(arrangement):
gs_row = gridspec.GridSpecFromSubplotSpec(1, ncols, subplot_spec=gs[r])
for col in range(ncols):
ax = plt.Subplot(fig, gs_row[col])
fig.add_subplot(ax)
for i, ax in enumerate(fig.axes):
ax.text(0.5, 0.5, "Axis: {}".format(i), fontweight='bold',
va="center", ha="center")
ax.tick_params(axis='both', bottom='off', top='off', left='off',
right='off', labelbottom='off', labelleft='off')
plt.tight_layout()
I know that I can set up a bunch of subplots and tweak their arrangement by working out the geometry of it, but I think it could get a bit complicated, so I was hoping that there might be a simpler method available.
I should note that even though I'm using a (2, 3, 2) arrangement as my example, I'd like to do this for arbitrary collections, not just this one.
The idea is usually to find the common denominator between the subplots, i.e. the largest subplot that the desired grid can be composed of, and span all subplots over several of those such that the desired layout is achieved.
Here you have 3 rows and 6 columns and each subplot spans 1 row and two columns, just that the subplots in the first row span subplot positions 1/2 and 3/4, while in the second row they span positions 0/1, 2/3, 4/5.
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
gs = gridspec.GridSpec(3, 6)
ax1a = plt.subplot(gs[0, 1:3])
ax1b = plt.subplot(gs[0, 3:5])
ax2a = plt.subplot(gs[1, :2])
ax2b = plt.subplot(gs[1, 2:4])
ax2c = plt.subplot(gs[1, 4:])
ax3a = plt.subplot(gs[2, 1:3])
ax3b = plt.subplot(gs[2, 3:5])
for i, ax in enumerate(plt.gcf().axes):
ax.text(0.5, 0.5, "Axis: {}".format(i), fontweight='bold',
va="center", ha="center")
ax.tick_params(axis='both', bottom='off', top='off', left='off',
right='off', labelbottom='off', labelleft='off')
plt.tight_layout()
plt.show()
I was working through matplotlib's documentation (http://matplotlib.org/users/gridspec.html#adjust-gridspec-layout), and in this particular example I do not understand the logic behind the layout of two GridSpecs in one figure. The significant part of the code they use (leaving out text, titles and labels) is
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
f = plt.figure()
gs1 = GridSpec(3, 3)
gs1.update(left=0.05, right=0.48, wspace=0.05)
ax1 = plt.subplot(gs1[:-1, :])
ax2 = plt.subplot(gs1[-1, :-1])
ax3 = plt.subplot(gs1[-1, -1])
gs2 = GridSpec(3, 3)
gs2.update(left=0.55, right=0.98, hspace=0.05)
ax4 = plt.subplot(gs2[:, :-1])
ax5 = plt.subplot(gs2[:-1, -1])
ax6 = plt.subplot(gs2[-1, -1])
This gives the following result (http://matplotlib.org/_images/demo_gridspec03.png):
These two GridSpecs seem to be aligned next to each other by default. Do I miss something in the code, that does this explicitly?
I tried to add a third GridSpec, like so:
gs3 = gridspec.GridSpec(3, 3)
ax7 = plt.subplot(gs3[:, 0])
ax8 = plt.subplot(gs3[:, 1:])
but this just fills the whole figure and the first two GridSpecs are "overpainted".
To restate my question, is there some implicit logic for the layout of two GridSpecs in a figure (note that I know of the method GridSpecFromSubplotSpec, but here it is not being used)?
The GridSpec extent can be adjusted with the update command. With this line you limit the first GridSpec to the left side (48%) of the Figure.
gs1.update(left=0.05, right=0.48, wspace=0.05)
The second GridSpec is then limited to the right side of the Figure with
gs1.update(left=0.55, right=0.98, hspace=0.05)
Similarly you can limit the vertical extent with the keywords top and bottom.
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()
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()
Another easy cheat is to give the title of the middle column as subplot row XX\n\nPlot title No.YY