Ipython: % matplotlib inline não funciona bem com ipywidgets 6.0 interact

Criado em 3 mar. 2017  ·  46Comentários  ·  Fonte: ipython/ipython

Parece que no notebook jupyter, %matplotlib inline não joga mais bem com os novos ipywidgets 6.0 @interact . Tanto quanto posso dizer, o back-end inline espera até que a célula esteja completamente concluída antes de enviar a mensagem display_data com a imagem (consulte o gancho pós-execução registrado em https://github.com/ipython/ipython/blob/7cde22957303ab53df8bd464ad5d7ed616197f31/ IPython / core / pylabtools.py # L383). No ipywidgets 6.0, mudamos a forma como as interações funcionam para ser mais específico sobre as saídas que capturamos - capturamos as mensagens de saída enviadas durante a execução da função e as exibimos na saída interativa. No entanto, como o backend inline matplotlib envia sua imagem depois que a função é executada, nós apenas obtemos uma série enorme de imagens na área de saída da célula.

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

Esta questão é sobre como iniciar a discussão para ver se há uma maneira de chegarmos a uma boa solução que funcione bem com o back-end inline (o que é útil) e também funcione bem com a interação do ipywidgets.

Por exemplo, se houvesse uma maneira de dizer ao backend embutido para liberar sua imagem imediatamente e não após a conclusão da execução de uma célula, isso pode ser o suficiente. Este código funciona, por exemplo ( sem o back-end inline 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.

Comentários muito úteis

Olá a todos, Não tenho seguido os detalhes técnicos, mas encontrei o problema. Independentemente dos detalhes de implementação, acho extremamente importante que matplotlib + interact "simplesmente funcione" tanto quanto possível.

Todos 46 comentários

Isso parece funcionar, mas alguém tem uma solução de uma maneira melhor?

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 trabalhou no backend do mpl embutido.

Não acho que possamos fazer muito.

Qual heurística você usaria para exibir uma figura? Você não pode fazer isso em todas as figuras, atualizar tantas figuras de reutilização de código, mas é comum ter animação.

Invólucro especial @interact para limpar após cada operação?

Invólucro especial @interact para enxaguar após cada operação?

Bom ponto, obrigado pela sugestão. Não parece muito limpo fazer isso (para o caso especial de um backend específico para matplotlib). Talvez eu espere para ver se é um grande problema para os usuários liberar explicitamente a saída como no exemplo acima. Quer dizer, explícito é melhor do que implícito e tudo isso ...

Edit: para resumir, acho que concordo que não há muito que possamos (ou deveríamos) fazer ...

Acontece que tudo isso é muito mais fácil do que eu pensava - devo ter feito algo errado quando tentei testar isso antes. Não precisamos chamar flush_figures, basta chamar o plt.show padrão:

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

Acho que é perfeitamente razoável pedir às pessoas que insiram um comando show () em sua interação, então estou fechando isso.

Se quisermos manter esse comportamento (e outras saídas pós-execução em potencial), talvez os ganchos pós-X possam ser invocados explicitamente em interact antes que a captura de saída seja concluída. Em geral, minha expectativa de chamar uma função via interact é que ela se comporte exatamente da mesma forma como se eu tivesse chamado a função uma vez em uma célula.

Eu esperaria que as pessoas ficassem surpresas com todos os exemplos de interact-with-matplotlib para parar de mostrar figuras, uma vez que matplotlib 'interativo' geralmente significa que plt.show é desnecessário. Colocá-lo em uma função interativa provavelmente não deveria mudar isso.

Isso me lembra um pouco de exigir sys.stdout.flush() para ver a saída, antes do IOThread ser introduzido. Fazia sentido por que era necessário quando você pensava sobre como as coisas funcionavam, mas isso não o tornava menos surpreendente porque não se comportava como as pessoas esperavam.

Não acho que devamos executar todos os manipuladores pós-execução automaticamente (quem sabe o que eles farão - talvez muitas coisas!). Que tal ter um gancho flush_display ? Nós o liberaríamos antes de iniciar a interação e o liberaríamos novamente antes de encerrá-lo. Dessa forma, é exatamente análogo a liberar stdout / stdin.

Bem, ou por outro lado, talvez faça sentido chamar a pós-execução, se pensarmos em uma interação realmente agindo como uma única célula própria. Mas então provavelmente desejaríamos chamar pós-execução antes que a interação fosse executada, assim como depois, apenas no caso da interação estar em uma célula com outras coisas.

Reabrindo, pois a conversa ainda não foi encerrada ...

Gosto da ideia de um gancho flush_display e funcionaria em teoria, embora exija lançamentos coordenados de ipython, ipykernel e ipywidgets para definir um novo gancho e alternar o back-end para usá-lo.

se pensarmos em uma interação realmente agindo como uma única célula própria

Acho que essa é a chave, e é um grande 'se'. É assim que penso, mas não sou todo mundo (ainda).

se pensamos em uma interação realmente agindo como uma única célula própria
Acho que essa é a chave, e é um grande 'se'. É assim que penso, mas não sou todo mundo (ainda).

Uma coisa confusa sobre isso é que se tivéssemos uma célula com código, então uma execução de interação e, em seguida, um pouco mais de código, a célula seria realmente executada como três células (ou seja, a execução pós-execução três vezes diferentes). Acho que flush_display faz mais sentido nesse caso.

Pergunta adicional: a saída espelhada no JLab funcionaria?

Os widgets

@SylvainCorlay Eu sei que eles funcionam agora, e o pessoal de Ann Arbor adora. Minha pergunta era se funcionaria se as alterações fossem feitas conforme discutido acima.

peguei desculpe pelo barulho.

No longo prazo, acho que uma abordagem que deve funcionar melhor é o backend de widgets ipympl para matplotlib.

No entanto, precisaremos tornar tudo isso mais facilmente instalado com o conf.d etc ...

Sim, a saída espelhada deve funcionar com a alteração discutida acima. Obrigado por ter certeza!

Uma coisa confusa sobre isso é que se tivéssemos uma célula com código, então uma execução de interação e, em seguida, um pouco mais de código, a célula seria realmente executada como três células (ou seja, a execução pós-execução três vezes diferentes).

Na verdade, isso seria um pouco estranho. Não seria duas vezes, entretanto? Um para a célula como um todo e outro para a primeira invocação de interact? Dependendo de como você pensa sobre isso, isso também faz sentido: com @interact , você tem duas execuções: uma para a declaração e outra para a primeira invocação da função interagida com.

Não acho que você teria mais de um se usasse @interact_manual , por exemplo (não tenho certeza).

Não seria duas vezes, entretanto? Um para a célula como um todo e outro para a primeira invocação de interact?

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

Como a plotagem é cumulativa e você deseja tratar a interação como uma célula separada em si mesma, é necessário esvaziar a figura antes que a interação seja executada, em seguida, esvaziar a figura dentro da interação e, em seguida, novamente no final da célula. Isso é exatamente o que fazemos com a limpeza de stdout também.

Na verdade, não tenho certeza do que o usuário espera no exemplo acima. O que você esperaria?

Apenas um pensamento: o exemplo acima não é muito lógico; Pessoalmente, esperaria três plotagens em que apenas uma mudasse dinamicamente, mas se eu fizesse algo assim, faria todas as chamadas de plotagem dentro de @interact .

Existe alguma razão para não chamar flush_figures após cada .plot() ?

@myyc é uma mudança semântica significativa do comportamento. plot adiciona itens ao lote ....

Eu quis dizer no comportamento inline específico ipython , em oposição ao matplotlib.

editar : Tenho certeza de que é um caso de uso mais comum ter uma interação por célula com isso como "a única função de plotagem", ao contrário do caso descrito acima ...

Eu entendo. Embora eu não ache que isso deva ter um comportamento tão diferente.

A abordagem adequada no futuro provavelmente será usar o backend do notebook ou ipympl e editar a figura.

A implementação de soluções alternativas específicas para matplotlib para o back-end embutido na interação do ipywidgets não parece correta.

No código fornecido por jason:

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

a saída esperada é uma figura com muitas linhas.

Sim, concordo plenamente. Eu consertei temporariamente dessa forma (substituindo os métodos de plotagem de pandas) na minha configuração local, "esperando por uma correção" :) Desconsiderando isso, isso introduz um monte de efeitos colaterais terríveis.

Já vi esse problema ser resolvido em uma variedade de projetos do github, então estou me perguntando qual deve ser o melhor caminho de resolução.

editar : por que não escrever desta forma então, de forma mais explícita?

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

Você pode verificar o pacote ipympl (https://github.com/matplotlib/jupyter-matplotlib), que é o widget matplotlib. ipympl ainda está em um estágio bem inicial, mas deve ter um cronograma de lançamento mais rápido do que o núcleo matplotlib e suporte anterior para jupyterlab etc ...

OK, parece que a substituição é uma péssima ideia. Estou ansioso por uma correção no upstream :)

Apenas para contribuir com um ponto de vista de usuário sem educação: depois de atualizar para o ipywidgets 6.0, descobri que muito do meu código de widget não funcionava (devido ao problema discutido aqui). Consegui corrigir isso adicionando chamadas a plt.show() , mas também descobri que preciso ter muito cuidado ao adicionar essas chamadas; parece que às vezes chamar isso muito cedo faz com que gráficos posteriores não apareçam (mesmo se plt.show() for chamado novamente). Não pesquisei o suficiente para criar um exemplo mínimo, mas o comportamento atual não me parece intuitivo - estou programando por tentativa e erro para contorná-lo.

Que mudança exata em ipywidgets realmente causou o novo comportamento? Os ganchos pós-execução não estão sendo invocados ou ainda estão sendo invocados, mas sua saída agora foi descartada porque o widget está sendo específico sobre quais saídas pertencem ao widget? Se for o último, então parece que isolar a saída capturada durante o corpo da função não é a coisa certa a fazer.

Os ganchos pós-execução estão sendo invocados (tenho quase certeza) - você pode ver isso fazendo:

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

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

e movendo o controle deslizante. Os gráficos serão anexados à célula - os ganchos de pós-execução estão enviando as mensagens de dados de exibição. As execuções de interação, de fato, estão sendo específicas sobre apenas capturar a saída que acontece durante a invocação da função. Acho que é a coisa certa a fazer. Por exemplo, duas interações diferentes podem compartilhar exatamente a mesma instância do controle 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()

Quando o único controle deslizante é ajustado (em qualquer IU), ambas as interações mostram o controle deslizante se movendo e as funções de interação são executadas na mesma chamada de execução. Seria errado que as duas interações tentassem capturar qualquer saída de um gancho pós-execução. Acho que a falha aqui é que estamos usando pós-execução para gerar saída, e isso é muito grosso de um martelo. Devemos ter um método mais refinado para liberar a saída gerada em um bloco de código, que pode ser chamado várias vezes em uma única execução.

Devemos ter um método mais refinado para liberar a saída gerada em um bloco de código

Para a plotagem de matplotlib, esse flushing acontece ao fazer plt.show

Tenho que ser muito cuidadoso sobre onde adiciono essas chamadas; parece que às vezes chamar isso muito cedo faz com que os gráficos posteriores não apareçam

Estou muito interessado em ver exemplos onde isso acontece. Queremos tornar o comportamento intuitivo e compreensível.

Tenho que ser muito cuidadoso sobre onde adiciono essas chamadas; parece que às vezes chamar isso muito cedo faz com que os gráficos posteriores não apareçam

Estou muito interessado em ver exemplos onde isso acontece. Queremos tornar o comportamento intuitivo e compreensível.

Para elaborar, acho que você deve ser capaz de obter o equivalente ao comportamento anterior apenas adicionando plt.show() como a última linha de qualquer função de interação que esteja fazendo plotagem. Estou muito interessado em ver exemplos em que isso não funcione.

@jasongrout Obrigado por olhar para isso. O código em que achei isso é bastante complicado e é possível que eu estivesse fazendo outra coisa errada. Se o vir novamente, tentarei isolá-lo e dar um exemplo.

@jasongrout Depois de pesquisar um pouco mais, acho que o que encontrei é uma propriedade do bloco de notas Jupyter em si, não de widgets (mas ele interage com widgets agora porque somos forçados a chamar plt.show() ).

Se alguém criar um gráfico em uma célula de notebook e chamar plt.show() , as modificações posteriores na figura não aparecerão. Aqui está um exemplo 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)

Apenas o gráfico da primeira linha aparece.

Aqui está um exemplo ainda mais simples. Duas células, sem o back-end %matplotlib inline . Talvez @tacaswell possa esclarecer por que o segundo plt.show não mostra um gráfico.

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

mostra o enredo

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

não mostra nada

Confirmado em minha máquina com o mais recente estável jupyter, ipython, ipywidgets, matplotlib, python 3.

Confirmado aqui também.

@jasongrout Esse é o comportamento esperado porque na primeira célula você cria uma figura (e tela e eixos) e depois a exibe. Na segunda célula, você adiciona uma linha aos eixos existentes (na figura existente). Com %matplotlib qt ou ipympl (ou qualquer uma das GUIs completas), o gráfico _deve_ ser atualizado com a segunda linha. Não espero que isso funcione com inline, pois você só tem uma chance para atualizá-lo (é por isso que não sou muito fã de inline, ele elimina _todas_ da funcionalidade interativa).

@tacaswell Esse comportamento de inline também faz sentido para o exemplo anterior que dei, que está tudo em uma célula? Aqui está de novo:

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

Eu acho que em plt.show o back-end inline renderiza a si mesmo em um png e, em seguida, cancela o registro da figura do pyplot para que ela não seja renderizada novamente no próximo show, mesmo se estiver na mesma célula, no entanto múltiplas chamadas para métodos de eixos sem show s extras devem funcionar.

Só para ficar claro, aqui está o código de back-end inline: https://github.com/ipython/ipykernel/blob/master/ipykernel/pylab/backend_inline.py

Existe uma versão de ipywidgets e / ou matplotlib que posso reverter para fazer meus widgets interativos funcionarem novamente?

@jasongrout Pelo código, parece que quero definir InlineBackend.close_figures como False . Mas não tenho certeza de como definir esse atributo - preciso usar algum tipo de magia?

(essa pergunta parece engraçada, mas quero dizer literalmente)

https://github.com/jupyter-widgets/ipywidgets/issues/1457 levantou esse problema novamente. @ellisonbg - e se implementarmos a solução alternativa flush_figures que mencionei acima - chamar flush_figures antes e depois da execução de uma interação, para eliminar quaisquer plotagens antes da interação e eliminar qualquer coisa plotada durante a interação? Acho que talvez tenhamos que verificar se estamos executando no back-end do mpl embutido.

É uma grande confusão, mas talvez seja importante o suficiente para fazê-lo de qualquer maneira. Talvez também possamos detectar se estamos realmente eliminando uma figura e emitindo um aviso pedindo às pessoas que façam uma chamada show () explícita.

Olá a todos, Não tenho seguido os detalhes técnicos, mas encontrei o problema. Independentemente dos detalhes de implementação, acho extremamente importante que matplotlib + interact "simplesmente funcione" tanto quanto possível.

Com plt.show() ou flush_figures() , se o notebook for atualizado (sem reiniciar o kernel), a figura desaparece mesmo com Salvar Notebook com Widgets. Sem essas atenuações, este não é o caso, embora ainda haja o problema de múltiplos resultados de plotagem. Isso é intencional? O mesmo se aplica se o kernel for desligado, enquanto com versões anteriores de ipywidgets o estado final da figura seria salvo no notebook semelhante a um gráfico não interativo, o que era bastante útil.

Desculpe se isso está fora do escopo ou se estou fazendo algo errado. Executando ipywidgets 6.0.0, matplotlib 2.0.2, notebook 5.0.0 e python 3.6.1.

Esta página foi útil?
0 / 5 - 0 avaliações