Warum ist das Plotten mit Matplotlib so langsam?

100

Ich evaluiere derzeit verschiedene Python-Plot-Bibliotheken. Im Moment versuche ich Matplotlib und bin ziemlich enttäuscht von der Leistung. Das folgende Beispiel wurde von SciPy-Beispielen modifiziert und gibt mir nur ~ 8 Bilder pro Sekunde!

Gibt es Möglichkeiten, dies zu beschleunigen, oder sollte ich eine andere Plotbibliothek auswählen?

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)
ich mich
quelle
Folgendes könnte relevant sein: stackoverflow.com/questions/5003094/…
NPE
2
@aix - Glumpy hat in diesem Beispiel nur geholfen, weil er es mit der schnellen Anzeige von Bilddaten zu tun hatte. In diesem Fall hilft es nicht.
Joe Kington
1
Versuchen Sie, das Backend zu ändern. Siehe meine Antwort: stackoverflow.com/a/30655528/2066079 . oder die diese FAQ über Backends: matplotlib.org/faq/usage_faq.html#what-is-a-backend
dberm22

Antworten:

115

Zunächst einmal (obwohl dies die Leistung überhaupt nicht ändert) sollten Sie Ihren Code bereinigen, ähnlich wie folgt:

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)

Mit dem obigen Beispiel erhalte ich ungefähr 10 fps.

Nur eine kurze Anmerkung: Abhängig von Ihrem genauen Anwendungsfall ist matplotlib möglicherweise keine gute Wahl. Es ist auf Zahlen in Publikationsqualität ausgerichtet, nicht auf Echtzeitanzeigen.

Es gibt jedoch eine Menge Dinge, die Sie tun können, um dieses Beispiel zu beschleunigen.

Es gibt zwei Hauptgründe, warum dies so langsam ist wie es ist.

1) Beim Aufrufen fig.canvas.draw()wird alles neu gezeichnet . Es ist dein Engpass. In Ihrem Fall müssen Sie Dinge wie Achsengrenzen, Häkchen usw. nicht neu zeichnen.

2) In Ihrem Fall gibt es viele Nebenhandlungen mit vielen Häkchen. Das Zeichnen dauert lange.

Beide können durch Blitting behoben werden.

Um das Blitting effizient durchzuführen, müssen Sie Backend-spezifischen Code verwenden. Wenn Sie sich in der Praxis wirklich Sorgen um flüssige Animationen machen, binden Sie in der Regel ohnehin Matplotlib-Plots in eine Art GUI-Toolkit ein, sodass dies kein großes Problem darstellt.

Ohne ein bisschen mehr darüber zu wissen, was Sie tun, kann ich Ihnen dort nicht helfen.

Trotzdem gibt es eine gui-neutrale Methode, die immer noch recht schnell ist.

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)

Das gibt mir ~ 200fps.

Um dies ein wenig bequemer zu machen, gibt es animationsin neueren Versionen von matplotlib ein Modul.

Als Beispiel:

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
quelle
Ihr Code ist in der Tat sehr schnell, aber ich habe 2000 Zeilen pro Achse! irgendwie erstellt "line.set_ydata" eine neue Zeile, anstatt sie zu aktualisieren - oder wird der Hintergrund einfach nicht gelöscht? Und warum ist Ihre Version so viel schneller? Nur weil Sie "draw ()" gelöscht und durch "ax.draw_artist" ersetzt haben?
Ich erinnere mich
In welchem ​​Beispiel? (Ich habe sie getestet, aber es ist möglich, dass die falsche Version in die Antwort eingefügt wurde.) Welche Version von matplotlib verwenden Sie?
Joe Kington
4
Hier ist ein Link zum resultierenden Bild i.imgur.com/aBRFz.png Könnte dies ein Artefakt sein, das durch meine Grafikkarte verursacht wurde?
erinnere mich
7
Ich habe das gleiche gesehen, was memyself in i.imgur.com/aBRFz.png gesehen hat, bis ich die Hintergrundaufnahme unter fig.show () verschoben habe.
Michael Browne
4
Schön, aber animationscheint den Plot nach intervalZeitraum zu aktualisieren. Was ist, wenn ich ihn nur aktualisieren möchte, wenn neue Daten verfügbar sind?
Alcott
28

Matplotlib liefert großartige Grafiken in Publikationsqualität, ist jedoch nicht sehr schnell optimiert. Es gibt eine Vielzahl von Python-Plot-Paketen, die auf Geschwindigkeit ausgelegt sind:

Luke
quelle
1
Ich genieße pyqtgraph.org/documentation für Echtzeit-Stream-Daten. tolle Arbeit
luke
11

Zu Beginn liefert die Antwort von Joe Kington sehr gute Ratschläge mit einem gui-neutralen Ansatz, und Sie sollten auf jeden Fall seinen Rat (insbesondere in Bezug auf Blitting) befolgen und in die Praxis umsetzen. Weitere Informationen zu diesem Ansatz finden Sie im Matplotlib-Kochbuch

Der nicht GUI-neutrale (GUI-voreingenommene?) Ansatz ist jedoch der Schlüssel zur Beschleunigung des Plots. Mit anderen Worten, das Backend ist äußerst wichtig, um die Geschwindigkeit zu zeichnen.

Setzen Sie diese beiden Zeilen, bevor Sie etwas anderes aus matplotlib importieren:

import matplotlib
matplotlib.use('GTKAgg') 

Natürlich gibt es verschiedene Optionen anstelle von GTKAgg, aber laut dem zuvor erwähnten Kochbuch war dies die schnellste. Weitere Optionen finden Sie unter dem Link zu Backends.

dberm22
quelle
Dies funktioniert jedoch nur unter Windows. Kennen Sie eine Möglichkeit, es auf dem Mac zum Laufen zu bringen? Der Grund, warum es Windows-spezifisch ist, ist, dass Pygtk Windows-spezifisch ist
user308827
2
pygtk ist nicht Windows-spezifisch. Tatsächlich ist es ein großer Schmerz, es unter Windows zum Laufen zu bringen (wenn es überhaupt möglich ist, habe ich aufgegeben.)
Joseph Redfern
7

Für die erste von Joe Kington vorgeschlagene Lösung (.copy_from_bbox & .draw_artist & canvas.blit) musste ich die Hintergründe nach der Zeile fig.canvas.draw () erfassen , andernfalls hatte der Hintergrund keine Auswirkung und ich erhielt das gleiche Ergebnis wie du hast erwähnt. Wenn Sie es nach fig.show () setzen, funktioniert es immer noch nicht wie von Michael Browne vorgeschlagen.

Setzen Sie also einfach die Hintergrundzeile nach 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
quelle
4
Sie sollten nur seine Antwort bearbeiten, anstatt sie als separaten Beitrag zu veröffentlichen
Endolith
1

Dies gilt möglicherweise nicht für viele von Ihnen, aber ich betreibe meine Computer normalerweise unter Linux. Daher speichere ich meine Matplotlib-Diagramme standardmäßig als PNG und SVG. Dies funktioniert unter Linux einwandfrei, ist aber in meinen Windows 7-Installationen [MiKTeX unter Python (x, y) oder Anaconda] unerträglich langsam. Daher habe ich diesen Code hinzugefügt, und dort funktioniert es wieder einwandfrei:

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
quelle