Problem statement:
You need to plot a large collection of line segments in Matplotlib.
Solution:
If you try to plot a collection of lines segments in Matplotlib using sequential calls to plot, it can take a lot of time to generate the graph. There are two ways to speed up the plotting. The first is to use pythons extended call syntax and pass multiple lines segments at a time. The other is to create a single list of points, where the line segments are separated by ‘None’ values. The extended call method yields a minor improvement in performance. Forming a single list of line segments separated by ‘None’ results in a significant time savings. For the test cases below, using extended call syntax decreased drawing time by about 10% and using a single list separated by ‘None’ decreased drawing time by more than 99%.
Execution time for sequential plotting = 11.083103 sec
Execution time for extended call plotting = 10.245883 sec
Execution time when using None = = 0.027801 sec
Sample Code:
import time import sys import matplotlib.pyplot as pp import random if sys.platform == "win32": # On Windows, the best timer is time.clock() default_timer = time.clock else: # On most other platforms the best timer is time.time() default_timer = time.time # generate ends for the line segments xpairs = [] ypairs = [] for i in range(1000): xends = [random.random(), random.random()] yends = [random.random(), random.random()] xpairs.append(xends) ypairs.append(yends) ############################ # time sequential call to matplotlib pp.figure() pp.subplot(1,3,1) t0 = default_timer() for xends,yends in zip(xpairs,ypairs): pp.plot(xends,yends,'b-',alpha=0.1) t1 = default_timer() pp.title('Sequential Plotting') print 'Execution time for sequential plotting = %f sec' % (t1-t0) ############################ # build argument list call_list = [] for xends,yends in zip(xpairs,ypairs): call_list.append(xends) call_list.append(yends) call_list.append('-b') ############################ # time single call to matplotlib pp.subplot(1,3,2) t0 = default_timer() pp.plot(*call_list,alpha=0.1) t1 = default_timer() pp.title('Single Plot extended call') print 'Execution time for extended call plotting = %f sec' % (t1-t0) ############################ # rebuild ends using none to separate line segments xlist = [] ylist = [] for xends,yends in zip(xpairs,ypairs): xlist.extend(xends) xlist.append(None) ylist.extend(yends) ylist.append(None) ############################ # time single call to matplotlib pp.subplot(1,3,3) t0 = default_timer() pp.plot(xlist,ylist,'b-',alpha=0.1) t1 = default_timer() pp.title('Single Plot Using None') print 'Execution time when using None = %f sec' % (t1-t0) pp.show()
Discussion:
Sequential call and extended call syntax produce the same results. However, the single list of points using ‘None’ can generate a different image if transparency is used. In the sample code, ‘alpha=0.1’ was used. When sequential plots or extended call syntax is used, the color from each line segment is additive. When the line segments were combined into a single list of points, the line segments’ color was not additive. This is only an issue when using transparency. If alpha is set to 1.0 (or not set at all), then all of the plots will be identical.
Test Conditions:
- Win 7, 64 bit
- Intel i7
- PythonXY 2.5.6.2
Answers:
- How to speed up Matplotlib plots
- How to plot lines in Matplotlib
This worked wonderfully for my application, thanks for the tip!
ReplyDeleteHello there,
ReplyDeletenice article! I found yet another method to draw the lines using linecollection. On my maschine, the times read
Execution time for sequential plotting = 1.308422 sec
Execution time for extended call plotting = 0.773470 sec
Execution time when using None = 0.016326 sec
Execution time when using LineCollection = 0.080493 sec
putting LineCollection in second place overall or in first place considering only solutions with additive color!
Just add the following lines:
@imports:
from matplotlib.collections import LineCollection
@rebuild ends using none to separate line segments
xlist = []
ylist = []
linecol = []
for xends,yends in zip(xpairs,ypairs):
xlist.extend(xends)
xlist.append(None)
ylist.extend(yends)
ylist.append(None)
linecol.append((xends,yends))
@just before the pp.show() command
# time single call to matplotlib with LineCollection
axes = pp.subplot(2,2,4)
t0 = default_timer()
lines = LineCollection(linecol,alpha=0.1)
axes.add_collection(lines)
t1 = default_timer()
pp.title('Single Plot Using LineCollection')
print 'Execution time when using LineCollection = %f sec' % (t1-t0)
Very nice! I'm trying to make it work on 3d plot, but it gives some problems.
ReplyDeleteNice tip.
ReplyDeleteFor a further boost, use NumPy arrays for the inputs.
it will plot additional lines around None positions upon zoom-in
ReplyDeletewhat is you use `nan` in ylist instead of None? it has same effect but works naturally with arrays.
ReplyDelete