How to create separate legend sections for colors and markers
Question:
I would like to plot two features in the same plot. My problem is with the legend. I would like to have one legend for colors(species) and one for the marker (Label defined by me). And I don’t need to repeat the colors in the legend, as is happening in this example.
this is what I’m trying:
import seaborn as sns
import matplotlib.pyplot as plt
import random
import matplotlib.colors as mcolors
random.seed(5)
iris = sns.load_dataset("iris")
species_list = list(iris.species.unique())
colors_name = list(mcolors.CSS4_COLORS.keys())
color_species = random.sample(colors_name,len(species_list))
fig,ax = plt.subplots(1,1,figsize=(10,6))
sns.scatterplot(data=iris,edgecolor="black", x=iris.index.values, y='sepal_length',s=50,alpha=0.8, hue='species',palette=color_species,ax=ax,label='Feat. 1')
sns.scatterplot(data=iris,edgecolor="black",marker='*', x=iris.index.values, y='sepal_width',s=90,alpha=0.8, hue='species',palette=color_species,ax=ax, label='Feat. 2')
ax.legend(loc='upper right',bbox_to_anchor=(1.5,1))
ax.set_xlabel('Sample')
ax.set_ylabel('Feature 1 and 2')
Answers:
To manipulate the legend, you need to get the legend handles using get_legend_handles_labels()
, then pick half the entries (3 in your case) and plot them as a legend. Then use matplotlib.lines
to create the kind of icon you need (color, shape, etc.) and pass that as the handles for second legend along with the label text you want. You can use title to add titles to the legends. The code is shown below. Hope this is what you are looking for.
import seaborn as sns
import matplotlib.pyplot as plt
import random
import matplotlib.colors as mcolors
random.seed(5)
iris = sns.load_dataset("iris")
species_list = list(iris.species.unique())
colors_name = list(mcolors.CSS4_COLORS.keys())
color_species = random.sample(colors_name,len(species_list))
fig,ax = plt.subplots(1,1,figsize=(10,6))
sns.scatterplot(data=iris,edgecolor="black", x=iris.index.values, y='sepal_length',s=50,alpha=0.8, hue='species',
palette=color_species,ax=ax)#,label='Feat. 1')
sns.scatterplot(data=iris,edgecolor="black",marker='*', x=iris.index.values, y='sepal_width',s=90,alpha=0.8,
hue='species',palette=color_species,ax=ax)#, label='Feat. 2')
h,l = ax.get_legend_handles_labels() ##Get the legend handles and lables
l1 = ax.legend(h[:int(len(h)/2)],l[:int(len(l)/2)], loc='upper right',bbox_to_anchor=(1.2,1), title='Title 1') ##Plot half as the first legend
from matplotlib.lines import Line2D
myHandle = [Line2D([], [], marker='.', color='red', markersize=10, linestyle='None'),
Line2D([], [], marker='*', color='blue', markersize=10, linestyle='None')] ##Create custom handles for 2nd legend
l2 = ax.legend(handles=myHandle, labels = ['MyLabel1', 'MyLabel2'], loc='upper right',bbox_to_anchor=(1.2,0.8), title='Title 2') ##Add 2nd legend
ax.add_artist(l1) # 2nd legend will erases the first, so need to add it
ax.set_xlabel('Sample')
ax.set_ylabel('Feature 1 and 2')
- This is more easily done by reshaping the dataframe into a long form with
pandas.DataFrame.melt
.
- The seaborn plot API works best with
data=
in a long form.
- Removes the need to plot each feature separately, and is the correct way to use seaborn.
- The legend section names will be the column name passed to
hue
and style
, therefore adjust the column names as desired.
- Tested in
python 3.10
, pandas 1.4.3
, matplotlib 3.5.2
, seaborn 0.12.0
import pandas as pd
import seaborn as sns
# load the sample data
iris = sns.load_dataset("iris")
# reshape the dataframe with the desired columns
im = (iris.iloc[:, [0, 1, 4]]
.melt(id_vars='species', var_name='Feature', value_name='Value', ignore_index=False)
.reset_index()
.rename({'index': 'Sample', 'species': 'Species'}, axis=1))
# display(im.head())
Sample Species Feature Value
0 0 setosa sepal_length 5.1
1 1 setosa sepal_length 4.9
2 2 setosa sepal_length 4.7
3 3 setosa sepal_length 4.6
4 4 setosa sepal_length 5.0
# display(im.tail())
Sample Species Feature Value
295 145 virginica sepal_width 3.0
296 146 virginica sepal_width 2.5
297 147 virginica sepal_width 3.0
298 148 virginica sepal_width 3.4
299 149 virginica sepal_width 3.0
# create a figure-level plot
g = sns.relplot(data=im, x='Sample', y='Value', hue='Species', style='Feature', aspect=1.5)
import matplotlib.pyplot as plt
# create a matplotlib figure
fig, ax = plt.subplots(1, 1, figsize=(10, 6))
# create an axes-level plot
sns.scatterplot(data=im, x='Sample', y='Value', hue='Species', style='Feature', ax=ax)
ax.spines[['top', 'right']].set_visible(False)
sns.move_legend(ax, bbox_to_anchor=(1, 0.5), loc='center left', frameon=False)
I would like to plot two features in the same plot. My problem is with the legend. I would like to have one legend for colors(species) and one for the marker (Label defined by me). And I don’t need to repeat the colors in the legend, as is happening in this example.
this is what I’m trying:
import seaborn as sns
import matplotlib.pyplot as plt
import random
import matplotlib.colors as mcolors
random.seed(5)
iris = sns.load_dataset("iris")
species_list = list(iris.species.unique())
colors_name = list(mcolors.CSS4_COLORS.keys())
color_species = random.sample(colors_name,len(species_list))
fig,ax = plt.subplots(1,1,figsize=(10,6))
sns.scatterplot(data=iris,edgecolor="black", x=iris.index.values, y='sepal_length',s=50,alpha=0.8, hue='species',palette=color_species,ax=ax,label='Feat. 1')
sns.scatterplot(data=iris,edgecolor="black",marker='*', x=iris.index.values, y='sepal_width',s=90,alpha=0.8, hue='species',palette=color_species,ax=ax, label='Feat. 2')
ax.legend(loc='upper right',bbox_to_anchor=(1.5,1))
ax.set_xlabel('Sample')
ax.set_ylabel('Feature 1 and 2')
To manipulate the legend, you need to get the legend handles using get_legend_handles_labels()
, then pick half the entries (3 in your case) and plot them as a legend. Then use matplotlib.lines
to create the kind of icon you need (color, shape, etc.) and pass that as the handles for second legend along with the label text you want. You can use title to add titles to the legends. The code is shown below. Hope this is what you are looking for.
import seaborn as sns
import matplotlib.pyplot as plt
import random
import matplotlib.colors as mcolors
random.seed(5)
iris = sns.load_dataset("iris")
species_list = list(iris.species.unique())
colors_name = list(mcolors.CSS4_COLORS.keys())
color_species = random.sample(colors_name,len(species_list))
fig,ax = plt.subplots(1,1,figsize=(10,6))
sns.scatterplot(data=iris,edgecolor="black", x=iris.index.values, y='sepal_length',s=50,alpha=0.8, hue='species',
palette=color_species,ax=ax)#,label='Feat. 1')
sns.scatterplot(data=iris,edgecolor="black",marker='*', x=iris.index.values, y='sepal_width',s=90,alpha=0.8,
hue='species',palette=color_species,ax=ax)#, label='Feat. 2')
h,l = ax.get_legend_handles_labels() ##Get the legend handles and lables
l1 = ax.legend(h[:int(len(h)/2)],l[:int(len(l)/2)], loc='upper right',bbox_to_anchor=(1.2,1), title='Title 1') ##Plot half as the first legend
from matplotlib.lines import Line2D
myHandle = [Line2D([], [], marker='.', color='red', markersize=10, linestyle='None'),
Line2D([], [], marker='*', color='blue', markersize=10, linestyle='None')] ##Create custom handles for 2nd legend
l2 = ax.legend(handles=myHandle, labels = ['MyLabel1', 'MyLabel2'], loc='upper right',bbox_to_anchor=(1.2,0.8), title='Title 2') ##Add 2nd legend
ax.add_artist(l1) # 2nd legend will erases the first, so need to add it
ax.set_xlabel('Sample')
ax.set_ylabel('Feature 1 and 2')
- This is more easily done by reshaping the dataframe into a long form with
pandas.DataFrame.melt
.- The seaborn plot API works best with
data=
in a long form. - Removes the need to plot each feature separately, and is the correct way to use seaborn.
- The legend section names will be the column name passed to
hue
andstyle
, therefore adjust the column names as desired.
- The seaborn plot API works best with
- Tested in
python 3.10
,pandas 1.4.3
,matplotlib 3.5.2
,seaborn 0.12.0
import pandas as pd
import seaborn as sns
# load the sample data
iris = sns.load_dataset("iris")
# reshape the dataframe with the desired columns
im = (iris.iloc[:, [0, 1, 4]]
.melt(id_vars='species', var_name='Feature', value_name='Value', ignore_index=False)
.reset_index()
.rename({'index': 'Sample', 'species': 'Species'}, axis=1))
# display(im.head())
Sample Species Feature Value
0 0 setosa sepal_length 5.1
1 1 setosa sepal_length 4.9
2 2 setosa sepal_length 4.7
3 3 setosa sepal_length 4.6
4 4 setosa sepal_length 5.0
# display(im.tail())
Sample Species Feature Value
295 145 virginica sepal_width 3.0
296 146 virginica sepal_width 2.5
297 147 virginica sepal_width 3.0
298 148 virginica sepal_width 3.4
299 149 virginica sepal_width 3.0
# create a figure-level plot
g = sns.relplot(data=im, x='Sample', y='Value', hue='Species', style='Feature', aspect=1.5)
import matplotlib.pyplot as plt
# create a matplotlib figure
fig, ax = plt.subplots(1, 1, figsize=(10, 6))
# create an axes-level plot
sns.scatterplot(data=im, x='Sample', y='Value', hue='Species', style='Feature', ax=ax)
ax.spines[['top', 'right']].set_visible(False)
sns.move_legend(ax, bbox_to_anchor=(1, 0.5), loc='center left', frameon=False)