Skip to content
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

Problems setting xticks #41

Closed
MartinDix opened this issue Dec 18, 2018 · 8 comments
Closed

Problems setting xticks #41

MartinDix opened this issue Dec 18, 2018 · 8 comments
Milestone

Comments

@MartinDix
Copy link

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.

@adamcpovey
Copy link

I have a similar problem. A minimal example is that,

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

x = [nc_time_axis.CalendarDateTime(cftime.datetime(y, 1, 1), "360_day")
     for y in range(2000, 2010)]
y = numpy.arange(10)
plt.scatter(x, y)
plt.show()

only works if numpy.array is wrapped around the definition of x.

I believe the solution is to put the following here,

        if isinstance(value, np.ndarray) or isinstance(value, list):

@bradyrx
Copy link

bradyrx commented Mar 16, 2020

I think I'm having a similar problem here. There is an incompatibility between nc-time-axis and native matplotlib datetime locators. See my issue at proplot-dev/proplot#126.

With a numpy datetime axis:

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

times = np.arange('1990', '2000', dtype='datetime64[M]')
data = xr.DataArray(
    np.random.rand(len(times)),
    dims=['time'],
    coords=[times]
)

f, ax = plt.subplots()

ax.plot(data.time, data)
ax.xaxis.set_major_locator(mdates.YearLocator(2))

Screen Shot 2020-03-16 at 11 17 40 AM

With a cftime axis:

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

times = xr.cftime_range('1990', '2000', freq='M')
data = xr.DataArray(
    np.random.rand(len(times)),
    dims=['time'],
    coords=[times]
)

f, ax = plt.subplots()

ax.plot(data.time, data)
ax.xaxis.set_major_locator(mdates.YearLocator(1))

Screen Shot 2020-03-16 at 11 18 05 AM

@bradyrx
Copy link

bradyrx commented Mar 16, 2020

@lukelbd, do you think this would be a relatively quick fix? This looks like a somewhat dead repo given how old this issue is. I am very unfamiliar with these locators. Would it just require a few lines of code in __init__? Any thoughts?

@lukelbd
Copy link

lukelbd commented Mar 16, 2020

This might be a pretty big PR unfortunately, but shouldn't be very conceptually complicated. You basically need to copy + modify YearLocator, MonthLocator, etc. in matplotlib/dates.py, and that section of code is ~300 lines long. You'd have to go through each line and convert the datetime64 syntax to nc-time-axis syntax -- e.g. wherever they get integer year from a datetime64 array, change that to how you'd get the integer year from a cftime array, etc.

If you have time go for it, but otherwise it might make more sense to convert your nc-time-axis times to datetime64 if possible, even if the calendars don't technically match (my understanding is the main difference between these two formats is that nc-time-axis can work with different calendar types?). Also if you don't get a response to a PR submitted to this repo, I can add your locators directly to proplot just like I add some fixes for currently active cartopy/matplotlib issues.

@lukelbd
Copy link

lukelbd commented Mar 16, 2020

Oh, now I see the specific issue at the top is different and possibly easier to fix.

The user was trying to manually set the tick locations by sending a cftime array to set_xticks, which results in matplotlib applying a FixedLocator as the axis locator. However, nc-time-axis evidently expects the axis locator to be its own special locator, and raises an error otherwise (at least this is what I suspect is happening; you should try to print(self.locator) in that example). I don't think there is an issue when using FixedLocator with datetime64 coordinates, so this appears to be an unfortunate limitation of nc-time-axis. Might be simple to fix or might be complicated, depends on how this was designed. But you'll need to play round.

Since this repo has had no commits for >1 year I'd be open to just adding the entire repo's codebase to proplot. Definitely fits within proplot's scope of easier plotting/xarray integration. But that's a long term goal... maybe this summer.

@spencerkclark
Copy link
Member

The issue in @MartinDix's initial comment is a symptom of the issue identified in #79 (comment): the ndays attribute -- now the resolution attribute -- cannot be computed without knowing the range of the axis, which is not known at the time the locator is constructed. In the case of setting the ticks it seems that matplotlib creates a new locator and so any axis range information is lost.

This is OK. I propose working around this in nc-time-axis in the following way:

  1. Set a default date format in NetCDFTimeDateFormatter if the locator does not have a resolution attribute. I would suggest "%Y-%m-%d" to be consistent with matplotlib. Note that when a user explicitly sets the ticks for a datetime axis, matplotlib also uses a default format.
  2. Create another formatter class that will let users set their own date formats to override this default format if they desire. Matplotlib does this with their DateFormatter class (see for example here). I think this would be a useful feature regardless.

To make the analogy with matplotlib clear, I might suggest renaming NetCDFTimeDateFormatter to AutoCFTimeFormatter -- possibly after a deprecation cycle, though I think it is unlikely anyone is using it outside nc-time-axis -- and naming the new formatter proposed in (2) CFTimeFormatter.

Does anyone in @SciTools/nc-time-axis-devs or this issue have any thoughts on this proposal?

@spencerkclark
Copy link
Member

See #84 for a proof of concept.

@spencerkclark spencerkclark added this to the v1.4.0 milestone Aug 16, 2021
@spencerkclark
Copy link
Member

The proposal in #41 (comment) has been implemented in #84. See the PR description for an example of using the new CFTimeFormatter.

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

No branches or pull requests

5 participants