How to remove a legend part of a seaborn facetgrid - python

In Matplotlib/seaborn I create a facetgrid with the relplot command where the data attribute use for therow parameter is also used for the style attribute. This leads to a legend with two parts. One part is redundant and I want to remove this redundant part of the legend.
Here the code:
df = pd.read_csv('data.csv')
# Dataframe df has columns 'size', 'pricepersize', 'date' and 'series'
g = sns.relplot(x='size',
y='pricepersize',
data=df,
kind='line',
hue='date',
style='series',
row='series',
markers=True
)
plt.show()
And here the resulting graph grid (with the part of the legend I want to remove marked up in green):
How can I get rid of the "series" part in the legend, but keep the style parameter set to the same data column as the row parameter?

I guess the easiest way is to let sns create the legend (this is the default), remove it and re-generate a new one from the desired entries of the original legend.
import seaborn as sns
tips = sns.load_dataset("tips")
fg = sns.relplot(data=tips, x="total_bill", y="tip", hue="day", row="time", kind='line', style='time')
gives
then use
fg.legend.remove()
fg.fig.legend(handles=fg.legend.legendHandles[:5], loc=7)
to finally get

Related

Customizing legend in Seaborn histplot subplots

I am trying to generate a figure with 4 subplots, each of which is a Seaborn histplot. The figure definition lines are:
fig,axes=plt.subplots(2,2,figsize=(6.3,7),sharex=True,sharey=True)
(ax1,ax2),(ax3,ax4)=axes
fig.subplots_adjust(wspace=0.1,hspace=0.2)
I would like to define strings for legend entries in each of the subplots. As an example, I am using the following code for the first subplot:
sp1=sns.histplot(df_dn,x="ktau",hue="statind",element="step", stat="density",common_norm=True,fill=False,palette=colvec,ax=ax1)
ax1.set_title(r'$d_n$')
ax1.set_xlabel(r'max($F_{a,max}$)')
ax1.set_ylabel(r'$\tau_{ken}$')
legend_labels,_=ax1.get_legend_handles_labels()
ax1.legend(legend_labels,['dep-','ind-','ind+','dep+'],title='Stat.ind.')
The legend is not showing correctly (legend entries are not plotted and the legend title is the name of the hue variable ("statind"). Please note I have successfully used the same code for other figures in which I used Seaborn relplots instead of histplots.
The main problem is that ax1.get_legend_handles_labels() returns empty lists (note that the first return value are the handles, the second would be the labels). At least for the current (0.11.1) version of seaborn's histplot().
To get the handles, you can do legend = ax1.get_legend(); handles = legend.legendHandles.
To recreate the legend, first the existing legend needs to be removed. Then, the new legend can be created starting from some handles.
Also note that to be sure of the order of the labels, it helps to set hue_order. Here is some example code to show the ideas:
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
df_dn = pd.DataFrame({'ktau': np.random.randn(4000).cumsum(),
'statind': np.repeat([*'abcd'], 1000)})
fig, ax1 = plt.subplots()
sp1 = sns.histplot(df_dn, x="ktau", hue="statind", hue_order=['a', 'b', 'c', 'd'],
element="step", stat="density", common_norm=True, fill=False, ax=ax1)
ax1.set_title(r'$d_n$')
ax1.set_xlabel(r'max($F_{a,max}$)')
ax1.set_ylabel(r'$\tau_{ken}$')
legend = ax1.get_legend()
handles = legend.legendHandles
legend.remove()
ax1.legend(handles, ['dep-', 'ind-', 'ind+', 'dep+'], title='Stat.ind.')
plt.show()

Overriding Seaborn legend

I made a line plot using seaborn's relplot and I wanted to customize my legend labels. For some reason when I do this, It creates another legend with out deleting the old one. How do I get rid of the initial legend (The legend with title "Sex")? Also how do I add a legend title to my new legend?
Here is the code I used to generate my plot:
plt.figure(figsize=(12,10))
sns.relplot(x='Year',y = 'cancer/100k pop' , data = dataset_sex,hue="Sex", kind="line",ci=None)
title_string = "Trend of Cancer incidencies by Sex "
plt.xlabel('Years')
plt.title(title_string)
plt.legend(['Men','Women'])
regplot is a figure-level function, and returns a FacetGrid. You can remove its legend via g.legend.remove().
import matplotlib.pyplot as plt
import seaborn as sns
tips = sns.load_dataset("tips")
g = sns.relplot(data=tips, x="total_bill", y="tip", hue="day")
g.legend.remove()
plt.legend(['Jeudi', 'Vendredi', 'Samedi', 'Dimanche'])
plt.show()
This code has been tested with seaborn 0.11. Possibly you'll need to upgrade. To add a title to the legend: plt.legend([...], title='New title').
Note that plt.legend(...) will create the legend inside the last (or only) subplot. If you prefer the figure-level legend next to the plot, to change the legend labels, you can call g.add_legend(labels=[...], title='new title') after having removed the old legend.
PS: Adding legend=False to sns.relplot() will not create the legend entries. So, you'll need to recreate both the legend markers and their labels, while you lost the information of which colors were used.

Edit legend title and labels of Seaborn scatterplot and countplot

I am using seaborn scatterplot and countplot on titanic dataset.
Here is my code to draw scatter plot. I also tried to edit legend label.
ax = seaborn.countplot(x='class', hue='who', data=titanic)
legend_handles, _ = ax.get_legend_handles_labels()
plt.show();
To edit legend label, I did this. In this case, there is no legend title anymore. How can I rename this title from 'who' to 'who1'?
ax = seaborn.countplot(x='class', hue='who', data=titanic)
legend_handles, _= ax.get_legend_handles_labels()
ax.legend(legend_handles, ['man1','woman1','child1'], bbox_to_anchor=(1,1))
plt.show()
I used the same method to edit legend labels on scatter plot and the result is different here. It uses 'dead' as legend title and use 'survived' as first legend label.
ax = seaborn.scatterplot(x='age', y='fare', data=titanic, hue = 'survived')
legend_handles, _= ax.get_legend_handles_labels()
ax.legend(legend_handles, ['dead', 'survived'],bbox_to_anchor=(1.26,1))
plt.show()
Is there a parameter to delete and add legend title?
I used same codes on two different graphs and outcome of legend is different. Why is that?
Try using
ax.legend(legend_handles, ['man1','woman1','child1'],
bbox_to_anchor=(1,1),
title='whatever title you want to use')
With seaborn v0.11.2 or later, use the move_legend() function.
From the FAQs page:
With seaborn v0.11.2 or later, use the move_legend() function.
On older versions, a common pattern was to call ax.legend(loc=...) after plotting. While this appears to move the legend, it actually replaces it with a new one, using any labeled artists that happen to be attached to the axes. This does not consistently work across plot types. And it does not propagate the legend title or positioning tweaks that are used to format a multi-variable legend.
The move_legend() function is actually more powerful than its name suggests, and it can also be used to modify other legend parameters (font size, handle length, etc.) after plotting.
Why does the legend order sometimes differ?
You can force the order of the legend via hue_order=['man', 'woman', 'child']. By default, the order is either the order in which they appear in the dataframe (when the values are just strings), or the order imposed by pd.Categorical.
How to rename the legend entries
The surest way is to rename the column values, e.g.
titanic["who"] = titanic["who"].map({'man': 'Man1', 'woman': 'Woman1', 'child': 'Child1'})
If the entries of the column exist of numbers in the range 0,1,..., you can use pd.Categorical.from_codes(...). This also forces an order.
Specific colors for specific hue values
There are many options to specify the colors to be used (via palette=). To assign a specific color to a specific hue value, the palette can be a dictionary, e.g.
palette = {'Man1': 'cornflowerblue', 'Woman1': 'fuchsia', 'Child1': 'limegreen'}
Renaming or removing the legend title
sns.move_legend(ax, title=..., loc='best') sets a new title. Setting the title to an empty string removes it (this is useful when the entries are self-explaining).
A code example
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
titanic = sns.load_dataset('titanic')
# titanic['survived'] = titanic['survived'].map({0:'Dead', 1:'Survived'})
titanic['survived'] = pd.Categorical.from_codes(titanic['survived'], ['Dead', 'Survived'])
palette = {'Dead': 'navy', 'Survived': 'turquoise'}
ax = sns.scatterplot(data=titanic, x='age', y='fare', hue='survived', palette=palette)
sns.move_legend(ax, title='', loc='best') # remove the title
plt.show()

Changing pointplot legend in seaborn

I would like to change the label for the legend and items in the legend for this plot. Right now the label for the legend is "Heart" and the items are 0 and 1. I would like to be able to change all of these to something else, but am unsure how. Here is what I have so far.
sns.set_context("talk",font_scale=3)
ax =sns.pointplot(x="Heart", y="FirstPersonPronouns", hue="Speech", data=df)
ax.set(xlabel='Condition', ylabel='First Person Pronouns')
ax.set(xticklabels=["Control", "Heart"])
Any help would be appreciated! Also, I'm assuming this is a set parameter that I don't know about, is there a comprehensive list of these? I can't seem to find one in the documentation.
An alternative to changing the column names of the data frame, is to create a new legend using the same legend handles (this is what determines the colored markers), but with new text labels:
import seaborn as sns
tips = sns.load_dataset('tips')
ax = sns.pointplot(x='sex', y='total_bill', hue='time', data=tips)
leg_handles = ax.get_legend_handles_labels()[0]
ax.legend(leg_handles, ['Blue', 'Orange'], title='New legend')

Modify the legend of pandas bar plot

I am always bothered when I make a bar plot with pandas and I want to change the names of the labels in the legend. Consider for instance the output of this code:
import pandas as pd
from matplotlib.pyplot import *
df = pd.DataFrame({'A':26, 'B':20}, index=['N'])
df.plot(kind='bar')
Now, if I want to change the name in the legend, I would usually try to do:
legend(['AAA', 'BBB'])
But I end up with this:
In fact, the first dashed line seems to correspond to an additional patch.
So I wonder if there is a simple trick here to change the labels, or do I need to plot each of the columns independently with matplotlib and set the labels myself. Thanks.
To change the labels for Pandas df.plot() use ax.legend([...]):
import pandas as pd
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
df = pd.DataFrame({'A':26, 'B':20}, index=['N'])
df.plot(kind='bar', ax=ax)
#ax = df.plot(kind='bar') # "same" as above
ax.legend(["AAA", "BBB"]);
Another approach is to do the same by plt.legend([...]):
import matplotlib.pyplot as plt
df.plot(kind='bar')
plt.legend(["AAA", "BBB"]);
If you need to call plot multiply times, you can also use the "label" argument:
ax = df1.plot(label='df1', y='y_var')
ax = df2.plot(label='df2', y='y_var')
While this is not the case in the OP question, this can be helpful if the DataFrame is in long format and you use groupby before plotting.
This is slightly an edge case but I think it can add some value to the other answers.
If you add more details to the graph (say an annotation or a line) you'll soon discover that it is relevant when you call legend on the axis: if you call it at the bottom of the script it will capture different handles for the legend elements, messing everything.
For instance the following script:
df = pd.DataFrame({'A':26, 'B':20}, index=['N'])
ax = df.plot(kind='bar')
ax.hlines(23, -.5,.5, linestyles='dashed')
ax.annotate('average',(-0.4,23.5))
ax.legend(["AAA", "BBB"]); #quickfix: move this at the third line
Will give you this figure, which is wrong:
While this a toy example which can be easily fixed by changing the order of the commands, sometimes you'll need to modify the legend after several operations and hence the next method will give you more flexibility. Here for instance I've also changed the fontsize and position of the legend:
df = pd.DataFrame({'A':26, 'B':20}, index=['N'])
ax = df.plot(kind='bar')
ax.hlines(23, -.5,.5, linestyles='dashed')
ax.annotate('average',(-0.4,23.5))
ax.legend(["AAA", "BBB"]);
# do potentially more stuff here
h,l = ax.get_legend_handles_labels()
ax.legend(h[:2],["AAA", "BBB"], loc=3, fontsize=12)
This is what you'll get:

Categories