Qual è il modo attualmente corretto per aggiornare dinamicamente i grafici in Jupyter / iPython?


93

Nelle risposte su come aggiornare dinamicamente un grafico in un loop in ipython notebook (all'interno di una cella) , viene fornito un esempio di come aggiornare dinamicamente un grafico all'interno di un notebook Jupyter all'interno di un loop Python. Tuttavia, questo funziona distruggendo e ricreando la trama ad ogni iterazione, e un commento in uno dei thread fa notare che questa situazione può essere migliorata usando la nuova %matplotlib nbaggmagia, che fornisce una figura interattiva incorporata nel taccuino, piuttosto di un'immagine statica.

Tuttavia, questa meravigliosa nuova nbaggfunzionalità sembra essere completamente priva di documenti per quanto ne so, e non sono in grado di trovare un esempio di come usarla per aggiornare dinamicamente una trama. Quindi la mia domanda è: come si aggiorna in modo efficiente una trama esistente in un notebook Jupyter / Python, utilizzando il backend nbagg? Poiché l'aggiornamento dinamico dei grafici in matplotlib è un problema complesso in generale, un semplice esempio funzionante sarebbe di enorme aiuto. Anche un puntatore a qualsiasi documentazione sull'argomento sarebbe estremamente utile.

Per essere chiari quello che sto chiedendo: quello che voglio fare è eseguire del codice di simulazione per alcune iterazioni, quindi disegnare un grafico del suo stato corrente, quindi eseguirlo per qualche altra iterazione, quindi aggiornare il grafico per riflettere lo stato attuale e così via. Quindi l'idea è di disegnare una trama e quindi, senza alcuna interazione da parte dell'utente, aggiornare i dati nella trama senza distruggere e ricreare l'intera cosa.

Ecco un po 'di codice leggermente modificato dalla risposta alla domanda collegata sopra, che ottiene ciò ridisegnando ogni volta l'intera figura. Voglio ottenere lo stesso risultato, ma utilizzando in modo più efficiente 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)

Risposte:


62

Ecco un esempio che aggiorna un grafico in un ciclo. Aggiorna i dati nella figura e non ridisegna l'intera figura ogni volta. Blocca l'esecuzione, anche se se sei interessato a eseguire un set finito di simulazioni e salvare i risultati da qualche parte, potrebbe non essere un problema per te.

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

L'ho messo su nbviewer qui.

Esiste una versione di IPython Widget nbaggche è attualmente in fase di elaborazione nel repository Matplotlib . Quando sarà disponibile, sarà probabilmente il modo migliore per utilizzarlo nbagg.

EDIT: aggiornato per mostrare più trame


1
Fantastico, sembra funzionare bene. La mancanza di interattività durante l'esecuzione non è un grosso problema per me. Una cosa un po 'strana: se cambio il while True:ciclo in un ciclo for, quando il ciclo finisce ottengo due immagini statiche dell'ultima trama, piuttosto che una nbagg interattiva. Qualche idea sul perché sia?
Nathaniel

Ho cambiato il while in un ciclo for e l'ho provato su tmpnb.org, ma non vedo una seconda immagine o una perdita di interattività. Girato al buio, ma potresti provare a spostare il loop attorno alla chiamata alla funzione, piuttosto che avere il loop nella funzione. per f nell'intervallo (10): pltsin (ax) time.sleep (1)
pneumatica

3
@pneumatics Sfortunatamente ha alcuni problemi con Matplotlib 2.0 sul display Retina: nel loop i grafici sono due volte più piccoli del solito.
Alexander Rodin

1
Sembra che la figura non abbia il tempo di ridimensionarsi correttamente. Quindi ho avuto un'esperienza molto migliore quando ho inserito un plt.show()e spostando il ciclo for alla cella successiva.
ImportanceOfBeingErnest

2
Assicurati di avere il taccuino% matplotlib nella stessa cella del taccuino jupyter del tuo grafico - Ho passato più di 2 ore oggi a risolvere questo problema perché avevo il taccuino% matplotlib nella prima cella con le istruzioni di importazione
aguazul

12

Sto usando jupyter-lab e questo funziona per me (adattalo al tuo 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();

Quindi in un ciclo si popola un dizionario e lo si passa a 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)

assicurati di avere alcune celle sotto il grafico, altrimenti la vista scatta in posizione ogni volta che il grafico viene ridisegnato.


1
questo crea ogni volta una nuova trama piuttosto che aggiornare la trama esistente
pneumatica

2
Corretta. Non ho trovato un modo migliore per avere una trama dinamica in jupyter-lab.
Ziofil

1
C'è un modo per impostare il tempo di attesa tra le iterazioni? piuttosto che avere solo un 'wait = True'
Ahmad Moussa

1
Ogni volta che il grafico viene ridisegnato, il grafico lampeggia. C'è un modo per risolvere questo problema? Ho alcune celle vuote sotto la trama, ma questo non sembra aiutare.
MasayoMusic

@MasayoMusic vedi "Flickering and jumping output" in buildmedia.readthedocs.org/media/pdf/ipywidgets/latest/…
leo

0

Ho adattato la risposta di @Ziofil e l'ho modificata per accettare x, y come elenco e produrre un grafico a dispersione più una tendenza lineare sullo stesso grafico.

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

devi solo chiamare live_plot(x, y)all'interno di un ciclo. ecco come appare: inserisci qui la descrizione dell'immagine

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.