why is plotting with Matplotlib so slow?
Asked Answered
P

5

134

I'm currently evaluating different python plotting libraries. Right now I'm trying matplotlib and I'm quite disappointed with the performance. The following example is modified from SciPy examples and gives me only ~ 8 frames per second!

Any ways of speeding this up or should I pick a different plotting library?

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)
Parvis answered 21/1, 2012 at 19:11 Comment(4)
The following might be relevant: #5003594Preshrunk
@aix - Glumpy only helped in that example because he was dealing with rapidly displaying image data. It won't help in this case.Massarelli
Try changing the backend. See my answer: https://mcmap.net/q/49374/-why-is-plotting-with-matplotlib-so-slow. or the this FAQ about backends: matplotlib.org/faq/usage_faq.html#what-is-a-backendCathodoluminescence
Using fig.canvas.draw_idle() instead of fig.canvas.draw() worked for me.Benevolence
M
138

First off, (though this won't change the performance at all) consider cleaning up your code, similar to this:

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)

With the above example, I get around 10fps.

Just a quick note, depending on your exact use case, matplotlib may not be a great choice. It's oriented towards publication-quality figures, not real-time display.

However, there are a lot of things you can do to speed this example up.

There are two main reasons why this is as slow as it is.

1) Calling fig.canvas.draw() redraws everything. It's your bottleneck. In your case, you don't need to re-draw things like the axes boundaries, tick labels, etc.

2) In your case, there are a lot of subplots with a lot of tick labels. These take a long time to draw.

Both these can be fixed by using blitting.

To do blitting efficiently, you'll have to use backend-specific code. In practice, if you're really worried about smooth animations, you're usually embedding matplotlib plots in some sort of gui toolkit, anyway, so this isn't much of an issue.

However, without knowing a bit more about what you're doing, I can't help you there.

Nonetheless, there is a gui-neutral way of doing it that is still reasonably fast.

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)

This gives me ~200fps.

To make this a bit more convenient, there's an animations module in recent versions of matplotlib.

As an example:

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()
Massarelli answered 21/1, 2012 at 20:1 Comment(11)
your code is very fast indeed, however I end up with 2000 lines per axis! somehow "line.set_ydata" creates a new line instead of updating it - or is the background just not being cleared? Plus, why is your version so much faster? just because you dropped "draw()" and replaced it with "ax.draw_artist"?Parvis
In which example? (I tested them, but it's possible copy-pasted the wrong version into the answer.) Also, which version of matplotlib are you using?Massarelli
I mean the second example. I use matplotlib version 1.0.1.Parvis
As for why it's so much faster, yes, it's basically because I'm not drawing the entire canvas, only the line itself.Massarelli
Hmm... The second example works perfectly for me... I'm using a development version of matplotlib at the moment, though... I've used that same strategy plenty of times in the past, though normally I'd register the drawing as a idle event in gtk or tk. I've got to run at the moment, but I'll have a look at it later. I'm probably doing something that only happens to work with the most recent version, but won't with earlier versions. The general strategy will certainly work on earlier versions (and it's your best option if you don't have the animation module).Massarelli
here's a link to the resulting image i.imgur.com/aBRFz.png might this be an artifact caused by my graphics card?Parvis
I was seeing the same thing that memyself was seeing in i.imgur.com/aBRFz.png until I moved the background capture below the fig.show().Friar
Nice, but animation seems to update the plot by interval period of time, what if I just want to update it when new data is ready?Disproportion
I just run the first code example in your solution, it doesn't show any animation or dynamically updating plot, I mean the plot just showed up after firing the code, why?Disproportion
I wrote a PyQt application once using matplotlib for data visualization, and it was smooth, fast, and efficient, also the image quality was ok, for publication quality I would never use a png image, or similar, that's why there exists tikz pgfplots they are so nice, that is real high quality. As for matplotlib it has become so slow, that it's unusable, I am uninstalling it right now and going back to gnuplot.Hurryscurry
@JoeKington Where can I get more information on these techniques for accelarating matplotlib for live updating data?Louannlouanna
L
43

Matplotlib makes great publication-quality graphics, but is not very well optimized for speed. There are a variety of python plotting packages that are designed with speed in mind:

Lymphangial answered 19/6, 2012 at 0:50 Comment(1)
i thoroughly enjoy pyqtgraph.org/documentation for real time stream data. great job lukeSzymanowski
C
15

To start, Joe Kington's answer provides very good advice using a gui-neutral approach, and you should definitely take his advice (especially about Blitting) and put it into practice. More info on this approach, read the Matplotlib Cookbook

However, the non-GUI-neutral (GUI-biased?) approach is key to speeding up the plotting. In other words, the backend is extremely important to plot speed.

Put these two lines before you import anything else from matplotlib:

import matplotlib
matplotlib.use('GTKAgg') 

Of course, there are various options to use instead of GTKAgg, but according to the cookbook mentioned before, this was the fastest. See the link about backends for more options.

Cathodoluminescence answered 4/6, 2015 at 22:49 Comment(7)
This only works on windows though, do you know of a way to make it work on Mac. The reason it is windows specific is that pygtk is windows specificHeadwork
pygtk is not windows specific. In fact, it's a huge pain getting it working under Windows (if it's even possible, I've given up.)Narrowminded
Changing to non-interactive ("Agg") backend made my script run in 2.65 seconds instead of 133 seconds.Assorted
@np8 Glad it's still helpful 7 years later!Cathodoluminescence
@np8 Hower, there's a drawback: setting the backend to "Agg" does not allow to show() the plot. According to the manual, "Agg is a non-interactive backend that can only write to files."Pr
@DanielK. that's correct. I think I had a script creating many plots and saving them to files. For interactive sessions one needs an interactive backend.Assorted
@Cathodoluminescence I think GTKAgg cannot be used in the current version 3.7 of matplotlib any more. It's not in the list of valid backends. Further, using it throws "ValueError: 'gtkagg' is not a valid value for backend" for me.Pr
W
9

For the first solution proposed by Joe Kington ( .copy_from_bbox & .draw_artist & canvas.blit), I had to capture the backgrounds after the fig.canvas.draw() line, otherwise the background had no effect and I got the same result as you mentioned. If you put it after the fig.show() it still does not work as proposed by Michael Browne.

So just put the background line after the 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]
Whitcomb answered 17/4, 2013 at 13:16 Comment(1)
you should just edit his answer instead of posting as a separate oneDubbing
T
0

This may not apply to many of you, but I'm usually operating my computers under Linux, so by default I save my matplotlib plots as PNG and SVG. This works fine under Linux but is unbearably slow on my Windows 7 installations [MiKTeX under Python(x,y) or Anaconda], so I've taken to adding this code, and things work fine over there again:

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')
Tatro answered 16/9, 2016 at 7:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.