Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sns.histplot has a mystery/ghost patch, new with update from 0.11.2--> 0.12.2 #3241

Closed
PhillipMaire opened this issue Feb 2, 2023 · 9 comments

Comments

@PhillipMaire
Copy link

the problem is outlined here in this colab notebook
https://colab.research.google.com/drive/1i69qTX1SPSPKogUBa2tMUmjC3cHO_VPn?usp=sharing

sns.version, mpl.version
('0.11.2', '3.2.2')
displot and histplot display correct patched when finding the patched plotted as a hist (used for key)
image

sns.version, mpl.version
('0.12.2', '3.6.3')
there is a blue patch somewhere only for histplot but not for displot

image

@mwaskom
Copy link
Owner

mwaskom commented Feb 2, 2023

Please convert this to a reproducible example and simplify (do you need the complicated hatching business to demonstrate it? etc.)

@PhillipMaire
Copy link
Author

PhillipMaire commented Feb 2, 2023

colors_in = ['y', 'r']
tmp1 = pd.DataFrame([[  1.,   1.],
[  1.,   0.],
[  9.,   7.],
[-10.,   1.],
[  5.,   2.],
[  0.,   0.],
[  0.,   0.]])

sns.histplot(data=tmp1, kde=True, multiple='layer', palette=colors_in, alpha=.5)
plt.legend(['KDE_1', 'KDE_2', 'name_1', 'name_2'], loc='upper right', bbox_to_anchor=(.7, .5, 0.5, 0.5), fontsize = 18)  

image

this is as simple as I can make it i think. thank you!

@PhillipMaire
Copy link
Author

or I guess this is even simpler

colors_in = ['y', 'r']
tmp1 = pd.DataFrame([[  1.,   1.],
[  1.,   0.],
[  9.,   7.],
[-10.,   1.],
[  5.,   2.],
[  0.,   0.],
[  0.,   0.]])

sns.histplot(data=tmp1, multiple='layer', palette=colors_in)
plt.legend(['name_1', 'name_2'])

image

My guess is that blue is the default value and somehow even when passing in another color, it uses blue instead. for example either a hard code issue that was meant to be temporary? just a guess

@mwaskom
Copy link
Owner

mwaskom commented Feb 2, 2023

Calling plt.legend like that is not supported behavior (or a good idea); you're making assumptions about the existence and order of artists attached to the axes that don't have any reason to hold or be consistent over time.

@PhillipMaire
Copy link
Author

okay I thought that was a good assumption but I guess not. They make that assumption at least for line order in the matplotlib documentation directly copied below.

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.text as text
a = np.arange(0, 3, .02)
b = np.arange(0, 3, .02)
c = np.exp(a)
d = c[::-1]
fig, ax = plt.subplots()
plt.plot(a, c, 'k--', a, d, 'k:', a, c + d, 'k')
plt.legend(('Model length', 'Data length', 'Total message length'),
loc='upper center', shadow=True)

what is best practice for sns.histplot? should I explicitly make the patches? normally I would just put legend=true but I needed to set_hatch for some bars in this case.

@jhncls
Copy link

jhncls commented Feb 2, 2023

As Seaborn creates rather complicated plots with many options, and tries to have legends with compact information, legends are build up from custom elements. Matplotlib's default legend, on the other hand, searches for labeled graphical elements in the plot. Or, when only given labels are provided, matplotlib searches for all suitable elements.

A real best practice doesn't exist for what you want to achieve. Below is an approach that works with the current Seaborn/matplotlib versions, but still depends on where in the ax the elements happen to be.

import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import numpy as np

colors_in = ['y', 'r']
tmp1 = pd.DataFrame([[1., 1.], [1., 0.], [9., 7.], [-10., 1.], [5., 2.], [0., 0.], [0., 0.]],
                    columns=['name_1', 'name_2'])
ax = sns.histplot(data=tmp1, kde=True, multiple='layer', palette=colors_in, alpha=.5)

ax.lines[0].set_label('KDE 1')  # leave this out if it's not needed in the legend
ax.lines[1].set_label('KDE 2')
for bar in ax.containers[1]:
    bar.set_hatch('//')
ax.containers[1].set_label('name_1')
ax.containers[2].set_label('name_2')

ax.legend(loc='upper right', bbox_to_anchor=(1, 1), fontsize=12)
plt.show()

image

@PhillipMaire
Copy link
Author

Okay makes sense! thanks you so much, I didn't even know about ax.containers. That makes it much easier to work with the sets of bars. I was stuck trying to set hatches based on ax.patches combined with number of patches (bins) for each color, which isn't clean at all.

thank you both for your help, I'll close the issue now.

@mwaskom
Copy link
Owner

mwaskom commented Feb 6, 2023

I'd actually suggest a slightly different approach which would be to add hatches to artists based on their color and to modify the legend handles themselves directly:

from matplotlib.colors import same_color, to_rgb

tmp1 = pd.DataFrame([[  1.,   1.],
[  1.,   0.],
[  9.,   7.],
[-10.,   1.],
[  5.,   2.],
[  0.,   0.],
[  0.,   0.]])

colors_in = ['y', 'r']
hatch_color = colors_in[-1]

ax = sns.histplot(data=tmp1, palette=colors_in)

for patch in ax.patches:
    if same_color(to_rgb(patch.get_facecolor()), hatch_color):
        patch.set_hatch("//")

legend = ax.get_legend()
for h in legend.legend_handles:
    if isinstance(h, mpl.patches.Rectangle) and same_color(to_rgb(h.get_facecolor()), hatch_color):
        h.set_hatch("//")

image

This way you're not making any assumptions about artist order and will ensure that the hatching information represents the same variable as the color information.

BTW the underlying issue here is something that we can address (#3246) but I'd still strongly recommend learning how to make any post-hoc manipulations like this explicit rather than implicit, because otherwise you're bound to get yourself into trouble at some point.

@PhillipMaire
Copy link
Author

Thanks for the the detailed approach! I see your point and will be careful to not make that assumption again, much appreciated!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants