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')

enter image description here

Asked By: Jeniffer Barreto

||

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')

enter image description here

Answered By: Redox
  • 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)

enter image description here

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)

enter image description here

Answered By: Trenton McKinney