Como atualizar dinamicamente um gráfico em um loop no notebook Ipython (dentro de uma célula)

92

Ambiente: Python 2.7, matplotlib 1.3, IPython notebook 1.1, linux, chrome. O código está em uma única célula de entrada, usando--pylab=inline

Quero usar notebook IPython e pandas para consumir um stream e atualizar dinamicamente um gráfico a cada 5 segundos.

Quando eu apenas uso a instrução print para imprimir os dados em formato de texto, funciona perfeitamente bem: a célula de saída apenas continua imprimindo dados e adicionando novas linhas. Mas quando tento plotar os dados (e depois atualizá-los em um loop), o gráfico nunca aparece na célula de saída. Mas se eu remover o loop, plote-o apenas uma vez. Funciona bem.

Então fiz um teste simples:

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)

A saída não mostrará nada até que eu interrompa manualmente o processo (ctrl + m + i). E depois de interrompê-lo, o gráfico mostra corretamente como várias linhas sobrepostas. Mas o que eu realmente quero é um gráfico que apareça e seja atualizado a cada 5 segundos (ou sempre que a plot()função for chamada, assim como as saídas da instrução de impressão que mencionei acima, que funciona bem). Mostrar apenas o gráfico final depois que a célula está completamente pronta NÃO é o que eu quero.

Eu até tentei adicionar explicitamente a função draw () após cada uma plot(), etc. Nenhuma delas funciona. Gostaria de saber como atualizar dinamicamente um gráfico por um loop for / while dentro de uma célula no notebook IPython.

user3236895
fonte

Respostas:

118

usar o IPython.displaymódulo:

%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)
HYRY
fonte
4
esta não é uma opção suave, o enredo é recriado do zero com a célula subindo e descendo no meio
denfromufa
3
Adicionar clear_output(wait=True)resolve esse problema. Veja a resposta do wabu abaixo.
Alex Williams,
3
Você pode fazer melhor hoje em dia com o %matplotlib nbaggque lhe dá uma figura viva para brincar.
tacaswell
@tcaswell Eu adicionei uma nova pergunta perguntando como alguém usa nbaggpara fazer isso. (Enviando um ping para você caso esteja interessado em responder.) Stackoverflow.com/questions/34486642/…
Nathaniel
3
isso funciona, mas também destrói qualquer outra coisa na célula, como as medidas impressas. Existe uma maneira de apenas atualizar o enredo e manter todo o resto no lugar?
KIC
30

Você pode melhorar ainda mais adicionando wait=Truea clear_output:

display.clear_output(wait=True)
display.display(pl.gcf())
wabu
fonte
1
+1. Isto é muito importante. Acho que a resposta de HYRY deve ser atualizada com essas informações.
Alex Williams,
5
Isso é bom, mas tem o efeito colateral irritante de limpar a saída de impressão também.
Peter
30

Algumas melhorias na resposta de HYRY :

  • ligue displayantes clear_outputpara que você termine com um gráfico, ao invés de dois, quando a célula for interrompida.
  • pegar o KeyboardInterrupt, para que a saída da célula não seja suja com o rastreamento.
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
Tom Phillips
fonte
7
Na verdade, display.display(gcf())deve ir ANTES display.clear_output(wait=True)
herrlich10
Obrigado, @csta. Adicionado.
Tom Phillips de
@ herrlich10 Por que deveria displayser chamado antes clear_output? Você não deveria primeiro limpar a saída e, em seguida, exibir os novos dados, em vez de fazer o contrário?
Jakub Arnold
1
Ainda estou recebendo uma tela piscando com as atualizações do gráfico, mas não é o tempo todo. Existe uma solução alternativa para isso?
MasayoMusic
2

Tente adicionar show()ou gcf().show()após a plot()função. Isso forçará a atualização da figura atual (gcf () retorna uma referência para a figura atual).

Saullo GP Castro
fonte
2
obrigado. gcf (). show () também funciona. É necessário adicionar o clear_output () sugerido por HYRY para mostrar coisas na mesma fig
user3236895
Isso é além de "display.display (pl.gcf ())"?
MasayoMusic
2

Adicionar rótulo a outras soluções postadas aqui continuará adicionando novos rótulos a cada loop. Para lidar com isso, limpe o enredo usandoclf

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

muon
fonte
4
Obrigado plt.clf()trabalhos. No entanto, há como se livrar da oscilação das atualizações?
MasayoMusic
0

Você pode fazer assim. Ele aceita x, y como lista e produz 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. Veja como fica: insira a descrição da imagem aqui

Miguel Silva
fonte