swarmplot with hue affecting marker beyond color

by Miguel   Last Updated October 20, 2018 19:26 PM

I'm trying to make my swarmplot easier to read in black&white and for people that are color-blind, by having the hue affect the color but also another geometrical aspect of the marker.

MWE

import seaborn as sns
import matplotlib.pyplot as plt
sns.set(style="whitegrid")
tips = sns.load_dataset("tips")

fig, ax = plt.subplots(1,1)
ax = sns.swarmplot(x="day", y="total_bill", hue="sex",data=tips,size=8,ax=ax)
plt.show()

Result

enter image description here

Desired Result (the left one)

enter image description here



Answers 1


I actually had thought of the same problem a while ago. I did not come up with the greatest of solutions, but I have a hack that works OK. Unfortunately, it's much easier to implement if you use dodge=True.

The idea is to collect the PathCollections objects created by swarmplot. If dodge=True then you'll get N_hues+2 collections (the 2 extras are used to create the legend). Each pathcollection will correspond to one hue, and you can simply update the paths of that collection to whatever Path object you choose. Refer to the documentation for Path to learn how to create paths.

To simplify things, I created some dummy scatter plots before hands to get some premade Paths that I can use. Of course, any Path should be able to work.

import seaborn as sns
import matplotlib.pyplot as plt
sns.set(style="whitegrid")
tips = sns.load_dataset("tips")

fig, ax = plt.subplots(1,1)
# dummy plots, just to get the Path objects
a = ax.scatter([1,2],[3,4], marker='s')
b = ax.scatter([1,2],[3,4], marker='^')
square_mk, = a.get_paths()
triangle_up_mk, = b.get_paths()
a.remove()
b.remove()

ax = sns.swarmplot(x="day", y="total_bill", hue="sex",data=tips,size=8,ax=ax, dodge=True)
N_hues = len(pd.unique(tips.sex))

c = ax.collections
for a in c[::N_hues]:
    a.set_paths([triangle_up_mk])
for a in c[1::N_hues]:
    a.set_paths([square_mk])
#update legend
ax.legend(c[-2:],pd.unique(tips.sex))

plt.show()

enter image description here

UPDATE A solution that "works" with dodge=False.

If you use dodge=False, then you'll get N+2 collections, one for each category, +2 for the legend. The problem is that all the different marker colors are jumbled up in these collections.

A possible, but ugly, solution is to loop through each element of the collection, and create an array of Path objects based one the color of each element.

import seaborn as sns
import matplotlib.pyplot as plt
sns.set(style="whitegrid")
tips = sns.load_dataset("tips")

fig, ax = plt.subplots(1,1)
ax = sns.swarmplot(x="day", y="total_bill", hue="sex",data=tips,size=8,ax=ax, dodge=False)

collections = ax.collections
unique_colors = np.unique(collections[0].get_facecolors(), axis=0)
markers = [triangle_up_mk, square_mk]  # this array must be at least as large as the number of unique colors
for collection in collections:
    paths = []
    for current_color in collection.get_facecolors():
        for possible_marker,possible_color in zip(markers, unique_colors):
            if np.array_equal(current_color,possible_color):
                paths.append(possible_marker)
                break
    collection.set_paths(paths)
#update legend
ax.legend(collections[-2:],pd.unique(tips.sex))  

plt.show()

enter image description here

Diziet Asahi
Diziet Asahi
October 20, 2018 19:18 PM

Related Questions


Create a cycleplot in seaborn

Updated March 06, 2017 16:26 PM


Seaborn colormap shows numbers not exist in the data

Updated August 12, 2017 08:26 AM

Using lmplot for shared axis plotting

Updated September 30, 2017 16:26 PM