Ipython: %matplotlib inline ne fonctionne pas bien avec ipywidgets 6.0 interagir

Créé le 3 mars 2017  ·  46Commentaires  ·  Source: ipython/ipython

Il semble que dans le cahier jupyter, %matplotlib inline ne fonctionne plus correctement avec le nouveau ipywidgets 6.0 @interact . Pour autant que je sache, le backend en ligne attend que la cellule soit complètement terminée avant d'envoyer le message display_data avec l'image (voir le hook post-execute enregistré sur https://github.com/ipython/ipython/blob/7cde22957303ab53df8bd464ad5d7ed616197f31/ IPython/core/pylabtools.py#L383). Dans ipywidgets 6.0, nous avons modifié le fonctionnement des interactions pour être plus précis sur les sorties que nous capturons - nous capturons les messages de sortie envoyés pendant l'exécution de la fonction et les affichons dans la sortie interactive. Cependant, étant donné que le backend en ligne matplotlib envoie son image après l'exécution de la fonction, nous obtenons simplement une énorme série d'images dans la zone de sortie de la cellule.

import matplotlib.pyplot as plt
from ipywidgets import interact

%matplotlib inline
x = [1,2,5,4,3]
@interact(n=(2,5))
def f(n):
    plt.plot(x[:n])

Ce problème concerne le démarrage de la discussion pour voir s'il existe un moyen pour nous de trouver une bonne solution qui fonctionne bien avec le backend en ligne (ce qui est utile) et fonctionne également bien avec l'interaction ipywidgets.

Par exemple, s'il existait un moyen de dire au backend en ligne de vider son image immédiatement et non après la fin de l'exécution d'une cellule, cela pourrait suffire. Ce code fonctionne, par exemple ( sans que le backend en ligne ne soit activé)

import matplotlib.pyplot as plt
from ipywidgets import interact

x = [1,2,5,4,3]
@interact(n=(2,5))
def f(n):
    plt.plot(x[:n])
    plt.show()

CC @SylvainCorlay , @tacaswell.

Commentaire le plus utile

Salut à tous, je n'avais pas suivi les détails techniques de cela, mais j'ai rencontré le problème. Indépendamment des détails de l'implémentation, je pense qu'il est extrêmement important que matplotlib+interact "fonctionne" autant que possible.

Tous les 46 commentaires

Cela semble fonctionner, mais quelqu'un a-t-il une solution pour une meilleure façon?

import matplotlib.pyplot as plt
from ipywidgets import interact
from ipykernel.pylab.backend_inline import flush_figures

%matplotlib inline
x = [1,2,5,4,3]
@interact(n=(2,5))
def f(n):
    plt.plot(x[:n])
    flush_figures()

CC @minrk , qui a travaillé sur le backend inline mpl.

Je ne pense pas qu'on puisse faire grand chose.

Quelle heuristique utiliseriez-vous pour afficher une figure ? Vous ne pouvez pas le faire du tout, les figures mettent à jour autant de figures de réutilisation de code, mais il est courant d'avoir une animation.

Boitier spécial @interact à rincer après chaque opération ?

Boîtier spécial @interact pour rincer après chaque opération ?

Bon point, merci pour la suggestion. Cela ne semble pas très propre de le faire (dans un cas particulier, un backend spécifique pour matplotlib). Peut-être que j'attendrai de voir si c'est un énorme problème pour les utilisateurs de vider explicitement la sortie comme dans l'exemple ci-dessus. Je veux dire, explicite vaut mieux qu'implicite et tout ça...

Edit : pour résumer, je pense que je suis d'accord qu'il n'y a pas grand chose que nous puissions (ou devrions) faire...

Il s'avère que tout cela est beaucoup plus facile que je ne le pensais - j'ai dû faire quelque chose de mal lorsque j'ai essayé de tester cela plus tôt. Nous n'avons pas besoin d'appeler flush_figures, il suffit d'appeler le standard plt.show :

import matplotlib.pyplot as plt
from ipywidgets import interact

%matplotlib inline
x = [1,2,5,4,3]
@interact(n=(2,5))
def f(n):
    plt.plot(x[:n])
    plt.show()

Je pense qu'il est parfaitement raisonnable de demander aux gens d'insérer une commande show() dans leur interaction, donc je ferme ceci.

Si nous voulons conserver ce comportement (et d'autres sorties post-exécution potentielles), peut-être que les crochets post-X pourraient être invoqués explicitement dans interact avant que la capture de sortie ne soit terminée ? En général, je m'attends à ce que l'appel d'une fonction via interact se comporte exactement de la même manière que si j'avais appelé la fonction une fois dans une cellule.

Je m'attendrais à ce que les gens soient surpris par tous les exemples d'interaction avec matplotlib qui cessent d'afficher des chiffres, car matplotlib 'interactif' signifie généralement que plt.show est inutile. Le mettre dans une fonction interactive ne devrait probablement pas changer cela.

Cela me rappelle un peu d'avoir besoin de sys.stdout.flush() pour voir la sortie, avant l'introduction de l'IOThread. Il était logique que cela soit nécessaire lorsque vous réfléchissiez à la façon dont les choses fonctionnaient, mais cela ne le rendait pas moins surprenant car il ne se comportait pas comme les gens s'y attendaient.

Je ne pense pas que nous devrions exécuter automatiquement tous les gestionnaires post-exécution (qui sait ce qu'ils feront - peut-être beaucoup de choses !). Que diriez-vous d'avoir un crochet flush_display ? Nous le vidions avant de commencer l'interaction et le vidions à nouveau juste avant de la terminer. De cette façon, c'est exactement comme vider stdout/stdin.

Eh bien, ou d'un autre côté, il est peut-être logique d'appeler la post-exécution, si nous pensons à une interaction agissant vraiment comme une seule cellule à part entière. Mais alors nous voudrions probablement appeler post-execute avant que l'interaction ne soit exécutée ainsi qu'après, juste au cas où l'interaction se trouverait dans une cellule avec d'autres éléments.

Réouverture, puisque la conversation n'est pas encore close...

J'aime l'idée d'un hook flush_display, et cela fonctionnerait en théorie, même si cela nécessiterait des versions coordonnées d'ipython et d'ipykernel et d'ipywidgets pour définir un nouveau hook et basculer le backend pour l'utiliser.

si nous pensons à une interaction agissant vraiment comme une seule cellule à part entière

Je pense que c'est leur clé, et c'est un grand "si". C'est comme ça que

f nous pensons à une interaction agissant vraiment comme une seule cellule à part entière
Je pense que c'est leur clé, et c'est un grand "si". C'est comme ça que je pense, mais je ne suis pas (encore) tout le monde.

Une chose déroutante à ce sujet est que si nous avions une cellule avec du code, puis une exécution d'interaction, puis un peu plus de code, la cellule serait en fait exécutée comme trois cellules (c'est-à-dire, post-exécution exécutée trois fois différentes). Je pense que flush_display a plus de sens dans ce cas.

Question supplémentaire : la sortie en miroir dans JLab fonctionnerait-elle ?

Les widgets

@SylvainCorlay Je sais qu'ils fonctionnent maintenant, et les gens d'Ann Arbor adorent ça. Ma question était de savoir si cela fonctionnerait si des modifications étaient apportées comme indiqué ci-dessus.

Je suis désolé pour le bruit.

À long terme, je pense qu'une approche qui devrait mieux fonctionner est le backend des widgets ipympl vers matplotlib.

Cependant, nous aurons besoin de rendre tout cela plus facile à installer avec le conf.d etc...

Oui, la sortie en miroir devrait fonctionner avec la modification décrite ci-dessus. Merci de vous être assuré !

Une chose déroutante à ce sujet est que si nous avions une cellule avec du code, puis une exécution d'interaction, puis un peu plus de code, la cellule serait en fait exécutée comme trois cellules (c'est-à-dire, post-exécution exécutée trois fois différentes).

Effectivement, ce serait un peu bizarre. Ne serait-ce pas deux fois, cependant? Un pour la cellule dans son ensemble et un pour la première invocation d'interagir ? Selon la façon dont vous y pensez, cela a également du sens : avec @interact , vous avez deux exécutions : une pour la déclaration et une autre exécution pour la première invocation de la fonction d'interaction.

Je ne pense pas que vous en auriez plus d'un si vous utilisiez @interact_manual , par exemple (je ne suis pas sûr).

Ne serait-ce pas deux fois, cependant? Un pour la cellule dans son ensemble et un pour la première invocation d'interagir ?

Considérez ce code :

plt.plot([1,2,3])
@interact(n=(1,10))
def f(n):
    plt.plot([1,n])
plt.plot([4,5,6])

Étant donné que le tracé est cumulatif et que vous souhaitez traiter l'interaction comme une cellule distincte, vous devez vider la figure avant l'exécution de l'interaction, puis vider la figure à l'intérieur de l'interaction, puis à nouveau à la fin de la cellule. C'est exactement ce que nous faisons également avec le rinçage de stdout.

En fait, je ne suis pas sûr de ce que l'utilisateur attend dans l'exemple ci-dessus. A quoi vous attendriez-vous ?

Juste une idée : l'exemple ci-dessus n'est pas très logique ; Personnellement, je m'attendrais à trois tracés où un seul change dynamiquement, mais si je devais faire quelque chose comme ça, je ferais tous les appels de tracé à l'intérieur de @interact .

Y a-t-il une raison de ne pas appeler flush_figures après chaque .plot() ?

@myyc c'est un changement sémantique important du comportement. plot ajoute des éléments sur l'intrigue....

Je voulais dire dans le comportement en ligne spécifique ipython , par opposition à matplotlib.

edit : je suis sûr que c'est un cas d'utilisation plus courant d'avoir une interaction par cellule avec cela comme "la seule fonction de traçage", par opposition au cas décrit ci-dessus ...

Je comprends. Bien que je ne pense pas que cela devrait avoir un comportement si différent.

La bonne approche à l'avenir sera probablement d'utiliser le backend du notebook ou ipympl et de modifier la figure.

L'implémentation de solutions de contournement spécifiques à matplotlib pour le backend en ligne dans l'interaction d'ipywidgets ne semble pas correcte.

Dans le code donné par jason :

plt.plot([1,2,3])
@interact(n=(1,10))
def f(n):
    plt.plot([1,n])
plt.plot([4,5,6])

le résultat attendu est un chiffre avec beaucoup de lignes.

Oui je suis tout à fait d'accord. Je l'ai temporairement corrigé de cette façon (en remplaçant les méthodes de traçage des pandas) dans ma configuration locale, "en attente d'un correctif" :) Ne

J'ai vu ce problème rebondir sur une variété de projets github, donc je me demande quel devrait être le meilleur chemin de résolution.

edit : pourquoi ne pas l'écrire de cette façon alors, bien plus explicite ?

@interact(n=(1,10))
def f(n):
    plt.plot([1,2,3])
    plt.plot([1,n])
    plt.plot([4,5,6])

Vous pouvez consulter le package ipympl (https://github.com/matplotlib/jupyter-matplotlib), qui est le widget matplotlib. ipympl est encore à un stade assez précoce, mais devrait avoir un calendrier de publication plus rapide que le noyau matplotlib, et une prise en charge plus précoce de jupyterlab, etc.

OK s'avère que la dérogation est une idée terrible. J'attendrai un correctif en amont :)

Juste pour apporter le point de vue d'un utilisateur non averti : après la mise à jour vers ipywidgets 6.0, j'ai découvert qu'une grande partie de mon code de widget ne fonctionnait pas (en raison du problème discuté ici). J'ai pu résoudre ce problème en ajoutant des appels à plt.show() , mais j'ai également constaté que je devais faire très attention à l'endroit où j'ajoutais de tels appels ; il semble que parfois appeler cela trop tôt fait que les tracés ultérieurs n'apparaissent pas (même si plt.show() est à nouveau appelé). Je n'ai pas assez recherché cela pour créer un exemple minimal, mais le comportement actuel me semble peu intuitif - je programme par essais et erreurs pour le contourner.

Quel changement exact dans ipywidgets a réellement causé le nouveau comportement ? Les hooks post-exécution ne sont-ils pas du tout invoqués, ou sont-ils toujours invoqués mais leur sortie est maintenant rejetée car le widget est précis sur les sorties qui appartiennent au widget ? S'il s'agit de ce dernier, il semble qu'isoler la sortie capturée pendant le corps de la fonction n'est pas la bonne chose à faire.

Les crochets post-exécution sont invoqués (j'en suis presque sûr) - vous pouvez le voir en faisant :

import matplotlib.pyplot as plt
from ipywidgets import interact
%matplotlib inline

@interact(n=(0,10))
def f(n):
    plt.plot([0,n])

et en déplaçant le curseur. Les tracés seront ajoutés à la cellule - les crochets de post-exécution envoient les messages de données d'affichage. Les exécutions d'interaction sont en effet particulières à ne capturer que la sortie se produisant lors de l'invocation de la fonction. Je pense que c'est la bonne chose à faire. Par exemple, deux interactions différentes peuvent partager exactement la même instance de curseur :

import matplotlib.pyplot as plt
from ipywidgets import interact, IntSlider
%matplotlib inline

x = IntSlider()
@interact(n=x)
def f(n):
    plt.plot([0,n])
    plt.show()
@interact(n=x)
def f(n):
    plt.plot([0,n,0,n])
    plt.show()

Lorsque le curseur unique est modifié (dans l'une ou l'autre interface utilisateur), les deux interactions montrent le curseur en mouvement et les deux fonctions d'interaction s'exécutent dans le même appel d'exécution. Il serait erroné que les deux interactions essaient de capturer toute sortie d'un hook post-exécution. Je pense que le défaut ici est que nous utilisons la post-exécution pour générer une sortie, et c'est tout simplement trop grossier d'un marteau. Nous devrions avoir une méthode plus fine pour vider la sortie générée dans un bloc de code, qui peut être appelé plusieurs fois en une seule exécution.

Nous devrions avoir une méthode plus fine pour vider la sortie générée dans un bloc de code

Pour le tracé matplotlib, ce vidage se produit lors de l'exécution de plt.show

Je dois faire très attention à l'endroit où j'ajoute de tels appels ; il semble que parfois appeler cela trop tôt fait que les intrigues ultérieures n'apparaissent pas

Je suis vraiment intéressé de voir des exemples où cela se produit. Nous voulons rendre le comportement intuitif et compréhensible.

Je dois faire très attention à l'endroit où j'ajoute de tels appels ; il semble que parfois appeler cela trop tôt fait que les intrigues ultérieures n'apparaissent pas

Je suis vraiment intéressé de voir des exemples où cela se produit. Nous voulons rendre le comportement intuitif et compréhensible.

Pour élaborer, je pense que vous devriez pouvoir obtenir l'équivalent du comportement précédent en ajoutant simplement plt.show() comme dernière ligne de toute fonction d'interaction qui effectue un traçage. Je suis très intéressé de voir des exemples où cela ne fonctionne pas.

@jasongrout Merci d'avoir regardé ça. Le code où j'ai tapé ceci est plutôt compliqué, et il est possible que j'aie fait autre chose de mal. Si je le revois, j'essaierai de l'isoler et de donner un exemple.

@jasongrout Après avoir plt.show() ).

Si l'on crée un tracé dans une cellule de cahier et appelle plt.show() , les modifications ultérieures apportées à la figure n'apparaissent pas. Voici un exemple minimal :

%matplotlib inline
import matplotlib.pyplot as plt

def plotfun(t):
    fig, ax = plt.subplots(1,1)
    ax.plot([0,t],[0,t])
    plt.show()
    ax.plot([t,0],[0,t])
    plt.show()

plotfun(2)

Seul le tracé de la première ligne apparaît.

Voici un exemple encore plus simple. Deux cellules, sans le backend %matplotlib inline . Peut-être que @tacaswell pourrait expliquer pourquoi le deuxième plt.show ne montre pas d'intrigue.

import matplotlib.pyplot as plt
fig, ax = plt.subplots(1,1)
ax.plot([0,1],[0,1])
plt.show()

montre l'intrigue

ax.plot([1,0],[0,1])
plt.show()

ne montre rien

Confirmé sur ma machine avec le dernier jupyter stable, ipython, ipywidgets, matplotlib, python 3.

Confirmé ici aussi.

@jasongrout C'est le comportement attendu car dans la première cellule, vous créez une figure (et un canevas et des axes), puis vous l'affichez. Dans la deuxième cellule, vous ajoutez ensuite une ligne aux axes existants (dans la figure existante). Avec %matplotlib qt ou ipympl (ou l'une des interfaces graphiques complètes), le tracé _devrait_ être mis à jour avec la deuxième ligne. Je ne m'attends pas à ce que cela fonctionne avec inline car vous n'avez qu'une seule chance de le mettre à jour (c'est pourquoi je ne suis vraiment pas un fan d'inline, cela jette _toutes_ les fonctionnalités interactives).

@tacaswell Ce comportement d'inline a-t-il également un sens pour l'exemple précédent que j'ai donné, qui se trouve dans une seule cellule ? Le voici à nouveau :

%matplotlib inline
import matplotlib.pyplot as plt

def plotfun(t):
    fig, ax = plt.subplots(1,1)
    ax.plot([0,t],[0,t])
    plt.show()
    ax.plot([t,0],[0,t])
    plt.show()

plotfun(2)

Je pense que sur plt.show le backend en ligne se rend lui-même en png, puis désenregistre la figure de pyplot afin qu'elle ne soit plus rendue lors du prochain spectacle, même s'il se trouve dans la même cellule, cependant plusieurs appels aux méthodes d'axes sans show supplémentaires devraient fonctionner.

Juste pour être clair, voici le code backend en ligne : https://github.com/ipython/ipykernel/blob/master/ipykernel/pylab/backend_inline.py

Existe-t-il une version de ipywidgets et/ou matplotlib je peux revenir pour que mes widgets interactifs fonctionnent à nouveau ?

@jasongrout Il semble que, d'après le code, je voudrais définir InlineBackend.close_figures sur False . Mais je ne sais pas comment définir cet attribut - dois-je utiliser une sorte de magie ?

(cette question semble drôle mais je le pense littéralement)

https://github.com/jupyter-widgets/ipywidgets/issues/1457 a de nouveau soulevé ce problème. @ellisonbg - et si nous implémentions la solution de contournement flush_figures que j'ai mentionnée ci-dessus - appelez flush_figures avant et après l'exécution d'une interaction, pour éliminer tous les tracés avant l'interaction et éliminer tout ce qui a été tracé pendant l'interaction ? Je pense que nous devrons peut-être vérifier si nous utilisons le backend mpl en ligne.

C'est un gros kludge, mais c'est peut-être assez important pour le faire quand même. Peut-être pouvons-nous également détecter si nous vidons réellement un chiffre et émettre un avertissement demandant aux gens de faire un appel explicite à show() ?

Salut à tous, je n'avais pas suivi les détails techniques de cela, mais j'ai rencontré le problème. Indépendamment des détails de l'implémentation, je pense qu'il est extrêmement important que matplotlib+interact "fonctionne" autant que possible.

Avec plt.show() ou flush_figures() , si le notebook est rafraîchi (sans redémarrer le noyau), le chiffre disparaît même avec Save Notebook with Widgets. Sans ces atténuations, ce n'est pas le cas, bien qu'il y ait toujours le problème des sorties de parcelles multiples. Est-ce prévu ? La même chose s'applique si le noyau est arrêté, alors qu'avec les anciennes versions d'ipywidgets, l'état final de la figure serait enregistré dans le bloc-notes à la manière d'un tracé non interactif, ce qui était très pratique.

Désolé si c'est hors de portée, ou si je fais quelque chose de mal. Exécuter ipywidgets 6.0.0, matplotlib 2.0.2, notebook 5.0.0 et python 3.6.1.

Cette page vous a été utile?
0 / 5 - 0 notes