10

我在 matplotlib 中的日期的 x 轴自动标记中看到一些奇怪的行为。当我发出命令时:

from datetime import datetime as dt
plot( [ dt(2013, 1, 1), dt(2013, 5, 17)], [ 1 , 1 ], linestyle='None', marker='.')

我得到了非常合理的标记图表:

在此处输入图像描述

但如果我将结束日期增加 1 天:

plot( [ dt(2013, 1, 1), dt(2013, 5, 18)], [ 1 , 1 ], linestyle='None', marker='.')

我明白了:

在此处输入图像描述

我已经在几个不同的日历日期范围(2012 年)复制了这个,每次触发 bug 所需的神奇天数大约是 140 天(在本例中为 136/137)。有人知道这里发生了什么吗?这是一个已知的错误,如果是,是否有解决方法?

几点注意事项:在上面的命令中,我在 --pylab 模式下使用 IPython 来创建绘图,但我第一次遇到这个问题是直接使用 matplotlib 的,它可以以脚本形式重现(即我不认为这是一个 IPython 问题)。此外,我在 matplotlib 1.1.0 和 1.2.X 中都观察到了这一点。

更新:

看起来有一个窗口,如果你向前推得足够远,标签会再次开始正常运行。对于上面的示例,从 5 月 18 日到 5 月 31 日,标签仍然是乱码,但在 6 月 1 日,标签又开始正常绘图。所以,

(labels are garbled)
plot( [ dt(2013, 1, 1), dt(2013, 5, 31)], [ 1 , 1 ], linestyle='None', marker='.')

(labels are fine)
plot( [ dt(2013, 1, 1), dt(2013, 6, 1)], [ 1 , 1 ], linestyle='None', marker='.')
4

3 回答 3

8

它是由AutoDateLocator. 似乎此错误尚未报告给问题跟踪器。
它看起来很奇怪,只是因为绘制了太多的标签和刻度。

当使用带有日期的数据进行绘图时,默认情况下,matplotlibmatplotlib.dates.AutoDateLocator用作主要定位器。即,AutoDateLocator用于确定刻度间隔和刻度位置。

假设,数据序列由 给出[datetime(2013, 1, 1), datetime(2013, 5, 18)]
时间增量为 4 个月零 17 天。月增量为 4,日增量为 4*31+17=141。

根据matplotlib 文档

类 matplotlib.dates.AutoDateLocator(tz=None, minticks=5, maxticks=None, interval_multiples=False)

minticks 是所需的最小刻度数,用于选择刻度类型(每年、每月等)。

maxticks 是所需的最大滴答数,它控制滴答之间的任何间隔(每隔一个、每 3 个等)。对于真正细粒度的控制,这可以是一个字典,将单个规则频率常数(每年、每月等)映射到它们自己的最大刻度数。这可用于保持刻度数适合于在 class:AutoDateFormatter 中选择的格式。此字典中未指定的任何频率都被赋予默认值。

AutoDateLocator 有一个间隔字典,它映射滴答的频率(来自 dateutil.rrule 的常数)和该滴答允许的倍数。默认如下所示:

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

间隔用于指定适合滴答频率的倍数。例如,对于每日刻度,每 7 天是合理的,但对于分钟/秒,15 或 30 是有意义的。您可以通过执行以下操作自定义此字典:

由于月 delta 为 4,小于 5,而日 delta 为 141,不小于 5。tick 的类型将是每天。
在解析了tick的类型后,AutoDateLocator会使用interval字典和maxticks字典来确定tick的间隔。

maxticksis时NoneAutoDateLocator使用其默认的 maxticks 字典。文档向我们展示了默认的区间字典,并没有告诉我们默认的 maxticks 字典是什么样的。
我们可以在dates.py中找到它。

self.maxticks = {YEARLY : 16, MONTHLY : 12, DAILY : 11, HOURLY : 16,
            MINUTELY : 11, SECONDLY : 11}

确定刻度间隔的算法

# Find the first available interval that doesn't give too many ticks
for interval in self.intervald[freq]:
    if num <= interval * (self.maxticks[freq] - 1):
        break
else:
    # We went through the whole loop without breaking, default to 1
    interval = 1

滴答的类型是DAILY现在。日差141 也是freq如此。上面的代码将等价于DAILYnum

for interval in [1, 2, 3, 7, 14]:
    if 141 <= interval * (11 - 1):
        break
else:
    interval = 1

141 太大了。所有的每日间隔都会给出太多的滴答声。else子句将被执行,刻度间隔将设置为 1。
这意味着将绘制 140 多个标签和刻度。我们可以期待一个丑陋的 x 轴。

如果数据序列由 给出[datetime(2013, 1, 1), datetime(2013, 5, 17)],则仅短一天。日期增量为 140。然后AutoDateLocator将选择 14 作为刻度间隔,并且仅绘制 10 个标签。因此,您的第一张图看起来不错。

实际上我不明白为什么如果maxticks约束不能满足,matplotlib 选择将间隔设置为 1。如果间隔为 1,它只会导致更多的滴答声。我更喜欢使用最长的间隔。

结论:
给定范围大于或等于 4 个月 18 天且小于 5 个月的任何日期序列,AutoDateLocator将选择 1 作为刻度间隔。当使用默认的主要定位器(即AutoDateLocator.

解决方案:
最简单的解决方案是将每日 maxticks 增加到 12。例如:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.dates import DAILY
from datetime import datetime

ax = plt.subplot(111)
plt.plot_date([datetime(2013, 1, 1), datetime(2013, 5, 31)],
              [datetime(2013, 1, 1), datetime(2013, 5, 10)])

loc = ax.xaxis.get_major_locator()
loc.maxticks[DAILY] = 12

plt.show()
于 2013-03-06T14:53:41.947 回答
2

它确实感觉像一个错误。我会把它带到 matplotlib 邮件列表,看看那里的人能说些什么。

我可以提供的一种解决方法如下:

from datetime import datetime as dt
from matplotlib import pylab as pl

fig = pl.figure()
axes = fig.add_subplot(111)
axes.plot( [ dt(2013, 1, 1), dt(2013, 5, 18)], [ 1 , 1 ], linestyle='None', marker='.')
ticks = axes.get_xticks()
n = len(ticks)//6
axes.set_xticks(ticks[::n])
fig.savefig('dateticks.png')

为 OO 方法道歉(这不是您所做的),但这使得将刻度与情节联系起来变得容易得多。该数字6只是我想要沿 x 轴的标签数量,然后我通过计算的n.

于 2013-03-05T10:00:39.410 回答
0

您需要将 locator.MAXTICKS 重置为更大的数字以避免错误:超过 Locator.MAXTICKS * 2 (2000)

例如:

    alldays = DayLocator()              # minor ticks on the days
    alldays.MAXTICKS = 2000
于 2014-10-15T12:01:13.670 回答