mwaskom / seaborn

Statistical data visualization in Python
https://seaborn.pydata.org
BSD 3-Clause "New" or "Revised" License
12.6k stars 1.93k forks source link

set_hatch not work in boxplot #3716

Closed eeeXun closed 5 months ago

eeeXun commented 5 months ago

Hi, I just follow this code to use the set_hatch in boxplot. But it doesn't seem to work for me.

from itertools import cycle

from matplotlib import pyplot as plt
import seaborn as sns; sns.set()

# Load dataset
tips = sns.load_dataset('tips')

# Define some hatches
hatches = cycle(['///', 'x'])

# Boxplot
ax = sns.boxplot(x="day", y="total_bill", hue="sex", data=tips)
for i, patch in enumerate(ax.artists):
    # Boxes from left to right
    hatch = next(hatches)
    patch.set_hatch(hatch)

ax.legend(loc='best')
plt.show()

download

My python version is 3.10.12. seaborn version is 0.13.2. matplotlib version is 3.9.0. Is there any change in the set_hatch or it just break by matplotlib?

mwaskom commented 5 months ago

It's possible that matplotlib changed the underlying data model as ax.artists is empty in your example script. But you can access the box patches more directly at ax.patches. Also seaborn v0.13 started packaging the boxplot artists in a container and storing it at ax.containers which is another way of making your post-processing a bit more explicit.

eeeXun commented 5 months ago

Thanks. Changing ax.artists to ax.patches works for me.

from itertools import cycle

from matplotlib import pyplot as plt
import seaborn as sns; sns.set()

# Load dataset
tips = sns.load_dataset('tips')

# Define some hatches
hatches = cycle(['///', 'x'])

# Boxplot
ax = sns.boxplot(x="day", y="total_bill", hue="sex", data=tips)
for i, patch in enumerate(ax.patches):
    # Boxes from left to right
    hatch = next(hatches)
    patch.set_hatch(hatch)

ax.legend()
plt.show()

But what do you mean ax.containers? Changing ax.artists to ax.containers gives the error that BoxPlotContainer has no attribute set_hatch. Do I call it the wrong way?

mwaskom commented 5 months ago

Do I call it the wrong way?

Yes — do e.g.

bxp = ax.containers[0]
bxp.boxes
[<matplotlib.patches.PathPatch at 0x12b095a50>,
 <matplotlib.patches.PathPatch at 0x12b0a4950>,
 <matplotlib.patches.PathPatch at 0x12b0602d0>,
 <matplotlib.patches.PathPatch at 0x12b0636d0>]
eeeXun commented 5 months ago

Oh, I get it. Thank you for answering me so quickly. Appreciate!

eeeXun commented 5 months ago

Hi @mwaskom, after trying again. ax.containers is exactly what I want.

from matplotlib import pyplot as plt
import seaborn as sns; sns.set()

# Load dataset
tips = sns.load_dataset('tips')

# Define some hatches
hatches = ['///', 'x']

# Boxplot
ax = sns.boxplot(x="day", y="total_bill", hue="sex", data=tips)
for i in range(2):
    for box in ax.containers[i].boxes:
        box.set_hatch(hatches[i % len(hatches)])

ax.legend()
plt.show()

download

But the legend did not get the hatch style. Is there any way to do that?

mwaskom commented 5 months ago

You can modify those artists too, i think it's something like ax.get_legend().legend_handles

eeeXun commented 5 months ago
from matplotlib import pyplot as plt
import seaborn as sns; sns.set()

# Load dataset
tips = sns.load_dataset('tips')

# Define some hatches
hatches = ['///', 'x']

# Boxplot
ax = sns.boxplot(x="day", y="total_bill", hue="sex", data=tips)
ax.legend()
for i in range(2):
    for box in ax.containers[i].boxes:
        box.set_hatch(hatches[i % len(hatches)])
    ax.get_legend().legend_handles[i].set_hatch(hatches[i % len(hatches)])

plt.show()

download

Thank you again for your help!