Cómo actualizar dinámicamente un gráfico en un bucle en el cuaderno Ipython (dentro de una celda)

93

Entorno: Python 2.7, matplotlib 1.3, IPython notebook 1.1, linux, chrome. El código está en una sola celda de entrada, usando--pylab=inline

Quiero usar el cuaderno IPython y los pandas para consumir un flujo y actualizar dinámicamente un gráfico cada 5 segundos.

Cuando solo uso la declaración de impresión para imprimir los datos en formato de texto, funciona perfectamente bien: la celda de salida sigue imprimiendo datos y agregando nuevas filas. Pero cuando trato de trazar los datos (y luego actualizarlos en un bucle), la trama nunca aparece en la celda de salida. Pero si elimino el bucle, solo grábelo una vez. Funciona bien.

Luego hice una prueba simple:

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)

La salida no mostrará nada hasta que interrumpa manualmente el proceso (ctrl + m + i). Y después de que lo interrumpo, la trama se muestra correctamente como múltiples líneas superpuestas. Pero lo que realmente quiero es una trama que aparezca y se actualice cada 5 segundos (o cuando elplot() que se llame a función, al igual que los resultados de la declaración de impresión que mencioné anteriormente, que funciona bien). Solo mostrar el gráfico final después de que la celda esté completamente hecha NO es lo que quiero.

Incluso intenté agregar explícitamente la función draw () después de cada plot(), etc. Ninguno de ellos funciona. Me pregunto cómo actualizar dinámicamente un gráfico mediante un bucle for / while dentro de una celda en el cuaderno IPython.

user3236895
fuente

Respuestas:

122

IPython.displaymódulo de uso :

%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
fuente
4
esta no es una opción
fácil
3
Agregar clear_output(wait=True)resuelve este problema. Vea la respuesta de wabu a continuación.
ahwillia
3
Puedes hacerlo mejor en estos días, lo %matplotlib nbaggque te da una figura viva con la que jugar.
tacaswell
@tcaswell He agregado una nueva pregunta preguntando cómo se usa nbaggpara lograr esto. (Haciendo ping en caso de que esté interesado en responder.) Stackoverflow.com/questions/34486642/…
Nathaniel
3
esto funciona pero también destruye cualquier otra cosa en la celda como las medidas impresas. ¿Hay alguna manera de actualizar la trama y mantener todo lo demás en su lugar?
KIC
31

Puede mejorar aún más esto agregando wait=Truea clear_output:

display.clear_output(wait=True)
display.display(pl.gcf())
wabu
fuente
1
+1. Esto es muy importante. Creo que la respuesta de HYRY debería actualizarse con esta información.
ahwillia
5
Esto es bueno, pero tiene el efecto secundario molesto de borrar también la salida de impresión.
Peter
31

Un par de mejoras en la respuesta de HYRY :

  • llame displayantes clear_outputpara que termine con una parcela, en lugar de dos, cuando la celda se interrumpa.
  • capturar el KeyboardInterrupt, para que la salida de la celda no esté llena de rastreo.
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
fuente
7
De hecho, display.display(gcf())debería ir ANTES display.clear_output(wait=True)
herrlich10
Gracias, @csta. Lo agregó.
Tom Phillips
@ herrlich10 ¿Por qué debería displayser llamado antes clear_output? ¿No debería primero borrar la salida y luego mostrar los nuevos datos, en lugar de hacerlo al revés?
Jakub Arnold
1
Sigo recibiendo un parpadeo de pantalla con las actualizaciones de gráficos, sin embargo, no es todo el tiempo. ¿Hay alguna solución para esto?
MasayoMusic
3

Agregar etiquetas a las otras soluciones publicadas aquí seguirá agregando nuevas etiquetas en cada ciclo. Para lidiar con eso, borre la trama 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
fuente
4
Gracias plt.clf()funciona. Sin embargo, ¿hay alguna forma de deshacerse del parpadeo de las actualizaciones?
MasayoMusic
2

Intente agregar show()o gcf().show()después de la plot()función. Esto forzará la actualización de la figura actual (gcf () devuelve una referencia para la figura actual).

Saullo GP Castro
fuente
2
Gracias. gcf (). show () también funciona. Es necesario agregar clear_output () sugerido por HYRY para mostrar cosas en la misma figura
user3236895
¿Es esto además de "display.display (pl.gcf ())"?
MasayoMusic
0

Puedes hacerlo así. Acepta x, y como lista y genera un gráfico de dispersión más una tendencia lineal en el mismo 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();

solo necesitas llamar live_plot(x, y)dentro de un bucle. Así es como se ve: ingrese la descripción de la imagen aquí

Miguel Silva
fuente