Apply unit conversion early in errorbar().#19526
Conversation
698f065 to
cc2517c
Compare
|
Probably these changes should be repeated in |
|
Let's merge the two PRs as separate improvements and figure out how to reuse the logic in both cases later? I actually already have a followup patch for the 2D case that factors together the x and y handling, probably I can make it handle z too. |
lib/matplotlib/axes/_axes.py
Outdated
| x, y, xerr, yerr = self._process_unit_info( | ||
| [("x", x), ("y", y), ("x", xerr), ("y", yerr)], kwargs) |
There was a problem hiding this comment.
So now convert=True, but I seem to be missing where the old conversion was that would be removed by this change?
There was a problem hiding this comment.
I think we just used to propagate unitful data to Line2D and rely on Line2D being (the only artist) able to handle them.
|
This breaks mixed unit error bars like: x = np.arange(10)
y = [datetime(2020, 5, i * 2 + 1) for i in x]
plt.errorbar(x, y, timedelta(days=0.5)) |
Ooops, good catch! |
This allow using normal numpy constructs rather than manually looping and broadcasting. _process_unit_info was already special-handling `data is None` in a few places; the change here only handle the (theoretical) extra case where a custom unit converter would fail to properly pass None through.
cc2517c to
8cd22b4
Compare
|
Good catch, now fixed by using object arrays whenever the inputs are not already arrays (test included). |
|
I think this has broken |
|
I'll have a look. |
|
Looks like the following fixes the problem: diff --git i/lib/matplotlib/axes/_axes.py w/lib/matplotlib/axes/_axes.py
index e906dba6c8..e9e8c3ef25 100644
--- i/lib/matplotlib/axes/_axes.py
+++ w/lib/matplotlib/axes/_axes.py
@@ -3427,13 +3427,17 @@ class Axes(_AxesBase):
the note in the main docstring about this parameter's name.
"""
try:
- low, high = np.broadcast_to(err, (2, len(data)))
+ np.broadcast_to(err, (2, len(data)))
except ValueError:
raise ValueError(
f"'{name}err' (shape: {np.shape(err)}) must be a scalar "
f"or a 1D or (2, n) array-like whose shape matches "
f"'{name}' (shape: {np.shape(data)})") from None
- return data - low * ~lolims, data + high * ~uplims # low, high
+ # This is like
+ # low, high = np.broadcast_to(...)
+ # return data - low * ~lolims, data + high * ~uplims
+ # except that broadcast_to would strip units.
+ return data + np.row_stack([-(1 - lolims), 1 - uplims]) * err
if xerr is not None:
left, right = extract_err('x', xerr, x, xlolims, xuplims)but if you can help with writing a minimal test case to check that, that would be great :-) |
|
Here is a minimal test case from the broken import matplotlib.pyplot as plt
from astropy import units as u
from astropy.visualization.units import quantity_support
with quantity_support():
x = [1, 2, 3] * u.s
y = [1, 2, 3] * u.m
yerr = [3, 2, 1] * u.cm
fig, ax = plt.subplots()
ax.errorbar(x, y, yerr=yerr)
assert ax.xaxis.get_units() == u.s
assert ax.yaxis.get_units() == u.m
plt.show() # Only if you want to see it, not for CI |
|
p.s. Here is the actual implementation of |
|
@pllim I'm loving that astropy is testing against our default branch :) |

This allow using normal numpy constructs rather than manually looping
and broadcasting.
_process_unit_info was already special-handling
data is Nonein a fewplaces; the change here only handle the (theoretical) extra case where a
custom unit converter would fail to properly pass None through.
PR Summary
PR Checklist
pytestpasses).flake8on changed files to check).flake8-docstringsand runflake8 --docstring-convention=all).doc/users/next_whats_new/(follow instructions in README.rst there).doc/api/next_api_changes/(follow instructions in README.rst there).