How to include the outside legend into the generated file? - python

I am plotting many lines on several axes, so I have a several fairly busy plots, thus I need to place the legend outside of the figure:
import numpy as np
nrows = 4
fig = plt.figure(figsize=(6, 2*nrows))
axes = fig.subplots(nrows=nrows, ncols=1)
names = [f"name-{n}" for n in range(10)]
for ax in axes:
for n in names:
ax.plot(np.arange(10),np.random.normal(size=10),label=n)
fig.tight_layout()
axes[0].legend(loc="upper left", bbox_to_anchor=(1,0,1,1))
which produces something like
However, when I save the figure using fig.savefig("test.png"), I get this:
note the missing legend.
How do I save the figure so that the legend is included?

One option is to tell tight_layout() not to use the full width. That leaves enough room for your legend. I'm not sure if there's a way measure the width of your legend in code, but I experimentally found this fits your legend:
import matplotlib.pyplot as plt
import numpy as np
nrows = 4
fig = plt.figure(figsize=(6, 2*nrows))
axes = fig.subplots(nrows=nrows, ncols=1)
names = [f"name-{n}" for n in range(10)]
for ax in axes:
for n in names:
ax.plot(np.arange(10),np.random.normal(size=10),label=n)
fig.tight_layout(rect=(0, 0, 0.84, 1))
axes[0].legend(loc="upper left", bbox_to_anchor=(1,0,1,1))
fig.savefig("test.png")
After some experimentation, though, it seems like simplifying the call to legend() tells tight_layout() about the legend and to leave room for it. Now, making the names longer automatically makes the plots smaller so that everything fits.
There was a problem with tight_layout() leaving gaps between subplots, because the legend was taller than the subplot. We put a single entry in the legend, call tight_layout(), then put all the entries in the legend. The legend extends below the bottom of the first subplot, but that's what we want.
If the names are different lengths, you'd have to do some more trickery to use the longest name instead of the first name or split all the entries across all the subplots before calling tight_layout().
import matplotlib.pyplot as plt
import numpy as np
nrows = 4
fig = plt.figure(figsize=(6, 2*nrows))
axes = fig.subplots(nrows=nrows, ncols=1)
names = [f"name-{n}" for n in range(10)]
for ax in axes:
for n in names:
ax.plot(np.arange(10),np.random.normal(size=10),label=n)
# Create a legend with only one entry, so tight_layout doesn't stretch down.
handles, labels = axes[0].get_legend_handles_labels()
axes[0].legend(handles[:1], labels[:1], bbox_to_anchor=(1, 1))
fig.tight_layout()
# Use all the entries without worrying about expanding below the subplot.
axes[0].legend(handles, labels, bbox_to_anchor=(1, 1))
fig.savefig("test.png")

Use plt.subplots_adjust and you can customize the space around the figure. Here I just used plt.subplots_adjust(right=0.8) but you can adjust all the settings (including top, bottom, left, hspace, wspace)
import numpy as np
nrows = 4
fig = plt.figure(figsize=(6, 2*nrows))
axes = fig.subplots(nrows=nrows, ncols=1)
names = [f"name-{n}" for n in range(10)]
for ax in axes:
for n in names:
ax.plot(np.arange(10),np.random.normal(size=10),label=n)
fig.tight_layout()
axes[0].legend(loc="upper left", bbox_to_anchor=(1,0,1,1))
fig.subplots_adjust(right=0.80)
fig.savefig("test.png")
Saved image:

Related

Removing legend from mpl parallel coordinates plot?

I have a parallel coordinates plot with lots of data points so I'm trying to use a continuous colour bar to represent that, which I think I have worked out. However, I haven't been able to remove the default key that is put in when creating the plot, which is very long and hinders readability. Is there a way to remove this table to make the graph much easier to read?
This is the code I'm currently using to generate the parallel coordinates plot:
parallel_coordinates(data[[' male_le','
female_le','diet','activity','obese_perc','median_income']],'median_income',colormap = 'rainbow',
alpha = 0.5)
fig, ax = plt.subplots(figsize=(6, 1))
fig.subplots_adjust(bottom=0.5)
cmap = mpl.cm.rainbow
bounds = [0.00,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0]
norm = mpl.colors.BoundaryNorm(bounds, cmap.N,)
plt.colorbar(mpl.cm.ScalarMappable(norm = norm, cmap=cmap),cax = ax, orientation = 'horizontal',
label = 'normalised median income', alpha = 0.5)
plt.show()
Current Output:
I want my legend to be represented as a color bar, like this:
Any help would be greatly appreciated. Thanks.
You can use ax.legend_.remove() to remove the legend.
The cax parameter of plt.colorbar indicates the subplot where to put the colorbar. If you leave it out, matplotlib will create a new subplot, "stealing" space from the current subplot (subplots are often referenced to by ax in matplotlib). So, here leaving out cax (adding ax=ax isn't necessary, as here ax is the current subplot) will create the desired colorbar.
The code below uses seaborn's penguin dataset to create a standalone example.
import matplotlib.pyplot as plt
import matplotlib as mpl
import seaborn as sns
import numpy as np
from pandas.plotting import parallel_coordinates
penguins = sns.load_dataset('penguins')
fig, ax = plt.subplots(figsize=(10, 4))
cmap = plt.get_cmap('rainbow')
bounds = np.arange(penguins['body_mass_g'].min(), penguins['body_mass_g'].max() + 200, 200)
norm = mpl.colors.BoundaryNorm(bounds, 256)
penguins = penguins.dropna(subset=['body_mass_g'])
parallel_coordinates(penguins[['bill_length_mm', 'bill_depth_mm', 'flipper_length_mm', 'body_mass_g']],
'body_mass_g', colormap=cmap, alpha=0.5, ax=ax)
ax.legend_.remove()
plt.colorbar(mpl.cm.ScalarMappable(norm=norm, cmap=cmap),
ax=ax, orientation='horizontal', label='body mass', alpha=0.5)
plt.show()

Seaborn heatmaps in subplots - align x-axis

I am trying to plot a figure containing two subplots, a seaborn heatmap and simple matplotlib lines. However, when sharing the x-axis for both plots, they do not align as can be seen in this figure:
It would seem that the problem is similar to this post, but when displaying ax[0].get_xticks() and ax[1].get_xticks() I get the same positions, so I don't know what to change. And in my picture the the deviation seems to be more than a 0.5 shift.
What am I doing wrong?
The code I used to plot the figure is the following:
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
M_1=np.random.random((15,15))
M_2=np.random.random((15,15))
L_1=np.random.random(15)
L_2=np.random.random(15)
x=range(15)
cmap = sns.color_palette("hot", 100)
sns.set(style="white")
fig, ax = plt.subplots(2, 1, sharex='col', figsize=(10, 12))
ax[0].plot(x,L_1,'-', marker='o',color='tab:orange')
sns.heatmap(M_1, cmap=cmap, vmax=np.max(M_1), center=np.max(M_1)/2., square=False, ax=ax[1])
#Mr-T 's comment is spot on. The easiest would be to create the axes beforehand instead of letting heatmap() shrink your axes in order to make room for the colorbar.
There is the added complication that the labels for the heatmap are not actually placed at [0,1,...] but are in the middle of each cell at [0.5, 1.5, ...]. So if you want your upper plot to align with the labels at the bottom (and with the center of each cell), you may have to shift your plot by 0.5 units to the right:
M_1=np.random.random((15,15))
M_2=np.random.random((15,15))
L_1=np.random.random(15)
L_2=np.random.random(15)
x=np.arange(15)
cmap = sns.color_palette("hot", 100)
sns.set(style="white")
fig, ax = plt.subplots(2, 2, sharex='col', gridspec_kw={'width_ratios':[100,5]})
ax[0,1].remove() # remove unused upper right axes
ax[0,0].plot(x+0.5,L_1,'-', marker='o',color='tab:orange')
sns.heatmap(M_1, cmap=cmap, vmax=np.max(M_1), center=np.max(M_1)/2., square=False, ax=ax[1,0], cbar_ax=ax[1,1])

change specific subplot background color (outside of pie chart)

I have the following subplots with pie charts, output by the code below.
I want to shade in a different color the background of the odd-numbered subplots (only the middle one in the image above), but I haven't been able make it work.
I looked at a few places and from a few answers to this question I tried both ax.set_facecolor('red') and ax.patch.set_facecolor('red'), none of which resulted in the alternative shading/coloring pattern I'm looking for.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
n = 3
nums_df = pd.DataFrame([np.random.randint(1, 20, size=5) for _ in xrange(n)])
row_labels = ["row {:d}".format(i) for i in xrange(n)]
nums_df.index = row_labels
# create a figure with n subplots
fig, axes = plt.subplots(1, n)
# create pie charts
for i, ax in enumerate(axes):
ax.pie(nums_df.loc[row_labels[i]], labels=nums_df.loc[row_labels[i]])
ax.axis("equal")
if i%2 == 1:
ax.set_facecolor('red')
# ax.patch.set_facecolor('red')
plt.show()
By default the complete axes of a pie plot is "off". You can set it on, use the frame argument.
ax.pie(..., frame=True)
This produces ticks and ticklabels on the axes, hence, it might be better to set it on externally,
ax.pie(..., frame=False)
ax.set_frame_on(True)
In addition you probably want to set the spines off,
for _, spine in ax.spines.items():
spine.set_visible(False)
or, in a single line
plt.setp(ax.spines.values(),visible=False)
Finally, for the ticklabels not to exceed the axes area, one may fix the axis range, e.g. ax.axis([-1,1,-1,1]) and use a smaller pie radius, e.g. radius=.27.
Complete code for reproduction
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
n = 3
nums_df = pd.DataFrame([np.random.randint(1, 20, size=5) for _ in xrange(n)])
row_labels = ["row {:d}".format(i) for i in xrange(n)]
nums_df.index = row_labels
fig, axes = plt.subplots(1, n)
for i, ax in enumerate(axes):
ax.pie(nums_df.loc[row_labels[i]], labels=nums_df.loc[row_labels[i]], frame=False, radius=0.27)
ax.set_frame_on(True)
ax.axis("equal")
ax.axis([-1,1,-1,1])
plt.setp(ax.spines.values(),visible=False)
if i%2 == 1:
ax.set_facecolor('red')
plt.show()

Correctly aligning polar plots in Python with matplotlib

fig, ax = plt.subplots(1, 2, subplot_kw=dict(projection='polar'))
ax[0].set_theta_zero_location("N")
ax[0].set_theta_direction(-1)
cax = ax[0].contourf(theta, r, values_2d,100)
cb = fig.colorbar(cax)
cb.set_label("Distance")
dax = ax[1].contourf(theta, r, d_values_2d,2)
db = fig.colorbar(dax)
db.set_label("Gradient")
plt.tight_layout(pad=0.4, w_pad=0.5)
plt.show()
The above figure has two plots on it. I can't find a way to make the colorbar sit with each respective figure, though. Also, they're different sizes, why?
You can pass the Axes instance to which the colorbar should be attached to fig.colorbar() with the keyword ax. From the documentation:
ax : Axes, list of Axes, optional
Parent axes from which space for a new colorbar axes will be stolen.
If a list of axes is given they will all be resized to make room for
the colorbar axes.
Also, to avoid overlap, you can pass the keyword pad. Here an a little bit altered version of your code:
from matplotlib import pyplot as plt
import numpy as np
#the coordinates
theta = np.linspace(0,2*np.pi, 100)
r = np.linspace(0,1,100)
#making up some data
theta,r = np.meshgrid(theta,r)
values_2d = np.sin(theta)*np.exp(-r)
d_values_2d = np.cos(theta)*np.sqrt(r)
fig, ax = plt.subplots(
1, 2, subplot_kw=dict(projection='polar'),
figsize = (10,4)
)
ax[0].set_theta_zero_location("N")
ax[0].set_theta_direction(-1)
cax = ax[0].contourf(theta, r, values_2d,100)
#the first altered colorbar command
cb = fig.colorbar(cax, ax = ax[0], pad = 0.1)
cb.set_label("Distance")
dax = ax[1].contourf(theta, r, d_values_2d,2)
#the second altered colorbar command
db = fig.colorbar(dax, ax = ax[1], pad = 0.1)
db.set_label("Gradient")
plt.tight_layout(pad=0.4, w_pad=0.5)
plt.show()
This gives the following result:
As to why you get the figure you get with your original code, I'm guessing that without the ax keyword, colorbar has to guess where to put the colorbar and it uses either the current active Axes instance or the last created one. Also, as both colorbars are attached to the same Axes there is less room for the actual plot, which is why the right plot in your example is way smaller than the left one.

How can I make a barplot and a lineplot in the same seaborn plot with different Y axes nicely?

I have two different sets of data with a common index, and I want to represent the first one as a barplot and the second one as a lineplot in the same graph. My current approach is similar to the following.
ax = pt.a.plot(alpha = .75, kind = 'bar')
ax2 = ax.twinx()
ax2.plot(ax.get_xticks(), pt.b.values, alpha = .75, color = 'r')
And the result is similar to this
This image is really nice and almost right. My only problem is that ax.twinx() seems to create a new canvas on top of the previous one, and the white lines are clearly seen on top of the barplot.
Is there any way to plot this without including the white lines?
You can use twinx() method along with seaborn to create a seperate y-axis, one for the lineplot and the other for the barplot. To control the style of the plot (default style of seaborn is darkgrid), you can use set_style method and specify the preferred theme. If you set style=None it resets to white background without the gridlines. You can also try whitegrid. If you want to further customize the gridlines, you can do it on the axis level using the ax2.grid(False).
import matplotlib
import matplotlib.pyplot as plt
import seaborn as sns
matplotlib.rc_file_defaults()
ax1 = sns.set_style(style=None, rc=None )
fig, ax1 = plt.subplots(figsize=(12,6))
sns.lineplot(data = df['y_var_1'], marker='o', sort = False, ax=ax1)
ax2 = ax1.twinx()
sns.barplot(data = df, x='x_var', y='y_var_2', alpha=0.5, ax=ax2)
You have to remove grid lines of the second axis. Add to the code ax2.grid(False). However y-ticks of the second axis will be not align to y-ticks of the first y-axis, like here:
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
import pandas as pd
fig = plt.figure()
ax1 = fig.add_subplot(111)
ax1.plot(pd.Series(np.random.uniform(0,1,size=10)), color='g')
ax2 = ax1.twinx()
ax2.plot(pd.Series(np.random.uniform(0,17,size=10)), color='r')
ax2.grid(False)
plt.show()

Categories

Resources