Qual é a maneira correta de atualizar gráficos dinamicamente no Jupyter / iPython?

93

Nas respostas sobre como atualizar dinamicamente um gráfico em um loop no notebook ipython (dentro de uma célula) , é dado um exemplo de como atualizar dinamicamente um gráfico dentro de um notebook Jupyter dentro de um loop Python. No entanto, isso funciona destruindo e recriando o enredo em cada iteração, e um comentário em um dos tópicos observa que esta situação pode ser melhorada usando a %matplotlib nbaggmagia nova , que fornece uma figura interativa embutida no caderno, ao invés do que uma imagem estática.

No entanto, esse novo nbaggrecurso maravilhoso parece ser completamente indocumentado, pelo que posso dizer, e não consigo encontrar um exemplo de como usá-lo para atualizar um enredo dinamicamente. Portanto, minha pergunta é: como atualizar com eficiência um gráfico existente em um notebook Jupyter / Python, usando o back-end nbagg? Visto que atualizar gráficos dinamicamente em matplotlib é uma questão complicada em geral, um exemplo simples de trabalho seria de grande ajuda. Uma indicação para qualquer documentação sobre o tópico também seria extremamente útil.

Para deixar claro o que estou pedindo: o que eu quero fazer é executar algum código de simulação para algumas iterações, em seguida, desenhar um gráfico de seu estado atual, executá-lo para mais algumas iterações e atualizar o gráfico para refletir o estado atual e assim por diante. Então a ideia é desenhar um enredo e depois, sem nenhuma interação do usuário, atualizar os dados do enredo sem destruir e recriar tudo.

Aqui está um código ligeiramente modificado da resposta à pergunta vinculada acima, que consegue isso redesenhando a figura inteira todas as vezes. Quero obter o mesmo resultado, mas com mais eficiência usando 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)
Nathaniel
fonte

Respostas:

62

Aqui está um exemplo que atualiza um gráfico em um loop. Ele atualiza os dados na figura e não redesenha a figura inteira todas as vezes. Ele bloqueia a execução, embora se você estiver interessado em executar um conjunto finito de simulações e salvar os resultados em algum lugar, pode não ser um problema para você.

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

Eu coloquei isso no nbviewer aqui.

Existe uma versão IPython Widget nbaggque é atualmente um trabalho em andamento no repositório Matplotlib . Quando estiver disponível, provavelmente será a melhor maneira de usar nbagg.

EDITAR: atualizado para mostrar vários gráficos

pneumática
fonte
1
Ótimo, parece funcionar bem. A falta de interatividade durante a execução não é um grande problema para mim. Uma coisa um pouco estranha: se eu mudar o while True:para um loop for, quando o loop terminar, obtenho duas imagens estáticas do último enredo, em vez de um nbagg interativo. Alguma ideia do porquê?
Nathaniel
Mudei o while para um loop for e tentei em tmpnb.org, mas não estou vendo uma segunda imagem ou perda de interatividade. Filmado no escuro, mas você pode tentar mover o loop em torno da chamada para a função, em vez de colocar o loop na função. para f no intervalo (10): pltsin (ax) time.sleep (1)
pneumática
3
@pneumatics Infelizmente ele tem alguns problemas com Matplotlib 2.0 na tela Retina: no loop, os gráficos são duas vezes menores do que normalmente.
Alexander Rodin de
1
Parece que a figura não tem tempo para se redimensionar corretamente. Portanto, tive uma experiência muito melhor ao colocar um plt.show()e mover o loop for para a próxima célula.
ImportanceOfBeingErnest
2
Certifique-se de que você tem o notebook% matplotlib na mesma célula do notebook jupyter que seu plot - passei mais de 2 horas solucionando isso porque eu tinha o notebook% matplotlib na primeira célula com as
instruções de
15

Estou usando o jupyter-lab e isso funciona para mim (adapte ao seu caso):

from IPython.display import clear_output
from matplotlib import pyplot as plt
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();

Em seguida, em um loop, você preenche um dicionário e o passa para 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)

certifique-se de ter algumas células abaixo do gráfico, caso contrário, a visualização se encaixará cada vez que o gráfico for redesenhado.

Ziofil
fonte
1
isso cria um novo gráfico a cada vez em vez de atualizar o existente
pneumática
2
Corrigir. Não encontrei uma maneira melhor de ter um enredo dinâmico no jupyter-lab.
Ziofil de
1
Existe uma maneira de definir quanto tempo ele espera entre as iterações? em vez de apenas ter um 'esperar = True'
Ahmad Moussa
1
Cada vez que o gráfico é redesenhado, o gráfico pisca. Existe alguma forma de corrigir esse problema? Tenho algumas células vazias no gráfico, mas isso não parece ajudar.
MasayoMusic
@MasayoMusic veja "Flickering and jumping output" em buildmedia.readthedocs.org/media/pdf/ipywidgets/latest/…
leo
0

Eu adaptei a resposta @Ziofil e a modifiquei para aceitar x, y como lista e gerar um gráfico de dispersão mais uma tendência linear no mesmo gráfico.

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

você só precisa chamar live_plot(x, y)dentro de um loop. é assim que parece: insira a descrição da imagem aqui

Miguel Silva
fonte
0

Se não quiser limpar todas as saídas, você pode usar display_id=Truepara obter um identificador e usá .update()-lo:

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)

(adaptado de @pneumatics)

BlackHC
fonte