How to get outline color instead of fill color in grouped violinplot
Question:
I am using seaborn.violinplot() to derive the differences between two groups. Here is my code:
import seaborn
seaborn.set(style="whitegrid")
tips = seaborn.load_dataset("tips")
seaborn.violinplot(x="day", y="total_bill", hue="smoker", data=tips, palette="Set2", dodge=True)
However, I don’t want the fill color in the violin, and would like to distinguish these two groups with the color of the edge, here is an example:
How can I achieve this?
Answers:
Coloring the outline instead of filling
You could loop through the generated violins and set their edgecolor (which normally is dark grey) to their facecolor. You can do the same with the legend.
As seaborn prefers to saturate colors that will be used for areas, you can add saturate=1
in the call to sns.violinplot
.
import matplotlib.pyplot as plt
import matplotlib
import seaborn as sns
sns.set(style="whitegrid")
tips = sns.load_dataset("tips")
ax = sns.violinplot(x="day", y="total_bill", hue="smoker", data=tips, palette="Set2", dodge=True, saturation=1)
for collection in ax.collections:
if isinstance(collection, matplotlib.collections.PolyCollection):
collection.set_edgecolor(collection.get_facecolor())
collection.set_facecolor('none')
for h in ax.legend_.legendHandles:
if isinstance(h, matplotlib.patches.Rectangle):
h.set_edgecolor(h.get_facecolor())
h.set_facecolor('none')
h.set_linewidth(1.5)
plt.show()
Also changing the inner box
The following code supposes the inner
option isn’t changed from its default 'box'
. It is tested with the current seaborn version (0.12.2). The inner box is represented by two line elements: a long thin line indicating the position of the boxplot whisker, and a thicker, shorter line indicating the quartiles. In addition, there is a white point to indicate the median (represented by a PathCollection
).
import matplotlib.pyplot as plt
import matplotlib
import seaborn as sns
sns.set(style="whitegrid")
tips = sns.load_dataset("tips")
ax = sns.violinplot(x="sex", y="tip", hue="day", data=tips, palette="bright", dodge=True, saturation=1)
colors = []
for collection in ax.collections:
if isinstance(collection, matplotlib.collections.PolyCollection):
colors.append(collection.get_facecolor())
collection.set_edgecolor(colors[-1])
collection.set_facecolor('none')
if len(ax.lines) == 2 * len(colors): # suppose inner=='box'
for lin1, lin2, color in zip(ax.lines[::2], ax.lines[1::2], colors):
lin1.set_color(color)
lin2.set_color(color)
for h in ax.legend_.legendHandles:
if isinstance(h, matplotlib.patches.Rectangle):
h.set_edgecolor(h.get_facecolor())
h.set_facecolor('none')
h.set_linewidth(1.5)
plt.show()
I am using seaborn.violinplot() to derive the differences between two groups. Here is my code:
import seaborn
seaborn.set(style="whitegrid")
tips = seaborn.load_dataset("tips")
seaborn.violinplot(x="day", y="total_bill", hue="smoker", data=tips, palette="Set2", dodge=True)
However, I don’t want the fill color in the violin, and would like to distinguish these two groups with the color of the edge, here is an example:
How can I achieve this?
Coloring the outline instead of filling
You could loop through the generated violins and set their edgecolor (which normally is dark grey) to their facecolor. You can do the same with the legend.
As seaborn prefers to saturate colors that will be used for areas, you can add saturate=1
in the call to sns.violinplot
.
import matplotlib.pyplot as plt
import matplotlib
import seaborn as sns
sns.set(style="whitegrid")
tips = sns.load_dataset("tips")
ax = sns.violinplot(x="day", y="total_bill", hue="smoker", data=tips, palette="Set2", dodge=True, saturation=1)
for collection in ax.collections:
if isinstance(collection, matplotlib.collections.PolyCollection):
collection.set_edgecolor(collection.get_facecolor())
collection.set_facecolor('none')
for h in ax.legend_.legendHandles:
if isinstance(h, matplotlib.patches.Rectangle):
h.set_edgecolor(h.get_facecolor())
h.set_facecolor('none')
h.set_linewidth(1.5)
plt.show()
Also changing the inner box
The following code supposes the inner
option isn’t changed from its default 'box'
. It is tested with the current seaborn version (0.12.2). The inner box is represented by two line elements: a long thin line indicating the position of the boxplot whisker, and a thicker, shorter line indicating the quartiles. In addition, there is a white point to indicate the median (represented by a PathCollection
).
import matplotlib.pyplot as plt
import matplotlib
import seaborn as sns
sns.set(style="whitegrid")
tips = sns.load_dataset("tips")
ax = sns.violinplot(x="sex", y="tip", hue="day", data=tips, palette="bright", dodge=True, saturation=1)
colors = []
for collection in ax.collections:
if isinstance(collection, matplotlib.collections.PolyCollection):
colors.append(collection.get_facecolor())
collection.set_edgecolor(colors[-1])
collection.set_facecolor('none')
if len(ax.lines) == 2 * len(colors): # suppose inner=='box'
for lin1, lin2, color in zip(ax.lines[::2], ax.lines[1::2], colors):
lin1.set_color(color)
lin2.set_color(color)
for h in ax.legend_.legendHandles:
if isinstance(h, matplotlib.patches.Rectangle):
h.set_edgecolor(h.get_facecolor())
h.set_facecolor('none')
h.set_linewidth(1.5)
plt.show()