Giter Club home page Giter Club logo

nc-time-axis's People

Contributors

aulemahal avatar bjlittle avatar dependabot[bot] avatar dpeterk avatar jbusecke avatar lbdreyer avatar marqh avatar mbeedie avatar ocefpaf avatar pelson avatar pre-commit-ci[bot] avatar rcomer avatar scitools-ci[bot] avatar spencerkclark avatar trexfeathers avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

nc-time-axis's Issues

New cftime 1.5.0 breaks nc-time-axis

Hi!

Looks like cftime 1.5.0 breaks nc-time-axis... I think it is due to Unidata/cftime#235, which removed cftime.utime...

With my use case, I get (end part of the traceback):

 ~/work/xclim/xclim/.tox/doctests/lib/python3.7/site-packages/nc_time_axis/__init__.py in convert(cls, value, unit, axis)
    286                                  '`cftime.datetime`.')
    287 
--> 288         ut = cftime.utime(cls.standard_unit, calendar=first_value.calendar)
    289 
    290         if isinstance(value, (CalendarDateTime, cftime.datetime)):

AttributeError: module 'cftime' has no attribute 'utime'

Issues with dates with non default unit

This is more of a usage question so apologies if this is the incorrect place to ask this. I'm trying to plot some xarray data but am running into issues with dates that have custom 'days since x' units, e.g. adapting the example from the README this code errors for me:

import random

import matplotlib.pyplot as plt
import xarray as xr
import nc_time_axis
import cftime

dates = xr.cftime_range(start='0001', periods=24, freq='MS', calendar='noleap')
c_d_time = [nc_time_axis.CalendarDateTime(item, "noleap") for item in dates]
temperatures = [round(random.uniform(0, 12), 3) for _ in range(len(c_d_time))]

plt.plot(c_d_time, temperatures)
plt.margins(0.1)
plt.ylim(0, 12)
plt.xlabel("Date")
plt.ylabel("Temperature")
plt.show()
ValueError: invalid year provided in cftime.DatetimeNoLeap(0, 10, 23, 2, 24, 0, 8, 3, 296)

Apologies if I've missed something obvious but I would appreciate a pointer on how to deal with dates like this.

`NetCDFDateTimeLocator.min_n_ticks` not used

๐Ÿ“ฐ Custom Issue

I'm adding full docstrings to the main objects in nc-time-axis and noticed that min_n_ticks, an optional argument for the NetCDFDateTimeLocator constructor, is set as an attribute, but never used. We may consider removing it.

Deprecate `CalendarDateTime`?

CalendarDateTime served a purpose in the early days of nc-time-axis, but I now think it may not be needed anymore, particularly once #75 is addressed (#80). cftime.datetime instances now have a meaningful calendar attribute and their comparisons now check that calendars and datetime components are equal. Should we include a DeprecationWarning in the next release of nc-time-axis?

As an aside, reading some of the existing tests for CalendarDateTime makes me a little uneasy now that cftime.datetime objects have a default calendar of "gregorian" instead of None:

def test_cftime_CalendarDateTime(self):
val = CalendarDateTime(cftime.datetime(2014, 8, 12), "365_day")
result = NetCDFTimeConverter().convert(val, None, None)
expected = 5333.0
assert result == expected
assert np.isscalar(result)

Apparently these still pass despite the fact that the calendar of the datetime object does not match the calendar of the CalendarDateTime object.

Enable plotting of now calendar-aware `cftime.datetime` objects

โœจ Feature Request

In cftime version 1.3.0 cftime.datetime objects are now calendar-aware. Eventually this will obviate the need for calendar-specific subclasses, like cftime.DatetimeNoLeap. nc-time-axis currently does not register cftime.datetime objects in matplotlib's units registry, so matplotlib does not attempt to convert them. It would be great if -- similar to the subclasses -- we could also directly plot the new calendar-aware base class using nc-time-axis.

Motivation

E.g. while we can currently do:

import random

import cftime
import matplotlib.pyplot as plt
import nc_time_axis

dt = [cftime.DatetimeNoLeap(2017, 2, day) for day in range(1, 28)]
temperatures = [round(random.uniform(0, 12), 3) for _ in range(len(dt))]
plt.plot(dt, temperatures)

It would be great if we could also do:

dt = [cftime.datetime(2017, 2, day, calendar="noleap") for day in range(1, 28)]
temperatures = [round(random.uniform(0, 12), 3) for _ in range(len(dt))]
plt.plot(dt, temperatures)

This currently results in an error, because matplotlib does not convert the dates.

`nc-time-axis` dates don't work with `plt.fill_between(...)`

๐Ÿ› Bug Report

Hi there,

I am trying to use the matplotlib fill_between graph type but this fails with the latest version of nc-time-axis

How to Reproduce

Steps to reproduce the behaviour:

  1. Follow example on nc-time-axis homepage for plt.plot but replace plt.plot(cdt, temperatures) with plt.fill_between(cdt, temperatures,0)

I get this error...

ValueError: The values must be numbers or instances of "nc_time_axis.CalendarDateTime" or "cftime.datetime".

... but the elements of the cdt array are of the type nc_time_axis.CalendarDateTime...

>>> type(cdt[0])

nc_time_axis.CalendarDateTime

Expected Behaviour

Like this example but having datetimes as the x-axis...

https://matplotlib.org/stable/gallery/pyplots/whats_new_98_4_fill_between.html#sphx-glr-gallery-pyplots-whats-new-98-4-fill-between-py

image

Environment

  • OS & Version:
> cat /etc/*-release
CentOS Linux release 7.4.1708 (Core)
Cluster Manager v8.0
slave
NAME="CentOS Linux"
VERSION="7 (Core)"
ID="centos"
ID_LIKE="rhel fedora"
VERSION_ID="7"
PRETTY_NAME="CentOS Linux 7 (Core)"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:centos:centos:7"
HOME_URL="https://www.centos.org/"
BUG_REPORT_URL="https://bugs.centos.org/"

CENTOS_MANTISBT_PROJECT="CentOS-7"
CENTOS_MANTISBT_PROJECT_VERSION="7"
REDHAT_SUPPORT_PRODUCT="centos"
REDHAT_SUPPORT_PRODUCT_VERSION="7"

CentOS Linux release 7.4.1708 (Core)
CentOS Linux release 7.4.1708 (Core)

  • nc-time-axis Version: [e.g., From the command line run python -c "import nc_time_axis; print(nc_time_axis.__version__)"]
> python -c "import nc_time_axis; print(nc_time_axis.__version__)"
1.3.1

Thanks in advance for any tips!

Additional Context

Click to expand this section...

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
/nesi/nobackup/niwa00013/williamsjh/miniconda3/envs/warming/lib/python3.9/site-packages/matplotlib/axis.py in convert_units(self, x)
   1499         try:
-> 1500             ret = self.converter.convert(x, self.units, self)
   1501         except Exception as e:

/nesi/nobackup/niwa00013/williamsjh/miniconda3/envs/warming/lib/python3.9/site-packages/nc_time_axis/__init__.py in convert(cls, value, unit, axis)
    348         if not isinstance(first_value, (CalendarDateTime, cftime.datetime)):
--> 349             raise ValueError(
    350                 "The values must be numbers or instances of "

ValueError: The values must be numbers or instances of "nc_time_axis.CalendarDateTime" or "cftime.datetime".

The above exception was the direct cause of the following exception:

ConversionError                           Traceback (most recent call last)
/tmp/ipykernel_13716/2307464752.py in <module>
     16 temperatures = [round(random.uniform(0, 12), 3) for _ in range(len(cdt))]
     17 
---> 18 plt.fill_between(cdt, temperatures,0)
     19 plt.margins(0.1)
     20 plt.ylim(0, 12)

/nesi/nobackup/niwa00013/williamsjh/miniconda3/envs/warming/lib/python3.9/site-packages/matplotlib/pyplot.py in fill_between(x, y1, y2, where, interpolate, step, data, **kwargs)
   2804         x, y1, y2=0, where=None, interpolate=False, step=None, *,
   2805         data=None, **kwargs):
-> 2806     return gca().fill_between(
   2807         x, y1, y2=y2, where=where, interpolate=interpolate, step=step,
   2808         **({"data": data} if data is not None else {}), **kwargs)

/nesi/nobackup/niwa00013/williamsjh/miniconda3/envs/warming/lib/python3.9/site-packages/matplotlib/__init__.py in inner(ax, data, *args, **kwargs)
   1359     def inner(ax, *args, data=None, **kwargs):
   1360         if data is None:
-> 1361             return func(ax, *map(sanitize_sequence, args), **kwargs)
   1362 
   1363         bound = new_sig.bind(ax, *args, **kwargs)

/nesi/nobackup/niwa00013/williamsjh/miniconda3/envs/warming/lib/python3.9/site-packages/matplotlib/axes/_axes.py in fill_between(self, x, y1, y2, where, interpolate, step, **kwargs)
   5384     def fill_between(self, x, y1, y2=0, where=None, interpolate=False,
   5385                      step=None, **kwargs):
-> 5386         return self._fill_between_x_or_y(
   5387             "x", x, y1, y2,
   5388             where=where, interpolate=interpolate, step=step, **kwargs)

/nesi/nobackup/niwa00013/williamsjh/miniconda3/envs/warming/lib/python3.9/site-packages/matplotlib/axes/_axes.py in _fill_between_x_or_y(self, ind_dir, ind, dep1, dep2, where, interpolate, step, **kwargs)
   5291         # Handle united data, such as dates
   5292         ind, dep1, dep2 = map(
-> 5293             ma.masked_invalid, self._process_unit_info(
   5294                 [(ind_dir, ind), (dep_dir, dep1), (dep_dir, dep2)], kwargs))
   5295 

/nesi/nobackup/niwa00013/williamsjh/miniconda3/envs/warming/lib/python3.9/site-packages/matplotlib/axes/_base.py in _process_unit_info(self, datasets, kwargs, convert)
   2357                     if dataset_axis_name == axis_name and data is not None:
   2358                         axis.update_units(data)
-> 2359         return [axis_map[axis_name].convert_units(data) if convert else data
   2360                 for axis_name, data in datasets]
   2361 

/nesi/nobackup/niwa00013/williamsjh/miniconda3/envs/warming/lib/python3.9/site-packages/matplotlib/axes/_base.py in <listcomp>(.0)
   2357                     if dataset_axis_name == axis_name and data is not None:
   2358                         axis.update_units(data)
-> 2359         return [axis_map[axis_name].convert_units(data) if convert else data
   2360                 for axis_name, data in datasets]
   2361 

/nesi/nobackup/niwa00013/williamsjh/miniconda3/envs/warming/lib/python3.9/site-packages/matplotlib/axis.py in convert_units(self, x)
   1500             ret = self.converter.convert(x, self.units, self)
   1501         except Exception as e:
-> 1502             raise munits.ConversionError('Failed to convert value(s) to axis '
   1503                                          f'units: {x!r}') from e
   1504         return ret

ConversionError: Failed to convert value(s) to axis units: [<CalendarDateTime: datetime=2017-02-01 00:00:00, calendar={self.calendar}>, <CalendarDateTime: datetime=2017-02-02 00:00:00, calendar={self.calendar}>, <CalendarDateTime: datetime=2017-02-03 00:00:00, calendar={self.calendar}>, <CalendarDateTime: datetime=2017-02-04 00:00:00, calendar={self.calendar}>, <CalendarDateTime: datetime=2017-02-05 00:00:00, calendar={self.calendar}>, <CalendarDateTime: datetime=2017-02-06 00:00:00, calendar={self.calendar}>, <CalendarDateTime: datetime=2017-02-07 00:00:00, calendar={self.calendar}>, <CalendarDateTime: datetime=2017-02-08 00:00:00, calendar={self.calendar}>, <CalendarDateTime: datetime=2017-02-09 00:00:00, calendar={self.calendar}>, <CalendarDateTime: datetime=2017-02-10 00:00:00, calendar={self.calendar}>, <CalendarDateTime: datetime=2017-02-11 00:00:00, calendar={self.calendar}>, <CalendarDateTime: datetime=2017-02-12 00:00:00, calendar={self.calendar}>, <CalendarDateTime: datetime=2017-02-13 00:00:00, calendar={self.calendar}>, <CalendarDateTime: datetime=2017-02-14 00:00:00, calendar={self.calendar}>, <CalendarDateTime: datetime=2017-02-15 00:00:00, calendar={self.calendar}>, <CalendarDateTime: datetime=2017-02-16 00:00:00, calendar={self.calendar}>, <CalendarDateTime: datetime=2017-02-17 00:00:00, calendar={self.calendar}>, <CalendarDateTime: datetime=2017-02-18 00:00:00, calendar={self.calendar}>, <CalendarDateTime: datetime=2017-02-19 00:00:00, calendar={self.calendar}>, <CalendarDateTime: datetime=2017-02-20 00:00:00, calendar={self.calendar}>, <CalendarDateTime: datetime=2017-02-21 00:00:00, calendar={self.calendar}>, <CalendarDateTime: datetime=2017-02-22 00:00:00, calendar={self.calendar}>, <CalendarDateTime: datetime=2017-02-23 00:00:00, calendar={self.calendar}>, <CalendarDateTime: datetime=2017-02-24 00:00:00, calendar={self.calendar}>, <CalendarDateTime: datetime=2017-02-25 00:00:00, calendar={self.calendar}>, <CalendarDateTime: datetime=2017-02-26 00:00:00, calendar={self.calendar}>, <CalendarDateTime: datetime=2017-02-27 00:00:00, calendar={self.calendar}>, <CalendarDateTime: datetime=2017-02-28 00:00:00, calendar={self.calendar}>, <CalendarDateTime: datetime=2017-02-29 00:00:00, calendar={self.calendar}>, <CalendarDateTime: datetime=2017-02-30 00:00:00, calendar={self.calendar}>]


Update for cftime

netcdftime has now moved to cftime. We should update our dependency.

New minor release?

Would it be possible to release a new minor version to pypi/conda-forge which includes #50? I would love to have this feature in my normal production environments.

Happy to help with this too!

Issues with year 0 when plotting cftime with gregorian calendar

I just ran into the problem I encountered over at xarray/#3841.

The basic problem is that if one plots a timeseries with cftime dates close to year 0 with the gregorian calendar, the NetCDFTimeDateLocator seems to create a date that is crossing year 0, which is not defined.

I noticed I never actually opened an issue here, so here it is with a bit simpler example to reproduce the error:

import numpy as np
import matplotlib.pyplot as plt
import nc_time_axis
import cftime

d_time = [cftime.DatetimeGregorian(year=year, month=1, day=1) for year in range(100, 131)]
temperatures = np.random.rand(len(d_time))

plt.plot(d_time, temperatures)
plt.show()
# this works

when I move the years closer to 0:

d_time = [cftime.DatetimeGregorian(year=year, month=1, day=1) for year in range(1, 31)]
temperatures = np.random.rand(len(d_time))

plt.plot(d_time, temperatures)
plt.show()
# this gives the zero error.

I get the following error:


ValueError Traceback (most recent call last)
in
3
4 plt.plot(d_time, temperatures)
----> 5 plt.show()
6 # this gives the zero error.

/scratch/gpfs2/jbusecke/conda_tigressdata/envs/euc_dynamics/lib/python3.8/site-packages/matplotlib/pyplot.py in show(*args, **kw)
270 """
271 global _show
--> 272 return _show(*args, **kw)
273
274

/scratch/gpfs2/jbusecke/conda_tigressdata/envs/euc_dynamics/lib/python3.8/site-packages/ipykernel/pylab/backend_inline.py in show(close, block)
41 display(
42 figure_manager.canvas.figure,
---> 43 metadata=_fetch_figure_metadata(figure_manager.canvas.figure)
44 )
45 finally:

/scratch/gpfs2/jbusecke/conda_tigressdata/envs/euc_dynamics/lib/python3.8/site-packages/ipykernel/pylab/backend_inline.py in _fetch_figure_metadata(fig)
178 if _is_transparent(fig.get_facecolor()):
179 # the background is transparent
--> 180 ticksLight = _is_light([label.get_color()
181 for axes in fig.axes
182 for axis in (axes.xaxis, axes.yaxis)

/scratch/gpfs2/jbusecke/conda_tigressdata/envs/euc_dynamics/lib/python3.8/site-packages/ipykernel/pylab/backend_inline.py in (.0)
181 for axes in fig.axes
182 for axis in (axes.xaxis, axes.yaxis)
--> 183 for label in axis.get_ticklabels()])
184 if ticksLight.size and (ticksLight == ticksLight[0]).all():
185 # there are one or more tick labels, all with the same lightness

/scratch/gpfs2/jbusecke/conda_tigressdata/envs/euc_dynamics/lib/python3.8/site-packages/matplotlib/axis.py in get_ticklabels(self, minor, which)
1318 if minor:
1319 return self.get_minorticklabels()
-> 1320 return self.get_majorticklabels()
1321
1322 def get_majorticklines(self):

/scratch/gpfs2/jbusecke/conda_tigressdata/envs/euc_dynamics/lib/python3.8/site-packages/matplotlib/axis.py in get_majorticklabels(self)
1274 def get_majorticklabels(self):
1275 'Return a list of Text instances for the major ticklabels.'
-> 1276 ticks = self.get_major_ticks()
1277 labels1 = [tick.label1 for tick in ticks if tick.label1.get_visible()]
1278 labels2 = [tick.label2 for tick in ticks if tick.label2.get_visible()]

/scratch/gpfs2/jbusecke/conda_tigressdata/envs/euc_dynamics/lib/python3.8/site-packages/matplotlib/axis.py in get_major_ticks(self, numticks)
1429 'Get the tick instances; grow as necessary.'
1430 if numticks is None:
-> 1431 numticks = len(self.get_majorticklocs())
1432
1433 while len(self.majorTicks) < numticks:

/scratch/gpfs2/jbusecke/conda_tigressdata/envs/euc_dynamics/lib/python3.8/site-packages/matplotlib/axis.py in get_majorticklocs(self)
1346 def get_majorticklocs(self):
1347 """Get the array of major tick locations in data coordinates."""
-> 1348 return self.major.locator()
1349
1350 def get_minorticklocs(self):

/scratch/gpfs2/jbusecke/conda_tigressdata/envs/euc_dynamics/lib/python3.8/site-packages/nc_time_axis/init.py in call(self)
136 def call(self):
137 vmin, vmax = self.axis.get_view_interval()
--> 138 return self.tick_values(vmin, vmax)
139
140 def tick_values(self, vmin, vmax):

/scratch/gpfs2/jbusecke/conda_tigressdata/envs/euc_dynamics/lib/python3.8/site-packages/nc_time_axis/init.py in tick_values(self, vmin, vmax)
192 raise ValueError(msg)
193
--> 194 return utime.date2num(ticks)
195
196

cftime/_cftime.pyx in cftime._cftime.utime.date2num()

cftime/_cftime.pyx in cftime._cftime.JulianDayFromDate()

cftime/_cftime.pyx in cftime._cftime._IntJulianDayFromDate()

ValueError: year zero does not exist in the standard calendar

Id be happy to submit a PR, but I would need some pointers on how to approach this problem best.

My thinking was to check the generated vmin and vmax values here and set anything that is < 1 equal to 1?

But perhaps a solution should be more general, and also allow all negative values? Do we need to make this dependent on the calendar, e.g. do some calendars allow a zero crossing?

Add an example of a(n horribly) wrong plot when NOT using nc-time-axis

๐Ÿ“š Documentation

This is a nice and useful extension of cftime and matplolib!

We spent some time this afternoon with @oliviermarti trying to understand why a time series based on data using a noleap calendar was plotted (seemingly) correctly with default settings, but was shifted (30 years in our case) when we tried to format the tick labels with set_major_formatter, following the appropriate examples in the matplotlib gallery. We were lucky to spot this, because matplotlib was not generating any error! I guess matplotlib was happy enough to handle cftime data as if it was regular datetime data

The question now is how to tell our climate data users that they (or most likely their unsuspecting interns) will get horribly wrong plots if they rely on matplolib defaults...

What could work would be to display side by side a matplotlib plot using some dummy data with a cftime+noleap time axis, and a correct plot using nc-time-axis, if somebody can come up with a simple example (I'm not volunteering!)

nc-time-axis==1.3.1 impossible to install with scitools-iris==3.0.2

๐Ÿ“ฐ Custom Issue

hey @bjlittle I am trying to install nc-time-axis the latest version, 1.3.1, since I found an issue in ESMValTool related to it (and we've not had it before, so I am fairly sure it's due to matplotlib 3.4.2 not liking the older nc-time-axis=1.2.0) but nc-time-axis 1.3.1 requires cftime>=1.5 whereas iris=3.0.2 requires cftime<1.3 so I'm shtuck proper ๐Ÿ˜

Not converting values when passed a list

It appears that some matplotlib method pass lists of coordinates to the converting methods. Such an example is plt.axhspan, that passes [xmin, xmax] to ax.convert_xunits and, thus, down to nc_time_axis.NetCDFTimeConverter.convert. However, the latter tests for np.ndarray but not for lists, so it raises a ValueError.

MWE:

import cftime
import matplotlib.pyplot as plt

x1 = cftime.DatetimeNoLeap(1990, 1, 1)
x2 = cftime.DatetimeNoLeap(1991, 1, 1)
x3 = cftime.DatetimeNoLeap(1992, 1, 1)

plt.plot([x1, x2, x3], [1, 2, 1])
plt.axvspan(x1, x2, color='red', alpha=0.5)

Expected:
image

Result:

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
~/.conda/envs/xclim-dev/lib/python3.8/site-packages/matplotlib/axis.py in convert_units(self, x)
   1519         try:
-> 1520             ret = self.converter.convert(x, self.units, self)
   1521         except Exception as e:

~/.conda/envs/xclim-dev/lib/python3.8/site-packages/nc_time_axis/__init__.py in convert(cls, value, unit, axis)
    277         if not isinstance(first_value, (CalendarDateTime, cftime.datetime)):
--> 278             raise ValueError('The values must be numbers or instances of '
    279                              '"nc_time_axis.CalendarDateTime" or '

ValueError: The values must be numbers or instances of "nc_time_axis.CalendarDateTime" or "cftime.datetime".

The above exception was the direct cause of the following exception:

ConversionError                           Traceback (most recent call last)
<ipython-input-47-fe6cec84569d> in <module>
      4 
      5 plt.plot([x1, x2, x3], [1, 2, 1])
----> 6 plt.axvspan(x1, x2, color='red', alpha=0.5)

~/.conda/envs/xclim-dev/lib/python3.8/site-packages/matplotlib/pyplot.py in axvspan(xmin, xmax, ymin, ymax, **kwargs)
   2460 @_copy_docstring_and_deprecators(Axes.axvspan)
   2461 def axvspan(xmin, xmax, ymin=0, ymax=1, **kwargs):
-> 2462     return gca().axvspan(xmin, xmax, ymin=ymin, ymax=ymax, **kwargs)
   2463 
   2464 

~/.conda/envs/xclim-dev/lib/python3.8/site-packages/matplotlib/axes/_axes.py in axvspan(self, xmin, xmax, ymin, ymax, **kwargs)
   1105 
   1106         # first we need to strip away the units
-> 1107         xmin, xmax = self.convert_xunits([xmin, xmax])
   1108         ymin, ymax = self.convert_yunits([ymin, ymax])
   1109 

~/.conda/envs/xclim-dev/lib/python3.8/site-packages/matplotlib/artist.py in convert_xunits(self, x)
    173         if ax is None or ax.xaxis is None:
    174             return x
--> 175         return ax.xaxis.convert_units(x)
    176 
    177     def convert_yunits(self, y):

~/.conda/envs/xclim-dev/lib/python3.8/site-packages/matplotlib/axis.py in convert_units(self, x)
   1520             ret = self.converter.convert(x, self.units, self)
   1521         except Exception as e:
-> 1522             raise munits.ConversionError('Failed to convert value(s) to axis '
   1523                                          f'units: {x!r}') from e
   1524         return ret

ConversionError: Failed to convert value(s) to axis units: [cftime.DatetimeNoLeap(1990, 1, 1, 0, 0, 0, 0), cftime.DatetimeNoLeap(1991, 1, 1, 0, 0, 0, 0)]

I hacked this into nc-time-axis (NetCDFTimeConverter.convert):

if isinstance(value, (list, tuple)):
    first_value = value

to make it work. However, I am not an expert of matplotlib and I have no idea if this could be dangerous or if it is appropriate...

Problems setting xticks

Consider the following modification of the basic example

import random

import matplotlib.pyplot as plt
import nc_time_axis
import cftime, numpy as np

calendar = "360_day"

d_time = [cftime.datetime(y,1,1,0,0,0) for y in range(1850, 2001)]
c_d_time = [nc_time_axis.CalendarDateTime(item, calendar) for item in d_time]
temperatures = [round(random.uniform(0, 12), 3) for _ in range(len(c_d_time))]

t0 = nc_time_axis.CalendarDateTime(cftime.datetime(1850,1,1,0,0,0),calendar)
t1 = nc_time_axis.CalendarDateTime(cftime.datetime(2000,1,1,0,0,0),calendar)

plt.plot(c_d_time, temperatures)
plt.margins(0.1)
plt.ylim(0, 12)
plt.xlim(t0,t1)
plt.xlabel("Year")
plt.ylabel("Temperature")
plt.show()

This works, but the tick marks are slightly oddly located. If I try to add ticks with

ticks = [nc_time_axis.CalendarDateTime(cftime.datetime(y,1,1,0,0,0),calendar) for y in range(1850,2001,25)]
plt.xticks(ticks)

I get an error

  File "/.../miniconda3/lib/python3.6/site-packages/nc_time_axis/__init__.py", line 270, in convert
    raise ValueError('The values must be numbers or instances of '
ValueError: The values must be numbers or instances of "nc_time_axis.CalendarDateTime".

This seems to be because convert checks whether its argument is a numpy array but doesn't handle lists properly.

Using

ticks = nparray([nc_time_axis.CalendarDateTime(cftime.datetime(y,1,1,0,0,0),calendar) for y in range(1850,2001,25)])
plt.xticks(ticks)

fails with

  File "/.../miniconda3/lib/python3.6/site-packages/nc_time_axis/__init__.py", line 83, in __call__
    format_string = self.pick_format(ndays=self.locator.ndays)
AttributeError: 'NetCDFTimeDateLocator' object has no attribute 'ndays'

It seems that ndays is needed for working out the formatting of tick labels but isn't set when using explicit locations.

At the moment I'm using an ugly workaround that gets the locator and sets the ndays attribute. E.g,

import random

import matplotlib.pyplot as plt
import nc_time_axis
import cftime, numpy as np

calendar = "360_day"

d_time = [cftime.datetime(y,1,1,0,0,0) for y in range(1850, 2001)]
c_d_time = [nc_time_axis.CalendarDateTime(item, calendar) for item in d_time]
temperatures = [round(random.uniform(0, 12), 3) for _ in range(len(c_d_time))]

t0 = nc_time_axis.CalendarDateTime(cftime.datetime(1850,1,1,0,0,0),calendar)
t1 = nc_time_axis.CalendarDateTime(cftime.datetime(2000,1,1,0,0,0),calendar)
ticks = np.array([nc_time_axis.CalendarDateTime(cftime.datetime(y,1,1,0,0,0),calendar) for y in range(1850,2001,25)])

plt.plot(c_d_time, temperatures)
plt.margins(0.1)
plt.ylim(0, 12)
plt.xlim(t0,t1)
axes = plt.gca()
locator = axes.xaxis.get_major_locator()
locator.ndays = 365*150
plt.xticks(ticks)
plt.xlabel("Year")
plt.ylabel("Temperature")
plt.show()

Versions are matplotlib 3.0.2, cftime 1.0.3.4, nc_time_axis 1.1.0.

Use netcdf4 version 1.2.4

Currently nc-time-axis is using netcdf version 1.0.2. This was chosen as that is what the users have access to. The latest version of netcdf4 (1.2.4) has had some api changes that will need to be taken care of before we could bump up to the latest version of netcdf4.

Request for contributor ORCIDs

๐Ÿ“ฐ Custom Issue

Dear awesome nc-time-axis contributors!

nc-time-axis is now citable and contains a CITATION.cff file.

If you'd like to be recognised as a citable contributor then please share your ORCID details in a comment in this issue, and I'll add them in batch to the CITATION.cff. Alternatively, if you want to raise a pull-request yourself instead, then go for it... whatever you prefer ๐Ÿ‘

If you don't want to add your details, then that's also totally fine too.

Regardless, thanks for caring and contributing to nc-time-axis ๐Ÿป

Hopefully, I've not missed anyone from the list:

Use of 'matplotlib<2' not what you think

We have matplotlib<2 in our requirements.txt, which is being used by conda.
Turns out, that doesn't do exactly what you'd expect...

conda create -n nc_time_axis pip 'matplotlib<2' netcdf4
Fetching package metadata ...........
Solving package specifications: .

Package plan for installation in environment /Users/pelson/miniconda/envs/nc_time_axis:

The following NEW packages will be INSTALLED:

    ca-certificates: 2017.7.27.1-0        conda-forge
    certifi:         2017.7.27.1-py36_0   conda-forge
    curl:            7.54.1-0             conda-forge
    cycler:          0.10.0-py36_0        conda-forge
    freetype:        2.6.3-1              conda-forge
    hdf4:            4.2.12-0             conda-forge
    hdf5:            1.8.18-0             conda-forge
    jpeg:            9b-0                 conda-forge
    krb5:            1.14.2-0             conda-forge
    libgfortran:     3.0.0-0              conda-forge
    libnetcdf:       4.4.1.1-5            conda-forge
    libpng:          1.6.28-0             conda-forge
    libssh2:         1.8.0-1              conda-forge
    matplotlib:      2.0.0rc2-np112py36_2 conda-forge
    mkl:             2017.0.3-0           defaults   
    ncurses:         5.9-10               conda-forge
    netcdf4:         1.2.9-py36_1         conda-forge
    numpy:           1.12.1-py36_0        defaults   

Notice how a version before 2.0.0 has been selected, it's just that it is a 2.0 release candidate. I attribute this to be a bug in conda, but for now, we need to be more explicit and replace 'matplotlib<2' with 'matplotlib 1*'.

Harmonize tick-resolution-choosing and tick-format-choosing logic

The code that chooses the resolution of the ticks and the code that chooses the format of the ticks are sometimes not consistent with each other. This can lead to confusing axis tick labels, e.g. in the following example provided by @klindsay28:

import cftime
import matplotlib.pyplot as plt
import nc_time_axis

time_vals = [cftime.DatetimeNoLeap(1+year, 1+month, 15)
             for year in range(3) for month in range(12)]

x_vals = [time_val.year + time_val.dayofyr / 365.0
          for time_val in time_vals]

fig, ax = plt.subplots(1, 1)
ax.plot(time_vals, x_vals, "-o")

image

In this case a monthly resolution is chosen for the ticks, but a tick format of "%Y" is used, giving the false impression that the ticks represent the beginning of years. I would expect for monthly resolution ticks, a format of "%Y-%m" would be used. In this example this would result in a plot that looked like this:

updated

If possible it would be great if we could make sure that inconsistencies like the above example could be avoided.

xref: pydata/xarray#4401

New developer

I've just added @ocefpaf to the nc-time-axis developers. We all know he is consistently awesome ๐Ÿ‘ (e.g. #30), and is a huge asset to the developer team.

cc @SciTools/nc-time-axis-devs

Switch internal time units to microseconds

๐Ÿ“ฐ Custom Issue

Historically the time units used in nc-time-axis have been "days since 2000-01-01". cftime.num2date and cftime.date2num are exact as of cftime version 1.2.1 if units of microseconds are used, since all arithmetic can be done with integers. Therefore it might be beneficial to switch to using microseconds internally within nc-time-axis. Errors are very small if we do not use microseconds, but we might as well be as exact as possible.

Improve the way NetCDFTimeConverter.convert handles scalars

Currently NetCDFTimeConverter.convert promotes scalar CalendarDateTime or cftime.datetime objects to arrays of floats:

>>> import cftime
>>> from nc_time_axis import NetCDFTimeConverter
>>> NetCDFTimeConverter.convert(cftime.DatetimeNoLeap(2010, 1, 1), None, None)
array([3650.])

Within matplotlib, this can lead to unusual assignment behavior, which will eventually be deprecated in NumPy (numpy/numpy#16943):

$ python -Wd
>>> import cftime
>>> import matplotlib.pyplot as plt
>>> import nc_time_axis
>>> times = [cftime.DatetimeNoLeap(2000, 1, 1), cftime.DatetimeNoLeap(2000, 1, 2), cftime.DatetimeNoLeap(2000, 1, 3)]
>>> data = range(3)
>>> plt.plot(times, data)
.../matplotlib/transforms.py:943: DeprecationWarning: setting an array element with a sequence. This was supported in some cases where the elements are arrays with a single element. For example `np.array([1, np.array([2])], dtype=int)`. In the future this will raise the same ValueError as `np.array([1, [2]], dtype=int)`.
  self._points[:, 0] = interval
.../nc_time_axis/__init__.py:266: DeprecationWarning: `np.object` is a deprecated alias for the builtin `object`. Use `object` by itself, which is identical in behavior, to silence this warning. If you specifically wanted the numpy scalar type, use `np.object_` here.
  if value.dtype != np.object:
[<matplotlib.lines.Line2D object at 0x7fc550278c40>]

In the above example, self._points is np.array([[0., 0.], [1., 1.]]) and interval is (np.array([0.]), np.array([3650.])). If the converter handled scalars without promoting them to arrays, interval would be (0., 3650.), and the assignment would proceed without warning.

xref: pydata/xarray#4265

Implement a cftime-compatible version of matplotlib's `ConciseDateFormatter`

โœจ Feature Request

A few years ago, matplotlib introduced a date formatter that helps to display date information in a more concise manner. It does so by minimizing the amount of duplicate information displayed in the tick labels through the axis offset label. See this example taken from their documentation:

It would be cool if nc-time-axis supported something similar.

Motivation

Datetime tick labels can often take up a lot of space. This feature in matplotlib does a nice job of minimizing that without losing information.

Update requirements

I think nc-time-axis now requires cftime >= 1.5 (#69). It would be good if that was recorded in the main branch of the repo.

Cannot import nc_time_axis (v1.2.0, python v3.8.5, conda environment)

(This may also be a problem with conda rather than nc-time-axis, but I'm posting here as it is the only package for which this problem occurs.)

I have nc-time-axis installed (v1.2.0, build: py_1, channel: conda-forge), mainly to use it with xarray.
However, running import nc_time_axis results in ModuleNotFoundError: No module named 'nc_time_axis'.

The conda environment in which nc-time-axis is installed is activated when this error occurs, and has python v3.8.5. OS: linux (hpc)

Is this likely to be a problem with nc-time-axis?

how to best set lim? // allow string in `convert`?

โœจ Feature Request

Should/ could allow passing a string to convert (of the NetCDFTimeConverter) be beneficial?

Motivation

I want to set the xlim of a nc-time axis. Is there a more convinent way than to pass cftime._cftime.datetime?

It seems I need to do:

import numpy as np
import matplotlib.pyplot as plt
import xarray as xr

data = np.random.randn(100)
time = xr.date_range("2000", "2100", freq="A", calendar="noleap")
da = xr.DataArray(data, coords={"time": time})

f, ax = plt.subplots()

da.plot(ax=ax)
ax.set_xlim(None, da.time.sel(time="2050").item())

It would be nice if we could pass a string - similar as we do with sel in xarray, e.g.:

ax.set_xlim(None, "2050")

Additional context

Click to expand this section...
Please add additional verbose information in this section e.g., references, screenshots, listings etc

Control max number of ticks

Currently, the max number of ticks is set to 4 (on this line). This is a bit restrictive.

It would be nice if the user had control over the number of ticks, perhaps through the use of some kind of set_max_ticks method.

small change to tests needed for upcoming cftime 1.3.0 release

PR #51 will ensure the tests pass with cftime >= 1.3.0.

cftime 1.3.0 will allow vanilla cftime.datetime instances to be calendar-specific using the calendar __init__ kwarg (the calendar specific sub-classes will no longer be required to instantiate 'calendar-aware' instances).

Documentation

๐Ÿ“š Documentation

The nc-time-axis project needs some basic documentation on readthedocs to help the community understand what the package is and how to use it.

Reboot nc-time-axis

๐Ÿ“ฐ Custom Issue

Breath some life back into nc-time-axis.

To-do checklist:

  • Enable GH discussions
    • Create Releases GH discussions category
    • Create 1.3.0 release discussion
  • Add Issue templates
  • Add PR template
  • Create 1.3.0 release milestone
  • Create 1.3.0 project board
  • Update labels
  • Configure repo settings
    • Allow squash merging only
    • Require conversation resolution before merging
    • Require linear history
  • Migrate CI from travis-ci to cirrus-ci
    • Linux container testing for py37, py38, py39, coverage only for py39
    • Add linting CI for black, flake8, and isort
  • Adopt black
  • Adopt flake8
  • Adopt isort
  • Adopt setuptools-scm and drop versioneer
  • Adopt pre-commit
  • Migrate setup.py to setup.cfg
  • Rationalise package dependencies for conda and PyPI
  • Adopt pytest
  • README.rst to README.md
  • Add badges
    • will add cirrus-ci, codecov and pre-commit post merge in separate pr
  • Drop Python2
    • Decide on the minimum Python3 version
      • Going with py37, py38, and py39

investigate use of matplotlib.use("agg")

๐Ÿ“ฐ Custom Issue

The context of this issue is from #66 and this comment raise by @mathause.

Investigate whether it is safe to remove the use of matplotlib.use("agg") from nc-time-axis with a modern version of matplotlib across the tested versions of Python.

Add citation information?

I am trying to cite this amazing tool in a paper revision and noticed this does not have any citation info?
Would it be possible to mint a DOI via e.g. zenodo?
That way I could cite it properly in the final version of the paper.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.