注意
跳转至底部以下载完整示例代码。
日期精度与纪元#
Matplotlib 可以使用一个单位转换器来处理 datetime
对象和 numpy.datetime64
对象,该转换器能识别这些日期并将它们转换为浮点数。
在 Matplotlib 3.3 之前,此转换的默认值返回一个浮点数,表示自 "0000-12-31T00:00:00" 以来的天数。自 Matplotlib 3.3 起,默认值是自 "1970-01-01T00:00:00" 以来的天数。这为现代日期提供了更高的解析度。"2020-01-01" 使用旧纪元转换后为 730120,并且一个 64 位浮点数的解析度为 2^{-52},约等于 14 微秒,因此微秒精度会丢失。使用新的默认纪元,"2020-01-01" 为 10957.0,因此可实现的解析度为 0.21 微秒。
import datetime
import matplotlib.pyplot as plt
import numpy as np
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()
Datetime#
Python datetime
对象具有微秒级解析度,因此,在使用旧的默认设置时,Matplotlib 日期无法无损往返完整的微秒级解析度的 datetime 对象。
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
请注意,这仅仅是舍入误差,对于更接近旧纪元的日期则没有问题。
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
如果用户希望以微秒级精度使用现代日期,他们可以使用 set_epoch
更改纪元。但是,纪元必须在任何日期操作之前设置,以防止不同纪元之间的混淆。尝试稍后更改纪元将引发一个 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.
对于本教程,我们使用私有方法重置了哨兵,但用户(如果需要设置的话)应该只设置纪元一次。
_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
datetime64#
numpy.datetime64
对象比 datetime
对象在更大的时间范围内具有微秒级精度。然而,目前 Matplotlib 时间只会被转换回 datetime 对象,这些 datetime 对象具有微秒级解析度,并且年份范围仅限于 0000 到 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
绘图#
这当然会对绘图产生影响。使用旧的默认纪元时,时间在内部的 date2num
转换过程中被舍入,导致数据出现跳跃。
_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(layout='constrained')
ax.plot(xold, y)
ax.set_title('Epoch: ' + mdates.get_epoch())
ax.xaxis.set_tick_params(rotation=40)
plt.show()

对于使用更新的纪元绘制的日期,绘图是平滑的。
fig, ax = plt.subplots(layout='constrained')
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.

参考
本示例展示了以下函数、方法、类和模块的使用
脚本总运行时间:(0 分 1.964 秒)