当前在 Jupyter/iPython 中动态更新情节的正确方法是什么?

如何动态更新一个循环中的一个情节在 ipython 笔记本(在一个单元格) 的答案中,给出了一个示例,说明如何在 Python 循环中动态更新 Jupiter 笔记本中的情节。然而,这是通过在每次迭代中破坏和重新创建情节来实现的,其中一个线程中的一条评论指出,这种情况可以通过使用新的 %matplotlib nbagg魔法来改进,它提供了嵌入在笔记本中的交互式图形,而不是静态图像。

然而,就我所知,这个美妙的新 nbagg特性似乎完全没有文档记录,而且我无法找到一个如何使用它来动态更新图表的例子。因此,我的问题是,如何使用 nbag 后端有效地更新 Jupiter/Python 笔记本中的现有情节?因为在 matplotlib 中动态更新绘图是一个棘手的问题,一个简单的工作示例将是一个巨大的帮助。指向有关该主题的任何文档的指针也会非常有帮助。

为了弄清楚我的要求: 我想要做的是运行一些模拟代码进行几次迭代,然后绘制其当前状态的图,然后再运行几次迭代,然后更新该图以反映当前状态,以此类推。所以这个想法是画一个图,然后,没有任何交互从用户,更新数据的情节,而不破坏和重新创建整个事情。

下面是对上面链接问题的答案稍作修改的代码,它通过每次重新绘制整个图来实现这一点。我想达到同样的结果,但更有效地使用 nbagg

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

Here is an example that updates a plot in a loop. It updates the data in the figure and does not redraw the whole figure every time. It does block execution, though if you're interested in running a finite set of simulations and saving the results somewhere, it may not be a problem for you.

%matplotlib notebook


import numpy as np
import matplotlib.pyplot as plt
import time


def pltsin(ax, colors=['b']):
x = np.linspace(0,1,100)
if ax.lines:
for line in ax.lines:
line.set_xdata(x)
y = np.random.random(size=(100,1))
line.set_ydata(y)
else:
for color in colors:
y = np.random.random(size=(100,1))
ax.plot(x, y, color)
fig.canvas.draw()


fig,ax = plt.subplots(1,1)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_xlim(0,1)
ax.set_ylim(0,1)
for f in range(5):
pltsin(ax, ['b', 'r'])
time.sleep(1)

I put this up on nbviewer here.

There is an IPython Widget version of nbagg that is currently a work in progress at the Matplotlib repository. When that is available, that will probably be the best way to use nbagg.

EDIT: updated to show multiple plots

I'm using jupyter-lab and this works for me (adapt it to your case):

from IPython.display import clear_output
from matplotlib import pyplot as plt
import numpy as np
import collections
%matplotlib inline


def live_plot(data_dict, figsize=(7,5), title=''):
clear_output(wait=True)
plt.figure(figsize=figsize)
for label,data in data_dict.items():
plt.plot(data, label=label)
plt.title(title)
plt.grid(True)
plt.xlabel('epoch')
plt.legend(loc='center left') # the plot evolves to the right
plt.show();

Then in a loop you populate a dictionary and you pass it to live_plot():

data = collections.defaultdict(list)
for i in range(100):
data['foo'].append(np.random.random())
data['bar'].append(np.random.random())
data['baz'].append(np.random.random())
live_plot(data)

make sure you have a few cells below the plot, otherwise the view snaps in place each time the plot is redrawn.

I've adapted @Ziofil answer and modified it to accept x,y as list and output a scatter plot plus a linear trend on the same plot.

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();

you just need to call live_plot(x, y) inside a loop. here's how it looks: enter image description here

If you don't want to clear all outputs, you can use display_id=True to obtain a handle and use .update() on it:

import numpy as np
import matplotlib.pyplot as plt
import time


from IPython import display


def pltsin(ax, *,hdisplay, colors=['b']):
x = np.linspace(0,1,100)
if ax.lines:
for line in ax.lines:
line.set_xdata(x)
y = np.random.random(size=(100,1))
line.set_ydata(y)
else:
for color in colors:
y = np.random.random(size=(100,1))
ax.plot(x, y, color)
hdisplay.update(fig)




fig,ax = plt.subplots(1,1)
hdisplay = display.display("", display_id=True)


ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_xlim(0,1)
ax.set_ylim(0,1)
for f in range(5):
pltsin(ax, colors=['b', 'r'], hdisplay=hdisplay)
time.sleep(1)
    

plt.close(fig)

(adapted from @pneumatics)

The canvas.draw method of the figure dynamically updates its graphs, for the current figure:

from matplotlib import pyplot as plt


plt.gcf().canvas.draw()