Comment mettre à jour dynamiquement un tracé dans une boucle dans un notebook Ipython (dans une cellule)

92

Environnement: Python 2.7, matplotlib 1.3, notebook IPython 1.1, linux, chrome. Le code est dans une seule cellule d'entrée, en utilisant--pylab=inline

Je souhaite utiliser le notebook IPython et les pandas pour consommer un flux et mettre à jour dynamiquement un tracé toutes les 5 secondes.

Lorsque j'utilise simplement une instruction d'impression pour imprimer les données au format texte, cela fonctionne parfaitement: la cellule de sortie continue simplement d'imprimer des données et d'ajouter de nouvelles lignes. Mais lorsque j'essaie de tracer les données (puis de les mettre à jour dans une boucle), le tracé n'apparaît jamais dans la cellule de sortie. Mais si je supprime la boucle, tracez-la une fois. Ça fonctionne bien.

Ensuite, j'ai fait un test 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 sortie n'affichera rien tant que j'interromprai manuellement le processus (ctrl + m + i). Et après l'avoir interrompu, l'intrigue s'affiche correctement sous la forme de plusieurs lignes superposées. Mais ce que je veux vraiment, c'est un tracé qui s'affiche et qui est mis à jour toutes les 5 secondes (ou chaque fois que la plot()fonction est appelée, tout comme les résultats de l'instruction d'impression que j'ai mentionnés ci-dessus, qui fonctionne bien). Ne montrer que le graphique final une fois la cellule terminée n'est PAS ce que je veux.

J'ai même essayé d'ajouter explicitement la fonction draw () après chacun plot(), etc. Aucun d'eux ne fonctionne. Je me demande comment mettre à jour dynamiquement un tracé par une boucle for / while dans une cellule du notebook IPython.

user3236895
la source

Réponses:

118

utiliser le IPython.displaymodule:

%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
la source
4
ce n'est pas une option fluide, le tracé est recréé à partir de zéro avec une cellule qui monte et descend entre
denfromufa
3
L'ajout clear_output(wait=True)résout ce problème. Voir la réponse de wabu ci-dessous.
Alex Williams
3
Vous pouvez faire mieux ces jours-ci, %matplotlib nbaggce qui vous donne une figurine en direct avec laquelle jouer.
tacaswell
@tcaswell J'ai ajouté une nouvelle question demandant comment on utilise nbaggpour y parvenir. (Ping vous au cas où vous seriez intéressé à y répondre.) Stackoverflow.com/questions/34486642/…
Nathaniel
3
cela fonctionne mais détruit également tout ce qui se trouve dans la cellule, comme les mesures imprimées. Y a-t-il un moyen de simplement mettre à jour l'intrigue et de garder tout le reste en place?
KIC
30

Vous pouvez encore améliorer cela en ajoutant wait=Trueà clear_output:

display.clear_output(wait=True)
display.display(pl.gcf())
wabu
la source
1
+1. C'est très important. Je pense que la réponse de HYRY devrait être mise à jour avec cette information.
Alex Williams
5
C'est bien, mais cela a aussi pour effet secondaire gênant d'effacer la sortie d'impression.
Peter
30

Quelques améliorations sur la réponse de HYRY :

  • appelez displayavant clear_outputpour que vous vous retrouviez avec un tracé, plutôt que deux, lorsque la cellule est interrompue.
  • catch the KeyboardInterrupt, de sorte que la sortie de la cellule ne soit pas jonchée de traceback.
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
la source
7
En effet, display.display(gcf())devrait aller AVANT display.clear_output(wait=True)
herrlich10
Merci, @csta. L'a ajouté.
Tom Phillips
@ herrlich10 Pourquoi devrait displayêtre appelé avant clear_output? Ne devriez-vous pas d'abord effacer la sortie, puis afficher les nouvelles données, au lieu de le faire dans l'autre sens?
Jakub Arnold
1
Je reçois toujours un scintillement d'écran avec les mises à jour du graphique, mais ce n'est pas tout le temps. Y a-t-il une solution de contournement à cela?
MasayoMusic
2

Essayez d'ajouter show()ou gcf().show()après la plot()fonction. Cela forcera la mise à jour de la figure actuelle (gcf () renvoie une référence pour la figure actuelle).

Saullo GP Castro
la source
2
Merci. gcf (). show () fonctionne également. Besoin d'ajouter le clear_output () suggéré par HYRY pour afficher des trucs sur la même figure
user3236895
Est-ce en plus de "display.display (pl.gcf ())"?
MasayoMusic
2

L'ajout d'étiquettes aux autres solutions publiées ici continuera d'ajouter de nouvelles étiquettes dans chaque boucle. Pour résoudre ce problème, effacez l'intrigue en utilisantclf

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
la source
4
Merci plt.clf()fonctionne. Cependant, est-il possible de se débarrasser du scintillement des mises à jour?
MasayoMusic
0

Vous pouvez le faire comme ça. Il accepte x, y comme liste et génère un nuage de points plus une tendance linéaire sur le même graphique.

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

il vous suffit d'appeler à l' live_plot(x, y)intérieur d'une boucle. Voici à quoi ça ressemble: entrez la description de l'image ici

Miguel Silva
la source