Add multiple suptitles [duplicate] - python

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()

Related

Why is Seaborn plotting two legends, how do I remove one and fix the other?

When I run the code shown below I get a figure containing 2 legends. I can't figure out why two are being plotted and I havent been able to remove one of them. My aim is to keep the legend that is outside of the figure, remove the one thats inside the figure and also somehow stop the weird cropping that is cutting off the right side of the legend outside the figure.
I had a previous question asking something similar, but that issue was solved by using seaborns scatterplot instead of the relplot. Sadly neither of the answers that worked in that question work here. If this problem is arising out of an "uncoventional" way of plotting the type of figure I'm trying to make, then please let me know. Doing it properly is better than hacking your way to the solution...
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
import pandas as pd
#setup
sns.set(font_scale=2)
sns.set_context('poster')
#figure and axes
fig = plt.figure(figsize=(20,20))
axs = {i:fig.add_subplot(330+i) for i in range(1,10)}
#create random data
r = np.random.randint
N=10
df = pd.DataFrame(columns=['No.','x1','x2','x3','y1','y2','y3'])
for i in range(N):
df.loc[i] = i+1,r(50,high=100),r(50,high=100),r(50,high=100),r(50,high=100),r(50,high=100),r(50,high=100)
#create axes labels
x_labels = ['x1','x2','x3']
y_labels = ['y1','y2','y3']
xy_labels = [(x,y) for y in y_labels for x in x_labels ]
#plot on axes
for i,(x_label,y_label) in enumerate(xy_labels):
if i ==0:#if statement so only one of the plots has legend='full'
a = sns.scatterplot(
data=df,
x=x_label,
y=y_label,
legend='full', #create the legend
ax=axs[i+1],
hue='No.',
palette=sns.color_palette("hls", N)
)
fig.legend(bbox_to_anchor=(1, 0.7), loc=2, borderaxespad=0.) #Move the legend outside the plot
a.legend_.remove() #attempt to remove the legend
else:
a = sns.scatterplot(
data=df,
x=x_label,
y=y_label,
legend=False,
ax=axs[i+1],
hue='No.',
palette=sns.color_palette("hls", N)
)
#remove axes labels from specific plots
if i not in [0,3,6]: axs[i+1].set_ylabel('')
if i not in [6,7,8]: axs[i+1].set_xlabel('')
#add line plots and set limits
for ax in axs.values():
sns.lineplot(x=range(50,100),y=range(50,100), ax=ax, linestyle='-')
ax.set_xlim([50,100])
ax.set_ylim([50,100])
fig.tight_layout()
You can add legend=False in the last part of your code.
#setup
sns.set(font_scale=2)
sns.set_context('poster')
#figure and axes
fig = plt.figure(figsize=(20,20))
axs = {i:fig.add_subplot(330+i) for i in range(1,10)}
#create axes labels
x_labels = ['x1','x2','x3']
y_labels = ['y1','y2','y3']
xy_labels = [(x,y) for y in y_labels for x in x_labels ]
#plot on axes
for i,(x_label,y_label) in enumerate(xy_labels):
if i ==0:#if statement so only one of the plots has legend='full'
a = sns.scatterplot(
data=df,
x=x_label,
y=y_label,
legend='full', #create the legend
ax=axs[i+1],
hue='No.',
palette=sns.color_palette("hls", N)
)
fig.legend(bbox_to_anchor=(1, 0.7), loc=2, borderaxespad=0.) #Move the legend outside the plot
a.legend_.remove() #attempt to remove the legend
else:
a = sns.scatterplot(
data=df,
x=x_label,
y=y_label,
legend=False,
ax=axs[i+1],
hue='No.',
palette=sns.color_palette("hls", N)
)
#remove axes labels from specific plots
if i not in [0,3,6]: axs[i+1].set_ylabel('')
if i not in [6,7,8]: axs[i+1].set_xlabel('')
#add line plots and set limits
for ax in axs.values():
sns.lineplot(x=range(50,100),y=range(50,100), ax=ax, linestyle='-', legend=False)
ax.set_xlim([50,100])
ax.set_ylim([50,100])
fig.tight_layout()
Result:

dividing a subplot using gridspecs to have 6 equal size buttons

I am new to data visualization world and has been assigned to create a
GUI(graphical user interface) to visualize data and have some fancy controls
in it.
The problem is that I am using subplots using gridspecs and I need to have
equal size buttons in "ax5 subplot" as shown in the figure.
I can't seem to find a solution to access that "ax5" and then divide this
area into 6 equal buttons.
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
def make_ticklabels_invisible(fig):
for i, ax in enumerate(fig.axes):
ax.text(0.5, 0.5, "ax%d" % (i+1), va="center", ha="center")
ax.tick_params(labelbottom=False, labelleft=False)
# demo 3 : gridspec with subplotpars set.
fig = plt.figure(facecolor = '#0F0F0F')
fig.suptitle("GridSpec w/ different subplotpars", color= '#e21f1f')
gs1 = GridSpec(3, 3)
gs1.update(left=0.01, right=0.49, wspace=0.05, hspace=0.05)
ax1 = plt.subplot(gs1[:-1, :-1])
ax2 = plt.subplot(gs1[:-1, -1:])
ax3 = plt.subplot(gs1[-1, :])
gs2 = GridSpec(3, 3)
gs2.update(left=0.50, right=0.98, hspace=0.05, wspace=0.05)
ax4 = plt.subplot(gs2[:-2, :])
# need to have six buttons in 5th subplot
ax5 = plt.subplot(gs2[-2:-1, :])
ax6 = plt.subplot(gs2[-1, :])
make_ticklabels_invisible(fig)
pos5 = ax5.get_position()
print(pos5)
plt.show()

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)

matplotlib: hide subplot and fill space with other subplots

I've got a figure that contains three subplots which are arranged vertically. Once I click into the figure, I want the second subplot ax2 to be hidden and the other plots to fill the space. A second click into the figure should restore the original plot and layout.
Hiding the subplot ax2 isn't a problem, but how can I rearrange the positions of the other subplots?
I've tried creating a new GridSpec, using the set_position and set_subplotspec methods, but nothing worked out. I'm sure I'm missing something here, any help would be appreciated.
This is my code:
import matplotlib.pyplot as plt
from matplotlib import gridspec
fig = plt.figure()
gs = gridspec.GridSpec(3, 1, height_ratios=[5, 2, 1])
ax1 = fig.add_subplot(gs[0])
ax2 = fig.add_subplot(gs[1], sharex=ax1)
ax3 = fig.add_subplot(gs[2], sharex=ax2)
visible = True
def toggle_ax2(event):
global visible
visible = not visible
ax2.set_visible(visible)
plt.draw()
fig.canvas.mpl_connect('button_press_event', toggle_ax2)
plt.show()
You can define two different GridSpecs. One would have 3 subplots, the other 2. Depending on the visibility of the middle axes, you change the position of the other two axes to obey to the first or second GridSpec.
(There is no need for any dummy figure or so, like other answers might suggest.)
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
fig = plt.figure()
gs = gridspec.GridSpec(3, 1, height_ratios=[5, 2, 1], hspace=0.3)
gs2 = gridspec.GridSpec(2,1, height_ratios=[5,3])
ax1 = fig.add_subplot(gs[0])
ax2 = fig.add_subplot(gs[1], sharex=ax1)
ax3 = fig.add_subplot(gs[2], sharex=ax2)
ax1.plot([1,2,3], [1,2,3], color="crimson")
ax2.plot([1,2,3], [2,3,1], color="darkorange")
ax3.plot([1,2,3], [3,2,1], color="limegreen")
visible = True
def toggle_ax2(event):
global visible
visible = not visible
ax2.set_visible(visible)
if visible:
ax1.set_position(gs[0].get_position(fig))
ax3.set_position(gs[2].get_position(fig))
else:
ax1.set_position(gs2[0].get_position(fig))
ax3.set_position(gs2[1].get_position(fig))
plt.draw()
fig.canvas.mpl_connect('button_press_event', toggle_ax2)
plt.show()
Left: original; right: after clicking
You can create a new gridspec instance, and use that to create some dummy figures in a second figure (you can close this before you plt.show, so you never actually see it, we just want to grab some positions from the axes here).
By storing the two possible positions for ax1 and ax3 from that dummy figure and the original figure, then you can use ax.set_position() in your toggle_ax2 function to change the positions of the remaining two axes.
import matplotlib.pyplot as plt
from matplotlib import gridspec
fig = plt.figure()
gs = gridspec.GridSpec(3, 1, height_ratios=[5, 2, 1])
ax1 = fig.add_subplot(gs[0])
ax2 = fig.add_subplot(gs[1], sharex=ax1)
ax3 = fig.add_subplot(gs[2], sharex=ax2)
# Store the original positions of ax1 and ax3
pos1_1 = ax1.get_position()
pos3_1 = ax3.get_position()
# Create a second gridspec for when ax2 is hidden. Keep 5:1 ratio
gs2 = gridspec.GridSpec(2, 1, height_ratios=[5, 1])
fig2 = plt.figure()
ax1_2 = fig2.add_subplot(gs2[0])
ax3_2 = fig2.add_subplot(gs2[1])
# Store the positions of ax1 and ax3 in the new gridspec
pos1_2 = ax1_2.get_position()
pos3_2 = ax3_2.get_position()
# Close the dummy figure2
plt.close(fig2)
visible = True
def toggle_ax2(event):
global visible
visible = not visible
ax2.set_visible(visible)
# Use the stored positions to switch between
# different arrangements of ax1 and ax3
if visible:
ax1.set_position(pos1_1)
ax3.set_position(pos3_1)
else:
ax1.set_position(pos1_2)
ax3.set_position(pos3_2)
plt.draw()
fig.canvas.mpl_connect('button_press_event', toggle_ax2)
plt.show()
Original configuration:
After removing ax2:

Strange subplot effect with matplotlib

I try to plot my data as follows:
First subplot
alpha = ['Joy', 'fear', 'sadness', 'thankful','anger','surprise','love']
fig = pl.figure()
ax = fig.add_subplot(511,title='SGD')
cax = ax.matshow(cm)
fig.colorbar(cax)
ax.set_xticklabels(['']+alpha)
ax.set_yticklabels(['']+alpha)
Second subplot later on with new cm:
ax = fig.add_subplot(521,title='LIBLINEAR')
cax = ax.matshow(cm)
fig.colorbar(cax)
ax.set_xticklabels(['']+alpha)
ax.set_yticklabels(['']+alpha)
Third subplot later on with new cm:
ax = fig.add_subplot(512,title='MNB')
cax = ax.matshow(cm)
fig.colorbar(cax)
ax.set_xticklabels(['']+alpha)
ax.set_yticklabels(['']+alpha)
Fourth subplot later on with new cm
ax = fig.add_subplot(522,title='BNB')
cax = ax.matshow(cm)
fig.colorbar(cax)
ax.set_xticklabels(['']+alpha)
ax.set_yticklabels(['']+alpha)
Last subplot with new cm
ax = fig.add_subplot(532,title='NC')
cax = ax.matshow(cm)
fig.colorbar(cax)
ax.set_xticklabels(['']+alpha)
ax.set_yticklabels(['']+alpha)
pl.show()
I get this:
What am I doing wrong ?
You change the layout of the subplots every time. You use fig.add_subplot(511) which is short for fig.add_subplot(n_rows, n_columns, index). n_rows and n_columns determine the layout of the sub plots in the figure, index the position (starting with 1).
So if you want to have five rows and two columns, you make
ax = fig.add_subplot(5,2,1)
(...)
ax = fig.add_subplot(5,2,2)
(...)
ax = fig.add_subplot(5,2,3)
to plot 1st row, 1st column; 1st row, 2nd column; 2nd row, 1st column etc.
Again, note that fig.add_subplot(5,2,1) and fig.add_subplot(521) are equivalent.

Categories

Resources