Search code examples
pythonseabornlegend-propertiesgetcolor

How can I combine hue and style groups in a Seaborn scatter plot legend?


I have followed the code in this question: How can I combine hue and style groups in a Seaborn legend?.

I want to change the legend so the styles and hues are not separate lists, but are actually combined.

#practice dataset before applying to above
df = pd.DataFrame({"Sites" : ["Site 1","Site 1", "Site 2", "Site 2", "Site 3", "Site 3"],
                   "Depth" : ["a" , "b", "c", "d", "e", "f"],
                   "D/L_FAA" : [0.1 , 0.2, 0.3, 0.4, 0.5, 0.6],
                   "D/L_THAA" : [0.1 , 0.2, 0.3, 0.4, 0.5, 0.6]})

#creating dictionary for legend

dictionary = df.set_index("Depth")["Sites"].to_dict()
df['Subscale'] = df['Depth'].apply(lambda i: dictionary[i])
df['Subscale'] = pd.Categorical(df['Subscale'])  # creates a fixed order


#create plot
ax = sns.scatterplot(data = df, x = "D/L_FAA", y = "D/L_THAA", style = "Depth", 
                     hue = "Subscale", palette="colorblind", markers=True)
# create a dictionary mapping the subscales to their color
handles, labels = ax.get_legend_handles_labels()
index_depth_title = labels.index('Depth')
color_dict = {label: handles.get_color()
              for handle, label in zip(handles[1:index_depth_title], labels[1:index_depth_title])}

# loop through the items, assign color via the subscale of the item idem
for handle, label in zip(handles[index_depth_title + 1:], labels[index_depth_title + 1:]):
   handle.set_color(color_dict[dictionary[handle]])

# create a legend only using the items
ax.legend(handles[index_depth_title + 1:], labels[index_depth_title + 1:], title='Item',
          bbox_to_anchor=(1.03, 1.02), fontsize=10)

plt.tight_layout()
plt.show()

I am currently getting this error:

AttributeError: 'list' object has no attribute 'get_color'

I'm not sure why my code isn't working when the code in the question does.

Any help would be much appreciated.

Thanks,

Ellie


Solution

  • The code below makes the following changes:

    • renaming the variable dictionary to depth_to_site_dict
    • in color_dict = {label: handles.get_color() ...}: changing handles to handle and get_color to get_facecolor() (the linked plot used a line plot, in this code we have a scatter plot)
    • in handle.set_color(color_dict[dictionary[handle]]): use color_dict[depth_to_site_dict[label]]
    from matplotlib import pyplot as plt
    import seaborn as sns
    import pandas as pd
    import numpy as np
    
    df = pd.DataFrame({"Sites": ["Site 1", "Site 1", "Site 2", "Site 2", "Site 3", "Site 3"],
                       "Depth": ["a", "b", "c", "d", "e", "f"],
                       "D/L_FAA": [0.1, 0.2, 0.3, 0.4, 0.5, 0.6],
                       "D/L_THAA": [0.1, 0.2, 0.3, 0.4, 0.5, 0.6]})
    # creating dictionary for legend
    depth_to_site_dict = df.set_index("Depth")["Sites"].to_dict()
    df['Subscale'] = df['Depth'].map(depth_to_site_dict)
    df['Subscale'] = pd.Categorical(df['Subscale'])  # creates a fixed order
    
    # create plot
    ax = sns.scatterplot(data=df, x="D/L_FAA", y="D/L_THAA", style="Depth",
                         hue="Subscale", palette="colorblind", markers=True)
    # create a dictionary mapping the subscales to their color
    handles, labels = ax.get_legend_handles_labels()
    index_depth_title = labels.index('Depth')
    color_dict = {label: handle.get_facecolor()
                  for handle, label in zip(handles[1:index_depth_title], labels[1:index_depth_title])}
    
    # loop through the items, assign color via the subscale of the item idem
    for handle, label in zip(handles[index_depth_title + 1:], labels[index_depth_title + 1:]):
        handle.set_color(color_dict[depth_to_site_dict[label]])
    
    # create a legend only using the items
    ax.legend(handles[index_depth_title + 1:], labels[index_depth_title + 1:], title='Item',
              bbox_to_anchor=(1.03, 1.02), fontsize=10)
    
    plt.tight_layout()
    plt.show()
    

    scatterplot with recolored legend