From de1e5ba8002f6bbb75dbe203cfabcd23c9e3d79d Mon Sep 17 00:00:00 2001 From: kislovskiy Date: Sat, 2 Oct 2021 08:41:34 +0200 Subject: [PATCH 01/10] add validator to errorbar method --- lib/matplotlib/axes/_axes.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 4eca2e6b0bdb..b041373c3049 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3283,6 +3283,11 @@ def errorbar(self, x, y, yerr=None, xerr=None, x, y = np.atleast_1d(x, y) # Make sure all the args are iterable. if len(x) != len(y): raise ValueError("'x' and 'y' must have the same size") + + if xerr is not None and np.any(xerr < 0): + raise ValueError("'xerr' must have positive numbers") + if yerr is not None and np.any(yerr < 0): + raise ValueError("'yerr' must have positive numbers") if isinstance(errorevery, Integral): errorevery = (0, errorevery) From ec1da3b961345e869922b0e387347bc954ff3901 Mon Sep 17 00:00:00 2001 From: kislovskiy Date: Sat, 2 Oct 2021 09:26:23 +0200 Subject: [PATCH 02/10] add check when two inputs have negative numbers --- 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 b041373c3049..5f82df500d53 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3283,9 +3283,13 @@ def errorbar(self, x, y, yerr=None, xerr=None, x, y = np.atleast_1d(x, y) # Make sure all the args are iterable. if len(x) != len(y): raise ValueError("'x' and 'y' must have the same size") - + if xerr is not None and np.any(xerr < 0): - raise ValueError("'xerr' must have positive numbers") + if yerr is not None and np.any(yerr < 0): + raise ValueError( + "'xerr' and 'yerr' must have positive numbers") + else: + raise ValueError("'xerr' must have positive numbers") if yerr is not None and np.any(yerr < 0): raise ValueError("'yerr' must have positive numbers") From 3496ba2e581d18921e6e1b422c9497e3a64ef14b Mon Sep 17 00:00:00 2001 From: kislovskiy Date: Sat, 2 Oct 2021 09:26:50 +0200 Subject: [PATCH 03/10] add test_xer_yerr_positive --- lib/matplotlib/tests/test_axes.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 3168fe86ad66..2a8be5680d4a 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -3513,6 +3513,18 @@ def test_errorbar_every_invalid(): ax.errorbar(x, y, yerr, errorevery='foobar') +def test_xerr_yerr_positive(): + ax = plt.figure().subplots() + + with pytest.raises(ValueError, + match="'xerr' and 'yerr' must have positive numbers"): + ax.errorbar(x=[0], y=[0], xerr=[[-0.5], [1]], yerr=[[-0.5], [1]]) + with pytest.raises(ValueError, match="'xerr' must have positive numbers"): + ax.errorbar(x=[0], y=[0], xerr=[[-0.5], [1]]) + with pytest.raises(ValueError, match="'yerr' must have positive numbers"): + ax.errorbar(x=[0], y=[0], yerr=[[-0.5], [1]]) + + @check_figures_equal() def test_errorbar_every(fig_test, fig_ref): x = np.linspace(0, 1, 15) From e2381b53c36833e65ccaa7943a971f5909ae9627 Mon Sep 17 00:00:00 2001 From: kislovskiy Date: Sat, 2 Oct 2021 13:14:51 +0200 Subject: [PATCH 04/10] don't fail on test_units.py --- lib/matplotlib/axes/_axes.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 5f82df500d53..40953794b1f1 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3284,13 +3284,20 @@ def errorbar(self, x, y, yerr=None, xerr=None, if len(x) != len(y): raise ValueError("'x' and 'y' must have the same size") - if xerr is not None and np.any(xerr < 0): - if yerr is not None and np.any(yerr < 0): + def check_if_negative(array): + try: + if np.any(array < 0): + return True + except: + pass + + if xerr is not None and check_if_negative(xerr): + if yerr is not None and check_if_negative(yerr): raise ValueError( "'xerr' and 'yerr' must have positive numbers") else: raise ValueError("'xerr' must have positive numbers") - if yerr is not None and np.any(yerr < 0): + if check_if_negative(yerr): raise ValueError("'yerr' must have positive numbers") if isinstance(errorevery, Integral): From 11b1c4b8ab6ffbb77052fe4e5a785952b038f91f Mon Sep 17 00:00:00 2001 From: kislovskiy Date: Sat, 2 Oct 2021 13:18:51 +0200 Subject: [PATCH 05/10] add a comment to TypeError --- lib/matplotlib/axes/_axes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 40953794b1f1..cb92b0254c0c 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3288,7 +3288,7 @@ def check_if_negative(array): try: if np.any(array < 0): return True - except: + except TypeError: # Don't fail on 'datetime.*' types pass if xerr is not None and check_if_negative(xerr): From 9b5bbe7808bfd2f98919f38f4aaaca0342ea1b55 Mon Sep 17 00:00:00 2001 From: kislovskiy Date: Mon, 4 Oct 2021 09:56:40 +0200 Subject: [PATCH 06/10] check None in check_if_negative --- lib/matplotlib/axes/_axes.py | 12 ++++-------- lib/matplotlib/tests/test_axes.py | 6 ++++-- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index cb92b0254c0c..cff3535b1c5f 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3285,20 +3285,16 @@ def errorbar(self, x, y, yerr=None, xerr=None, raise ValueError("'x' and 'y' must have the same size") def check_if_negative(array): + if array is None: + return False try: if np.any(array < 0): return True except TypeError: # Don't fail on 'datetime.*' types pass - if xerr is not None and check_if_negative(xerr): - if yerr is not None and check_if_negative(yerr): - raise ValueError( - "'xerr' and 'yerr' must have positive numbers") - else: - raise ValueError("'xerr' must have positive numbers") - if check_if_negative(yerr): - raise ValueError("'yerr' must have positive numbers") + if check_if_negative(xerr) or check_if_negative(yerr): + raise ValueError("'xerr' and 'yerr' must have positive numbers") if isinstance(errorevery, Integral): errorevery = (0, errorevery) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 2a8be5680d4a..2319c1aa5654 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -3519,9 +3519,11 @@ def test_xerr_yerr_positive(): with pytest.raises(ValueError, match="'xerr' and 'yerr' must have positive numbers"): ax.errorbar(x=[0], y=[0], xerr=[[-0.5], [1]], yerr=[[-0.5], [1]]) - with pytest.raises(ValueError, match="'xerr' must have positive numbers"): + with pytest.raises(ValueError, + match="'xerr' and 'yerr' must have positive numbers"): ax.errorbar(x=[0], y=[0], xerr=[[-0.5], [1]]) - with pytest.raises(ValueError, match="'yerr' must have positive numbers"): + with pytest.raises(ValueError, + match="'xerr' and 'yerr' must have positive numbers"): ax.errorbar(x=[0], y=[0], yerr=[[-0.5], [1]]) From b025271b8b901e5470fbaf18dd01719d1820b3b5 Mon Sep 17 00:00:00 2001 From: kislovskiy Date: Mon, 4 Oct 2021 14:58:07 +0200 Subject: [PATCH 07/10] fix wording & simplify function --- lib/matplotlib/axes/_axes.py | 12 ++++++++---- lib/matplotlib/tests/test_axes.py | 11 +++++------ 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index cff3535b1c5f..d9842f74d5f2 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3180,7 +3180,7 @@ def errorbar(self, x, y, yerr=None, xerr=None, errors. - *None*: No errorbar. - Note that all error arrays should have *positive* values. + Note that all error arrays should have *non-negative* values. See :doc:`/gallery/statistics/errorbar_features` for an example on the usage of ``xerr`` and ``yerr``. @@ -3284,17 +3284,21 @@ def errorbar(self, x, y, yerr=None, xerr=None, if len(x) != len(y): raise ValueError("'x' and 'y' must have the same size") - def check_if_negative(array): + def has_negative_values(array): if array is None: return False try: + return np.any(array < 0) + except TypeError: + pass # Don't fail on 'datetime.*' types if np.any(array < 0): return True except TypeError: # Don't fail on 'datetime.*' types pass - if check_if_negative(xerr) or check_if_negative(yerr): - raise ValueError("'xerr' and 'yerr' must have positive numbers") + if has_negative_values(xerr) or has_negative_values(yerr): + raise ValueError( + "'xerr' and 'yerr' must have non-negative numbers") if isinstance(errorevery, Integral): errorevery = (0, errorevery) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 2319c1aa5654..2d726cea9fda 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -3516,14 +3516,13 @@ def test_errorbar_every_invalid(): def test_xerr_yerr_positive(): ax = plt.figure().subplots() - with pytest.raises(ValueError, - match="'xerr' and 'yerr' must have positive numbers"): + error_message = "'xerr' and 'yerr' must have non-negative numbers" + + with pytest.raises(ValueError, match=error_message): ax.errorbar(x=[0], y=[0], xerr=[[-0.5], [1]], yerr=[[-0.5], [1]]) - with pytest.raises(ValueError, - match="'xerr' and 'yerr' must have positive numbers"): + with pytest.raises(ValueError, match=error_message): ax.errorbar(x=[0], y=[0], xerr=[[-0.5], [1]]) - with pytest.raises(ValueError, - match="'xerr' and 'yerr' must have positive numbers"): + with pytest.raises(ValueError, match=error_message): ax.errorbar(x=[0], y=[0], yerr=[[-0.5], [1]]) From ced1eb25e4a66cf5d47e72d547f132f0d844a246 Mon Sep 17 00:00:00 2001 From: kislovskiy Date: Mon, 4 Oct 2021 15:03:34 +0200 Subject: [PATCH 08/10] Stage all changes in has_negative_values --- lib/matplotlib/axes/_axes.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index d9842f74d5f2..2cebd2c06789 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3291,10 +3291,6 @@ def has_negative_values(array): return np.any(array < 0) except TypeError: pass # Don't fail on 'datetime.*' types - if np.any(array < 0): - return True - except TypeError: # Don't fail on 'datetime.*' types - pass if has_negative_values(xerr) or has_negative_values(yerr): raise ValueError( From 8f63438fa3ae3e0238031248146d913dbb31c1c8 Mon Sep 17 00:00:00 2001 From: kislovskiy Date: Mon, 4 Oct 2021 15:10:23 +0200 Subject: [PATCH 09/10] allign wording in description and error --- lib/matplotlib/axes/_axes.py | 2 +- lib/matplotlib/tests/test_axes.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 2cebd2c06789..3897c53c4cc8 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3294,7 +3294,7 @@ def has_negative_values(array): if has_negative_values(xerr) or has_negative_values(yerr): raise ValueError( - "'xerr' and 'yerr' must have non-negative numbers") + "'xerr' and 'yerr' must have non-negative values") if isinstance(errorevery, Integral): errorevery = (0, errorevery) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 2d726cea9fda..50a1bb078ec9 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -3516,7 +3516,7 @@ def test_errorbar_every_invalid(): def test_xerr_yerr_positive(): ax = plt.figure().subplots() - error_message = "'xerr' and 'yerr' must have non-negative numbers" + error_message = "'xerr' and 'yerr' must have non-negative values" with pytest.raises(ValueError, match=error_message): ax.errorbar(x=[0], y=[0], xerr=[[-0.5], [1]], yerr=[[-0.5], [1]]) From 9d182aebd7c4c268a0dbe3bf75dd8e89e4a0b0a6 Mon Sep 17 00:00:00 2001 From: kislovskiy Date: Mon, 4 Oct 2021 15:44:52 +0200 Subject: [PATCH 10/10] add validator for timedelta --- lib/matplotlib/axes/_axes.py | 5 +++-- lib/matplotlib/tests/test_axes.py | 6 ++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 3897c53c4cc8..25cafef60d79 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3,6 +3,7 @@ import logging import math from numbers import Integral, Number +from datetime import timedelta import numpy as np from numpy import ma @@ -3289,8 +3290,8 @@ def has_negative_values(array): return False try: return np.any(array < 0) - except TypeError: - pass # Don't fail on 'datetime.*' types + except TypeError: # if array contains 'datetime.timedelta' types + return np.any(array < timedelta(0)) if has_negative_values(xerr) or has_negative_values(yerr): raise ValueError( diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 50a1bb078ec9..d270136f9667 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -3524,6 +3524,12 @@ def test_xerr_yerr_positive(): ax.errorbar(x=[0], y=[0], xerr=[[-0.5], [1]]) with pytest.raises(ValueError, match=error_message): ax.errorbar(x=[0], y=[0], yerr=[[-0.5], [1]]) + with pytest.raises(ValueError, match=error_message): + x = np.arange(5) + y = [datetime.datetime(2021, 9, i * 2 + 1) for i in x] + ax.errorbar(x=x, + y=y, + yerr=datetime.timedelta(days=-10)) @check_figures_equal()