如何在IPython笔记本中的循环中动态更新绘图(在一个单元格内)

115

环境:Python 2.7,Matplotlib 1.3,IPython笔记本1.1,Linux和Chrome。代码在一个单独的输入单元格中,使用--pylab=inline

我想使用IPython笔记本和Pandas来消费流并动态更新绘图,每五秒更新一次。

当我只使用print语句以文本格式打印数据时,它非常完美:输出单元格不断打印数据并添加新行。但是当我尝试绘制数据(然后在循环中更新它)时,绘图永远不会显示在输出单元格中。但是如果我删除循环,只是绘制一次,就能正常工作。

然后我进行了一些简单的测试:

i = pd.date_range('2013-1-1',periods=100,freq='s')
while True:
    plot(pd.Series(data=np.random.randn(100), index=i))
    #pd.Series(data=np.random.randn(100), index=i).plot() also tried this one
    time.sleep(5)

在我手动中断进程(Ctrl + M + I)之前,程序不会显示任何输出。当我中断它后,图表正确地显示为多个重叠的线条。但我真正想要的是一个每五秒钟或每次调用plot()函数(就像我上面提到的print语句输出一样好用)更新并显示的图表。仅在单元格完全完成后显示最终图表不是我想要的。

我甚至尝试在每个plot()之后显式添加draw()函数等,但都没有奏效。如何在IPython笔记本中的一个for/while循环中动态更新图表?

9个回答

144

使用 IPython.display 模块:

%matplotlib inline
import time
import pylab as pl
from IPython import display
for i in range(10):
    pl.plot(pl.randn(100))
    display.clear_output(wait=True)
    display.display(pl.gcf())
    time.sleep(1.0)

9
这不是一个流畅的选项,情节将从头开始重新制作,细胞会在其中上下移动。 - denfromufa
4
添加 clear_output(wait=True) 可以解决这个问题。请参考下面 wabu 的回答。 - ahwillia
3
现在你可以使用%matplotlib nbagg更好地进行操作,它提供了一个实时的图形界面让你随意调整。 - tacaswell
@tcaswell 我添加了一个新问题,询问如何使用nbagg来实现这一点。(在此提醒您,如果您有兴趣回答,请回答它。)https://dev59.com/hVsW5IYBdhLWcg3w4qew - N. Virgo
4
这个方法可行,但会破坏单元格内的其他内容,比如打印出来的指标。有没有一种方法只更新图表而保留其他所有内容不变呢? - KIC
显示剩余3条评论

41

HYRY的答案进行了一些改进:

  • 在调用clear_output之前调用display,这样当中断单元格时,您就会得到一个图而不是两个。
  • 捕获KeyboardInterrupt,这样单元格输出就不会被追溯信息污染。
import matplotlib.pylab as plt
import pandas as pd
import numpy as np
import time
from IPython import display
%matplotlib inline

i = pd.date_range('2013-1-1',periods=100,freq='s')

while True:
    try:
        plt.plot(pd.Series(data=np.random.randn(100), index=i))
        display.display(plt.gcf())
        display.clear_output(wait=True)
        time.sleep(1)
    except KeyboardInterrupt:
        break

8
确实,display.display(gcf()) 应该放在 display.clear_output(wait=True) 之前 - herrlich10
谢谢,@csta。已添加。 - Tom Phillips
1
@herrlich10 为什么在调用 clear_output 前要先调用 display?难道不应该先清除输出,然后再显示新数据,而不是反过来吗? - Jakub Arnold
4
我仍然在图表更新时遇到屏幕闪烁问题,但并非每次都有。是否有解决方法? - MasayoMusic
如果您也尝试在循环开始时打印文本,我发现这会导致图形消失,仅在短暂的一瞬间可见。当display()调用放置在clear_output()之后时,我不会遇到这个问题。 - Neil Traft

36

您可以通过在clear_output中添加wait=True来进一步改善:

display.clear_output(wait=True)
display.display(pl.gcf())

1
+1。这非常重要。我认为HYRY的答案应该更新这个信息。 - ahwillia
5
这很好,但有一个令人讨厌的副作用,会同时清除打印输出。 - Peter

6

我尝试了很多方法,但我发现这是最简单和最容易的方法——>例如添加 clear_output(wait=True)。

from IPython.display import clear_output

for i in range(n_iterations):
     clear_output(wait=True)
     x = some value
     y = some value
     plt.plot(x, y, '-r')
     plt.show()

这会在同一张图上进行覆盖,产生绘图动画的幻觉。


4

在这里发布其他的解决方案时,如果添加一个标签,它将在每个循环中不断添加新的标签。为了解决这个问题,可以使用clf清除绘图。

例如:

for t in range(100):
   if t % refresh_rate == 0:

     plt.clf()
     plt.plot(history['val_loss'], 'r-', lw=2, label='val')
     plt.plot(history['training_loss'], 'b-', lw=1, label='training')
     plt.legend()
     display.clear_output(wait=True)
     display.display(plt.gcf())

5
谢谢,plt.clf() 起了作用。不过有没有办法消除更新时的闪烁? - MasayoMusic

2
尝试在 plot() 函数后添加 show()gcf().show()。这将强制更新当前图形(gcf() 返回当前图形的引用)。

2
感谢您。gcf().show() 也可以使用。需要添加 HYRY 建议的 clear_output() 函数,以在同一图形上显示内容。 - user3236895
这是要加到"display.display(pl.gcf())"之外吗? - MasayoMusic

2

您可以这样做。它接受x、y作为列表,并在同一图上输出散点图和线性趋势。

from IPython.display import clear_output
from matplotlib import pyplot as plt
%matplotlib inline
    
def live_plot(x, y, figsize=(7,5), title=''):
    clear_output(wait=True)
    plt.figure(figsize=figsize)
    plt.xlim(0, training_steps)
    plt.ylim(0, 100)
    x = [float(i) for i in x]
    y = [float(i) for i in y]
    
    if len(x) > 1:
        plt.scatter(x,y, label='axis y', color='k') 
        m, b = np.polyfit(x, y, 1)
        plt.plot(x, [x * m for x in x] + b)

    plt.title(title)
    plt.grid(True)
    plt.xlabel('axis x')
    plt.ylabel('axis y')
    plt.show();

在循环内部调用live_plot(x, y)即可。以下是示例代码:

Enter image description here


1
在相关帖子中,@BlackHC 提出了一个不错的解决方案。 它使用 IPython.display.displaydisplay_id=True 来获取一个 handle,然后使用 update() 方法来更新它。
例如,
import time

from IPython.display import display
from matplotlib import pyplot as plt

import numpy as np


hdisplay_img = display(display_id=True)
hdisplay_txt = display(display_id=True)

fig = plt.figure()
ax = fig.add_subplot(111)
im = ax.imshow(np.random.random((10,10,3)))
plt.close()

def update(i):
    im.set_data(np.random.random((10,10,3)))
    ax.add_image(im)
    hdisplay_img.update(fig)
    hdisplay_txt.update(f"update {i}")

for f in range(10):
    update(f)
    time.sleep(1)

0

在Jupyter Lab(版本3.6.1)中,为笔记本提供简洁现代的解决方案:

from matplotlib import pyplot as plt

fig, ax = plt.subplots(1)
ax.set(xlabel=f'Epochs', ylabel='Value',
       title='Dynamics')

for i in range(n_step):
   ...
   ax.plot(...)
   
   if i==0:
      ax.legend(labels, loc='upper right')
   else:
      display(fig, clear=True);

注意!不需要 %matplotlib inline 或者 from IPython.display import clear_output, display,因为 默认情况下一个新的笔记本已经有了这样一个带有签名的函数:

display(
    *objs,
    include=None,
    exclude=None,
    metadata=None,
    transient=None,
    display_id=None,
    raw=False,
    clear=False,
    **kwargs,
)

请注意关键字参数clear!将其设置为True会有好处。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接