【问题标题】:Animation doesn't work with datetime X values, but it works properly with integers动画不适用于日期时间 X 值,但它适用于整数
【发布时间】:2018-03-20 18:20:47
【问题描述】:

我正在尝试使用matplotlib 创建动画情节。当我为 X 值使用整数时,它按预期工作:

#!/usr/bin/env python
import os
import random
import numpy as np
from datetime import datetime as dt, timedelta
from collections import deque

import matplotlib.pyplot as plt  # $ pip install matplotlib
import matplotlib.animation as animation

%matplotlib notebook

npoints = 30
x = deque([0], maxlen=npoints)
y = deque([0], maxlen=npoints)
fig, ax = plt.subplots()
[line] = ax.plot(x, y)

def get_data():
    t = random.randint(-100, 100)
    return t * np.sin(t**2)

def data_gen():
    while True:
        yield get_data()

def update(dy):
    x.append(x[-1] + 1)
    y.append(dy)
    line.set_data(x, y)
    ax.relim()
    ax.autoscale_view(True, True, True)
    return line, ax

plt.rcParams['animation.convert_path'] = 'c:/bin/convert.exe'
ani = animation.FuncAnimation(fig, update, data_gen, interval=500, blit=True)
#ani.save(os.path.join('C:/','temp','test.gif'), writer='imagemagick', fps=30)
plt.show()

这会产生以下动画:

但是,一旦我尝试使用 datetime 值作为 x 值 - 情节是空的:

npoints = 30
x = deque([dt.now()], maxlen=npoints)   # NOTE: `dt.now()`
y = deque([0], maxlen=npoints)
fig, ax = plt.subplots()
[line] = ax.plot(x, y)

def get_data():
    t = random.randint(-100, 100)
    return t * np.sin(t**2)

def data_gen():
    while True:
        yield get_data()

def update(dy):
    x.append(dt.now())                  # NOTE: `dt.now()`
    y.append(dy)
    line.set_data(x, y)
    ax.relim()
    ax.autoscale_view(True, True, True)
    return line, ax

plt.rcParams['animation.convert_path'] = 'c:/bin/convert.exe'
ani = animation.FuncAnimation(fig, update, data_gen, interval=1000, blit=True)
#ani.save(os.path.join('C:/','temp','test.gif'), writer='imagemagick', fps=30)
plt.show()

我做错了什么?

PS 我用的是matplotlib 版本:2.1.2

【问题讨论】:

  • 你检查过这个答案吗:stackoverflow.com/questions/1574088/…?它涉及在绘制图表之前使用 date2num 将日期时间对象转换为数字。
  • 我无法重现此内容。动画按预期显示。
  • @ImportanceOfBeingErnest,谢谢你的链接!我试过date2num - 情节仍然是空的。日期时间对你有用吗?您使用哪个 matplotlib 版本?
  • 不是我给你那个链接的,因为我不认为这里需要 date2num 转换。我最初使用 2.2.0 进行了测试,但现在我也使用 2.1.0 进行了测试。两种代码在两个版本中都可以正常工作。
  • @ImportanceOfBeingErnest,感谢您的测试!

标签: python datetime animation matplotlib


【解决方案1】:

问题中的代码在 Jupyter 笔记本 (%matplotlib notebook) 的 matplotlib 2.2.0 中运行良好。但是,在作为脚本运行时使用以下任何后端都会失败:Qt4Agg、Qt4Cairo、TkAgg、TkCairo。

因此我怀疑@M.F. 上面的评论确实是真的,并且 date2num 转换是必要的。

这就是下面的代码所做的,除了摆脱 blitting 之外,这在轴本身也必须被绘制的情况下是没有用的。

import random
import numpy as np
from datetime import datetime as dt, timedelta
from collections import deque
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import matplotlib.animation as animation

npoints = 30
x = deque([mdates.date2num(dt.now())], maxlen=npoints)   # NOTE: `dt.now()`
y = deque([0], maxlen=npoints)
fig, ax = plt.subplots()
[line] = ax.plot_date(x, y, ls="-", marker="")

def get_data():
    t = random.randint(-100, 100)
    return t * np.sin(t**2)

def data_gen():
    while True:
        yield get_data()


def update(dy):
    x.append(mdates.date2num(dt.now()))                  # NOTE: `dt.now()`
    y.append(dy)
    line.set_data(x, y)
    ax.relim()
    ax.autoscale_view(True, True, True)    

ani = animation.FuncAnimation(fig, update, data_gen, interval=1000)
#ani.save("anidates.gif", writer='imagemagick', fps=30)
plt.show()

【讨论】:

  • 你用的是什么后端?
  • 这可能是重点,我最初在 jupyter 中使用笔记本后端进行了测试,代码运行良好,但后来我在脚本中尝试了 Qt4Agg 后端,但它失败了。但是,此答案尝试中的代码在所有情况下都有效。它对你有用吗?
  • @ImportanceOfBeingErnest,它有效,谢谢!当我尝试使用 date2num...
  • 您的代码在GTK3CairoQt5Agg 下运行良好。 MaxU 的原始代码对我来说很好(使用 matplotlib v2.2.0)GTK3Cairo 但后端Qt5Agg xlabels 不会更新。
【解决方案2】:

使用 pandas,您可以注册一个转换器(通过调用 register_matplotlib_converters())来告诉 matplotlib 在调用 line.set_data 时如何处理 datetime.datetime 对象,这样您就不必自己对每个值调用 date2num

import datetime as DT
import collections 
import random
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import matplotlib.dates as mdates
import pandas.plotting as pdplt
pdplt.register_matplotlib_converters()

npoints = 30
x = collections.deque([DT.datetime.now()], maxlen=npoints)   
y = collections.deque([0], maxlen=npoints)
fig, ax = plt.subplots()
[line] = ax.plot(x, y)
# Not necessary, but offers more control over the format
xfmt = mdates.DateFormatter('%H:%M:%S')
ax.xaxis.set_major_formatter(xfmt)

def get_data():
    t = random.randint(-100, 100)
    return t * np.sin(t**2)

def data_gen():
    while True:
        yield get_data()

def update(dy):
    x.append(DT.datetime.now())                  
    y.append(dy)
    line.set_data(list(x), y)
    ax.relim()
    ax.autoscale_view()
    # Not necessary, but it rotates the labels, making them more readable
    fig.autofmt_xdate()
    return [line]

ani = animation.FuncAnimation(fig, update, data_gen, interval=1000, blit=False)
plt.show()

使用 matplotlib 2.2.0 版测试,后端有 TkAgg、Qt4Agg、Qt5Agg、GTK3Agg 和 GTK3Cairo。


matplotlib.units 维护一个它使用的转换器的注册表 到convert non "numlike" values 到可绘制的值。

In [91]: import matplotlib.units as munits
In [92]: munits.registry
Out[92]: 
{numpy.str_: <matplotlib.category.StrCategoryConverter at 0x7f1d65cc99e8>,
 numpy.bytes_: <matplotlib.category.StrCategoryConverter at 0x7f1d65cc9a58>,
 str: <matplotlib.category.StrCategoryConverter at 0x7f1d65cc9a20>,
 bytes: <matplotlib.category.StrCategoryConverter at 0x7f1d65cc99b0>}

plt.plot 这样的高级绘图函数会自动处理datetimes,但像line.set_data 这样的低级方法则不会。因此,如果我们想要制作一个使用datetime 对象的动画,并且我们不希望在每个值上手动调用date2num,那么我们可以改为register a converter

如果我们安装了pandas,那么我们可以使用pandas.plotting.register_matplotlib_converters,而不是从头开始编写转换器,它可以教matplotlib 处理datetime.datetime 对象的(除其他外)列表 .

In [96]: import pandas.plotting as pdplt
In [97]: pdplt.register_matplotlib_converters()
In [98]: munits.registry
Out[98]: 
{datetime.datetime: <pandas.plotting._converter.DatetimeConverter at 0x7f1d400145f8>,
 numpy.str_: <matplotlib.category.StrCategoryConverter at 0x7f1d65cc99e8>,
 numpy.bytes_: <matplotlib.category.StrCategoryConverter at 0x7f1d65cc9a58>,
 pandas._libs.tslibs.timestamps.Timestamp: <pandas.plotting._converter.DatetimeConverter at 0x7f1d40014668>,
 str: <matplotlib.category.StrCategoryConverter at 0x7f1d65cc9a20>,
 numpy.datetime64: <pandas.plotting._converter.DatetimeConverter at 0x7f1d400142b0>,
 datetime.date: <pandas.plotting._converter.DatetimeConverter at 0x7f1d40014748>,
 datetime.time: <pandas.plotting._converter.TimeConverter at 0x7f1d40014240>,
 bytes: <matplotlib.category.StrCategoryConverter at 0x7f1d65cc99b0>,
 pandas._libs.tslibs.period.Period: <pandas.plotting._converter.PeriodConverter at 0x7f1d40014710>}

不幸的是,DatetimeConverter 它不处理 datetime.datetime 对象的 deques。

要绕过这个小障碍,请致电

line.set_data(list(x), y)

而不是

line.set_data(x, y)

【讨论】:

  • 谢谢!它在 Jupyter 中正常工作。在iPythonbackend: Qt5Agg,matplotlib 2.1.2)它给了我TypeError: float() argument must be a string or a number, not 'datetime.datetime'
  • 这很有趣。我会尝试编译 2.1.2 看看我是否可以重现这种情况。目前我只能说使用 2.2.0 和 Qt5Agg 似乎对我有用。
  • 我的错误。我可以使用 2.1.2(以及 2.2.0)在 IPython 中重现该问题。抱歉,我不知道如何解决。
  • 我认为注册转换器可能是手动调用date2num 的替代方法。我已经编辑了上面的代码来说明我的意思。我成功地测试了它作为一个脚本,并在 IPython 中使用后端 TkAgg、Qt4Agg、Qt5Agg、GTK3Agg 和 GTK3Cairo 作为 %cpaste 代码。 我没有在 jupyter notebook 中为我工作,但我希望它可能对你有用,因为我怀疑我的 jupyter 没有正确编译或配置......
  • 非常感谢!我检查了它 - 它在ipython(后端:Qt5Agg)中完美运行。我会把它添加到我的食谱集合中......不幸的是,我不能再一次支持你的解决方案;-)
猜你喜欢
  • 2014-12-29
  • 2013-08-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-04
相关资源
最近更新 更多