Data Precisão e Épocas #

O Matplotlib pode lidar com datetimeobjetos e numpy.datetime64objetos usando um conversor de unidades que reconhece essas datas e as converte em números de ponto flutuante.

Antes do Matplotlib 3.3, o padrão para esta conversão retornava um float que era dias desde "0000-12-31T00:00:00". A partir do Matplotlib 3.3, o padrão é dias a partir de "1970-01-01T00:00:00". Isso permite mais resolução para datas modernas. "2020-01-01" com a época antiga convertida em 730120 e um número de ponto flutuante de 64 bits com uma resolução de 2^{-52}, ou aproximadamente 14 microssegundos, portanto, a precisão de microssegundos foi perdida. Com a nova época padrão "2020-01-01" é 10957,0, portanto, a resolução alcançável é de 0,21 microssegundos.

import datetime
import numpy as np

import matplotlib.pyplot as plt
import matplotlib.dates as mdates


def _reset_epoch_for_tutorial():
    """
    Users (and downstream libraries) should not use the private method of
    resetting the epoch.
    """
    mdates._reset_epoch_test_example()

Data e hora #

Os objetos Python datetimetêm resolução de microssegundos, portanto, com as datas matplotlib padrão antigas, não era possível percorrer objetos datetime com resolução total.

old_epoch = '0000-12-31T00:00:00'
new_epoch = '1970-01-01T00:00:00'

_reset_epoch_for_tutorial()  # Don't do this.  Just for this tutorial.
mdates.set_epoch(old_epoch)  # old epoch (pre MPL 3.3)

date1 = datetime.datetime(2000, 1, 1, 0, 10, 0, 12,
                          tzinfo=datetime.timezone.utc)
mdate1 = mdates.date2num(date1)
print('Before Roundtrip: ', date1, 'Matplotlib date:', mdate1)
date2 = mdates.num2date(mdate1)
print('After Roundtrip:  ', date2)
Before Roundtrip:  2000-01-01 00:10:00.000012+00:00 Matplotlib date: 730120.0069444446
After Roundtrip:   2000-01-01 00:10:00.000020+00:00

Observe que este é apenas um erro de arredondamento e não há problema para datas mais próximas da época antiga:

date1 = datetime.datetime(10, 1, 1, 0, 10, 0, 12,
                          tzinfo=datetime.timezone.utc)
mdate1 = mdates.date2num(date1)
print('Before Roundtrip: ', date1, 'Matplotlib date:', mdate1)
date2 = mdates.num2date(mdate1)
print('After Roundtrip:  ', date2)
Before Roundtrip:  0010-01-01 00:10:00.000012+00:00 Matplotlib date: 3288.006944444583
After Roundtrip:   0010-01-01 00:10:00.000012+00:00

Se um usuário quiser usar datas modernas com precisão de microssegundos, ele pode alterar a época usando set_epoch. No entanto, a época deve ser definida antes de qualquer operação de data para evitar confusão entre diferentes épocas. Tentar alterar a época posteriormente gerará um arquivo RuntimeError.

try:
    mdates.set_epoch(new_epoch)  # this is the new MPL 3.3 default.
except RuntimeError as e:
    print('RuntimeError:', str(e))
RuntimeError: set_epoch must be called before dates plotted.

Para este tutorial, redefinimos o sentinela usando um método privado, mas os usuários devem definir a época apenas uma vez, se o fizerem.

_reset_epoch_for_tutorial()  # Just being done for this tutorial.
mdates.set_epoch(new_epoch)

date1 = datetime.datetime(2020, 1, 1, 0, 10, 0, 12,
                          tzinfo=datetime.timezone.utc)
mdate1 = mdates.date2num(date1)
print('Before Roundtrip: ', date1, 'Matplotlib date:', mdate1)
date2 = mdates.num2date(mdate1)
print('After Roundtrip:  ', date2)
Before Roundtrip:  2020-01-01 00:10:00.000012+00:00 Matplotlib date: 18262.006944444583
After Roundtrip:   2020-01-01 00:10:00.000012+00:00

datahora64 #

numpy.datetime64os objetos têm precisão de microssegundos para um espaço de tempo muito maior do que os datetimeobjetos. No entanto, atualmente o tempo Matplotlib só é convertido de volta para objetos datetime, que têm resolução de microssegundos e anos que abrangem apenas 0000 a 9999.

_reset_epoch_for_tutorial()  # Don't do this.  Just for this tutorial.
mdates.set_epoch(new_epoch)

date1 = np.datetime64('2000-01-01T00:10:00.000012')
mdate1 = mdates.date2num(date1)
print('Before Roundtrip: ', date1, 'Matplotlib date:', mdate1)
date2 = mdates.num2date(mdate1)
print('After Roundtrip:  ', date2)
Before Roundtrip:  2000-01-01T00:10:00.000012 Matplotlib date: 10957.006944444583
After Roundtrip:   2000-01-01 00:10:00.000012+00:00

Plotando #

É claro que tudo isso tem um efeito na plotagem. Com a época padrão antiga, os tempos eram arredondados durante a date2numconversão interna, levando a saltos nos dados:

_reset_epoch_for_tutorial()  # Don't do this.  Just for this tutorial.
mdates.set_epoch(old_epoch)

x = np.arange('2000-01-01T00:00:00.0', '2000-01-01T00:00:00.000100',
              dtype='datetime64[us]')
# simulate the plot being made using the old epoch
xold = np.array([mdates.num2date(mdates.date2num(d)) for d in x])
y = np.arange(0, len(x))

# resetting the Epoch so plots are comparable
_reset_epoch_for_tutorial()  # Don't do this.  Just for this tutorial.
mdates.set_epoch(new_epoch)

fig, ax = plt.subplots(constrained_layout=True)
ax.plot(xold, y)
ax.set_title('Epoch: ' + mdates.get_epoch())
ax.xaxis.set_tick_params(rotation=40)
plt.show()
Época: 1970-01-01T00:00:00

Para datas plotadas usando a época mais recente, o gráfico é suave:

fig, ax = plt.subplots(constrained_layout=True)
ax.plot(x, y)
ax.set_title('Epoch: ' + mdates.get_epoch())
ax.xaxis.set_tick_params(rotation=40)
plt.show()

_reset_epoch_for_tutorial()  # Don't do this.  Just for this tutorial.
Época: 1970-01-01T00:00:00

Referências

O uso das seguintes funções, métodos, classes e módulos é mostrado neste exemplo:

Galeria gerada por Sphinx-Gallery