Ipython: % matplotlib inline no funciona bien con ipywidgets 6.0 interact

Creado en 3 mar. 2017  ·  46Comentarios  ·  Fuente: ipython/ipython

Parece que en el cuaderno jupyter, %matplotlib inline ya no funciona bien con los nuevos ipywidgets 6.0 @interact . Lo mejor que puedo decir, el backend en línea espera hasta que la celda esté completamente terminada antes de enviar el mensaje display_data con la imagen (consulte el gancho de ejecución posterior registrado en https://github.com/ipython/ipython/blob/7cde22957303ab53df8bd464ad5d7ed616197f31/ IPython / core / pylabtools.py # L383). En ipywidgets 6.0, hemos cambiado la forma en que funcionan las interacciones para que sean más específicos sobre las salidas que capturamos: capturamos los mensajes de salida enviados durante la ejecución de la función y los mostramos en la salida interactiva. Sin embargo, dado que el backend en línea de matplotlib envía su imagen después de que se ejecuta la función, solo obtenemos una gran serie de imágenes en el área de salida de la celda.

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

Este problema se trata de comenzar la discusión para ver si hay una manera de que podamos encontrar una buena solución que funcione bien con el backend en línea (lo cual es útil) y también funcione bien con la interacción de ipywidgets.

Por ejemplo, si hubiera una forma de decirle al backend en línea que vacíe su imagen inmediatamente y no después de que una celda termine de ejecutarse, eso podría ser suficiente. Este código funciona, por ejemplo ( sin que el backend en línea esté habilitado)

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.

Comentario más útil

Hola a todos, no había estado siguiendo los detalles técnicos de esto, pero me encontré con el problema. Independientemente de los detalles de implementación, creo que es extremadamente importante que matplotlib + interact "simplemente funcione" tanto como sea posible.

Todos 46 comentarios

Esto parece funcionar, pero ¿alguien tiene una solución para una mejor manera?

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 , que trabajó en el backend mpl en línea.

No creo que podamos hacer mucho.

¿Qué heurística usarías para mostrar una figura? No puede hacerlo en todas las figuras, ya que se actualizan muchas figuras de reutilización de código, pero es común tener animación.

Carcasa especial @interact para descargar después de cada operación?

¿Carcasa especial @interact para enjuagar después de cada operación?

Buen punto, gracias por la sugerencia. No se siente muy limpio para hacerlo (en un caso especial, un backend específico para matplotlib). Tal vez esperaré para ver si es un gran problema para los usuarios eliminar explícitamente la salida como en el ejemplo anterior. Quiero decir, explícito es mejor que implícito y todo eso ...

Editar: para resumir, creo que estoy de acuerdo en que no hay mucho que podamos (o debamos) hacer ...

Resulta que todo esto es mucho más fácil de lo que pensaba: debo haber hecho algo mal cuando intenté probar esto antes. No necesitamos llamar a flush_figures, basta con llamar al estándar 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()

Creo que es perfectamente razonable pedirle a la gente que inserte un comando show () en su interacción, así que voy a cerrar esto.

Si queremos mantener este comportamiento (y otras posibles salidas posteriores a la ejecución), ¿quizás los ganchos posteriores a X podrían invocarse explícitamente en interact antes de que finalice la captura de salida? En general, mi expectativa de llamar a una función a través de interactuar es que se comporte exactamente igual que si hubiera llamado a la función una vez en una celda.

Esperaría que la gente se sorprenda con todos los ejemplos de interact-with-matplotlib para dejar de mostrar cifras, ya que matplotlib 'interactivo' generalmente significa que plt.show es innecesario. Ponerlo en una función interactiva probablemente no debería cambiar eso.

Me recuerda un poco la necesidad de sys.stdout.flush() para ver la salida, antes de que se introdujera IOThread. Tenía sentido por qué era necesario cuando pensabas en cómo funcionaban las cosas, pero eso no lo hacía menos sorprendente porque no se comportaba como la gente esperaba.

No creo que debamos ejecutar todos los controladores posteriores a la ejecución automáticamente (quién sabe lo que harán, ¡tal vez muchas cosas!). ¿Qué tal tener un gancho flush_display ? Lo descargamos antes de iniciar la interacción, y lo descargamos de nuevo justo antes de finalizarlo. De esa forma, es exactamente análogo a eliminar stdout / stdin.

Bueno, o por otro lado, tal vez tenga sentido llamar a la ejecución posterior, si pensamos en una interacción que realmente actúa como una sola celda por sí misma. Pero entonces probablemente querríamos llamar a la ejecución posterior antes de que se ejecutara la interacción, así como después, en caso de que la interacción estuviera en una celda con otras cosas.

Reapertura, ya que la conversación aún no se ha cerrado ...

Me gusta la idea de un gancho flush_display, y funcionaría en teoría, aunque requeriría lanzamientos coordinados de ipython e ipykernel e ipywidgets para definir un nuevo gancho y cambiar el backend para usarlo.

si pensamos en una interacción que realmente actúa como una sola célula por sí misma

Creo que esta es la clave, y es un gran 'si'. Así es como lo pienso, pero no soy todo el mundo (todavía).

Si pensamos en una interacción que realmente actúa como una sola célula por sí misma
Creo que esta es la clave, y es un gran 'si'. Así es como lo pienso, pero no soy todo el mundo (todavía).

Algo confuso de esto es que si tuviéramos una celda con código, luego una ejecución interactiva, luego algo más de código, la celda en realidad se ejecutaría como tres celdas (es decir, ejecución posterior a la ejecución tres veces diferentes). Creo que flush_display tiene más sentido en ese caso.

Pregunta adicional: ¿funcionaría la salida reflejada en JLab?

Los widgets de

@SylvainCorlay Sé que funcionan ahora y a la gente de Ann Arbor le encanta. Mi pregunta era si funcionaría si se hicieran cambios como se discutió anteriormente.

Lo siento por el ruido.

A largo plazo, creo que un enfoque que debería funcionar mejor es el backend de widgets ipympl para matplotlib.

Sin embargo, tendremos que hacer que todo esto se instale más fácilmente con conf.d, etc.

Sí, la salida reflejada debería funcionar con los cambios descritos anteriormente. ¡Gracias por asegurarte!

Algo confuso de esto es que si tuviéramos una celda con código, luego una ejecución interactiva, luego algo más de código, la celda en realidad se ejecutaría como tres celdas (es decir, ejecución posterior a la ejecución tres veces diferentes).

De hecho, eso sería un poco extraño. Sin embargo, ¿no sería dos veces? ¿Uno para la celda como un todo y otro para la primera invocación de interactuar? Dependiendo de cómo lo piense, esto también tiene sentido: con @interact , tiene dos ejecuciones: una para la declaración y otra ejecución para la primera invocación de la función interactuada con.

No creo que tengas más de uno si usas @interact_manual , por ejemplo (no estoy seguro).

Sin embargo, ¿no sería dos veces? ¿Uno para la celda como un todo y otro para la primera invocación de interactuar?

Considere este código:

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

Dado que el trazado es acumulativo y desea tratar la interacción como una celda separada en sí misma, debe limpiar la figura antes de que se ejecute la interacción, luego vaciar la figura dentro de la interacción y luego una vez más al final de la celda. Esto es exactamente lo que hacemos con el lavado estándar también.

En realidad, no estoy seguro de lo que espera el usuario en el ejemplo anterior. ¿Qué esperabas?

Solo un pensamiento: el ejemplo anterior no es muy lógico; personalmente, esperaría tres parcelas donde solo uno cambia dinámicamente, pero si tuviera que hacer algo así, haría todas las llamadas de parcela dentro de @interact .

¿Hay alguna razón para no llamar flush_figures después de cada .plot() ?

@myyc es un cambio semántico significativo del comportamiento. plot agrega elementos en la trama ....

Me refiero al comportamiento en línea específico ipython , a diferencia de matplotlib.

editar : Estoy seguro de que es un caso de uso más común tener una interacción por celda con eso como "la única función de trazado", a diferencia del caso descrito anteriormente ...

Entiendo. Aunque no creo que esto deba tener un comportamiento tan diferente.

El enfoque adecuado en el futuro probablemente será utilizar el backend del portátil o ipympl y editar la figura.

Implementar soluciones alternativas específicas de matplotlib para el backend en línea en ipywidgets 'interact no parece correcto.

En el código dado por Jason:

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

la salida esperada es una figura con muchas líneas.

Sí, estoy completamente de acuerdo. Lo arreglé temporalmente de esa manera (anulando los métodos de trazado de pandas) en mi configuración local, "esperando una solución" :) Ignore eso, introduce un montón de efectos secundarios horribles.

He visto que este problema rebota en una variedad de proyectos de github, así que me pregunto cuál debería ser la mejor ruta de resolución.

editar : ¿por qué no escribirlo de esta manera entonces, mucho más explícito?

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

Puede consultar el paquete ipympl (https://github.com/matplotlib/jupyter-matplotlib), que es el widget matplotlib. ipympl aún se encuentra en una etapa bastante temprana, pero debería tener un programa de lanzamiento más rápido que el núcleo de matplotlib, y soporte anterior para jupyterlab, etc.

OK resulta que la anulación es una idea terrible. Esperaré una solución aguas arriba :)

Solo para contribuir con un punto de vista de usuario sin educación: después de actualizar a ipywidgets 6.0, descubrí que gran parte del código de mi widget no funcionaba (debido al problema que se analiza aquí). Pude solucionarlo agregando llamadas a plt.show() , pero también descubrí que debo tener mucho cuidado con el lugar donde agrego tales llamadas; parece que a veces llamar a esto demasiado pronto hace que los gráficos posteriores no aparezcan (incluso si se vuelve a llamar plt.show() ). No he buscado esto lo suficiente como para crear un ejemplo mínimo, pero el comportamiento actual me parece poco intuitivo: estoy programando por prueba y error para evitarlo.

¿Qué cambio exacto en ipywidgets causó realmente el nuevo comportamiento? ¿Los hooks posteriores a la ejecución no se invocan en absoluto, o todavía se invocan pero su salida ahora se descarta porque el widget está siendo particular acerca de qué salidas son propiedad del widget? Si es lo último, parece que aislar la salida capturada durante el cuerpo de la función no es lo correcto.

Se están invocando los ganchos posteriores a la ejecución (estoy bastante seguro); puede ver esto al hacer:

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

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

y moviendo el control deslizante. Los gráficos se agregarán a la celda: los ganchos posteriores a la ejecución envían los mensajes de datos de visualización. De hecho, las ejecuciones interactivas son particulares acerca de solo capturar la salida que ocurre durante la invocación de la función. Creo que es lo correcto. Por ejemplo, dos interacciones diferentes pueden compartir exactamente la misma instancia de control deslizante:

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

Cuando se modifica el control deslizante único (en cualquier interfaz de usuario), ambas interacciones mostrarían el movimiento del control deslizante y ambas funciones de interacción se ejecutarían en la misma llamada de ejecución. Sería incorrecto que ambas interacciones intentaran capturar cualquier salida de un gancho posterior a la ejecución. Creo que el defecto aquí es que estamos usando la ejecución posterior para generar resultados, y eso es demasiado tosco. Deberíamos tener un método más detallado para vaciar la salida generada en un bloque de código, que se puede llamar varias veces en una sola ejecución.

Deberíamos tener un método más detallado para vaciar la salida generada en un bloque de código

Para el trazado de matplotlib, este vaciado ocurre al hacer plt.show

Tengo que tener mucho cuidado con el lugar donde agrego tales llamadas; parece que a veces llamar a esto demasiado pronto hace que las tramas posteriores no aparezcan

Estoy realmente interesado en ver ejemplos en los que esto sucede. Queremos que el comportamiento sea intuitivo y comprensible.

Tengo que tener mucho cuidado con el lugar donde agrego tales llamadas; parece que a veces llamar a esto demasiado pronto hace que las tramas posteriores no aparezcan

Estoy realmente interesado en ver ejemplos en los que sucede esto. Queremos que el comportamiento sea intuitivo y comprensible.

Para elaborar, creo que debería poder obtener el equivalente del comportamiento anterior simplemente agregando plt.show() como la última línea de cualquier función interactiva que esté trazando. Estoy muy interesado en ver ejemplos en los que esto no funciona.

@jasongrout Gracias por mirar esto. El código donde golpeé esto es bastante complicado, y es posible que estuviera haciendo algo más mal. Si lo vuelvo a ver, intentaré aislarlo y dar un ejemplo.

@jasongrout Después de plt.show() ).

Si uno crea un gráfico en una celda de cuaderno y llama a plt.show() , las modificaciones posteriores a la figura no aparecen. Aquí hay un ejemplo mínimo:

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

Solo aparece el gráfico de primera línea.

Aquí hay un ejemplo aún más simple. Dos celdas, sin el backend %matplotlib inline . Quizás @tacaswell podría arrojar algo de luz sobre por qué el segundo plt.show no muestra un gráfico.

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

muestra la trama

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

no muestra nada

Confirmado en mi máquina con los últimos jupyter estable, ipython, ipywidgets, matplotlib, python 3.

Confirmado aquí también.

@jasongrout Ese es el comportamiento esperado porque en la primera celda creas una figura (y un lienzo y ejes) y luego la muestras. En la segunda celda, luego agrega una línea a los ejes existentes (en la figura existente). Con %matplotlib qt o ipympl (o cualquiera de las GUI completas) el gráfico _debería_ actualizarse con la segunda línea. No espero que esto funcione con inline, ya que solo tienes una oportunidad para actualizarlo (por eso no soy un fanático de inline, elimina _todas_ las funciones interactivas).

@tacaswell ¿Este comportamiento de inline también tiene sentido para el ejemplo anterior que di, que está todo en una celda? Aquí está de nuevo:

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

Creo que en plt.show el backend en línea se renderiza a sí mismo en un png y luego elimina el registro de la figura de pyplot para que no se vuelva a renderizar en el próximo programa, incluso si está en la misma celda, sin embargo varias llamadas a métodos de ejes sin show s adicionales deberían funcionar.

Para que quede claro, aquí está el código de backend en línea: https://github.com/ipython/ipykernel/blob/master/ipykernel/pylab/backend_inline.py

¿Existe una versión de ipywidgets y / o matplotlib que pueda volver para que mis widgets interactivos vuelvan a funcionar?

@jasongrout Parece por el código que podría querer establecer InlineBackend.close_figures en False . Pero no estoy seguro de cómo establecer ese tributo, ¿necesito usar algún tipo de magia?

(esa pregunta suena graciosa pero lo digo literalmente)

https://github.com/jupyter-widgets/ipywidgets/issues/1457 volvió a plantear este problema. @ellisonbg : ¿qué

Es un gran error, pero tal vez sea lo suficientemente importante como para hacerlo de todos modos. ¿Quizás también podamos detectar si realmente estamos tirando una figura y emitir una advertencia pidiendo a las personas que hagan una llamada explícita a show ()?

Hola a todos, no había estado siguiendo los detalles técnicos de esto, pero me encontré con el problema. Independientemente de los detalles de implementación, creo que es extremadamente importante que matplotlib + interact "simplemente funcione" tanto como sea posible.

Con plt.show() o flush_figures() , si el cuaderno se actualiza (sin reiniciar el kernel), la cifra desaparece incluso con Guardar cuaderno con widgets. Sin estas mitigaciones, este no es el caso, aunque todavía existe el problema de múltiples resultados de la trama. ¿Es esto intencionado? Lo mismo se aplica si se apaga el kernel, mientras que con versiones anteriores de ipywidgets, el estado final de la figura se guardaría en el cuaderno de forma similar a una trama no interactiva, lo cual fue bastante útil.

Lo siento si esto está fuera de alcance o si estoy haciendo algo mal. Ejecutando ipywidgets 6.0.0, matplotlib 2.0.2, notebook 5.0.0 y python 3.6.1.

¿Fue útil esta página
0 / 5 - 0 calificaciones