Sunday, February 27, 2011

How to quickly plot multiple line segments in Matplotlib

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.

ExamplePlot


Test Conditions:


Answers:

  • How to speed up Matplotlib plots
  • How to plot lines in Matplotlib
This work is licensed under a Creative Commons Attribution By license.

6 comments:

  1. This worked wonderfully for my application, thanks for the tip!

    ReplyDelete
  2. Hello there,

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

    ReplyDelete
  3. Very nice! I'm trying to make it work on 3d plot, but it gives some problems.

    ReplyDelete
  4. Nice tip.

    For a further boost, use NumPy arrays for the inputs.

    ReplyDelete
  5. it will plot additional lines around None positions upon zoom-in

    ReplyDelete
  6. what is you use `nan` in ylist instead of None? it has same effect but works naturally with arrays.

    ReplyDelete