How to add hatches to boxplots with sns.boxplot or sns.catplot
Asked Answered
S

2

5

I need to add hatches to a categorical box plot. What I have is this: enter image description here

What I need is something like this (with the median lines):

enter image description here

And what I have tried is this code:

exercise = sns.load_dataset("exercise")
g = sns.catplot(x="time", y="pulse", hue="kind", data=exercise, kind="box")
bars = g.axes[0][0].patches
hatches=['//','..','xx','//','..','xx','//','..','xx']
for pat,bar in zip(hatches,bars):
    bar.set_hatch(pat)

That only generates the first figure. The idea for lines 3-6 comes from this question. But the idea to get axes[0][0] in line 3 comes from this question.

Because FacetGrids don't have attributes like patches or containers, it makes it harder to adapt the answers about hatches in individual plots to categorical plots, so I couldn't figure it out.

Other reviewed questions that don't work:

Shetrit answered 17/6, 2022 at 9:2 Comment(0)
C
7
  1. Iterate through each subplot / FacetGrid with for ax in g.axes.flat:.
  2. ax.patches contains matplotlib.patches.Rectangle and matplotlib.patches.PathPatch, so the correct ones must be used.
  • Caveat: all hues must appear for each group in each Facet, otherwise the patches and hatches will not match.
    • In this case, manual or conditional code will probably be required to correctly determine h, so zip(patches, h) works.
  • Tested in python 3.10, pandas 1.4.2, matplotlib 3.5.1, seaborn 0.11.2
import matplotlib as mpl
import seaborn as sns

# load test data
exercise = sns.load_dataset("exercise")

# plot
g = sns.catplot(x="time", y="pulse", hue="kind", data=exercise, col='diet', kind="box")

# hatches must equal the number of hues (3 in this case)
hatches = ['//', '..', 'xx']

# iterate through each subplot / Facet
for ax in g.axes.flat:

    # select the correct patches
    patches = [patch for patch in ax.patches if type(patch) == mpl.patches.PathPatch]
    # the number of patches should be evenly divisible by the number of hatches
    h = hatches * (len(patches) // len(hatches))
    # iterate through the patches for each subplot
    for patch, hatch in zip(patches, h):
        patch.set_hatch(hatch)
        fc = patch.get_facecolor()
        patch.set_edgecolor(fc)
        patch.set_facecolor('none')

enter image description here

  • Add the following, to change the legend.
for lp, hatch in zip(g.legend.get_patches(), hatches):
    lp.set_hatch(hatch)
    fc = lp.get_facecolor()
    lp.set_edgecolor(fc)
    lp.set_facecolor('none')

enter image description here


  • If only using the axes-level sns.boxplot, there's no need to iterate through multiple axes.
ax = sns.boxplot(x="time", y="pulse", hue="kind", data=exercise)

# select the correct patches
patches = [patch for patch in ax.patches if type(patch) == mpl.patches.PathPatch]
# the number of patches should be evenly divisible by the number of hatches
h = hatches * (len(patches) // len(hatches))
# iterate through the patches for each subplot
for patch, hatch in zip(patches, h):
    patch.set_hatch(hatch)
    fc = patch.get_facecolor()
    patch.set_edgecolor(fc)
    patch.set_facecolor('none')

l = ax.legend()
    
for lp, hatch in zip(l.get_patches(), hatches):
    lp.set_hatch(hatch)
    fc = lp.get_facecolor()
    lp.set_edgecolor(fc)
    lp.set_facecolor('none')

enter image description here


  • To keep the facecolor of the box plots:
    1. Remove patch.set_facecolor('none')
    2. Set the edgecolor as 'k' (black) instead of fc, patch.set_edgecolor('k').
    • Applies to the sns.catplot code too.
ax = sns.boxplot(x="time", y="pulse", hue="kind", data=exercise)

# select the correct patches
patches = [patch for patch in ax.patches if type(patch) == mpl.patches.PathPatch]
# the number of patches should be evenly divisible by the number of hatches
h = hatches * (len(patches) // len(hatches))
# iterate through the patches for each subplot
for patch, hatch in zip(patches, h):
    patch.set_hatch(hatch)
    patch.set_edgecolor('k')
    
l = ax.legend()
    
for lp, hatch in zip(l.get_patches(), hatches):
    lp.set_hatch(hatch)
    lp.set_edgecolor('k')

enter image description here

Class answered 17/6, 2022 at 14:35 Comment(0)
A
1

The answer by @trenton-mckinney did not work for the box plot case. My environment uses python 3.6.12, pandas 1.1.5, matplotlib 3.3.4, seaborn 0.11.2.

As an alternative, we can use the ax.artists element of the box plot axes to manually apply hatches to the individual box elements. Here's the updated code snippet:

import matplotlib as mpl
import seaborn as sns

exercise = sns.load_dataset("exercise")
ax = sns.boxplot(x="time", y="pulse", hue="kind", data=exercise)
hatches = ['//', '..', 'xx']
for i, artist in enumerate(ax.artists):
    artist.set_hatch(hatches[i % len(hatches)])

l = ax.legend()
for lp, hatch in zip(l.get_patches(), hatches):
    lp.set_hatch(hatch)
    lp.set_edgecolor('k')

enter image description here

Antipus answered 31/5, 2023 at 9:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.