AdaptiveTimeScale.py


  • Automatically creates dateticks, tick labels, and axis label for matplotlib time-series plots.
  • Alternative to matplotlib's autofmt_xdate().
  • Date ticks are compact, readable, and unambiguous. 
  • Works for dates from years 0000 to 9998.
  • Millisecond precision.
  • Easy customization and internationalization.


Matplotlib is the python programming language's plotting library. It is extraordinarily sophisticated, capable of producing a huge variety of plot types, as a glance at its gallery page will demonstrate.

When plotting time-series data in matplotlib, an experienced user can set the labels of the axis ticks individually to have whatever format s/he desires, but this requires skill and effort.

There is automated datetick-labelling functionality called "autofmt_xdate" built into matplotlib that works in many circumstances, but frequently the results are disappointing, as the following figures show:

Unsatisfactory results from matplotlib's automated autofmt_xdate functionality


import matplotlib.pyplot as plt
import numpy as np
import datetime as dt
import matplotlib.dates as mdate
numPts = 50
startTime = dt.datetime(2005, 1, 1, 16, 0, 0, 0)
endTime = dt.datetime(2005, 1, 10, 16, 0, 0, 0)
x = [mdate.num2date(i) for i in np.linspace(mdate.date2num(startTime),mdate.date2num(endTime),numPts)]
y = [np.sin(i) for i in np.linspace(0,2*np.pi,numPts)]
fig, ax = plt.subplots() 
plt.plot(x,y,'o-')
fig.autofmt_xdate()
plt.show()




Example 1: In this figure, created using autofmt_xdate, the yyyy-mm-dd date string format is used for the ticks. Even with the tick labels rotated to make better use of space, the result is crowded and difficult to read. Note that every tick is for a day in January 2005, so there is a lot of redundant information here.




Example 2: This is the same plot as in Example 1, but zoomed in. With the tighter axis limits, the ticks have to be in units of hours, but there isn't room for years and months and days and hours, so autofmt_xdate saves space by expressing the year 2005 as "05", the month as "01" and the hours as "06", "08", etc., which is ambiguous and difficult to read.

A better way: AdaptiveTimeScale uses time tick labels and axis label together to create non-ambiguous ticks without redundant information

The following two figures are duplicates of the earlier examples, but created with AdaptiveTimeScale.py instead of matplotlib's autofmt_xdate time-tick functionality.




Example 3: Plots produced automatically using AdaptiveTimeScale.py. These are duplicates of the first two figures, but AdaptiveTimeScale puts as much information into the x-axis label as it can, rather than trying to cram it all into the tick labels. The result is compact, readable time-tick labels that are nonetheless unambiguous. 

How hard is it to use AdaptiveTimeScale.py?

Not hard at all. The figures in Example 3 were created using the following code. The code is virtually identical to that used in Examples 1 and 2. Lines specific to AdaptiveTimeScale have been highlighted, and the call to autofmt_xdate struck through.

import AdaptiveTimeScale
import matplotlib.pyplot as plt
import numpy as np
import datetime as dt
import matplotlib.dates as mdate
numPts = 50
startTime = dt.datetime(2005, 1, 1, 16, 0, 0, 0)
endTime = dt.datetime(2005, 1, 10, 16, 0, 0, 0)
x = [mdate.num2date(i) for i in np.linspace(mdate.date2num(startTime),mdate.date2num(endTime),numPts)]
y = [np.sin(i) for i in np.linspace(0,2*np.pi,numPts)]
fig, ax = plt.subplots() 
plt.plot(x,y,'o-')
fig.autofmt_xdate()
ax.set_xscale('adaptivetime')
plt.show()



A more complicated example, showing optional input arguments

The behaviour of AdaptiveTimeScale.py is mostly automated, but some manual control is possible through the use of keyword arguments. In the example plot below, the user has chosen to suppress tick and axis labelling in the upper axes for a less-cluttered plot. The value of "maxTicks" has also been adjusted to prevent thinning of the ticks (the default maximum number of ticks is 8, but in this case it was decided that 9 ticks would not result in an excessively-crowded plot). Finally, the timezone string "UTC" was appended to the axis label.




The code used to generate this figure was:

import AdaptiveTimeScale
import datetime as dt
import numpy as np
import matplotlib.pyplot as plt
y = np.array([440, 100, 200, 50, 400, 300, 60, 70, 80, 90])
timeDelta = dt.timedelta(days=1)
t0 = dt.datetime(2015, 12, 31, 16, 0, 0, 0)
t = [t0 + timeDelta * j for j in range(len(y))]
tlims = [np.min(t),np.max(t)]
fig = plt.figure()
ax1 = fig.add_subplot(2,1,1)
ax1.plot(t, y,'o-')
ax1.set_xlim([tlims[0],tlims[1]])
ax1.set_xscale('adaptivetime',doLabelTicks=False,doLabelAx=False,maxTicks=10)
ax1.set_ylabel('y')
ax2 = fig.add_subplot(2,1,2)
ax2.plot(t, y,'o-')
ax2.set_xlim([tlims[0],tlims[1]])
ax2.set_xscale('adaptivetime',timeZoneStr="UTC",maxTicks=10)
ax2.set_ylabel('y')
plt.show()

Internationalization--An example in French

Internationalization can be implemented by writing your own custom module that overrides the generate_axlabel() and generate_ticklabel() functions. The following example uses the function definitions in a file called "my_custom_formats_french.py". Again, the code is almost identical to the autofmt_xdate examples, with the lines specific to AdaptiveTimeScale.py being highlighted.

import AdaptiveTimeScale
import matplotlib.pyplot as plt
import numpy as np
import datetime as dt
import matplotlib.dates as mdate
numPts = 50
startTime = dt.datetime(2005, 1, 1, 16, 0, 0, 0)
endTime = dt.datetime(2005, 1, 10, 16, 0, 0, 0)
x = [mdate.num2date(i) for i in np.linspace(mdate.date2num(startTime),mdate.date2num(endTime),numPts)]
y = [np.sin(i) for i in np.linspace(0,2*np.pi,numPts)]
fig, ax = plt.subplots() 
plt.plot(x,y,'o-')
ax.set_xscale('adaptivetime',CustomModule='my_custom_formats_french')
plt.show()


The result is shown below:


Incidentally, the same approach used for internationalization can also be used to customize tick and axis labels if you are unhappy with the defaults. Just write your own versions of the 
generate_axlabel() and generate_ticklabel() functions and put them into a .py file.

But where can I get AdaptiveTimeScale.py?

I'm glad you asked. The source code is available at AdaptiveTimeScale.py's bitbucket repository