使用Jupyter Notebook中的matplotlib绘制动态变化的图形

时间:2016-09-23 10:37:52

标签: python matplotlib plot graph jupyter-notebook

我有一个M x N 2D数组:第i行代表在时间i的N个点的值。

我希望以图形的形式可视化点[数组的1行],其中值会在一小段时间后更新。因此,图表一次显示1行,然后将值更新为下一行,依此类推。

我想在jupyter笔记本中这样做。寻找参考代码。

我尝试过但没有成功:

8 个答案:

答案 0 :(得分:41)

这是另一种可能更简单的解决方案:

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

答案 1 :(得分:6)

答案 2 :(得分:3)

我一直在为一个线程正在抽取数据并且我们希望Jupyter笔记本在不阻塞任何内容的情况下不断更新图形的情况下寻找一个好的答案。在浏览了大约十二个相关答案之后,下面是一些发现:

警告

如果想要实时图表,请不要使用下面的魔术。如果笔记本使用以下内容,则无法进行图形更新:

%load_ext autoreload
%autoreload 2

在导入matplotlib之前,您需要先在笔记本上做到以下几点:

%matplotlib notebook

方法1:使用FuncAnimation

这具有一个缺点,即使您的数据尚未更新,也会进行图形更新。下面的示例显示了另一个线程通过FuncAnimation更新数据时Jupyter Notebook更新图形的数据。

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

方法2:直接更新

第二种方法是在数据从另一个线程到达时更新图形。这是有风险的,因为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 :(得分:1)

除了@ 0aslam0之外,我还使用了来自here的代码。我刚刚更改了动画功能,以便每次都能获得下一行。它绘制了所有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())

希望它会有用

答案 4 :(得分:1)

通过对@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 :(得分:0)

这是一个处理实时绘图/记录数据(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)

答案 6 :(得分:0)

我对此进行了探索,并产生了以下内容,这些内容大部分是自我记录的:

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

答案 7 :(得分:-6)

我对matplotlib或jupyter了解不多。但是,Graphs让我感兴趣。我刚做了一些谷歌搜索,遇到了这个post。好像你必须将图形呈现为HTML视频才能看到动态图形。

我试过那个帖子。 This是笔记本,如果你想尝试的话。请注意,内核(python 2)需要一些时间来构建视频。您可以阅读更多相关信息here

现在要显示行到行的图形。我试过了this。在那个笔记本中,我有一个dump_data有10行。我随机拿一个并绘制它们并显示为视频。

了解jupyter很有趣。希望这会有所帮助。