'''
Hi there,
I created a clustermap using seaborn. Because the legend overlaps with the figure, I'd like to move it. However, plt.legend(bbox_to_anchor=(1,1)) gave the following error 'No handles with labels found to put in legend.'
That makes me wonder: what is the color scale -20 to 20 on the top left that I want to re-position? isn't that a legend?
Thank you in advance for shedding light on that for me.
'''
import matplotlib.pyplot as plt
import seaborn as sns
g = sns.clustermap(data=df_highestPivot,cmap='coolwarm')
plt.legend(bbox_to_anchor=(1,1)) #This line generate the error
plt.savefig('plot.png',dpi=300,bbox_to_inches='tight')
plt.show()
plt.close()
The colorbar is not a legend per se (not an object of type Legend at least). It is actually it's own subplots Axes, that you can access using g.ax_cbar.
If you want to move it, you can pass an argument cbar_pos= to clustermap(). However, it's complicated to find an empty space in the figure to place it. I would recommend you make some room using subplots_adjust() then move the ax_cbar Axes at the desired location
iris = sns.load_dataset('iris')
species = iris.pop("species")
g = sns.clustermap(iris)
g.fig.subplots_adjust(right=0.7)
g.ax_cbar.set_position((0.8, .2, .03, .4))
Related
I have just started using seaborn to produce my figures. However I can't seem to remove one of the legends produced here.
I am trying to plot two accuracies against each other and draw a line along the diagonal to make it easier to see which has performed better (if anyone has a better way of plotting this data in seaborn - let me know!). The legend I'd like to keep is the one on the left, that shows the different colours for 'N_bands' and different shapes for 'Subject No'
ax1 = sns.relplot(y='y',x='x',data=df,hue='N bands',legend='full',style='Subject No.',markers=['.','^','<','>','8','s','p','*','P','X','D','H','d']).set(ylim=(80,100),xlim=(80,100))
ax2 = sns.lineplot(x=range(80,110),y=range(80,110),legend='full')
I have tried setting the kwarg legend to 'full','brief' and False for both ax1 and ax2 (together and separately) and it only seems to remove the one on the left, or both.
I have also tried to remove the axes using matplotlib
ax1.ax.legend_.remove()
ax2.legend_.remove()
But this results in the same behaviour (left legend dissapearing).
UPDATE: Here is a minimal example you can run yourself:
test_data = np.array([[1.,2.,100.,9.],[2.,1.,100.,8.],[3.,4.,200.,7.]])
test_df = pd.DataFrame(columns=['x','y','p','q'], data=test_data)
sns.set_context("paper")
ax1=sns.relplot(y='y',x='x',data=test_df,hue='p',style='q',markers=['.','^','<','>','8'],legend='full').set(ylim=(0,4),xlim=(0,4))
ax2=sns.lineplot(x=range(0,5),y=range(0,5),legend='full')
Although this doesn't reproduce the error perfectly as the right legend is coloured (I have no idea how to reproduce this error then - does the way my dataframe was created make a difference?). But the essence of the problem remains - how do I remove the legend on the right but keep the one on the left?
You're plotting a lineplot in the (only) axes of a FacetGrid produced via relplot. That's quite unconventional, so strange things might happen.
One option to remove the legend of the FacetGrid but keeping the one from the lineplot would be
g._legend.remove()
Full code (where I also corrected for the confusing naming if grids and axes)
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns
test_data = np.array([[1.,2.,100.,9.],[2.,1.,100.,8.],[3.,4.,200.,7.]])
test_df = pd.DataFrame(columns=['x','y','p','q'], data=test_data)
sns.set_context("paper")
g=sns.relplot(y='y',x='x',data=test_df,hue='p',style='q',markers=['.','^','<','>','8'], legend='full')
sns.lineplot(x=range(0,5),y=range(0,5),legend='full', ax=g.axes[0,0])
g._legend.remove()
plt.show()
Note that this is kind of a hack, and it might break in future seaborn versions.
The other option is to not use a FacetGrid here, but just plot a scatter and a line plot in one axes,
ax1 = sns.scatterplot(y='y',x='x',data=test_df,hue='p',style='q',
markers=['.','^','<','>','8'], legend='full')
sns.lineplot(x=range(0,5),y=range(0,5), legend='full', ax=ax1)
plt.show()
I am trying to play a figure and I am having a black box pop up on the bottom of the plot where the x labels should be. I tried this command from a similar question on here in the past:
from matplotlib import rcParams
rcParams.update({'figure.autolayout': True})
But the problem was still the same. Here is my current code:
import pylab
from matplotlib import rcParams
rcParams.update({'figure.autolayout': True})
df['date'] = df['date'].astype('str')
pos = np.arange(len(df['date']))
plt.bar(pos,df['value'])
ticks = plt.xticks(pos, df['value'])
And my plot is attached here. Any help would be great!
pos = np.arange(len(df['date'])) and ticks = plt.xticks(pos, df['value']) are causing the problem you are having. You are putting an xtick at every value you have in the data frame.
Don't know how you data looks like and what's the most sensible way to do this. ticks = plt.xticks(pos[::20], df['value'].values[::20], rotation=90) will put a tick every 20 rows that would make the plot more readable.
It actually is not a black bar, but rather all of your x-axis labels being crammed into too small of a space. You can try rotating the axis labels to create more space or just remove them all together.
I am trying to set a space between the boxplots (between the green and orange boxes) created with Python Seaborn module's sns.boxplot(). Please see attached the graph, that the green and orange subplot boxes are stuck to each other, making it visually not the most appealing.
Can't find a way to do that, anyone could find a way (code attached)?
import numpy as np
import pandas as pd
import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns
tips = sns.load_dataset("tips")
sns.set(style="ticks", palette='Set2', font='Roboto Condensed')
sns.set_context("paper", font_scale=1.1, rc={"lines.linewidth": 1.1})
g=sns.factorplot(x="time", y="total_bill", hue="smoker",
col="day", data=tips, kind="box", size=4, aspect=0.5,
width=0.8,fliersize=2.5,linewidth=1.1, notch=False,orient="v")
sns.despine(trim=True)
g.savefig('test6.png', format='png', dpi=600)
The Seaborn boxplot documentation is here: http://stanford.edu/~mwaskom/software/seaborn/generated/seaborn.boxplot.html
Running the danger that this is not needed anymore, I found a solution to this problem. When drawing boxplots directly with matplotlib, the arrangement of the boxes can be controlled with the width and position keywords. However, when passing the positions keyword to sns.factorplot(kind='box',...), one gets a
TypeError: boxplot() got multiple values for keyword argument 'positions'
To get around this, one can set the widths of the boxes 'manually' after the boxplot has been created. This is a bit tedious, because the boxes are stored as PatchPatches within the individual Axes instances of the FacedGrid that is returned by sns.factorplot. Instead of the simple (x,y,width,height) syntax that Rects have, PathPatches use vertices to define the corners, which involves slightly more computation when one wants to adjust the boxes. On top of everything else, the PathPatches returned by matplotlib.boxplot contain an extra (ignored) vertex for the Path.CLOSEPOLY code, which is set to (0,0) and is best ignored. In addition to the box, the horizontal line that marks the median is now too wide and needs to be adjusted as well.
Below I define a function that adjusts widths of the boxes generated by the OP's example code(note the extra import):
from matplotlib.patches import PathPatch
def adjust_box_widths(g, fac):
"""
Adjust the withs of a seaborn-generated boxplot.
"""
##iterating through Axes instances
for ax in g.axes.flatten():
##iterating through axes artists:
for c in ax.get_children():
##searching for PathPatches
if isinstance(c, PathPatch):
##getting current width of box:
p = c.get_path()
verts = p.vertices
verts_sub = verts[:-1]
xmin = np.min(verts_sub[:,0])
xmax = np.max(verts_sub[:,0])
xmid = 0.5*(xmin+xmax)
xhalf = 0.5*(xmax - xmin)
##setting new width of box
xmin_new = xmid-fac*xhalf
xmax_new = xmid+fac*xhalf
verts_sub[verts_sub[:,0] == xmin,0] = xmin_new
verts_sub[verts_sub[:,0] == xmax,0] = xmax_new
##setting new width of median line
for l in ax.lines:
if np.all(l.get_xdata() == [xmin,xmax]):
l.set_xdata([xmin_new,xmax_new])
calling this function with
adjust_box_widths(g, 0.9)
gives the following output:
I am trying to create violinplots that shows confidence intervals for the mean. I thought an easy way to do this would be to plot a pointplot on top of the violinplot, but this is not working since they seem to be using different indices for the xaxis as in this example:
import matplotlib.pyplot as plt
import seaborn as sns
titanic = sns.load_dataset("titanic")
titanic.dropna(inplace=True)
fig, (ax1,ax2,ax3) = plt.subplots(1,3, sharey=True, figsize=(12,4))
#ax1
sns.pointplot("who", "age", data=titanic, join=False,n_boot=10, ax=ax1)
#ax2
sns.violinplot(titanic.age, groupby=titanic.who, ax=ax2)
#ax3
sns.pointplot("who", "age", data=titanic, join=False, n_boot=10, ax=ax3)
sns.violinplot(titanic.age, groupby=titanic.who, ax=ax3)
ax3.set_xlim([-0.5,4])
print(ax1.get_xticks(), ax2.get_xticks())
gives: [0 1 2] [1 2 3]
Why are these plots not assigning the same xtick numbers to the 'who'-variable and is there any way I can change this?
I also wonder if there is anyway I can change the marker for pointplot, because as you can see in the figure, the point is so big so that it covers the entire confidence interval. I would like just a horizontal line if possible.
I'm posting my final solution here. The reason I wanted to do this kind of plot to begin with, was to display information about the distribution shape, shift in means, and outliers in the same figure. With mwaskom's pointers and some other tweaks I finally got what I was looking for.
The left hand figure is there as a comparison with all data points plotted as lines and the right hand one is my final figure. The thick grey line in the middle of the violin is the bootstrapped 99% confidence interval of the mean, which is the white horizontal line, both from pointplot. The three dotted lines are the standard 25th, 50th and 75th percentile and the lines outside that are the caps of the whiskers of a boxplot I plotted on top of the violin plot. Individual data points are plotted as lines beyond this points since my data usually has a few extreme ones that I need to remove manually like the two points in the violin below.
For now, I am going to to continue making histograms and boxplots in addition to these enhanced violins, but I hope to find that all the information is accurately captured in the violinplot and that I can start and rely on it as my main initial data exploration plot. Here is the final code to produce the plots in case someone else finds them useful (or finds something that can be improved). Lots of tweaking to the boxplot.
import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns
#change the linewidth which to get a thicker confidence interval line
mpl.rc("lines", linewidth=3)
df = sns.load_dataset("titanic")
df.dropna(inplace=True)
x = 'who'
y = 'age'
fig, (ax1,ax2) = plt.subplots(1,2, sharey=True, figsize=(12,6))
#Left hand plot
sns.violinplot(df[y], groupby=df[x], ax=ax1, inner='stick')
#Right hand plot
sns.violinplot(df[y], groupby=df[x], ax=ax2, positions=0)
sns.pointplot(df[x],df[y], join=False, ci=99, n_boot=1000, ax=ax2, color=[0.3,0.3,0.3], markers=' ')
df.boxplot(y, by=x, sym='_', ax=ax2, showbox=False, showmeans=True, whiskerprops={'linewidth':0},
medianprops={'linewidth':0}, flierprops={'markeredgecolor':'k', 'markeredgewidth':1},
meanprops={'marker':'_', 'color':'w', 'markersize':6, 'markeredgewidth':1.5},
capprops={'linewidth':1, 'color':[0.3,0.3,0.3]}, positions=[0,1,2])
#One could argue that this is not beautiful
labels = [item.get_text() + '\nn=' + str(df.groupby(x).size().loc[item.get_text()]) for item in ax2.get_xticklabels()]
ax2.set_xticklabels(labels)
#Clean up
fig.suptitle('')
ax2.set_title('')
fig.set_facecolor('w')
Edit: Added 'n='
violinplot takes a positions argument that you can use to put the violins somewhere else (they currently just inherit the default matplotlib boxplot positions).
pointplot takes a markers argument that you can use to change how the point estimate is rendered.
Normally if you plot two different figures using the default settings in pyplot, they will be exactly the same size, and if saved can be neatly aligned in PowerPoint or the like. I'd like to generate one figure, however, which has a legend outside of the figure. The script I'm using is shown below.
import numpy as np
import matplotlib.pyplot as plt
x=np.linspace(0,1,201)
y1=x**2
y2=np.sin(x)
fig1=plt.figure(1)
plt.plot(x,y1,label='y1')
handles1,labels1=plt.gca().get_legend_handles_labels()
lgd1=plt.gca().legend(handles1,labels1,bbox_to_anchor=(1.27,1),borderaxespad=0.)
fig2=plt.figure(2)
plt.plot(x,y2)
fig1.savefig('fig1',bbox_extra_artists=(lgd1,),bbox_inches='tight')
fig2.savefig('fig2')
plt.show()
The problem is that in PowerPoint, I can no longer align the two figures left and have their axes aligned. Due to the use of the 'extra artists' and 'bbox_inches=tight' arguments for the first figure, the width of its margins becomes different from the second figure.
Is there any way to 'transfer' the clip box from the first figure to the second figure, such that they can be aligned by 'align left' in PowerPoint?
I think an easier way to achieve what you want is to just construct one figure with two subplots, and let matplotlib align everything for you.
Do you think doing something like this is a good idea?
import matplotlib.pyplot as plt
import numpy as np
x=np.linspace(0,1,201)
y1=x**2
y2=np.sin(x)
fig = plt.figure()
a = fig.add_subplot(211)
a.plot(x,y1, label='y1')
lgd1 = a.legend(bbox_to_anchor = (1.27,1), borderaxespad=0.)
a = fig.add_subplot(212)
a.plot(x,y2)
fig.savefig('fig',bbox_extra_artists=(lgd1,),bbox_inches='tight')