Matplotlib datetime xlabel issue

I see strange behavior in automatic x-axis marking for dates in matplotlib. When I issue the command:

from datetime import datetime as dt plot( [ dt(2013, 1, 1), dt(2013, 5, 17)], [ 1 , 1 ], linestyle='None', marker='.') 

I get a very reasonably labeled chart:

enter image description here

But if I increase the end date by 1 day:

 plot( [ dt(2013, 1, 1), dt(2013, 5, 18)], [ 1 , 1 ], linestyle='None', marker='.') 

I get this:

enter image description here

I reproduced this in several different ranges of calendar dates (in 2012), and each time the magic number of days needed to disable the error is about 140 (in this case 136/137). Does anyone know what is going on here? Is this a known bug, and if so, is there a workaround?

A few notes: in the above commands, I use IPython in -pylab mode to create graphs, but I first encountered this problem using matplotlib directly and it plays in the form of a script (ie I don’t think this is an IPython problem. Also , I observed this in both matplotlib 1.1.0 and 1.2.X.

UPDATE:

It looks like there is a window in which, if you move far enough forward, the labels begin to behave normally again. In the above example, the labels remain distorted from May 18 to May 31, but on June 1, the labels begin to display normally. Thus,

 (labels are garbled) plot( [ dt(2013, 1, 1), dt(2013, 5, 31)], [ 1 , 1 ], linestyle='None', marker='.') (labels are fine) plot( [ dt(2013, 1, 1), dt(2013, 6, 1)], [ 1 , 1 ], linestyle='None', marker='.') 
+8
python matplotlib datetime plot
source share
3 answers

It is counted by an error in AutoDateLocator . It seems that this error has not yet been sent to problem tracking.
It looks weird just because there are too many shortcuts and ticks applied.

When building with data with default dates, matplotlib uses matplotlib.dates.AutoDateLocator as the main locator. Namely, AutoDateLocator used to determine tick intervals and tick locations.

Suppose a data sequence is given [datetime(2013, 1, 1), datetime(2013, 5, 18)] .
The delta time is 4 months and 17 days. The delta of the month is 4, and the delta of the day is 4 * 31 + 17 = 141.

According to matplotlib docs :

class matplotlib.dates.AutoDateLocator (tz = None, minticks = 5, maxticks = None, interval_multiples = False)

minticks is the minimum number of desired ticks that is used to select the type of tick (annually, monthly, etc.).

maxticks - the maximum number of desired ticks that controls any interval between ticks (ticking each other, every 3, etc.). For really fine-grained control, it can be a dictionary comparing individual rrule frequency constants (YEARS, MONTHLY, etc.) with their own maximum number of ticks. This can be used to ensure that the number of ticks matches the format selected in the class: AutoDateFormatter. Any frequency not specified in this dictionary has a default value.

AutoDateLocator has an interval dictionary that displays the tick frequency (constant from dateutil.rrule) and several ticks allowed for this. The default value is as follows:

  self.intervald = { YEARLY : [1, 2, 4, 5, 10], MONTHLY : [1, 2, 3, 4, 6], DAILY : [1, 2, 3, 7, 14], HOURLY : [1, 2, 3, 4, 6, 12], MINUTELY: [1, 5, 10, 15, 30], SECONDLY: [1, 5, 10, 15, 30] } 

Interval is used to indicate multiple values ​​corresponding to the tick frequency. For example, every 7 days are reasonable for daily ticks, but within minutes / seconds 15 or 30 make sense. You can customize this dictionary by following these steps:

Since the delta of the month is 4, less than 5, and the delta of the day is 141, at least 5. The type of tick will be daily.
After determining the tick type, AutoDateLocator will use the interval dictionary and the maxticks dictionary to determine the tick interval.

When maxticks is None , AutoDateLocator uses its default maxticks dictionary. The documentation shows us the default interval dictionary and does not tell us what the default maxticks dictionary looks like.
We can find it in dates.py .

 self.maxticks = {YEARLY : 16, MONTHLY : 12, DAILY : 11, HOURLY : 16, MINUTELY : 11, SECONDLY : 11} 

Tick ​​Interval Algorithm

 # Find the first available interval that doesn't give too many ticks for interval in self.intervald[freq]: if num <= interval * (self.maxticks[freq] - 1): break else: # We went through the whole loop without breaking, default to 1 interval = 1 

Now the tick type is DAILY . So freq is DAILY , and num is 141, the delta of the day. The above code will be equivalent

 for interval in [1, 2, 3, 7, 14]: if 141 <= interval * (11 - 1): break else: interval = 1 

141 is too large. All daily intervals will produce too many ticks. else will execute and the tick interval will be set to 1.
This means that stickers and ticks will be + ++. We can expect the ugly x axis.

If the data sequence is specified [datetime(2013, 1, 1), datetime(2013, 5, 17)] , one day less. the daily delta is 140. Then AutoDateLocator will select 14 as the tick interval and only 10 marks will be applied. So your first chart looks fine.

Actually I don't understand why matplotlib chooses to set the interval to 1 if the maxticks constraint cannot be met. This will only result in a significantly larger number of ticks if the interval is 1. I prefer to use the longest interval.

OUTPUT:
For any sequence of days whose range is greater than or equal to 4 months and 18 days, and less than 5 months, AutoDateLocator will select 1 as the tick interval. You will see some ugly behavior on the x-axis or y-axis when constructing such a date sequence with a default default locator, namely AutoDateLocator .

DECISION:
The easiest solution is to increase your daily macchiques to 12. For example:

 import numpy as np import matplotlib.pyplot as plt from matplotlib.dates import DAILY from datetime import datetime ax = plt.subplot(111) plt.plot_date([datetime(2013, 1, 1), datetime(2013, 5, 31)], [datetime(2013, 1, 1), datetime(2013, 5, 10)]) loc = ax.xaxis.get_major_locator() loc.maxticks[DAILY] = 12 plt.show() 
+7
source share

It feels like a mistake; I'll take it to the matplotlib mailing list and see what people can say about it.

One workaround I can provide is this:

 from datetime import datetime as dt from matplotlib import pylab as pl fig = pl.figure() axes = fig.add_subplot(111) axes.plot( [ dt(2013, 1, 1), dt(2013, 5, 18)], [ 1 , 1 ], linestyle='None', marker='.') ticks = axes.get_xticks() n = len(ticks)//6 axes.set_xticks(ticks[::n]) fig.savefig('dateticks.png') 

Apologies for the OO approach (this is not what you did), but it greatly simplifies the binding of ticks to the plot. The number 6 is just the number of labels that I want on the x axis, and then reduce the actual number of ticks that matplotlib came to with the calculated n .

+1
source share

You need to reset locator.MAXTICKS to a larger number to avoid error: exceeds Locator.MAXTICKS * 2 (2000)

eg:

  alldays = DayLocator() # minor ticks on the days alldays.MAXTICKS = 2000 
0
source share

All Articles