-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
FIX: (broken)bar(h) math before units #12903
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2011,6 +2011,26 @@ def step(self, x, y, *args, where='pre', data=None, **kwargs): | |
kwargs['drawstyle'] = 'steps-' + where | ||
return self.plot(x, y, *args, data=data, **kwargs) | ||
|
||
@staticmethod | ||
def _convert_dx(dx, x0, x, convert): | ||
""" | ||
Small helper to do logic of width conversion flexibly. | ||
|
||
*dx* and *x0* have units, but *x* has already been converted | ||
to unitless. This allows the *dx* to have units that are | ||
different from *x0*, but are still accepted by the ``__add__`` | ||
operator of *x0*. | ||
""" | ||
try: | ||
# attempt to add the width to x0; this works for | ||
# datetime+timedelta, for instance | ||
dx = convert(x0 + dx) - x | ||
except (TypeError, AttributeError): | ||
# but doesn't work for 'string' + float, so just | ||
# see if the converter works on the float. | ||
dx = convert(dx) | ||
return dx | ||
|
||
@_preprocess_data() | ||
@docstring.dedent_interpd | ||
def bar(self, x, height, width=0.8, bottom=None, *, align="center", | ||
|
@@ -2172,23 +2192,25 @@ def bar(self, x, height, width=0.8, bottom=None, *, align="center", | |
else: | ||
raise ValueError('invalid orientation: %s' % orientation) | ||
|
||
x, height, width, y, linewidth = np.broadcast_arrays( | ||
# Make args iterable too. | ||
np.atleast_1d(x), height, width, y, linewidth) | ||
|
||
# lets do some conversions now since some types cannot be | ||
# subtracted uniformly | ||
if self.xaxis is not None: | ||
x0 = x | ||
x = self.convert_xunits(x) | ||
width = self.convert_xunits(width) | ||
width = self._convert_dx(width, x0, x, self.convert_xunits) | ||
if xerr is not None: | ||
xerr = self.convert_xunits(xerr) | ||
xerr = self._convert_dx(xerr, x0, x, self.convert_xunits) | ||
|
||
if self.yaxis is not None: | ||
y0 = y | ||
y = self.convert_yunits(y) | ||
height = self.convert_yunits(height) | ||
height = self._convert_dx(height, y0, y, self.convert_yunits) | ||
if yerr is not None: | ||
yerr = self.convert_yunits(yerr) | ||
|
||
x, height, width, y, linewidth = np.broadcast_arrays( | ||
# Make args iterable too. | ||
np.atleast_1d(x), height, width, y, linewidth) | ||
yerr = self._convert_dx(yerr, y0, y, self.convert_yunits) | ||
|
||
# Now that units have been converted, set the tick locations. | ||
if orientation == 'vertical': | ||
|
@@ -2465,10 +2487,19 @@ def broken_barh(self, xranges, yrange, **kwargs): | |
self._process_unit_info(xdata=xdata, | ||
ydata=ydata, | ||
kwargs=kwargs) | ||
xranges = self.convert_xunits(xranges) | ||
yrange = self.convert_yunits(yrange) | ||
|
||
col = mcoll.BrokenBarHCollection(xranges, yrange, **kwargs) | ||
xranges_conv = [] | ||
for xr in xranges: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
(Note that your current implementation will silently work with xranges items with more than 2 elements, silently dropping the extra ones.) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Even simpler with a list comprehension:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. doesnt work I think? convert_dx needs the converted x. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. True. Thanks for spotting the error. Either go back to using a loop, or change There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ahem, OK, reverted... @anntzer I don't see whats wrong w/ silently dropping extra stuff. Is that worse than throwing an explicit but mysterious error? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But then someone tries to directly build a BrokenBarHCollection with invalid data, and gets an unclear error message, and asks for validation to be implemented there again, and we end up with duplicated validation checks and error messages. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ... But we need the validation here, as you just pointed out above? But you want to do it implictly, by trying to unpack the tuple, which would also yield a ValueError, but more unhelpfully: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ah, fair enough There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. .... not sure what that is and google wasn’t particularly helpful. How would this get used in this case? |
||
if len(xr) != 2: | ||
raise ValueError('each range in xrange must be a sequence ' | ||
'with two elements (i.e. an Nx2 array)') | ||
# convert the absolute values, not the x and dx... | ||
x_conv = self.convert_xunits(xr[0]) | ||
x1 = self._convert_dx(xr[1], xr[0], x_conv, self.convert_xunits) | ||
xranges_conv.append((x_conv, x1)) | ||
|
||
yrange_conv = self.convert_yunits(yrange) | ||
|
||
col = mcoll.BrokenBarHCollection(xranges_conv, yrange_conv, **kwargs) | ||
self.add_collection(col, autolim=True) | ||
self.autoscale_view() | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this go below unit conversion? Otherwise I think units will be dropped if width is a list of unitized stuff?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this drops units.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will drop stuff like pint units or astropy units.
I remember some discussion a while ago which basically reached the conclusion that we cannot call any numpy function before unit handling (tbh I was rather unhappy about that, but the goal is not to relitigate this here), perhaps @dopplershift will remember this better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can someone provide an example? This works fine for all the categorical, unit, and date tests. If we are supporting something that doesn’t survive broadcasting we need to start testing it.
I’m not sure why we broadcast here at all anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AFAICS, broadcasting is used to support scalar parameters such as
plt.bar([1, 2, 3], [3,5,2], width=0.5)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the record,
broadcast_arrays
will drop pint units--it might possibly work with astropy since it's an ndarray subclass. Broadcasting itself works fine, it's calling functions that need to allocate new arrays that's the problem; there are currently no hooks to allow overriding what type to use when allocating an empty array.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed - sorry, should have responded here - I did move the broadcast after the convert...