por que a plotagem com Matplotlib é tão lenta?

100

Atualmente estou avaliando diferentes bibliotecas de plotagem de python. No momento estou tentando matplotlib e estou bastante decepcionado com o desempenho. O exemplo a seguir foi modificado a partir de exemplos SciPy e me dá apenas ~ 8 quadros por segundo!

Alguma maneira de acelerar isso ou devo escolher uma biblioteca de plotagem diferente?

from pylab import *
import time

ion()
fig = figure()
ax1 = fig.add_subplot(611)
ax2 = fig.add_subplot(612)
ax3 = fig.add_subplot(613)
ax4 = fig.add_subplot(614)
ax5 = fig.add_subplot(615)
ax6 = fig.add_subplot(616)

x = arange(0,2*pi,0.01)
y = sin(x)
line1, = ax1.plot(x, y, 'r-')
line2, = ax2.plot(x, y, 'g-')
line3, = ax3.plot(x, y, 'y-')
line4, = ax4.plot(x, y, 'm-')
line5, = ax5.plot(x, y, 'k-')
line6, = ax6.plot(x, y, 'p-')

# turn off interactive plotting - speeds things up by 1 Frame / second
plt.ioff()


tstart = time.time()               # for profiling
for i in arange(1, 200):
    line1.set_ydata(sin(x+i/10.0))  # update the data
    line2.set_ydata(sin(2*x+i/10.0))
    line3.set_ydata(sin(3*x+i/10.0))
    line4.set_ydata(sin(4*x+i/10.0))
    line5.set_ydata(sin(5*x+i/10.0))
    line6.set_ydata(sin(6*x+i/10.0))
    draw()                         # redraw the canvas

print 'FPS:' , 200/(time.time()-tstart)
eu mesmo
fonte
O seguinte pode ser relevante: stackoverflow.com/questions/5003094/…
NPE
2
@aix - Glumpy apenas ajudou nesse exemplo porque ele estava lidando com a exibição rápida de dados de imagem. Não vai ajudar neste caso.
Joe Kington
1
Tente alterar o back-end. Veja minha resposta: stackoverflow.com/a/30655528/2066079 . ou este FAQ sobre back-ends: matplotlib.org/faq/usage_faq.html#what-is-a-backend
dberm22

Respostas:

115

Em primeiro lugar, (embora isso não mude o desempenho de forma alguma), considere limpar seu código, semelhante a este:

import matplotlib.pyplot as plt
import numpy as np
import time

x = np.arange(0, 2*np.pi, 0.01)
y = np.sin(x)

fig, axes = plt.subplots(nrows=6)
styles = ['r-', 'g-', 'y-', 'm-', 'k-', 'c-']
lines = [ax.plot(x, y, style)[0] for ax, style in zip(axes, styles)]

fig.show()

tstart = time.time()
for i in xrange(1, 20):
    for j, line in enumerate(lines, start=1):
        line.set_ydata(np.sin(j*x + i/10.0))
    fig.canvas.draw()

print 'FPS:' , 20/(time.time()-tstart)

Com o exemplo acima, obtenho cerca de 10 fps.

Apenas uma nota rápida, dependendo do seu caso de uso exato, matplotlib pode não ser uma ótima escolha. É orientado para números de qualidade de publicação, não para exibição em tempo real.

No entanto, há muitas coisas que você pode fazer para acelerar este exemplo.

Existem duas razões principais pelas quais isso é tão lento quanto é.

1) Chamar fig.canvas.draw()redesenha tudo . É o seu gargalo. No seu caso, você não precisa redesenhar coisas como os limites dos eixos, rótulos de escala, etc.

2) No seu caso, há muitos subplots com muitos rótulos de escala. Demora muito para desenhar.

Ambos podem ser corrigidos usando blitting.

Para fazer blitting com eficiência, você terá que usar um código específico do backend. Na prática, se você está realmente preocupado com animações suaves, normalmente está incorporando gráficos matplotlib em algum tipo de kit de ferramentas de interface do usuário, então isso não é um grande problema.

Porém, sem saber um pouco mais sobre o que você está fazendo, não posso te ajudar nisso.

No entanto, existe uma maneira neutra de gui de fazer isso que ainda é razoavelmente rápida.

import matplotlib.pyplot as plt
import numpy as np
import time

x = np.arange(0, 2*np.pi, 0.1)
y = np.sin(x)

fig, axes = plt.subplots(nrows=6)

fig.show()

# We need to draw the canvas before we start animating...
fig.canvas.draw()

styles = ['r-', 'g-', 'y-', 'm-', 'k-', 'c-']
def plot(ax, style):
    return ax.plot(x, y, style, animated=True)[0]
lines = [plot(ax, style) for ax, style in zip(axes, styles)]

# Let's capture the background of the figure
backgrounds = [fig.canvas.copy_from_bbox(ax.bbox) for ax in axes]

tstart = time.time()
for i in xrange(1, 2000):
    items = enumerate(zip(lines, axes, backgrounds), start=1)
    for j, (line, ax, background) in items:
        fig.canvas.restore_region(background)
        line.set_ydata(np.sin(j*x + i/10.0))
        ax.draw_artist(line)
        fig.canvas.blit(ax.bbox)

print 'FPS:' , 2000/(time.time()-tstart)

Isso me dá ~ 200 fps.

Para tornar isso um pouco mais conveniente, há um animationsmódulo nas versões recentes do matplotlib.

Como um exemplo:

import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np

x = np.arange(0, 2*np.pi, 0.1)
y = np.sin(x)

fig, axes = plt.subplots(nrows=6)

styles = ['r-', 'g-', 'y-', 'm-', 'k-', 'c-']
def plot(ax, style):
    return ax.plot(x, y, style, animated=True)[0]
lines = [plot(ax, style) for ax, style in zip(axes, styles)]

def animate(i):
    for j, line in enumerate(lines, start=1):
        line.set_ydata(np.sin(j*x + i/10.0))
    return lines

# We'd normally specify a reasonable "interval" here...
ani = animation.FuncAnimation(fig, animate, xrange(1, 200), 
                              interval=0, blit=True)
plt.show()
Joe Kington
fonte
seu código é muito rápido, porém acabo com 2.000 linhas por eixo! de alguma forma, "line.set_ydata" cria uma nova linha em vez de atualizá-la - ou o plano de fundo simplesmente não está sendo limpo? Além disso, por que sua versão é tão mais rápida? só porque você largou "draw ()" e o substituiu por "ax.draw_artist"?
memyself
Em qual exemplo? (Eu testei, mas é possível copiar e colar a versão errada na resposta.) Além disso, qual versão do matplotlib você está usando?
Joe Kington
4
aqui está um link para a imagem resultante i.imgur.com/aBRFz.png pode ser um artefato causado pela minha placa de vídeo?
memyself
7
Eu estava vendo a mesma coisa que o memyself estava vendo em i.imgur.com/aBRFz.png até mover a captura de plano de fundo abaixo de fig.show ().
Michael Browne
4
Legal, mas animationparece atualizar o gráfico por intervalperíodo de tempo, e se eu só quiser atualizá-lo quando novos dados estiverem prontos?
Alcott
28

Matplotlib produz gráficos com ótima qualidade de publicação, mas não é muito bem otimizado para velocidade. Há uma variedade de pacotes de plotagem python que são projetados com a velocidade em mente:

Lucas
fonte
1
Eu adoro pyqtgraph.org/documentation para dados de stream em tempo real. ótimo trabalho luke
qrtLs
11

Para começar, a resposta de Joe Kington fornece conselhos muito bons usando uma abordagem neutra do gui, e você definitivamente deve seguir o conselho dele (especialmente sobre Blitting) e colocá-lo em prática. Mais informações sobre esta abordagem, leia o Matplotlib Cookbook

No entanto, a abordagem não neutra da GUI (polarizada pela GUI?) É a chave para acelerar a plotagem. Em outras palavras, o back - end é extremamente importante para traçar a velocidade.

Coloque essas duas linhas antes de importar qualquer outra coisa de matplotlib:

import matplotlib
matplotlib.use('GTKAgg') 

Claro, existem várias opções para usar em vez de GTKAgg, mas de acordo com o livro de receitas mencionado antes, esta foi a mais rápida. Veja o link sobre back-ends para mais opções.

dberm22
fonte
Isso só funciona no Windows, você conhece uma maneira de fazê-lo funcionar no Mac? O motivo de ser específico do Windows é que o pygtk é específico do Windows
user308827
2
pygtk não é específico do Windows. Na verdade, é uma grande dor fazê-lo funcionar no Windows (se for possível, eu desisti.)
Joseph Redfern
7

Para a primeira solução proposta por Joe Kington (.copy_from_bbox & .draw_artist & canvas.blit), tive que capturar os fundos após a linha fig.canvas.draw (), caso contrário, o fundo não teve efeito e obtive o mesmo resultado que você mencionou. Se você colocá-lo após fig.show (), ele ainda não funcionará como proposto por Michael Browne.

Portanto, basta colocar a linha de fundo após o canvas.draw ():

[...]
fig.show()

# We need to draw the canvas before we start animating...
fig.canvas.draw()

# Let's capture the background of the figure
backgrounds = [fig.canvas.copy_from_bbox(ax.bbox) for ax in axes]
Sebastian
fonte
4
você deve apenas editar sua resposta em vez de postar separadamente
endólito
1

Isso pode não se aplicar a muitos de vocês, mas geralmente estou operando meus computadores no Linux, então, por padrão, salvo meus gráficos matplotlib como PNG e SVG. Isso funciona bem no Linux, mas é insuportavelmente lento em minhas instalações do Windows 7 [MiKTeX em Python (x, y) ou Anaconda], então comecei a adicionar este código e as coisas funcionam bem lá novamente:

import platform     # Don't save as SVG if running under Windows.
#
# Plot code goes here.
#
fig.savefig('figure_name.png', dpi = 200)
if platform.system() != 'Windows':
    # In my installations of Windows 7, it takes an inordinate amount of time to save
    # graphs as .svg files, so on that platform I've disabled the call that does so.
    # The first run of a script is still a little slow while everything is loaded in,
    # but execution times of subsequent runs are improved immensely.
    fig.savefig('figure_name.svg')
marisano
fonte