Skip to content

Heatmap: nan cause "color leak" in other cells #17323

Open

Description

@domi84

Bug report

Bug summary

When I try to create a heatmap with some nan values, the color of some cells leak in other cells. It is fine if I replace them with zeros.

Code for reproduction

import matplotlib.pyplot as plt
import numpy as np

Z = np.random.rand(6, 10)
Z.ravel()[np.random.choice(Z.size, 20, replace=False)] = np.nan

fig, (ax0, ax1) = plt.subplots(2, 1)

c = ax0.pcolor(Z)
ax0.set_title('With nan')

c = ax1.pcolor(np.nan_to_num(Z))
ax1.set_title('replaced with zeros')

fig.tight_layout()
plt.show()

Actual/Expected outcome

nan-zeros

If I replace nan with zeros it works.

Matplotlib version

  • Operating system: Windows 10
  • Matplotlib version: 3.2.1
  • Matplotlib backend (print(matplotlib.get_backend())): module://ipykernel.pylab.backend_inline
  • Python version: 3.7.4
  • Jupyter version (if applicable): notebook server is: 6.0.0
  • Other libraries: numpay version 1.18.1

I installed matplotlib from pip, I realized I got this issue using seaborn and pandas.

Thanks

Activity

  1. added this to the v3.4.0 milestone on May 4, 2020
  2. tacaswell commented on May 4, 2020

    @tacaswell
    Member

    The issue is that pcolor in drawn using many individual rectangles. If you alpha is 1 and the rectangles overlap the "last one drawn" wins and you see what looks like no overlap (when the overlap is still there, just not noticeable. If you do

    c = ax0.pcolor(Z, alpha=.5)

    You will see where the overlaps are. The source of this problem is in the default code path we turn off anti-aliasing so we either fully fill a pixel or don't which causes the square to bleed out by one pixel. If you turn anti-aliasing on you solve this problem but now start to see gaps

    import matplotlib.pyplot as plt
    import numpy as np
    import copy
    cm = copy.copy(plt.get_cmap())
    cm.set_under('r', alpha=1)
    cm.set_bad('c', alpha=1)
    Z = np.random.rand(6, 10)
    Z.ravel()[np.random.choice(Z.size, 20, replace=False)] = np.nan
    
    
    
    fig, (ax0, ax1) = plt.subplots(2, 1)
    
    c = ax0.pcolor(Z, cmap=cm, antialiased=True)
    ax0.set_title('With nan')
    
    c = ax1.pcolor(np.nan_to_num(Z), cmap=cm, vmin=np.nanmin(Z), antialiased=True)
    ax1.set_title('replaced with zeros')
    
    fig.tight_layout()
    plt.show()

    pcolor2

    That said, I thought you would be able to work around this by setting a "bad" value for the color map to have full alpha (rather than being transparent) as you can see above that does not work as pcolor seems to ignore the bad value and just not draw the missing rectangles (and it seems to have been that way from when masked array support in pcolor came in via 44b3553).

    I'm not sure what the best path forward here is (as this is at the bottom an issue with quantizing squares specified in real numbers to filling in pixels). The most practical solution for you @domi84 is to either save the figures at a high enough dpi that this isn't a problem or to save to a vector backend (and make this the problem of the next renderer).

  3. jklymak commented on May 4, 2020

    @jklymak
    Member

    ... or use pcolormesh, which so far as I can tell does not have this problem.

  4. mwaskom commented on May 5, 2020

    @mwaskom

    The most practical solution...

    Another option could be to add linewidths=1, edgecolor="w" (or whatever width and color looks good for your application). This might not work for all plots, but it will hide the artifact.

    ... or use pcolormesh, which so far as I can tell does not have this problem.

    The original issue was reported with seaborn.heatmap which uses pcolormesh, and I see the artifact in the given example if I change the function and increase figure size a bit.

  5. domi84 commented on May 5, 2020

    @domi84
    Author

    Thanks for your answers.

    either save the figures at a high enough dpi that this isn't a problem or to save to a vector backend

    Workes if I save it as svg, I can see the issue with png dpi=300

    Another option could be to add linewidths=1

    Yes, this is what I am doing with seaborn

    ... or use pcolormesh, which so far as I can tell does not have this problem.

    The original issue was reported with seaborn.heatmap which uses pcolormesh, and I see the artifact in the given example if I change the function and increase figure size a bit.

    Yes, I tried pcolormesh and I got the same issue

  6. greglucas commented on May 5, 2020

    @greglucas
    Contributor

    I think this is an issue with the Agg backend. I thought that some of my fixes in #16090 might solve this, but even the snapping/edgecolor changes in that PR didn't seem to help this problem.

    Have you tried any of the Cairo backends to produce the png? When I ran this with mplcairo, I get nice snapped grids.

  7. greglucas commented on Jul 16, 2020

    @greglucas
    Contributor

    This issue is now fixed if one uses pcolormesh on master, no bleeding of the pixels over the edge. (Presumably fixing the seaborn issue because they use pcolormesh?)

    However, pcolor was not updated and still bleeds. The proper resolution here is to pixel snap if you've got rectangular objects. So, pcolor(Z, snap=True) produces a good figure for all examples above I believe.

    Action item: Do we want to default to pixel snapping for pcolor as well as pcolormesh?

  8. timhoffm commented on Jul 17, 2020

    @timhoffm
    Member

    Does pixel snapping have notable downsides?

  9. greglucas commented on Jul 19, 2020

    @greglucas
    Contributor

    There can be downsides. Here is a really good short example where gaps between lines can be artificially added to the rasterized output.
    https://www.artwork.com/raster/snap/index.htm

    I think pixel snapping and antialiasing are one of those problems that should be chosen differently depending on the desired result (i.e. do you want a smooth arc, or do you want a super dark single pixel line rather than two gray pixels), so there really is no one definitive answer here (other than keep increasing the dpi). Rather, I think we should be aiming for: 90+% of users want X, so we should default to that case. For the other 10% let's make sure we allow them to set the snap/antialiasing effects they want and don't override any user settings.

    I'm still not clear what 90% of people want. My guess is snap=True as the default though...

  10. modified the milestones: v3.4.0, v3.5.0 on Jan 27, 2021
  11. modified the milestones: v3.5.0, v3.6.0 on Sep 25, 2021
  12. modified the milestones: v3.6.0, v3.7.0 on Aug 24, 2022
  13. modified the milestones: v3.7.0, v3.7.1 on Feb 14, 2023
  14. modified the milestones: v3.7.1, v3.7.2 on Mar 4, 2023
  15. modified the milestones: v3.7.2, v3.7.3 on Jul 5, 2023
  16. modified the milestones: v3.7.3, v3.8.0 on Sep 9, 2023
  17. removed this from the v3.8.0 milestone on Sep 15, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions