在 Jupyter Notebook 中使用 matplotlib 绘制动态变化的图形
Posted
技术标签:
【中文标题】在 Jupyter Notebook 中使用 matplotlib 绘制动态变化的图形【英文标题】:Plot dynamically changing graph using matplotlib in Jupyter Notebook 【发布时间】:2017-02-01 04:37:45 【问题描述】:我有一个 M x N 二维数组:第 i 行表示在时间 i 的 N 个点的值。
我想以图表的形式可视化点 [数组的 1 行],其中值会在一小段时间间隔后更新。因此,图表一次显示 1 行,然后将值更新到下一行,依此类推。
我想在 jupyter 笔记本中执行此操作。寻找参考代码。
我尝试了以下操作但没有成功:
http://community.plot.ly/t/updating-graph-with-new-data-every-100-ms-or-so/812
https://pythonprogramming.net/live-graphs-matplotlib-tutorial/
Create dynamic updated graph with Python
Update Lines in matplotlib
【问题讨论】:
【参考方案1】:这里有一个替代的,可能更简单的解决方案:
%matplotlib notebook
import numpy as np
import matplotlib.pyplot as plt
m = 100
n = 100
matrix = np.random.normal(0,1,m*n).reshape(m,n)
fig = plt.figure()
ax = fig.add_subplot(111)
plt.ion()
fig.show()
fig.canvas.draw()
for i in range(0,100):
ax.clear()
ax.plot(matrix[i,:])
fig.canvas.draw()
【讨论】:
这行得通,但是在完全渲染之前,笔记本中的情节看起来非常小。当它完成渲染时,它会调整大小以正确的大小。知道为什么吗? 该图没有出现在 jupyter 实验室中。在只有一条消息:javascript output is disabled in JupyterLab
【参考方案2】:
对于一个线程正在抽取数据并且我们希望 Jupyter notebook 不断更新图形而不阻塞任何东西的场景,我一直在寻找一个好的答案。在浏览了大约十几个相关答案后,以下是一些发现:
注意
如果您想要实时图表,请不要使用以下魔法。如果笔记本使用以下内容,则图表更新不起作用:
%load_ext autoreload
%autoreload 2
在导入 matplotlib 之前,您的笔记本中需要以下魔法:
%matplotlib notebook
方法一:使用 FuncAnimation
这有一个缺点,即即使您的数据尚未更新,也会发生图表更新。下面的示例显示了另一个线程在 Jupyter notebook 通过FuncAnimation
更新图表时更新数据。
%matplotlib notebook
from matplotlib import pyplot as plt
from matplotlib.animation import FuncAnimation
from random import randrange
from threading import Thread
import time
class LiveGraph:
def __init__(self):
self.x_data, self.y_data = [], []
self.figure = plt.figure()
self.line, = plt.plot(self.x_data, self.y_data)
self.animation = FuncAnimation(self.figure, self.update, interval=1000)
self.th = Thread(target=self.thread_f, daemon=True)
self.th.start()
def update(self, frame):
self.line.set_data(self.x_data, self.y_data)
self.figure.gca().relim()
self.figure.gca().autoscale_view()
return self.line,
def show(self):
plt.show()
def thread_f(self):
x = 0
while True:
self.x_data.append(x)
x += 1
self.y_data.append(randrange(0, 100))
time.sleep(1)
g = LiveGraph()
g.show()
方法二:直接更新
第二种方法是在数据从另一个线程到达时更新图形。这是有风险的,因为 matplotlib 不是线程安全的,但只要只有一个线程进行更新,它似乎就可以工作。
%matplotlib notebook
from matplotlib import pyplot as plt
from matplotlib.animation import FuncAnimation
from random import randrange
from threading import Thread
import time
class LiveGraph:
def __init__(self):
self.x_data, self.y_data = [], []
self.figure = plt.figure()
self.line, = plt.plot(self.x_data, self.y_data)
self.th = Thread(target=self.thread_f, daemon=True)
self.th.start()
def update_graph(self):
self.line.set_data(self.x_data, self.y_data)
self.figure.gca().relim()
self.figure.gca().autoscale_view()
def show(self):
plt.show()
def thread_f(self):
x = 0
while True:
self.x_data.append(x)
x += 1
self.y_data.append(randrange(0, 100))
self.update_graph()
time.sleep(1)
from live_graph import LiveGraph
g = LiveGraph()
g.show()
【讨论】:
【参考方案3】:我对此进行了探索并制作了以下内容,主要是自我记录:
import matplotlib.pyplot as plt
%matplotlib notebook
print('This text appears above the figures')
fig1 = plt.figure(num='DORMANT')
print('This text appears betweeen the figures')
fig2 = plt.figure()
print('This text appears below the figures')
fig1.canvas.set_window_title('Canvas active title')
fig1.suptitle('Figure title', fontsize=20)
# Create plots inside the figures
ax1 = fig1.add_subplot(111)
ax1.set_xlabel('x label')
ax2 = fig2.add_subplot(111)
# Loop to update figures
end = 40
for i in range(end):
ax2.cla() # Clear only 2nd figure's axes, figure 1 is ADDITIVE
ax1.set_title('Axes title') # Reset as removed by cla()
ax1.plot(range(i,end), (i,)*(end-i))
ax2.plot(range(i,end), range(i,end), 'rx')
fig1.canvas.draw()
fig2.canvas.draw()
【讨论】:
【参考方案4】:通过对@Shital Shah 解决方案的适度修改,我创建了一个更通用的框架,可以简单地应用于各种场景:
import matplotlib
from matplotlib import pyplot as plt
class LiveLine:
def __init__(self, graph, fmt=''):
# LiveGraph object
self.graph = graph
# instant line
self.line, = self.graph.ax.plot([], [], fmt)
# holder of new lines
self.lines = []
def update(self, x_data, y_data):
# update the instant line
self.line.set_data(x_data, y_data)
self.graph.update_graph()
def addtive_plot(self, x_data, y_data, fmt=''):
# add new line in the same figure
line, = self.graph.ax.plot(x_data, y_data, fmt)
# store line in lines holder
self.lines.append(line)
# update figure
self.graph.update_graph()
# return line index
return self.lines.index(line)
def update_indexed_line(self, index, x_data, y_data):
# use index to update that line
self.lines[index].set_data(x_data, y_data)
self.graph.update_graph()
class LiveGraph:
def __init__(self, backend='nbAgg', figure_arg=, window_title=None,
suptitle_arg='t':None, ax_label='x':'', 'y':'', ax_title=None):
# save current backend for later restore
self.origin_backend = matplotlib.get_backend()
# check if current backend meets target backend
if self.origin_backend != backend:
print("original backend:", self.origin_backend)
# matplotlib.use('nbAgg',warn=False, force=True)
plt.switch_backend(backend)
print("switch to backend:", matplotlib.get_backend())
# set figure
self.figure = plt.figure(**figure_arg)
self.figure.canvas.set_window_title(window_title)
self.figure.suptitle(**suptitle_arg)
# set axis
self.ax = self.figure.add_subplot(111)
self.ax.set_xlabel(ax_label['x'])
self.ax.set_ylabel(ax_label['y'])
self.ax.set_title(ax_title)
# holder of lines
self.lines = []
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
def close(self):
# check if current beckend meets original backend, if not, restore it
if matplotlib.get_backend() != self.origin_backend:
# matplotlib.use(self.origin_backend,warn=False, force=True)
plt.switch_backend(self.origin_backend)
print("restore to backend:", matplotlib.get_backend())
def add_line(self, fmt=''):
line = LiveLine(graph=self, fmt=fmt)
self.lines.append(line)
return line
def update_graph(self):
self.figure.gca().relim()
self.figure.gca().autoscale_view()
self.figure.canvas.draw()
使用以上 2 类,您可以简单地重现 @Graham S 的示例:
import numpy as np
m = 100
n = 100
matrix = np.random.normal(0,1,m*n).reshape(m,n)
with LiveGraph(backend='nbAgg') as h:
line1 = h.add_line()
for i in range(0,100):
line1.update(range(len(matrix[i,:])), matrix[i,:])
请注意,默认后端是nbAgg
,您可以传递其他后端,例如qt5Agg
。完成后,它将恢复到您原来的后端。
还有@Tom Hale 的例子:
with LiveGraph(figure_arg='num':'DORMANT2', window_title='Canvas active title',
suptitle_arg='t':'Figure title','fontsize':20,
ax_label='x':'x label', 'y':'', ax_title='Axes title') as g:
with LiveGraph() as h:
line1 = g.add_line()
line2 = h.add_line('rx')
end = 40
for i in range(end):
line1.addtive_plot(range(i,end), (i,)*(end-i))
line2.update(range(i,end), range(i,end))
此外,您可以更新@Tom Hale 示例的加法图中的特定行:
import numpy as np
with LiveGraph(figure_arg='num':'DORMANT3', window_title='Canvas active title',
suptitle_arg='t':'Figure title','fontsize':20,
ax_label='x':'x label', 'y':'', ax_title='Axes title') as g:
line1 = g.add_line()
end = 40
for i in range(end):
line_index = line1.addtive_plot(range(i,end), (i,)*(end-i))
for i in range(100):
j = int(20*(1+np.cos(i)))
# update line of index line_index
line1.update_indexed_line(line_index, range(j,end), (line_index,)*(end-j))
请注意,第二个 for 循环仅用于更新索引为 line_index
的特定行。您可以将该索引更改为其他行的索引。
就我而言,我在机器学习训练循环中使用它来逐步更新学习曲线。
import numpy as np
import time
# create a LiveGraph object
g = LiveGraph()
# add 2 lines
line1 = g.add_line()
line2 = g.add_line()
# create 2 list to receive training result
list1 = []
list2 = []
# training loop
for i in range(100):
# just training
time.sleep(0.1)
# get training result
list1.append(np.random.normal())
list2.append(np.random.normal())
# update learning curve
line1.update(np.arange(len(list1)), list1)
line2.update(np.arange(len(list2)), list2)
# don't forget to close
g.close()
【讨论】:
【参考方案5】:另一个简单的解决方案,基于IPython.display
函数display
和clear_output
。我找到了here。这是代码(基于@graham-s 的answer):
from IPython.display import display, clear_output
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
m = 100
n = 100
matrix = np.random.normal(0, 1, size=(m, n))
fig = plt.figure()
ax = fig.add_subplot(111)
for i in range(m):
ax.clear()
ax.plot(matrix[i, :])
display(fig)
clear_output(wait=True)
plt.pause(0.2)
它使用%matplotlib inline
而不是notebook
,并且不会产生@MasterScrat 提到的小图像。适用于 Jupyter Notebook 和 Jupyter Lab。有时图像闪烁不是很好,但可用于快速调查。
如果您需要保持不同帧之间的坐标轴范围,请在ax.clear()
之后添加ax.set_xlim
/ax.set_ylim
。
【讨论】:
【参考方案6】:除了@0aslam0,我还使用了来自here 的代码。我刚刚更改了 animate 函数以每次下次获取下一行。它绘制了所有 N 个点的动画演化(M 步)。
from IPython.display import html
import numpy as np
from matplotlib import animation
N = 5
M = 100
points_evo_array = np.random.rand(M,N)
# First set up the figure, the axis, and the plot element we want to animate
fig = plt.figure()
ax = plt.axes(xlim=(0, M), ylim=(0, np.max(points_evo_array)))
lines = []
lines = [ax.plot([], [])[0] for _ in range(N)]
def init():
for line in lines:
line.set_data([], [])
return lines
def animate(i):
for j,line in enumerate(lines):
line.set_data(range(i), [points_evo_array[:i,j]])
return lines
# call the animator. blit=True means only re-draw the parts that have changed.
anim = animation.FuncAnimation(fig, animate,np.arange(1, M), init_func=init, interval=10, blit=True)
HTML(anim.to_html5_video())
希望对你有用
【讨论】:
我也看到了那个帖子。但是这篇文章的日期是 2013 年,并且谈到了 IPython。我提到的帖子是 2016 年发布的,讨论了 Jupyter(IPython 的重命名和更新版本) 我实际上直接从我提到的那个网站下载了 Jupyter notebook,只是稍微修复了代码并在我的机器上的 Jupyter 中运行它看起来工作正常 好的。你能告诉你机器的规格吗?对我来说,动画不起作用。我必须将其转换为 HTML 视频才能看到动画。 其实我在 Macbook Air CPU Intel i3 上工作过。据我记得我在使用 ffmpeg lib 时遇到了问题,但安装后它抛出了conda,它开始正常工作。我还更新了代码添加行HTML(anim.to_html5_video())
现在它可以使用 display_animation(...)
并且看起来更简单:)
什么是函数 HTML() 当我尝试运行代码时它会引发错误“名称'HTML'未定义”。是否需要额外进口?【参考方案7】:
这是一个处理实时绘图/记录数据的库 (joystick),尽管我不确定它是否与 jupyter 一起使用。您可以使用通常的pip install joystick
安装它。
如果没有关于您的数据的更多详细信息,很难制定可行的解决方案。这是一个选项:
import joystick as jk
import numpy as np
class test(jk.Joystick):
# initialize the infinite loop decorator
_infinite_loop = jk.deco_infinite_loop()
def _init(self, *args, **kwargs):
"""
Function called at initialization, see the docs
"""
# INIT DATA HERE
self.shape = (10, 4) # M, N
self.data = np.random.random(self.shape)
self.xaxis = range(self.shape[1])
############
# create a graph frame
self.mygraph = self.add_frame(
jk.Graph(name="TheName", size=(500, 500), pos=(50, 50),
fmt="go-", xnpts=self.shape[1], freq_up=5, bgcol="w",
xylim=(0, self.shape[1]-1, None, None)))
@_infinite_loop(wait_time=0.5)
def _generate_fake_data(self): # function looped every 0.5 second
"""
Loop starting with the simulation start, getting data and
pushing it to the graph every 0.5 seconds
"""
# NEW (RANDOM) DATA
new_data = np.random.random(self.shape[1])
# concatenate data
self.data = np.vstack((self.data, new_data))
# push new data to the graph
self.mygraph.set_xydata(self.xaxis, self.data[-1])
t = test()
t.start()
t.stop()
t.exit()
此代码将创建一个每秒自动更新 5 次 (freq_up=5) 的图表,同时每 0.5 秒 (wait_time=0.5) (随机)生成新数据并将其推送到图表中进行显示。
如果您不希望 Y 轴摆动,请输入 t.mygraph.xylim = (0, t.shape[1]-1, 0, 1)
。
【讨论】:
【参考方案8】:我对 matplotlib 或 jupyter 了解不多。但是,图表让我感兴趣。我刚刚做了一些谷歌搜索,发现了这个post。似乎您必须将图形呈现为 HTML 视频才能看到动态图形。
我试过那个帖子。 This 是笔记本,如果你想试试。请注意,内核(python 2)需要一些时间来构建视频。你可以阅读更多关于它的信息here。
现在您想逐行显示图表。我试过this。在那个笔记本中,我有一个有 10 行的 dump_data
。我随机取一个并将它们绘制并显示为视频。
了解 jupyter 很有趣。希望这会有所帮助。
【讨论】:
恕我直言,只写 “看看这个外部页面,或者那个” 在 SO 是一个非常糟糕的做法。两年后,这些链接将失效,您的回答将毫无用处。以上是关于在 Jupyter Notebook 中使用 matplotlib 绘制动态变化的图形的主要内容,如果未能解决你的问题,请参考以下文章
Jupyter Lab 在错误的路径中打开,与 Jupyter Notebook 不同,两者在“jupyter_notebook_config.py”中具有相同的映射。
如何在Jupyter Notebook中使用Python虚拟环境?
在 jupyter notebook 中使用 joblib 时不显示打印输出
解决不能再jupyter notebook中使用tensorflow