I\'m seeing some strange behavior in the x-axis auto-labeling for dates in matplotlib. When I issue the command:
from datetime import datetime as dt
plot( [ dt(2
You need to reset locator.MAXTICKS to a larger number to avoid error: exceeds Locator.MAXTICKS * 2 (2000)
for example:
alldays = DayLocator() # minor ticks on the days
alldays.MAXTICKS = 2000
It cauesed by a bug in AutoDateLocator
. It seems this bug has not been reported to the issue tracker yet.
It looks weird only because too many labels and ticks have been plotted.
When plotting with data with dates, by default, matplotlib uses matplotlib.dates.AutoDateLocator
as the major locator. Namely, AutoDateLocator
is used to determine the tick interval and tick locations.
Suppose, the data sequence is given by [datetime(2013, 1, 1), datetime(2013, 5, 18)]
.
The time delta is 4 months and 17 days. The month delta is 4, and the day delta 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 ticks desired, which is used to select the type of ticking (yearly, monthly, etc.).
maxticks is the maximum number of ticks desired, which controls any interval between ticks (ticking every other, every 3, etc.). For really fine-grained control, this can be a dictionary mapping individual rrule frequency constants (YEARLY, MONTHLY, etc.) to their own maximum number of ticks. This can be used to keep the number of ticks appropriate to the format chosen in class:AutoDateFormatter. Any frequency not specified in this dictionary is given a default value.
The AutoDateLocator has an interval dictionary that maps the frequency of the tick (a constant from dateutil.rrule) and a multiple allowed for that ticking. The default looks like this:
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] }
The interval is used to specify multiples that are appropriate for the frequency of ticking. For instance, every 7 days is sensible for daily ticks, but for minutes/seconds, 15 or 30 make sense. You can customize this dictionary by doing:
Since the month delta is 4, less than 5, and the day delta is 141, not less than 5. The type of ticking will be daily.
After resolving the type of ticking, AutoDateLocator
will use the interval dictionary and 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 the dates.py.
self.maxticks = {YEARLY : 16, MONTHLY : 12, DAILY : 11, HOURLY : 16,
MINUTELY : 11, SECONDLY : 11}
The algorithm to determine the tick interval is
# 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
The type of ticking is DAILY
now. So freq
is DAILY
and num
is 141, the day delta. The above code will be equivalent to
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 give too many ticks. else
clause will be executed and the tick interval will be set to 1.
It means 140+ labels and ticks would beplotted. We can expect an ugly x-axis.
If the data sequence is given by [datetime(2013, 1, 1), datetime(2013, 5, 17)]
, just one day shorter. the day delta is 140.
Then AutoDateLocator
will choose 14 as the tick interval and only 10 labels will be plotted.
Thus your first graph looks fine.
Actually I don't understand why matplotlib choose to set the interval to 1 if maxticks
constraint cannot be satisfied.
It will only result in a much larger number of ticks if the interval is 1. I prefer to use the longest interval.
CONCLUSION:
Given any date sequence whose range is greater than or equal to 4 months and 18 days, and less than 5 months, AutoDateLocator
will choose 1 as the tick interval.
You will see some ugly behavior in the x-axis or y-axis when plotting such date sequence with the default major locator, namely, AutoDateLocator
.
SOLUTION:
The simplest solution is increasing the daily maxticks 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()
It does feel like a bug; I would take it to the matplotlib mailing list and see what people there can say about it.
One workaround I can provide is the following:
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 (which isn't what you did), but that makes it a lot easier to tie the ticks to the plot.
The number 6
is just the number of labels I want along the x-axis, and then I reduce the actual number of ticks that matplotlib came up with, by the calculated n
.