0

I'm trying to create a figure with seaborn lmplot. I use hue to color the different levels for one category. But I'd like to have the marker shape for each point correspond to a different category than hue. However sns.lmplot will only accept markers for the levels of hue.

Is there a way maybe use scatter_kws to pass labels to the points, then somehow access the points within the AxesSubplots class within the seaborn.axisgrid.FacetGrid?

Simple example:

import seaborn as sns
import pandas as pd

snsdf = pd.DataFrame({'x' : [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
              'y' : [9, 9, 8, 6, 6, 4, 4, 3, 1, 1],
              'hue' : ['tree', 'animal', 'animal', 'tree', 'animal'] * 2,
              'marker' : ['o', 'o', '<', '<', 'o', '<', '<', '<', 'o', 'o']
             })

# plot it normally
g = sns.lmplot(data=snsdf,
               x='x',
               y='y',
               hue='hue',
               scatter_kws{'label' : snsdf.marker})


# iterate objects in `g` to find points and change marker
# (i'm not sure about this part, but maybe accessing the label can tell me which marker the point should be? but I'm not sure where the actual points are where I could access the label)

thanks!

BML
  • 191
  • 2
  • 12
  • Given the answers here https://stackoverflow.com/questions/43528300/python-matplotlib-scatter-different-markers-in-one-scatter and the way that seaborn uses `scatter` when creating `lmplot`s (see, https://github.com/mwaskom/seaborn/blob/master/seaborn/regression.py#L416), I doubt this is something that is possible to do (without creating your own custom plotting method). – Matt Pitkin Aug 21 '23 at 14:18
  • @MattPitkin it looks like the solution lies within `g.ax.collections[#].set_paths()` – BML Aug 21 '23 at 14:36
  • I've proposed a slightly different solution that might help. Looking in the `scatterplot` code, it looks like it does indeed use `set_paths` to change the styles as required https://github.com/mwaskom/seaborn/blob/af613f1db95e2ebc02810a985723f7fab19ff01c/seaborn/relational.py#L556 – Matt Pitkin Aug 21 '23 at 14:41

2 Answers2

2

You could try some combination of using a lmplot and scatterplot, which has a style keyword for using different markers (as in this answer).

import seaborn as sns
import pandas as pd
from matplotlib import pyplot as plt


snsdf = pd.DataFrame({'x' : [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
              'y' : [9, 9, 8, 6, 6, 4, 4, 3, 1, 1],
              'hue' : ['tree', 'animal', 'animal', 'tree', 'animal'] * 2,
              'marker' : ['o', 'o', '<', '<', 'o', '<', '<', '<', 'o', 'o']
             })

# plot the lmplot, but turn off the scatter
g = sns.lmplot(data=snsdf,
               x='x',
               y='y',
               hue='hue',
               scatter=False)

ax = plt.gca()  # get the axes

# do scatter plot on the same axes
g = sns.scatterplot(
    data=snsdf,
    x="x",
    y="y",
    hue="hue",
    style="marker",  # set style to marker data (it won't actually use those markers though!
    ax=ax,
    legend=False
)

Note: this doesn't look pretty, so I expect you'd need to play around with things a bit.

Matt Pitkin
  • 3,989
  • 1
  • 18
  • 32
1

Figured it out.

from matplotlib.collections import PathCollection
from matplotlib.markers import MarkerStyle
import pandas as pd
import seaborn as sns

snsdf = pd.DataFrame({'x' : [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
              'y' : [9, 9, 8, 6, 6, 4, 4, 3, 1, 1],
              'hue' : ['tree', 'animal', 'animal', 'tree', 'animal'] * 2,
              'marker' : ['o', 'o', '<', '<', 'o', '<', '<', '<', 'o', 'o']
             })

g = sns.lmplot(data=snsdf,
               x='x',
               y='y',
               hue='hue',
               scatter_kws={'marker' : snsdf.marker})

for collection in g.ax.collections:
    
    if isinstance(collection, PathCollection):
        
        hue_level = collection.get_label()
        
        markers = snsdf[snsdf.hue == hue_level].marker.tolist()
        
        paths = [MarkerStyle(marker).get_path() for marker in markers]
        
        collection.set_paths(paths)
        collection.set_sizes([20])
Matt Pitkin
  • 3,989
  • 1
  • 18
  • 32
BML
  • 191
  • 2
  • 12