Matplotlib: Assign legend to different figures - matplotlib

Inside a loop I am calculating some things and then I want to plot them in two different figures. I have set up the figures as
susc_comp, (ax1,ax2) = plt.subplots( 2, 1, sharex=True, sharey='none', figsize=(8.3,11.7))
cole_cole, (ax3) = plt.subplots( 1, 1, sharex='none', sharey='none', figsize=(8.3,11.7))
for j,temp in enumerate(indexes_T[i]):
Calculate and plot in the corresponding ax1,ax2,ax3
plt.legend(loc=0, fontsize='small', numpoints = 1, ncol=(len(indexes_T[i]))/2, frameon=False)
susc_comp.savefig('suscp_components'+str(field)+'Oe.png', dpi=300)
cole_cole.savefig('Cole_Cole'+str(field)+'Oe.png', dpi=300)
But I get the legend only in the sus_comp figure (it is the same legend for both figures). How can I select the figure and add the legend to each of them?
Thank you very much!

You can call figure.legend directly (although I think this may have less functionality than plt.legend). Therefore, I would do this a different way.
The question states that both legends are the same. In addition, the second figure only has 1 axes in it. Therefore one solution would be to get the handles and labels from ax3, then manually apply those to both figures. A simplified example is below:
import matplotlib.pyplot as plt
susc_comp, (ax1, ax2) = plt.subplots(1,2)
cole_cole, ax3 = plt.subplots()
ax1.plot([1,2,3], label="Test1")
ax2.plot([3,2,1], label="Test2")
ax3.plot([1,2,3], label="Test1")
ax3.plot([3,2,1], label="Test2")
handles, labels = ax3.get_legend_handles_labels()
ax2.legend(handles, labels, loc=1, fontsize='small', numpoints = 1)
ax3.legend(handles, labels, loc=1, fontsize='small', numpoints = 1)
plt.show()
This gives the following 2 figures:

Related

Set one colorbar for two images/subplots, and another colorbar for third image in 3 panel figure

This MWE from the matplotlib doc is a useful reference.
import numpy as np
import matplotlib.pyplot as plt
# Fixing random state for reproducibility
np.random.seed(19680801)
plt.subplot(311)
plt.imshow(np.random.random((100, 100)))
plt.subplot(312)
plt.imshow(np.random.random((100, 100)))
plt.subplot(313)
plt.imshow(np.random.random((100, 100)))
plt.subplots_adjust(bottom=0.1, right=0.8, top=.9)
cax = plt.axes([0.85, 0.1, 0.075, 0.8])
plt.colorbar(cax=cax)
plt.show()
This produces:
My two main questions are:
How do I get the first two plots to share a colorbar and the third to have its own?
I don't really understand what 'cax' is doing or why the values are what they are.
As the question just says - two plots share a colorbar, you can either have the first two in the first row with a common colorbar, while the third will have another one, or you could do all 3 in separate columns with the first two sharing a colorbar.
Code for first option
import numpy as np
import matplotlib.pyplot as plt
# Fixing random state for reproducibility
np.random.seed(19680801)
fig, ax = plt.subplots()
plt.subplot(221) ## 2x2 plot, 1st item or in position 1,1
plt.imshow(np.random.random((100, 100)))
ax2 = plt.subplot(222)
im2 = ax2.imshow(np.random.random((100, 100)))
plt.colorbar(im2, ax=ax2)
ax3 = plt.subplot(223)
im3 = ax3.imshow(np.random.random((100, 100)))
plt.colorbar(im3, ax=ax3)
plt.show()
Plot
Option 2 code
import numpy as np
import matplotlib.pyplot as plt
# Fixing random state for reproducibility
np.random.seed(19680801)
fig, ax = plt.subplots(figsize=(5,6))
ax1 = plt.subplot(311) ## 3 rows and 1 column, position 1,1 =1
im = ax1.imshow(np.random.random((100, 100)))
ax2 = plt.subplot(312)
im = ax2.imshow(np.random.random((100, 100)))
ax3 = plt.subplot(313)
im3 = ax3.imshow(np.random.random((100, 100)))
plt.colorbar(im3, ax=ax3)
plt.colorbar(im, ax=[ax1, ax2], aspect = 40) ##Common colobar for ax1 and ax2; aspect used to set colorbar thickness/width
plt.show()
Plot
Although I have not used colorbar axis, it is the axis into which the colorbar is drawn, similar to what we have above in ax1, ax2, ax3 above. The numbers are used to specify where the colorbar should be located. Look at the last example here to see how the position is set. Hope this helps

Move one subplot down

I am making a 4 panel plot and was wondering if there is any way to just move the subplot in the bottom left down a bit so it stays in line with the rest of the plots and the titles don't overlay. I believe it is because the bottom left plot doesn't have a colorbar but I'm not sure how I would fix that. I am using the add subplot function. So for the bottom left pot the axes looks like this. ax = fig.add_subplot(2,2,3,projection=proj)
I think it might work to read the current position with ax.get_position(), then move it down a bit with ax.set_position(). Something like this:
fig, axs = plt.subplots(2, 2)
pos = axs[1, 0].get_position()
new_pos = [pos.x0, pos.y0-0.1, pos.width, pos.height]
axs[1, 0].set_position(new_pos)
This results in:
As for 'how much?'... Not sure if there's a better way than trial and error though. Not ideal.
You can use inset_axes in combination with constrained layout:
import matplotlib.pyplot as plt
import numpy as np
fig, axs = plt.subplots(2, 2, layout='constrained')
for i, ax in enumerate(axs.flat):
pcm = ax.pcolormesh(np.random.random((20, 20)) * (i + 1))
ax.set_title("very long title\nthat strechtes over two lines")
if i != 2:
cax = ax.inset_axes([0, -0.35, 1, 0.1])
fig.colorbar(pcm, ax=ax, cax=cax, orientation='horizontal')
See also Placing Colorbars.

Seaborn jointplot link x-axis to Matplotlib subplots

Is there a way to add additional subplots created with vanilla Matplotlib to (below) a Seaborn jointplot, sharing the x-axis? Ideally I'd like to control the ratio between the jointplot and the additional plots (similar to gridspec_kw={'height_ratios':[3, 1, 1]}
I tried to fake it by tuning figsize in the Matplotlib subplots, but obviously it doesn't work well when the KDE curves in the marginal plot change. While I could manually resize the output PNG to shrink/grow one of the figures, I'd like to have everything aligned automatically.
I know this is tricky with the way the joint grid is set up, but maybe it is reasonably simple for someone fluent in the underpinnings of Seaborn.
Here is a minimal working example, but there are two separate figures:
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
Figure 1
diamonds = sns.load_dataset('diamonds')
g = sns.jointplot(
data=diamonds,
x="carat",
y="price",
hue="cut",
xlim=(1, 2),
)
g.ax_marg_x.remove()
Figure 2
fig, (ax1, ax2) = plt.subplots(2,1,sharex=True)
ax1.scatter(x=diamonds["carat"], y=diamonds["depth"], color="gray", edgecolor="black")
ax1.set_xlim([1, 2])
ax1.set_ylabel("depth")
ax2.scatter(x=diamonds["carat"], y=diamonds["table"], color="gray", edgecolor="black")
ax2.set_xlabel("carat")
ax2.set_ylabel("table")
Desired output:
I think this is a case where setting up the figure using matplotlib functions is going to be better than working backwards from a seaborn figure layout that doesn't really match the use-case.
If you have a non-full subplot grid, you'll have to decide whether you want to (A) set up all the subplots and then remove the ones you don't want or (B) explicitly add each of the subplots you do want. Let's go with option A here.
figsize = (6, 8)
gridspec_kw = dict(
nrows=3, ncols=2,
width_ratios=[5, 1],
height_ratios=[4, 1, 1],
)
subplot_kw = dict(sharex="col", sharey="row")
fig = plt.figure(figsize=figsize, constrained_layout=True)
axs = fig.add_gridspec(**gridspec_kw).subplots(**subplot_kw)
sns.kdeplot(data=df, y="price", hue="cut", legend=False, ax=axs[0, 1])
sns.scatterplot(data=df, x="carat", y="price", hue="cut", ax=axs[0, 0])
sns.scatterplot(data=df, x="carat", y="depth", color=".2", ax=axs[1, 0])
sns.scatterplot(data=df, x="carat", y="table", color=".2", ax=axs[2, 0])
axs[0, 0].set(xlim=(1, 2))
axs[1, 1].remove()
axs[2, 1].remove()
BTW, this is almost a bit easier with plt.subplot_mosaic, but it does not yet support axis sharing.
You could take the figure created by jointplot(), move its padding (with subplots_adjust()) and add 2 extra axes.
The example code will need some tweaking for each particular situation.
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
import seaborn as sns
diamonds = sns.load_dataset('diamonds')
g = sns.jointplot(data=diamonds, x="carat", y="price", hue="cut",
xlim=(1, 2), height=12)
g.ax_marg_x.remove()
g.fig.subplots_adjust(left=0.08, right=0.97, top=1.05, bottom=0.45)
axins1 = inset_axes(g.ax_joint, width="100%", height="30%",
bbox_to_anchor=(0, -0.4, 1, 1),
bbox_transform=g.ax_joint.transAxes, loc=3, borderpad=0)
axins2 = inset_axes(g.ax_joint, width="100%", height="30%",
bbox_to_anchor=(0, -0.75, 1, 1),
bbox_transform=g.ax_joint.transAxes, loc=3, borderpad=0)
shared_x_group = g.ax_joint.get_shared_x_axes()
shared_x_group.remove(g.ax_marg_x)
shared_x_group.join(g.ax_joint, axins1)
shared_x_group.join(g.ax_joint, axins2)
axins1.scatter(x=diamonds["carat"], y=diamonds["depth"], color="grey", edgecolor="black")
axins1.set_ylabel("depth")
axins2.scatter(x=diamonds["carat"], y=diamonds["table"], color="grey", edgecolor="black")
axins2.set_xlabel("carat")
axins2.set_ylabel("table")
g.ax_joint.set_xlim(1, 2)
plt.setp(axins1.get_xticklabels(), visible=False)
plt.show()
PS: How to share x axes of two subplots after they have been created contains some info about sharing axes (although here you simply get the same effect by setting the xlims for each of the subplots).
The code to position the new axes has been adapted from this tutorial example.

How to use mode='expand' and center a figure-legend label given only one label entry?

I would like to generate a centered figure legend for subplot(s), for which there is a single label. For my actual use case, the number of subplot(s) is greater than or equal to one; it's possible to have a 2x2 grid of subplots and I would like to use the figure-legend instead of using ax.legend(...) since the same single label entry will apply to each/every subplot.
As a brief and simplified example, consider the code just below:
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(10)
y = np.sin(x)
fig, ax = plt.subplots()
ax.plot(x, y, color='orange', label='$f(x) = sin(x)$')
fig.subplots_adjust(bottom=0.15)
fig.legend(mode='expand', loc='lower center')
plt.show()
plt.close(fig)
This code will generate the figure seen below:
I would like to use the mode='expand' kwarg to make the legend span the entire width of the subplot(s); however, doing so prevents the label from being centered. As an example, removing this kwarg from the code outputs the following figure.
Is there a way to use both mode='expand' and also have the label be centered (since there is only one label)?
EDIT:
I've tried using the bbox_to_anchor kwargs (as suggested in the docs) as an alternative to mode='expand', but this doesn't work either. One can switch out the fig.legend(...) line for the line below to test for yourself.
fig.legend(loc='lower center', bbox_to_anchor=(0, 0, 1, 0.5))
The handles and labels are flush against the left side of the legend. There is no mechanism to allow for aligning them.
A workaround could be to use 3 columns of legend handles and fill the first and third with a transparent handle.
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(10)
y = np.sin(x)
fig, ax = plt.subplots()
fig.subplots_adjust(bottom=0.15)
line, = ax.plot(x, y, color='orange', label='$f(x) = sin(x)$')
proxy = plt.Rectangle((0,0),1,1, alpha=0)
fig.legend(handles=[proxy, line, proxy], mode='expand', loc='lower center', ncol=3)
plt.show()

Labels on Gridspec [duplicate]

I'm facing a problem in showing the legend in the correct format using matplotlib.
EDIT: I have 4 subplots in a figure in 2 by 2 format and I want legend only on the first subplot which has two lines plotted on it. The legend that I got using the code attached below contained endless entries and extended vertically throughout the figure. When I use the same code using linspace to generate fake data the legend works absolutely fine.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as mtick
import os
#------------------set default directory, import data and create column output vectors---------------------------#
path="C:/Users/Pacman/Data files"
os.chdir(path)
data =np.genfromtxt('vrp.txt')
x=np.array([data[:,][:,0]])
y1=np.array([data[:,][:,6]])
y2=np.array([data[:,][:,7]])
y3=np.array([data[:,][:,9]])
y4=np.array([data[:,][:,11]])
y5=np.array([data[:,][:,10]])
nrows=2
ncols=2
tick_l=6 #length of ticks
fs_axis=16 #font size of axis labels
plt.rcParams['axes.linewidth'] = 2 #Sets global line width of all the axis
plt.rcParams['xtick.labelsize']=14 #Sets global font size for x-axis labels
plt.rcParams['ytick.labelsize']=14 #Sets global font size for y-axis labels
plt.subplot(nrows, ncols, 1)
ax=plt.subplot(nrows, ncols, 1)
l1=plt.plot(x, y2, 'yo',label='Flow rate-fan')
l2=plt.plot(x,y3,'ro',label='Flow rate-discharge')
plt.title('(a)')
plt.ylabel('Flow rate ($m^3 s^{-1}$)',fontsize=fs_axis)
plt.xlabel('Rupture Position (ft)',fontsize=fs_axis)
# This part is not working
plt.legend(loc='upper right', fontsize='x-large')
#Same code for rest of the subplots
I tried to implement a fix suggested in the following link, however, could not make it work:
how do I make a single legend for many subplots with matplotlib?
Any help in this regard will be highly appreciated.
If I understand correctly, you need to tell plt.legend what to put as legends... at this point it is being loaded empty. What you get must be from another source. I have quickly the following, and of course when I run fig.legend as you do I get nothing.
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
ax1 = fig.add_axes([0.1, 0.1, 0.4, 0.7])
ax2 = fig.add_axes([0.55, 0.1, 0.4, 0.7])
x = np.arange(0.0, 2.0, 0.02)
y1 = np.sin(2*np.pi*x)
y2 = np.exp(-x)
l1, l2 = ax1.plot(x, y1, 'rs-', x, y2, 'go')
y3 = np.sin(4*np.pi*x)
y4 = np.exp(-2*x)
l3, l4 = ax2.plot(x, y3, 'yd-', x, y4, 'k^')
fig.legend(loc='upper right', fontsize='x-large')
#fig.legend((l1, l2), ('Line 1', 'Line 2'), 'upper left')
#fig.legend((l3, l4), ('Line 3', 'Line 4'), 'upper right')
plt.show()
I'd suggest doing one by one, and then applying for all.
It is useful to work with the axes directly (ax in your case) when when working with subplots. So if you set up two plots in a figure and only wish to have a legend in your second plot:
t = np.linspace(0, 10, 100)
plt.figure()
ax1 = plt.subplot(2, 1, 1)
ax1.plot(t, t * t)
ax2 = plt.subplot(2, 1, 2)
ax2.plot(t, t * t * t)
ax2.legend('Cubic Function')
Note that when creating the legend, I am doing so on ax2 as opposed to plt. If you wish to create a second legend for the first subplot, you can do so in the same way but on ax1.