Optimal legend placement after y-axis limiting - matplotlib

I've a fig with two subplots, I want them to have the same y-scale.
fig, ax = plt.subplots(1, 2,figsize=(10,6))
ax[0].set_title('Over Global region, {} points'. Format(len(df)))
ax[0].plot(df.groupby('type')['thickness'].mean(),label='Cloud mean thickness',color='red')
ax[0].set_xlabel('Cloud Type',fontsize=13)
ax[0].set_ylabel('(in km)',fontsize=15)
#Put xticks with labels at the bottom of the plot
ax[0].set_xticks(cloudlabel,cloudtype_dict.values(),rotation=50)
lon_left, lon_right, lat_bot, lat_top = 60, 100, 0, 40
dfcopy = df.copy()
dfcopy_aoi = dfcopy[(dfcopy['lon']>=lon_left) & (dfcopy['lon']<=lon_right) & (dfcopy['lat']>=lat_bot) & (dfcopy['lat']<=lat_top)]
ax[1].set_title('Over Indian subcontinent region, {} points'.format(len(dfcopy_aoi)))
ax[1].plot(dfcopy_aoi.groupby('type')['thickness'].mean(),label='Cloud mean thickness',color='red')
ax[1].set_xlabel('Cloud Type',fontsize=13)
ax[1].set_ylabel('(in km)',fontsize=15)
ax[1].set_xticks(cloudlabel,cloudtype_dict.values(),rotation=50);
#Shared y-axis
y_lim=[0,0]
y_lim = [min(ax[0].get_ylim()[0], ax[1].get_ylim()[0]), max(ax[0].get_ylim()[1], ax[1].get_ylim()[1])]
for i in range(2):
ax[i].set_ylim(y_lim)
ax[0].legend(loc='best');
ax[1].legend(loc='best');
Probelm
I'm not getting the optimal legend placement for ax[0] even though I'm putting the legend argument at the end of everything.
Below is the image I am getting with bad legend loc for the first axis.

Related

Missing Labels from Legend With Subplots

I'm trying to plot multiple things in a figure with subplots.
When I try generating a legend for the figure, it is only showing labels for the data in the very last subplot, instead of for the entire figure.
fig, axs = plt.subplots(3, 2, figsize=(16,18))
first_pops = {"M71 (Masseron)":[1., "purple"], \
"6273 (Yong)":[0.1, "yellowgreen"], \
"2419 (Carretta)":[0.0, "olive"], \
"6218 (Carretta)":[0.25, "darkcyan"],
}
elements = ["O I", "Na I", "Mg I", "Si I", "Ti I", "Ca I"]
element_labels = ["[O/Fe]", "[Na/Fe]", "[Mg/Fe]", "[Si/Fe]", "[Ti/Fe]", "[Ca/Fe]"]
for k, ax in enumerate(axs.ravel()):
element = elements[k]
element_label = element_labels[k]
for GC in first_pops.keys():
try:
plot_background_stars(glob_dict[GC]["[Fe/H]"],\
glob_dict[GC][element_label],\
label = GC, ax=ax, color = first_pops[GC][1],\
s = 140, marker="*", facecolors='none', linewidth=2)
except KeyError:
print(GC+" doesn't have "+element+" measured")
ax.set_xlabel("[Fe/H]")
ax.set_ylabel(element_label)
handles, labels = fig.gca().get_legend_handles_labels()
by_label = dict(zip(labels, handles))
fig.legend(by_label.values(), by_label.keys())
I've tried replacing "fig" with "plt" and vice versa.
I'm using the following to plot each data set:
def plot_background_stars(x, y, ax=None, **plt_kwargs):
if ax is None:
ax = plt.gca()
ax.scatter(x, y, **plt_kwargs)
return(ax)
The resulting legend has just two of the plotted colors, instead of four. Like maybe it's only pulling the labels from the last subplot? But why would it do that?

How to generate several legends for single plot matplotlib

I was making a plot of f(x,y,z) and wanted this to be displayed in a 2D-plane. To avoid cluttering my legend i decided to have different linestyles for y, different colors for z and place the two in two separate legends. I couldn't find out how to do this even after a lot of digging, so I'm posting the solution i came up with here :) If anyone has more elegant solutions I'm all ears :)
Basically the solution was to make three plots, set two of them to have size (0,0) and place those two where i wanted the legends. It feels like an ugly way to do it, but it gave a nice plot and i didn't find any other way :) The resulting plot looks like this:
def plot_alt(style = 'log'):
cmap = cm.get_cmap('inferno')
color_scale = 1.2 #Variable to get colors from a certain part of the colormap
#Making grids for delta T and average concentration
D_T_axis = -np.logspace(np.log10(400), np.log10(1), 7)
C_bar_list = np.linspace(5,10,4)
ST_list = np.logspace(-3,-1,100)
# f(x,y,z)
DC_func = lambda C_bar, ST, DT: 2*C_bar * (1 - np.exp(ST*DT))/(1 + np.exp(ST*DT))
#Some different linestyles
styles = ['-', '--', '-.', ':']
fig, ax = plt.subplots(1,3, figsize = (10,5))
plt.sca(ax[0])
for i, C_bar in enumerate(C_bar_list): #See plot_c_rel_av_DT() for 'enumerate'
for j, DT in enumerate(D_T_axis):
plt.plot(ST_list, DC_func(C_bar, ST_list, DT), color = cmap(np.log10(-DT)/(color_scale*np.log10(-D_T_axis[0]))),
linestyle = styles[i])
# Generating separate legends by plotting lines in the two other subplots
# Basically: to get two separate legends i make two plots, place them where i want the legends
# and set their size to zero, then display their legends.
plt.sca(ax[1]) #Set current axes to ax[1]
for i, C_bar in enumerate(C_bar_list):
# Plotting the different linestyles
plt.plot(C_bar_list, linestyle = styles[i], color = 'black', label = str(round(C_bar, 2)))
plt.sca(ax[2])
for DT in D_T_axis:
#plotting the different colors
plt.plot(D_T_axis, color = cmap(np.log10(-DT)/(color_scale*np.log10(-D_T_axis[0]))), label = str(int(-DT)))
#Placing legend
#This is where i move and scale the three plots to make one plot and two legends
box0 = ax[0].get_position() #box0 is an object that contains the position and dimentions of the ax[0] subplot
box2 = ax[2].get_position()
ax[0].set_position([box0.x0, box0.y0, box2.x0 + 0.4*box2.width, box0.height])
box0 = ax[0].get_position()
ax[1].set_position([box0.x0 + box0.width, box0.y0 + box0.height + 0.015, 0,0])
ax[1].set_axis_off()
ax[2].set_position([box0.x0 + box0.width ,box0.y0 + box0.height - 0.25, 0,0])
ax[2].set_axis_off()
#Displaying plot
plt.sca(ax[0])
plt.xscale('log')
plt.xlim(0.001, 0.1)
plt.ylim(0, 5)
plt.xlabel(r'$S_T$')
plt.ylabel(r'$\Delta C$')
ax[1].legend(title = r'$\langle c \rangle$ [mol/L]',
bbox_to_anchor = (1,1), loc = 'upper left')
ax[2].legend(title = r'$-\Delta T$ [K]', bbox_to_anchor = (1,1), loc = 'upper left')
#Suptitle is the title of the figure. You can also have titles for the individual subplots
plt.suptitle('Steady state concentration gradient as a function of Soret-coefficient\n'
'for different temperature gradients and total concentrations')

y and x axis subplots matplotlib

A quite basic question about ticks' labels for x and y-axis. According to this code
fig, axes = plt.subplots(6,12, figsize=(50, 24), constrained_layout=True, sharex=True , sharey=True)
fig.subplots_adjust(hspace = .5, wspace=.5)
custom_xlim = (-1, 1)
custom_ylim = (-0.2,0.2)
for i in range(72):
x_data = ctheta[i]
y_data = phi[i]
y_err = err_phi[i]
ax = fig.add_subplot(6, 12, i+1)
ax.plot(x_data_new, bspl(x_data_new))
ax.axis('off')
ax.errorbar(x_data,y_data, yerr=y_err, fmt="o")
ax.set_xlim(custom_xlim)
ax.set_ylim(custom_ylim)
I get the following output:
With y labels for plots on the first column and x labels for theone along the last line, although I call them off.
Any idea?
As #BigBen wrote in their comment, your issue is caused by you adding axes to your figure twice, once via fig, axes = plt.subplots() and then once again within your loop via fig.add_subplot(). As a result, the first set of axes is still visible even after you applied .axis('off') to the second set.
Instead of the latter, you could change your loop to:
for i in range(6):
for j in range(12):
ax = axes[i,j] # these are the axes created via plt.subplots(6,12,...)
ax.axis('off')
# … your other code here

how to add variable error bars to scatter plot points with shared axes in python matplotlib

I have generated a plot that shows a topographic profile with points along the profile that represent dated points. However, these dated points also have symmetric uncertainty values/error bars (that typically vary for each point).
In this example, I treat non-dated locations as 'np.nan'. I would like to add uncertainty values to the y2 axis (Mean Age) with defined uncertainty values as y2err.
Everytime I use the ax2.errorbar( ... ) line, my graph is squeezed and distorted.
import numpy as np
import matplotlib.pyplot as plt
fig, ax1 = plt.subplots()
#Longitude = x; Elevation = y
x = (-110.75696,-110.75668,-110.75640,-110.75612,-110.75584,-110.75556,-110.75528)
y = (877,879,878,873,871,872,872)
ax1.plot(x, y)
ax1.set_xlabel('Longitude')
# Make the y-axis label, ticks and tick labels match the line color.
ax1.set_ylabel('Elevation', color='b')
ax1.tick_params('y', colors='b')
ax2 = ax1.twinx()
# Mean Age, np.nan = 0.0
y2 = (np.nan,20,np.nan,np.nan,np.nan,np.nan,np.nan)
y2err = (np.nan,5,np.nan,np.nan,np.nan,np.nan,np.nan)
ax2.scatter(x, y2, color='r')
#add error bars to scatter plot points
# (??????) ax2.errorbar(x, y, y2, y2err, capsize = 0, color='black')
ax2.set_ylim(10,30)
ax2.set_ylabel('Mean Age', color='r')
ax2.tick_params('y', colors='r')
fig.tight_layout()
plt.show()
If I do not apply the ax2.errorbar... line my plot looks like the first image, which is what I want but with the points showing uncertainty values (+/- equal on both side of point in the y-axis direction).
Plot of Elevation vs Age without error bars
When I use the ax2.errorbar line it looks like the second image:
Plot when using ax2.errorbar line
Thanks!

matplotlib shared row label (not y label) in plot containing subplots

I have a trellis-like plot I am trying to produce in matplotlib. Here is a sketch of what I'm going for:
One thing I am having trouble with is getting a shared row label for each row. I.e. in my plot, I have four rows for four different sets of experiments, so I want row labels "1 source node, 2 source nodes, 4 source nodes and 8 source nodes".
Note that I am not referring to the y axis label, which is being used to label the dependent variable. The dependent variable is the same in all subplots, but the row labels I am after are to describe the four categories of experiments conducted, one for each row.
At the moment, I'm generating the plot with:
fig, axes = plt.subplots(4, 5, sharey=True)
While I've found plenty of information on sharing the y-axis label, I haven't found anything on adding a single shared row label.
As far as I know there is no ytitle or something. You can use text to show some text. The x and y are in data-coordinates. ha and va are horizontal and vertical alignment, respectively.
import numpy
import matplotlib
import matplotlib.pyplot as plt
n_rows = 4
n_cols = 5
fig, axes = plt.subplots(n_rows, n_cols, sharey = True)
axes[0][0].set_ylim(0,10)
for i in range(n_cols):
axes[0][i].text(x = 0.5, y = 12, s = "column label", ha = "center")
axes[n_rows-1][i].set_xlabel("xlabel")
for i in range(n_rows):
axes[i][0].text(x = -0.8, y = 5, s = "row label", rotation = 90, va = "center")
axes[i][0].set_ylabel("ylabel")
plt.show()
You could give titles to subplots on the top row like Robbert suggested
fig, axes = plt.subplots(4,3)
for i, ax in enumerate(axes[0,:]):
ax.set_title('col%i'%i)