10 min read

Plotting dates

Sooner or later, we all have had the need to plot some information over time, be it for the bank account balance each month, the total web site accesses for each day of the year, or one of many other reasons.

Matplotlib has a plotting function ad hoc for dates, plot_date() that considers data on X, Y, or both axes, as dates, labeling the axis accordingly.

As usual, we now present an example, and we will discuss it later:

In [1]: import matplotlib as mpl
In [2]: import matplotlib.pyplot as plt
In [3]: import numpy as np
In [4]: import datetime as dt
In [5]: dates = [dt.datetime.today() + dt.timedelta(days=i)
...: for i in range(10)]In [6]: values = np.random.rand(len(dates))
In [7]: plt.plot_date(mpl.dates.date2num(dates), values, linestyle='-
');
In [8]: plt.show()

Advanced Matplotlib: Part 2

First, a note about linestyle keyword argument: without it, there’s no line connecting the markers that are displayed alone.

We  created  the dates array using timedelta(), a datetime function that helps us define a date interval—10 days in this case. Note how we had to convert our date values using the date2num() function. This is because Matplotlib represents dates as float values corresponding to the number of days since 0001-01-01 UTC.

Also note how the X-axis labels, the ones that have data values, are badly rendered.

Matplotlib provides ways to address the previous two points—date formatting and conversion, and axes formatting.

Date formatting

Commonly, in Python programs, dates are represented as datetime objects, so we have to first convert other data values into datetime objects, sometimes by using the dateutil companion module, for example:

import datetime
date = datetime.datetime(2009, 03, 28, 11, 34, 59, 12345)

or

import dateutil.parser
datestrings = ['2008-07-18 14:36:53.494013','2008-07-20
14:37:01.508990', '2008-07-28 14:49:26.183256']dates = [dateutil.parser.parse(s) for s in datestrings]

Once we have the datetime objects, in order to let Matplotlib use them, we have to convert them into floating point numbers that represent the number of days since 0001-01-01 00:00:00 UTC.

To do that, Matplotlib itself provides several helper functions contained in the matplotlib.dates module:

  • date2num():  This function converts one or a sequence of datetime objects to float values representing days since 0001-01-01 00:00:00 UTC (the fractional parts represent hours, minutes, and seconds)
  • num2date():  This function converts one or a sequence of float values representing days since 0001-01-01 00:00:00 UTC to datetime objects (or a sequence, if the input is a sequence)
  • drange(dstart, dend, delta): This function returns a date range (a sequence) of float values in Matplotlib date format; dstart and dend are datetime objects while delta is a datetime.timedelta instance

Usually, what we will end up doing is converting a sequence of datetime objects into a Matplotlib representation, such as:

dates = list of datetime objects
mpl_dates = matplotlib.dates.date2num(dates)

drange() can be useful in situations like this one:

import matplotlib as mpl
from matplotlib import dates
import datetime as dt
date1 = dt.datetime(2008, 9, 23)
date2 = dt.datetime(2009, 4, 12)
delta = dt.timedelta(days=10)
dates = mpl.dates.drange(date1, date2, delta)

where dates will be a sequence of floats starting from date1 and ending at date2 with a delta timestamp between each item of the list.

Axes formatting with axes tick locators and formatters

As we have already seen, the X labels on the first image are not that nice looking. We would expect Matplotlib to allow a better way to label the axis, and indeed, there is.

The solution is to change the two parts that form the axis   ticks—locators and formatters. Locators control the tick’s position, while formatters control the formatting of labels. Both have a major and minor mode: the major locator and formatter are active by default and are the ones we commonly see, while minor mode can be turned on by passing a relative locator or formatter function (because minors are turned off by default by assigning NullLocator and NullFormatter to them).

While this is a general tuning operation and can be applied to all Matplotlib plots, there are some specific locators and formatters for date plotting, provided by matplotlib.dates:

  • MinuteLocator, HourLocator,DayLocator, WeekdayLocator,MonthLocator, YearLocator are all the  locators available that place a tick at the time specified by the name, for example, DayLocator will draw a tick at each day. Of course, a minimum knowledge of the date interval that we are about to draw is needed to select the best locator.
  • DateFormatter is the tick formatter that uses strftime() to format strings.

 

The default locator and formatter are matplotlib.ticker.AutoDateLocator and matplotlib.ticker.AutoDateFormatter, respectively. Both are set by the plot_date() function when called. So, if you wish to set a different locator and/or formatter, then we suggest to do that after the plot_date() call in order to avoid the plot_date() function resetting them to the default values.

Let’s group all this up in an example:

In [1]: import matplotlib as mpl
In [2]: import matplotlib.pyplot as plt
In [3]: import numpy as np
In [4]: import datetime as dt
In [5]: fig = plt.figure()
In [6]: ax2 = fig.add_subplot(212)
In [7]: date2_1 = dt.datetime(2008, 9, 23)
In [8]: date2_2 = dt.datetime(2008, 10, 3)
In [9]: delta2 = dt.timedelta(days=1)
In [10]: dates2 = mpl.dates.drange(date2_1, date2_2, delta2)
In [11]: y2 = np.random.rand(len(dates2))
In [12]: ax2.plot_date(dates2, y2, linestyle='-');
In [13]: dateFmt = mpl.dates.DateFormatter('%Y-%m-%d')
In [14]: ax2.xaxis.set_major_formatter(dateFmt)
In [15]: daysLoc = mpl.dates.DayLocator()
In [16]: hoursLoc = mpl.dates.HourLocator(interval=6)
In [17]: ax2.xaxis.set_major_locator(daysLoc)
In [18]: ax2.xaxis.set_minor_locator(hoursLoc)
In [19]: fig.autofmt_xdate(bottom=0.18) # adjust for date labels
display
In [20]: fig.subplots_adjust(left=0.18)
In [21]: ax1 = fig.add_subplot(211)
In [22]: date1_1 = dt.datetime(2008, 9, 23)
In [23]: date1_2 = dt.datetime(2009, 2, 16)
In [24]: delta1 = dt.timedelta(days=10)
In [25]: dates1 = mpl.dates.drange(date1_1, date1_2, delta1)
In [26]: y1 = np.random.rand(len(dates1))
In [27]: ax1.plot_date(dates1, y1, linestyle='-');
In [28]: monthsLoc = mpl.dates.MonthLocator()
In [29]: weeksLoc = mpl.dates.WeekdayLocator()
In [30]: ax1.xaxis.set_major_locator(monthsLoc)
In [31]: ax1.xaxis.set_minor_locator(weeksLoc)
In [32]: monthsFmt = mpl.dates.DateFormatter('%b')
In [33]: ax1.xaxis.set_major_formatter(monthsFmt)
In [34]: plt.show()

The result of executing the previous code snippet is as shown:

Advanced Matplotlib: Part 2

We drew the subplots in reverse order to avoid some minor overlapping problems.

fig.autofmt_xdate() is used to nicely format date tick labels. In particular, this function rotates the labels (by using rotation keyword argument, with a default value of 30°) and gives them  more room (by using bottom keyword argument, with a default value of 0.2).

We can achieve the same result, at least for the additional spacing, with:

fig = plt.figure()
fig.subplots_adjust(bottom=0.2)
ax = fig.add_subplot(111)

This can also be done by creating the Axes instance directly with:

ax = fig.add_axes([left, bottom, width, height])

while specifying the explicit dimensions.

The subplots_adjust() function allows us to control the spacing around the subplots by using the following keyword arguments:

  • bottom, top, left, right: Controls the spacing at the bottom, top, left, and right of the subplot(s)
  •    

  • wspace, hspace: Controls the horizontal and vertical spacing between subplots

We can also control the spacing by using these parameters in the Matplotlib configuration file:

figure.subplot. = 

Custom formatters and locators

Even if it’s not strictly related to date plotting, tick formatters allow for custom formatters too:

...
import matplotlib.ticker as ticker
...
def format_func(x, pos):
return
...
formatter = ticker.FuncFormatter(format_func)
ax.xaxis.set_major_formatter(formatter)
...

The  function format_func will be called for each label to draw, passing its value and position on the axis. With those two arguments, we can apply a transformation (for example, divide x by 10) and then return a value that will be used to actually draw the tick label.

Here’s a general note on NullLocator: it can be used to remove axis ticks by simply issuing:

ax.xaxis.set_major_locator(matplotlib.ticker.NullLocator())

Text properties, fonts, and LaTeX

Matplotlib has excellent text support, including mathematical expressions, TrueType font support for raster and vector outputs, newline separated text with arbitrary rotations, and Unicode.

We have total control over every text property (font size, font weight, text location, color, and so on) with sensible defaults set in the rc configuration file. Specifically for those interested in mathematical or scientific figures, Matplotlib implements a large number of TeX math symbols and commands to support mathematical expressions anywhere in the figure.

We already saw some text functions, but the following list contains all the functions which can be used to insert text with the pyplot interface, presented along with the corresponding API method and a description:

Pyplot function

API method

Description

text()

mpl.axes.Axes.text()

Adds text at an arbitrary location to the Axes

xlabel()

mpl.axes.Axes.set_xlabel()

Adds an axis label to the X-axis

ylabel()

mpl.axes.Axes.set_ylabel()

Adds an axis label to the Y-axis

title()

mpl.axes.Axes.set_title()

Adds a title to the Axes

figtext()

mpl.figure.Figure.text()

Adds text at an arbitrary location to the Figure

suptitle()

mpl.figure.Figure.suptitle()

Adds a centered title to the Figure

annotate()

mpl.axes.Axes.annotate()

Adds an annotation with an optional arrow to the Axes

 

 

All of these commands return a matplotlib.text.Text instance. We can customize the text properties by passing keyword arguments to the functions or by using matplotlib.artist.setp():

t = plt.xlabel('some text', fontsize=16, color='green')

We can do it as:

t = plt.xlabel('some text')
plt.setp(t, fontsize=16, color='green')

Handling objects allows for several new possibilities; such as setting the same property to all the objects in a specific group. Matplotlib has several convenience functions to return the objects of a plot. Let’s take the example of the tick labels:

ax.get_xticklabels()

This line of code returns a sequence of object instances (the labels for the X-axis ticks) that we can tune:

for t in ax.get_xticklabels():
t.set_fontsize(5.)

or else, still using setp():

setp(ax.get_xticklabels(), fontsize=5.)

It can take a sequence of objects, and apply the same property to all of them.

To recap, all of the properties such as color, fontsize, position, rotation, and so on, can be set either:

  • At function call using keyword arguments
  • Using setp() referencing the Text instance
  • Using the modification function

Fonts

Where there is text, there are also fonts to draw it. Matplotlib allows for several font customizations.

The most complete documentation on this is currently available in the Matplotlib configuration file, /etc/matplotlibrc. We are now reporting that information here.

There are six font properties available for modification.

Property name

Values and description

font.family

It has five values:

  • serif (example, Times)
  • sans-serif (example, Helvetica)
  • cursive (example, Zapf-Chancery)
  • fantasy (example, Western)
  • monospace (example, Courier)

Each of these font families has a default list of font names in decreasing order of priority associated with them (next table). In addition to these generic font names, font.family may also be an explicit name of a font available on the system.

font.style

Three values: normal (or roman), italic, or oblique. The oblique style will be used for italic, if it is not present.

font.variant

Two values: normal or small-caps. For TrueType fonts, which are scalable fonts, small-caps is equivalent to using a font size of smaller, or about 83% of the current font size.

font.weight

Effectively has 13 values-normal, bold, bolder, lighter, 100, 200, 300, …, 900. normal is the same as 400, and bold is 700. bolder and lighter are relative values with respect to the current weight.

font.stretch

11 values-ultra-condensed, extra-condensed, condensed, semi-condensed, normal, semi-expanded, expanded, extra-expanded, ultra-expanded, wider, and narrower. This property is not currently implemented. It works if the font supports it, but only few do.

font.size

The default font size for text, given in points. 12pt is the standard value.


Subscribe to the weekly Packt Hub newsletter

* indicates required

LEAVE A REPLY

Please enter your comment!
Please enter your name here