Использование библиотеки Matplotlib. Как работать с календарными данными | jenyay.net

Использование библиотеки Matplotlib. Как работать с календарными данными

Отображение дат в Matplotlib

Иногда нужно построить график, на котором по оси X отложены не числа, а календарные значения (даты или время). Библиотека Matplotlib позволяет рисовать и такие графики. Если списки данных, которые нужно отложить по осям X и Y уже сформированы (в качестве календарного типа используется класс date или datetime), то для того, чтобы по оси были отложены даты, нужно проделать следующие шаги:

  1. Преобразовать даты в числовой формат, понятный для Matplotlib. Это делается с помощью функции matplotlib.dates.date2num.
  2. Установить для оси, по которой будут откладываться календарные данные, форматтер, специально предназначенный для отображения дат и времени. Напомню, что форматтеры - это классы, предназначенные для задания формата выводимых подписей под рисками. Про использование форматтеров вы можете прочитать в статье Как изменять формат меток на осях, про календарные форматтеры будет сказано ниже.
  3. При желании можно установить один из специальных локаторов, предназначенных для работы с календарными данными. Напомню, что локаторы - это классы, расставляющие метки по осям в нужных позициях. Про использование локаторов вы можете прочитать в статье Как управлять положением меток на осях. Про локаторы, предназначенные для календарных данных будет сказано ниже.
  4. Нарисовать график с помощью функции pylab.plot_date (или ее объектно-ориентированного аналога), передав в качестве данных для одной из осей числовые данные, полученные на первом шаге.

Давайте нарисуем простой график, на котором по оси X будут отложены даты. Результат работы скрипта будет выглядеть следующим образом:

А вот и сам скрипт:

#!/usr/bin/python
# -*- coding: UTF-8 -*-

from datetime import date

import pylab
import matplotlib.dates


if __name__ == "__main__":
    # Даты, которые будут отложены по оси X
    xdata = [date (2010, 5, 25),
            date (2010, 7, 5),
            date (2010, 12, 1),
            date (2011, 3, 17),
            date (2011, 8, 2),
            date (2011, 11, 13),
            date (2012, 3, 15),
            date (2012, 4, 8),
            date (2012, 12, 21)
            ]

    # Данные, которые будут отложены по оси Y
    ydata = [0.4, 0.3, 0.5, 0.4, 0.6, 0.3, 0.2, 0.1, 0.0]

    # Преобразуем даты в числовой формат
    xdata_float = matplotlib.dates.date2num (xdata)

    # Вызовем subplot явно, чтобы получить экземпляр класса AxesSubplot,
    # из которого будем иметь доступ к осям
    axes = pylab.subplot(1, 1, 1)

    # Пусть в качестве меток по оси X выводится только год
    axes.xaxis.set_major_formatter (matplotlib.dates.DateFormatter("%Y"))

    # Отобразим данные
    pylab.plot_date (xdata_float, ydata, fmt="b-")

    pylab.grid()
    pylab.show()

Пара слов про функцию date2num. По сути эта функция возвращает число дней в виде дробного числа, которое прошло с момента 0001-01-01 00:00:00 UTC. Строго говоря, к этому числу еще прибавляется 1. Дробность этого числа позволяет определять также время. На вход функции date2num можно передавать или одиночную дату, или список дат.

Определение функции plot_date выглядит следующим образом:

def plot_date (x, y, fmt='bo', tz=None, xdate=True, ydate=False, **kwargs):
    ...

Здесь, x и y - это списки данных, по которым будет построен график; параметр fmt задает стиль оформления кривой; tz определяет временную зону (не будем сейчас останавливаться на этом параметре). Наиболее важные из необязательных параметров - это булевы параметры xdate и ydate, которые должны равняться True, если для соответствующей оси используются календарные данные, и False в противном случае. В качестве значений по умолчанию используются xdate=True, ydate=False, что в данном примере вполне нас устраивает, поэтому эти переменные в предыдущем примере не задаются явно.

Также в этом примере функции plot_date() передается параметр fmt, который определяет стиль линии. По умолчанию используется значение fmt="bo", то есть вместо линии рисуются отдельные голубые кружки.

В примере используется класс форматтера DateFormatter таким образом, чтобы по оси отображались только года, но про форматтеры мы поговорим чуть позже.

С точки зрения эстетики этот график выглядит не очень презентабельно из-за того, что данные для его построения были взяты с потолка. Для демонстрации работы с датами в Matplotlib удобно использовать специальную функцию matplotlib.finance.quotes_historical_yahoo, которая возвращает некие финансовые показатели, получаемые с сайта Yahoo. Т.к. нас не интересует финансовая сторона дела, не будем вдаваться в подробности того, что это за данные. Внимание, эта функция требует подключения к интернету!

Функция quotes_historical_yahoo в качестве параметров принимает строковое обозначение финансового параметра, который нас интересует (мы будем использовать строку "INTC", не вдаваясь в подробности того, что это обозначает) и две даты (экземпляры класса datetime.date). Кроме того, можно передать еще дополнительные параметры, но мы ими пользоваться не будем.

В качестве результата quotes_historical_yahoo возвращает список кортежей из двух элементов. Первый элемент - дата, уже обработанная функцией date2num. Второе значение кортежа - финансовый показатель.

Давайте повторим предыдущий пример, используя более красивые данные. Скрипт и результаты его работы показаны ниже.

#!/usr/bin/python
# -*- coding: UTF-8 -*-

from datetime import date
from urllib2 import URLError

import pylab
import matplotlib.dates
from matplotlib.finance import quotes_historical_yahoo


if __name__ == "__main__":
    # Интервал дат, который нас будет интересовать
    date1 = date (1990, 1, 1)
    date2 = date (2013, 1, 1)

    try:
        quotes = quotes_historical_yahoo('INTC', date1, date2)
    except URLError:
        print u"Ошибка соединения"
        exit (1)

    # Выделим даты. Даты уже обработаны функцией date2num
    dates = [q[0] for q in quotes]

    # Выделим финансовый показатель
    values = [q[1] for q in quotes]

    # Вызовем subplot явно, чтобы получить экземпляр класса AxesSubplot,
    # из которого будем иметь доступ к осям
    axes = pylab.subplot(1, 1, 1)

    # Пусть в качестве меток по оси X выводится только год
    axes.xaxis.set_major_formatter (matplotlib.dates.DateFormatter("%Y"))

    # Отобразим данные
    pylab.plot_date (dates, values, fmt="b-")

    pylab.grid()
    pylab.show()

Форматтеры для работы с датами

Про форматтеры на сайте есть отдельная статья, здесь будут рассмотрены только форматтеры, предназначенные для работы с датами. Всего существует три таких форматтера: DateFormatter, AutoDateFormatter и IndexDateFormatter. Все форматтеры рассматривать не будем, чуть более подробно остановимся на DateFormatter, который мы уже использовали выше.

Конструктор DateFormatter принимает два параметра (обязательным является только первый):

  1. Строка, описывающая формат представления данных. Формат этой строки соответствует формату, используемому методом strftime.
  2. Экземпляр класса, производного от tzinfo, предназначенного для описания временной зоны.

В предыдущих примерах мы уже использовали этот форматтер, ниже приведен еще один пример с другой строкой форматирования.

#!/usr/bin/python
# -*- coding: UTF-8 -*-

from datetime import date
from urllib2 import URLError

import pylab
import matplotlib.dates
from matplotlib.finance import quotes_historical_yahoo


if __name__ == "__main__":
    # Интервал дат, который нас будет интересовать
    date1 = date (2011, 1, 1)
    date2 = date (2013, 1, 1)

    try:
        quotes = quotes_historical_yahoo('INTC', date1, date2)
    except URLError:
        print u"Ошибка соединения"
        exit (1)

    # Выделим даты. Даты уже обработаны функцией date2num
    dates = [q[0] for q in quotes]

    # Выделим финансовый показатель
    values = [q[1] for q in quotes]

    # Вызовем subplot явно, чтобы получить экземпляр класса AxesSubplot,
    # из которого будем иметь доступ к осям
    axes = pylab.subplot(1, 1, 1)

    # По оси X дата будет выводиться в формате "месяц.год"
    axes.xaxis.set_major_formatter (matplotlib.dates.DateFormatter("%m.%y"))

    # Отобразим данные
    pylab.plot_date (dates, values, fmt="b-")

    pylab.grid()
    pylab.show()

Локаторы для работы с датами

Что касается локаторов, то их существует несколько больше:

  • AutoDateLocator
  • RRuleLocator
  • YearLocator
  • MonthLocator
  • WeekdayLocator
  • DayLocator
  • HourLocator
  • MinuteLocator

AutoDateLocator

Локатор AutoDateLocator используется по умолчанию и предназначен для динамического подбора интервалов между метками в зависимости от количества точек данных. Конструктор AutoDateLocator имеет несколько параметров, однако все они являются необязательными. Если вам не нравится, как AutoDateLocator расставляет риски, можно попытаться повлиять на него несколькими параметрами, в том числе и параметрами конструктора minticks и maxticks, которые задают соответственно минимальное и максимальное желаемое количество рисок. Использование этих параметров не гарантирует, что количество рисок будет лежать между minticks и maxticks, но можно надеяться на то, что AutoDateLocator учтет ваше пожелание, когда будет рассчитывать, как расставить риски.

Например, пусть у нас есть следующий скрипт, который использует AutoDateLocator по умолчанию (локатор явно не установлен):

from datetime import date
from urllib2 import URLError

import pylab
import matplotlib.dates
from matplotlib.finance import quotes_historical_yahoo


if __name__ == "__main__":
    # Интервал дат, который нас будет интересовать
    date1 = date (2012, 1, 1)
    date2 = date (2012, 12, 31)

    try:
        quotes = quotes_historical_yahoo('INTC', date1, date2)
    except URLError:
        print u"Ошибка соединения"
        exit (1)

    # Выделим даты. Даты уже обработаны функцией date2num
    dates = [q[0] for q in quotes]

    # Выделим финансовый показатель
    values = [q[1] for q in quotes]

    # Вызовем subplot явно, чтобы получить экземпляр класса AxesSubplot,
    # из которого будем иметь доступ к осям
    axes = pylab.subplot(1, 1, 1)

    # Пусть в качестве меток по оси X выводится только год
    axes.xaxis.set_major_formatter (matplotlib.dates.DateFormatter("%m"))

    # Отобразим данные
    pylab.plot_date (dates, values, fmt="b-")

    pylab.grid()
    pylab.show()

Результат работы будет выглядеть следующим образом (риски расставлены с интервалом 1 месяц):

Теперь скажем AutoDateLocator, что мы хотели бы видеть от 20 до 40 рисок вдоль оси X.

#!/usr/bin/python
# -*- coding: UTF-8 -*-

from datetime import date
from urllib2 import URLError

import pylab
import matplotlib.dates
from matplotlib.finance import quotes_historical_yahoo


if __name__ == "__main__":
    # Интервал дат, который нас будет интересовать
    date1 = date (2012, 1, 1)
    date2 = date (2012, 12, 31)

    try:
        quotes = quotes_historical_yahoo('INTC', date1, date2)
    except URLError:
        print u"Ошибка соединения"
        exit (1)

    # Выделим даты. Даты уже обработаны функцией date2num
    dates = [q[0] for q in quotes]

    # Выделим финансовый показатель
    values = [q[1] for q in quotes]

    # Вызовем subplot явно, чтобы получить экземпляр класса AxesSubplot,
    # из которого будем иметь доступ к осям
    axes = pylab.subplot(1, 1, 1)

    # Пусть в качестве меток по оси X выводится только год
    axes.xaxis.set_major_formatter (matplotlib.dates.DateFormatter("%m"))

    # Изменим локатор, используемый по умочланию
    locator = matplotlib.dates.AutoDateLocator (minticks=20, maxticks=40)
    axes.xaxis.set_major_locator (locator)

    # Отобразим данные
    pylab.plot_date (dates, values, fmt="b-")

    pylab.grid()
    pylab.show()

После этого график будет выглядеть следующим образом (на месяц теперь приходится две риски):

Кроме того, у класса AutoDateLocator в качестве члена есть еще словарь intervald, который задает периоды, с которым могут располагаться риски. Это проще объяснить на примере. По умолчанию словарь intervald выглядит следующим образом:

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

Это значит, что если риски будут привязаны к годам, то они будут идти или через один, или через два, или четыре, или пять или десять лет. Аналогично с месяцами. К чему привязывать риски (к году, месяцу, дню и т.д.), а также какой интервал выбрать - это как раз работа AutoDateLocator. Но мы можем повлиять на расположение рисок, изменив соответствующие значения в словаре.

Следующий пример показывает использование словаря intervald. Мы изменим значение по ключу matplotlib.dates.MONTHLY таким образом, чтобы в случае, если AutoDateLocator выберет привязку по месяцам, риски шли с интервалом 2 или 3 месяца на усмотрение AutoDateLocator.

#!/usr/bin/python
# -*- coding: UTF-8 -*-

from datetime import date
from urllib2 import URLError

import pylab
import matplotlib.dates
from matplotlib.finance import quotes_historical_yahoo


if __name__ == "__main__":
    # Интервал дат, который нас будет интересовать
    date1 = date (2012, 1, 1)
    date2 = date (2012, 12, 31)

    try:
        quotes = quotes_historical_yahoo('INTC', date1, date2)
    except URLError:
        print u"Ошибка соединения"
        exit (1)

    # Выделим даты. Даты уже обработаны функцией date2num
    dates = [q[0] for q in quotes]

    # Выделим финансовый показатель
    values = [q[1] for q in quotes]

    # Вызовем subplot явно, чтобы получить экземпляр класса AxesSubplot,
    # из которого будем иметь доступ к осям
    axes = pylab.subplot(1, 1, 1)

    # Пусть в качестве меток по оси X выводится только год
    axes.xaxis.set_major_formatter (matplotlib.dates.DateFormatter("%m"))

    # Изменим локатор, используемый по умочланию
    locator = matplotlib.dates.AutoDateLocator ()

    # Если локатор привяжет риски к месяцам, то
    # риски должны идти с интервалом через 2 или через 3 месяца
    locator.intervald[matplotlib.dates.MONTHLY] = [2, 3]

    # Установим новый локатор
    axes.xaxis.set_major_locator (locator)

    # Отобразим данные
    pylab.plot_date (dates, values, fmt="b-")

    pylab.grid()
    pylab.show()

Результат будет выглядеть следующим образом:

AutoDateLocator посчитал, что лучше располагать риски с интервалом в 2 месяца.

YearLocator

Более предсказуемо ведут себя локаторы YearLocator, MonthLocator, WeekdayLocator, DayLocator, HourLocator, MinuteLocator, SecondLocator, предназначенные для явной привязки рисок к годам, месяцам, неделям, дням, часам, минутам и секундам соответственно. Показывать работу каждого из этих локаторов смысла нет, посмотрим, как работает локатор YearLocator.

Конструктор YearLocator выглядит следующим образом:

class YearLocator:
    def __init__ (self, base=1, month=1, day=1, tz=None):
        ...

Параметр base задает расстояние (в годах для YearLocator) между соседними рисками, а параметры month и day задают соответственно месяц и день, на который должна приходиться риска каждого года. По умолчанию риска ставится на первое января каждого года.

Следующий пример показывает использование локатора YearLocator с параметрами конструктора по умолчанию.

#!/usr/bin/python
# -*- coding: UTF-8 -*-

from datetime import date
from urllib2 import URLError

import pylab
import matplotlib.dates
from matplotlib.finance import quotes_historical_yahoo


if __name__ == "__main__":
    # Интервал дат, который нас будет интересовать
    date1 = date (1995, 1, 1)
    date2 = date (2012, 12, 31)

    try:
        quotes = quotes_historical_yahoo('INTC', date1, date2)
    except URLError:
        print u"Ошибка соединения"
        exit (1)

    # Выделим даты. Даты уже обработаны функцией date2num
    dates = [q[0] for q in quotes]

    # Выделим финансовый показатель
    values = [q[1] for q in quotes]

    # Вызовем subplot явно, чтобы получить экземпляр класса AxesSubplot,
    # из которого будем иметь доступ к осям
    axes = pylab.subplot(1, 1, 1)

    # Пусть в качестве меток по оси X выводится только год
    axes.xaxis.set_major_formatter (matplotlib.dates.DateFormatter("%y"))

    # Изменим локатор, используемый по умочланию
    locator = matplotlib.dates.YearLocator ()

    # Установим новый локатор
    axes.xaxis.set_major_locator (locator)

    # Отобразим данные
    pylab.plot_date (dates, values, fmt="b-")

    pylab.grid()
    pylab.show()

Следующий пример также располагает риски каждый год, однако сами риски располагаются на дате 4 октября.

#!/usr/bin/python
# -*- coding: UTF-8 -*-

from datetime import date
from urllib2 import URLError

import pylab
import matplotlib.dates
from matplotlib.finance import quotes_historical_yahoo


if __name__ == "__main__":
    # Интервал дат, который нас будет интересовать
    date1 = date (1995, 1, 1)
    date2 = date (2012, 12, 31)

    try:
        quotes = quotes_historical_yahoo('INTC', date1, date2)
    except URLError:
        print u"Ошибка соединения"
        exit (1)

    # Выделим даты. Даты уже обработаны функцией date2num
    dates = [q[0] for q in quotes]

    # Выделим финансовый показатель
    values = [q[1] for q in quotes]

    # Вызовем subplot явно, чтобы получить экземпляр класса AxesSubplot,
    # из которого будем иметь доступ к осям
    axes = pylab.subplot(1, 1, 1)

    # Пусть в качестве меток по оси X выводится только год
    axes.xaxis.set_major_formatter (matplotlib.dates.DateFormatter("%y"))

    # Изменим локатор, используемый по умочланию
    locator = matplotlib.dates.YearLocator (month=10, day=4)

    # Установим новый локатор
    axes.xaxis.set_major_locator (locator)

    # Отобразим данные
    pylab.plot_date (dates, values, fmt="b-")

    pylab.grid()
    pylab.show()

RRuleLocator

В Matplotlib есть еще один несколько экзотический локатор RRuleLocator, являющийся оболочкой над модулем rrule. Чтобы воспользоваться локатором RRuleLocator нужно сначала создать экземпляр класса matplotlib.dates.rrulewrapper, конструктор которого принимает параметры, перечисленные в описании rrule. Первый параметр представляет собой константу, описывающую к чему будут привязаны риски: к году, месяцу, дню и т.д. Здесь используются все те же константы, которые нам встречались при описании словаря intervald:

  • matplotlib.dates.YEARLY
  • matplotlib.dates.MONTHLY
  • matplotlib.dates.DAILY
  • matplotlib.dates.HOURLY
  • matplotlib.dates.MINUTELY
  • matplotlib.dates.SECONDLY

Следующий пример использует локатор RRuleLocator для того, чтобы риски располагались каждый три года первого июня.

#!/usr/bin/python
# -*- coding: UTF-8 -*-

from datetime import date
from urllib2 import URLError

import pylab
import matplotlib.dates
from matplotlib.finance import quotes_historical_yahoo


if __name__ == "__main__":
    # Интервал дат, который нас будет интересовать
    date1 = date (1995, 1, 1)
    date2 = date (2012, 12, 31)

    try:
        quotes = quotes_historical_yahoo('INTC', date1, date2)
    except URLError:
        print u"Ошибка соединения"
        exit (1)

    # Выделим даты. Даты уже обработаны функцией date2num
    dates = [q[0] for q in quotes]

    # Выделим финансовый показатель
    values = [q[1] for q in quotes]

    # Вызовем subplot явно, чтобы получить экземпляр класса AxesSubplot,
    # из которого будем иметь доступ к осям
    axes = pylab.subplot(1, 1, 1)

    # Пусть в качестве меток по оси X выводится только год
    axes.xaxis.set_major_formatter (matplotlib.dates.DateFormatter("%y"))

    # Изменим локатор, используемый по умочланию
    rule = matplotlib.dates.rrulewrapper(matplotlib.dates.YEARLY, interval=3, bymonth=6)
    locator = matplotlib.dates.RRuleLocator(rule)

    # Установим новый локатор
    axes.xaxis.set_major_locator (locator)

    # Отобразим данные
    pylab.plot_date (dates, values, fmt="b-")

    pylab.grid()
    pylab.show()

На этом закончим рассмотрение локаторов и форматтеров для работы с календарными данными.

Другие статьи про Matplotlib


Вы можете подписаться на новости сайта через RSS, Группу Вконтакте или Канал в Telegram.
5 stars

Рейтинг 4.6/5. Всего 10 голос(а, ов)




Подписаться на комментарии
Автор:
Тема:
 Ваш комментарий
 
 
Введите код 412