Skip to content

ax.bar doesn't work correctly when width is a timedelta64 object #11290

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

Closed
astrofrog opened this issue May 22, 2018 · 9 comments · Fixed by #12903
Closed

ax.bar doesn't work correctly when width is a timedelta64 object #11290

astrofrog opened this issue May 22, 2018 · 9 comments · Fixed by #12903

Comments

@astrofrog
Copy link
Contributor

astrofrog commented May 22, 2018

The following example

In [1]: x = np.array(range(100), dtype='M8[D]')

In [2]: ax = plt.subplot(1,1,1)

In [3]: y = np.random.random(100)

In [4]: ax.bar(x, y)
Out[4]: <BarContainer object of 100 artists>

works fine, but if I set width to be the diff of the datetime64 array, which gives a timedelta64 array, things break:

In [7]: ax.bar(x[:-1], y[:-1], width=np.diff(x))
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-7-f05692d600cb> in <module>()
----> 1 ax.bar(x[:-1], y[:-1], width=np.diff(x))

~/miniconda3/envs/dev/lib/python3.6/site-packages/matplotlib/__init__.py in inner(ax, *args, **kwargs)
   1853                         "the Matplotlib list!)" % (label_namer, func.__name__),
   1854                         RuntimeWarning, stacklevel=2)
-> 1855             return func(ax, *args, **kwargs)
   1856 
   1857         inner.__doc__ = _add_data_doc(inner.__doc__,

~/miniconda3/envs/dev/lib/python3.6/site-packages/matplotlib/axes/_axes.py in bar(self, *args, **kwargs)
   2257         if align == 'center':
   2258             if orientation == 'vertical':
-> 2259                 left = x - width / 2
   2260                 bottom = y
   2261             elif orientation == 'horizontal':

TypeError: ufunc subtract cannot use operands with types dtype('float64') and dtype('<m8[D]')

This should however be unambiguous, so it would be nice if it worked.

@dstansby
Copy link
Member

Can you provide the full example with the timedelta64 array that produces that error? I've encountered similar looking problems before, that depend on when we do our unit conversions, and this might actually be fixed in the master branch.

@astrofrog
Copy link
Contributor Author

@dstansby - the full example is:

import numpy as np
import matplotlib.pyplot as plt
x = np.array(range(100), dtype='M8[D]')
ax = plt.subplot(1,1,1)
y = np.random.random(100)
ax.bar(x[:-1], y[:-1], width=np.diff(x))

@timhoffm
Copy link
Member

The problem is our unit conversion. We do

x = self.convert_xunits(x)
width = self.convert_xunits(width)

However, we don't have a conversion implemented for timedelta64. A result, x is our internal float-based date format while width is still a timedelta64, which cannot be subtracted from each other.

The solution would be to implement a converter for timedelta64.

@dstansby
Copy link
Member

Ah, so this would be fixed by something like #9120 I think.

@timhoffm
Copy link
Member

Sort of. It needs a bit more infrastructure:

  • implement a converter analogously to DateConverter
  • add it to matplotlib.units.registry

@dstansby
Copy link
Member

I'm having a look at this now, but having trouble with your example @astrofrog ; when I do

import numpy as np
x = np.array(range(100), dtype='M8[D]')
print((x[:-1] + np.diff(x) / 2.0) - x[:-1])

I just get a bunch of zeros; dividing np.diff(x) by a number seems to just zero it out. Do you know of a way to fix this? Internally Matplotlib has to divide the width by 2, which is why I can't get your example working.

@QuLogic
Copy link
Member

QuLogic commented Sep 18, 2018

'M8[D]' has a single day resolution, and your array goes up by one, so dividing by two rounds down to 0. You'd need to at least change it to hourly resolution.

@jklymak
Copy link
Member

jklymak commented Sep 18, 2018

@dstansby, not sure of the approach you are taking here, but I think as @timhoffm says, we should add timedelta64 to dates.py, and register it. Then all the math can be done in floating point numbers...

@dstansby
Copy link
Member

Trying to take that approach, but running into the problem that np.timedelta64 is a subclass of numbers.Number, so the conversion interface assumes it doesn't need converting:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.units as munits
x = np.array(range(100), dtype='M8[D]')
xdiff = np.diff(x)
print(xdiff.dtype)
print(munits.ConversionInterface.is_numlike(xdiff))

prints

timedelta64[D]
True

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants