Unidata / cftime

Time-handling functionality from netcdf4-python.
https://unidata.github.io/cftime
MIT License
75 stars 39 forks source link

matplotlib real_datetime incompatibility #112

Closed taobrienlbl closed 5 years ago

taobrienlbl commented 5 years ago

Matplotlib appears not to immediately recognize the real_datetime objects used by cftime as datetime objects, even though they are simply datetime objects with a couple extra properties.

The code below illustrates the issue:

This first block of code demonstrates that matplotlib deals with datetime objects for plotting.

>>> import cftime
>>> import pylab
>>> import datetime
>>> import numpy
>>> 
>>> # generate a set of dates
>>> hours = numpy.arange(0,24,dtype = int)
>>> dates = numpy.array( [ datetime.datetime(2018,2,15,h) for h in hours ] )
>>> 
>>> # generate a sine wave for plotting purposes
>>> sine = numpy.sin(hours*2*numpy.pi/24)
>>> 
>>> # plot the sine wave using the datetime objects as the x values
>>> pylab.plot(dates,sine)
  [<matplotlib.lines.Line2D at 0x2aaad2e69588>]
>>># run the datetime objects through cftime routines to produce `real_datetime` objects
>>> units =  "hours since 2018-02-15 00:00:00"
>>> hours_since = cftime.date2num(dates, units)
>>> dates_cftime = cftime.num2date(hours_since, units)
>>> 
>>> # attempt to plot using the real_datetime objects as the x values
>>> pylab.plot(dates_cftime, sine)
  ---------------------------------------------------------------------------

  TypeError                                 Traceback (most recent call last)

  <ipython-input-6-0587aadf3c86> in <module>()
        3 dates_cftime = cftime.num2date(hours_since, units)
        4 
  ----> 5 pylab.plot(dates_cftime, sine)

  ... (ommitting most of the traceback)

  TypeError: float() argument must be a string or a number, not 'real_datetime'

In the older netCDF4 versions of num2date(), the return value was a datetime object, so code like the above would work. The change to real_datetime objects appears to break this type of code.

One workaround, based on the matplotlib units API, is to explicitly tell matplotlib to treat real_datetime objects like datetime objects:

>>> # fix a plotting issue with netCDF4
>>> import matplotlib
>>> matplotlib.units.registry[cftime.real_datetime] = matplotlib.units.registry[datetime.datetime]
>>> 
>>> pylab.plot(dates_cftime, sine)
  [<matplotlib.lines.Line2D at 0x2aaadba09b00>]

Given what I understand about cftime's real_datetime objects, I'm not sure this is a good, general purpose workaround, since matplotlib likely assumes a proleptic gregorian calendar. I suspect a better solution would be for cftime to have a converter routine and to work with the matplotlib developers to automatically register the cftime converter routine if cftime is importable.

jswhit commented 5 years ago

nctime-axis should solve the problem (see issue #111)

taobrienlbl commented 5 years ago

Yes, that looks like exactly what is needed: thanks! And apologies for overlooking #111 when writing this issue.

Cheers--