Search code examples
pythonseabornscatter-plot

Seaborn scatterplot set hollow markers instead of filled markers


Using a Seaborn scatterplot, how can I set the markers to be hollow circles instead of filled circles?

Here is a simple example:

import pandas as pd
import seaborn as sns

df = pd.DataFrame(
    {'x': [3,2,5,1,1,0],
     'y': [1,1,2,3,0,2],
     'cat': ['a','a','a','b','b','b']}
)

sns.scatterplot(data=df, x='x', y='y', hue='cat')

enter image description here

I have tried the following without success; most of these do not throw an error but instead produce the same plot as above. I think these don't work because the colors are set with the hue parameter, but I am not sure what the fix is.

sns.scatterplot(data=df, x='x', y='y', hue='cat', facecolors = 'none')
sns.scatterplot(data=df, x='x', y='y', hue='cat', facecolors = None)
sns.scatterplot(data=df, x='x', y='y', hue='cat', markerfacecolor = 'none')
sns.scatterplot(data=df, x='x', y='y', hue='cat', markerfacecolor = None)

with sns.plotting_context(rc={"markerfacecolor": None}):
    sns.scatterplot(data=df, x='x', y='y', hue='cat')

Solution

  • In principle you should be able to create a circular marker with fillstyle="none", but there are some deep complications there and it doesn't currently work as you'd hope.

    The simplest pure seaborn solution is to take advantage of the fact that you can use arbitrary latex symbols as the markers:

    sns.scatterplot(data=df, x='x', y='y', hue="cat", marker="$\circ$", ec="face", s=100)
    

    enter image description here

    That is somewhat limited because you lose control over the thickness of the circle.

    A hybrid seaborn-matplotlib approach is more flexible, but also more cumbersome (you need to create the legend yourself):

    palette = {"a": "C0", "b": "C1"}
    kws = {"s": 70, "facecolor": "none", "linewidth": 1.5}
    
    ax = sns.scatterplot(
        data=df, x='x', y='y',
        edgecolor=df["cat"].map(palette),
        **kws,
    )
    handles, labels = zip(*[
        (plt.scatter([], [], ec=color, **kws), key) for key, color in palette.items()
    ])
    ax.legend(handles, labels, title="cat")
    

    enter image description here

    A third option is to use FacetGrid. This is less flexible because the plot will have to be in its own figure. But it's reasonably simple; the other answer uses FacetGrid but it's a bit over-engineered because it forgets the hue_kws parameter:

    palette = ["C0", "C1"]
    g = sns.FacetGrid(
        data=df, hue="cat",
        height=4, aspect=1.25,
        hue_kws={"edgecolor": palette},
    )
    g.map(plt.scatter, "x", "y", facecolor="none", lw=1.5)
    g.add_legend()
    

    enter image description here