From d91d21648f74d4bb0f9dd131cc73b48fa8c8dc84 Mon Sep 17 00:00:00 2001 From: "Marten H. van Kerkwijk" Date: Thu, 10 Apr 2025 12:03:57 -0400 Subject: [PATCH 1/3] BUG: ensure that errorbar does not error on masked negative errors. errorbar checks that errors are not negative, but a bit convolutedly, in order to avoid triggering on nan. Unfortunately, the work-around for nan means that possible masks get discarded, and hence passing in a masked error array that has a negative but masked value leads to an exception. This PR solves that by simply combining the test for negative values with the indirect isnan test (err == err), so that if a masked array is passed, the test values are masked and ignored in the check. As a bonus, this also means that astropy's ``Masked`` arrays can now be used -- those refuse to write output of tests to unmasked values since that would discard the mask (which is indeed the underlying problem here). --- lib/matplotlib/axes/_axes.py | 3 +-- lib/matplotlib/tests/test_axes.py | 13 +++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index b46cbce39c58..11edd908b2b6 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3756,8 +3756,7 @@ def apply_mask(arrays, mask): f"'{dep_axis}err' must not contain None. " "Use NaN if you want to skip a value.") - res = np.zeros(err.shape, dtype=bool) # Default in case of nan - if np.any(np.less(err, -err, out=res, where=(err == err))): + if np.any((err < -err) & (err == err)): # like err<0, but also works for timedelta and nan. raise ValueError( f"'{dep_axis}err' must not contain negative values") diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 70d1671cafa3..64cfb3ab2e61 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -4538,6 +4538,19 @@ def test_errorbar_nan(fig_test, fig_ref): ax.errorbar([4], [3], [6], fmt="C0") +@check_figures_equal() +def test_errorbar_masked_negative(fig_test, fig_ref): + ax = fig_test.add_subplot() + xs = range(5) + mask = np.array([False, False, True, True, False]) + ys = np.ma.array([1, 2, 2, 2, 3], mask=mask) + es = np.ma.array([4, 5, -1, -10, 6], mask=mask) + ax.errorbar(xs, ys, es) + ax = fig_ref.add_subplot() + ax.errorbar([0, 1], [1, 2], [4, 5]) + ax.errorbar([4], [3], [6], fmt="C0") + + @image_comparison(['hist_stacked_stepfilled.png', 'hist_stacked_stepfilled.png']) def test_hist_stacked_stepfilled(): # make some data From bbe738f14aef14f6238067db1dbab9e7b90768e0 Mon Sep 17 00:00:00 2001 From: "Marten H. van Kerkwijk" Date: Thu, 10 Apr 2025 12:43:58 -0400 Subject: [PATCH 2/3] BUG: ensure we never do nan < nan --- lib/matplotlib/axes/_axes.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 11edd908b2b6..82ddc53904b3 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3756,8 +3756,12 @@ def apply_mask(arrays, mask): f"'{dep_axis}err' must not contain None. " "Use NaN if you want to skip a value.") - if np.any((err < -err) & (err == err)): - # like err<0, but also works for timedelta and nan. + # Raise if any errors are negative, but not if they are nan. + # To avoid nan comparisons (which lead to warnings on some + # platforms), we select with `err==err` (which is False for nan). + # Also, since datetime.timedelta cannot be compared with 0, + # we compare with the negative error instead. + if np.any((check := err[err == err]) < -check): raise ValueError( f"'{dep_axis}err' must not contain negative values") # This is like From ebeb83a05920f864d525740e78ec3e41e890bb1e Mon Sep 17 00:00:00 2001 From: Marten Henric van Kerkwijk Date: Sun, 13 Apr 2025 08:18:44 -0400 Subject: [PATCH 3/3] MAINT: make test code a little more explicit in what's done. --- lib/matplotlib/tests/test_axes.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 64cfb3ab2e61..c1758d2ec3e0 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -4532,10 +4532,10 @@ def test_errorbar_nan(fig_test, fig_ref): xs = range(5) ys = np.array([1, 2, np.nan, np.nan, 3]) es = np.array([4, 5, np.nan, np.nan, 6]) - ax.errorbar(xs, ys, es) + ax.errorbar(xs, ys, yerr=es) ax = fig_ref.add_subplot() - ax.errorbar([0, 1], [1, 2], [4, 5]) - ax.errorbar([4], [3], [6], fmt="C0") + ax.errorbar([0, 1], [1, 2], yerr=[4, 5]) + ax.errorbar([4], [3], yerr=[6], fmt="C0") @check_figures_equal() @@ -4545,10 +4545,10 @@ def test_errorbar_masked_negative(fig_test, fig_ref): mask = np.array([False, False, True, True, False]) ys = np.ma.array([1, 2, 2, 2, 3], mask=mask) es = np.ma.array([4, 5, -1, -10, 6], mask=mask) - ax.errorbar(xs, ys, es) + ax.errorbar(xs, ys, yerr=es) ax = fig_ref.add_subplot() - ax.errorbar([0, 1], [1, 2], [4, 5]) - ax.errorbar([4], [3], [6], fmt="C0") + ax.errorbar([0, 1], [1, 2], yerr=[4, 5]) + ax.errorbar([4], [3], yerr=[6], fmt="C0") @image_comparison(['hist_stacked_stepfilled.png', 'hist_stacked_stepfilled.png']) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy