Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion lib/matplotlib/axes/_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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``.
Expand Down Expand Up @@ -3284,6 +3284,22 @@ 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 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
Comment thread
Kislovskiy marked this conversation as resolved.
Outdated
except TypeError: # Don't fail on 'datetime.*' types
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's unclear to me why we wouldn't want to fail with datetime types. After all you can't add datetimes so using them as x/yerr should fail, afaict (you can add a datetime to a timedelta, but timedeltas can be meaningfully compared to zero).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @anntzer! Yeah, I added this line to not fail on datetime.timedelta

ax.errorbar(x, y, timedelta(days=0.5))

I've assumed that it's always positive. Let me see if there is a way to check that they are "meaningful compared to zero".

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I was thinking about np.timedelta, not datetime.timedelta, sorry for the careless reading (np.timedelta(...) > 0 works). Well, I guess my point remains: it would be nice to have the check also for datetime.timedelta inputs, but I don't know how easy that is.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@anntzer I've added a check for timedelta types. However, I'm a bit uncomfortable with checking only timedelta type. datetime.datetime handles only positive values -> should not be an issue. What about datetime.date?

you can add a datetime to a timedelta

Do you mean, that there is a way to convert anything to timedelta and check it? If yes, could you elaborate a bit more?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree the special-casing is a bit annoying, I don't have any good solution to offer right now though.

pass

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)
if isinstance(errorevery, tuple):
Expand Down
13 changes: 13 additions & 0 deletions lib/matplotlib/tests/test_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3513,6 +3513,19 @@ def test_errorbar_every_invalid():
ax.errorbar(x, y, yerr, errorevery='foobar')


def test_xerr_yerr_positive():
ax = plt.figure().subplots()

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=error_message):
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]])


@check_figures_equal()
def test_errorbar_every(fig_test, fig_ref):
x = np.linspace(0, 1, 15)
Expand Down