Search code examples
pythonmatplotlibseaborndisplotkdeplot

How to set a different linestyle for each hue group in a kdeplot / displot


  • How can each hue group of a seaborn.kdeplot, or seaborn.displot with kind='kde' be given a different linestyle?
    • Both axes-level and figure-level options will accept a str for linestyle/ls, which applies to all hue groups.
import seaborn as sns
import matplotlib.pyplot as plt

# load sample data
iris = sns.load_dataset("iris")

# convert data to long form
im = iris.melt(id_vars='species')

# axes-level plot works with 1 linestyle
fig = plt.figure(figsize=(6, 5))
p1 = sns.kdeplot(data=im, x='value', hue='variable', fill=True, ls='-.')

# figure-level plot works with 1 linestyle
p2 = sns.displot(kind='kde', data=im, x='value', hue='variable', fill=True, ls='-.')
  • kdeplot

enter image description here

  • displot

enter image description here

Reviewed Questions


Solution

    • With fill=True the object to update is in .collections
    • With fill=False the object to update is in .lines
    • Updating the legend is fairly simple:
      • handles = p.legend_.legendHandles[::-1] extracts and reverses the legend handles. They're reversed to update because they're in the opposite order in which the plot linestyle is updated
      • Note that figure-level plots extract the legend with ._legend, with the axes-level plots use .legend_.
    • Tested in python 3.8.12, matplotlib 3.4.3, seaborn 0.11.2

    kdeplot: axes-level

    • Extract and iterate through .collections or .lines from the axes object and use .set_linestyle

    fill=True

    fig = plt.figure(figsize=(6, 5))
    p = sns.kdeplot(data=im, x='value', hue='variable', fill=True)
    
    lss = [':', '--', '-.', '-']
    
    handles = p.legend_.legendHandles[::-1]
    
    for line, ls, handle in zip(p.collections, lss, handles):
        line.set_linestyle(ls)
        handle.set_ls(ls)
    

    enter image description here

    fill=False

    fig = plt.figure(figsize=(6, 5))
    p = sns.kdeplot(data=im, x='value', hue='variable')
    
    lss = [':', '--', '-.', '-']
    
    handles = p.legend_.legendHandles[::-1]
    
    for line, ls, handle in zip(p.lines, lss, handles):
        line.set_linestyle(ls)
        handle.set_ls(ls)
    

    enter image description here

    displot: figure-level

    • Similar to the axes-level plot, but each axes must be iterated through
    • The legend handles could be updated in for line, ls, handle in zip(ax.collections, lss, handles), but that applies the update for each subplot. Therefore, a separate loop is created to update the legend handles only once.

    fill=True

    g = sns.displot(kind='kde', data=im, col='variable', x='value', hue='species', fill=True, common_norm=False, facet_kws={'sharey': False})
    
    axes = g.axes.flat
    
    lss = [':', '--', '-.']
    
    for ax in axes:
        for line, ls in zip(ax.collections, lss):
            line.set_linestyle(ls)
            
    handles = g._legend.legendHandles[::-1]
    for handle, ls in zip(handles, lss):
        handle.set_ls(ls)
    

    enter image description here

    fill=False

    g = sns.displot(kind='kde', data=im, col='variable', x='value', hue='species', common_norm=False, facet_kws={'sharey': False})
    
    axes = g.axes.flat
    
    lss = [':', '--', '-.']
    
    for ax in axes:
        for line, ls in zip(ax.lines, lss):
            line.set_linestyle(ls)
            
    handles = g._legend.legendHandles[::-1]
    for handle, ls in zip(handles, lss):
        handle.set_ls(ls)
    

    enter image description here