Leon palafox
Leon palafox

Reputation: 2785

Animate a Histogram in Python

I'm trying to animate a histogram over time, and so far the code I have is the following one:

import matplotlib.pyplot as plt
import numpy as np
import time
plt.ion()
fig = plt.figure()
ax = fig.add_subplot(111)

alphab = ['A', 'B', 'C', 'D', 'E', 'F']
frequencies = [1, 44, 12, 11, 2, 10]

pos = np.arange(len(alphab))
width = 1.0     # gives histogram aspect to the bar diagram
ax.set_xticks(pos + (width / 2))
ax.set_xticklabels(alphab)
for bin_idx in np.linspace(0,1000000,100000000):
     t = time.time()
     #Here we just change the first bin, so it increases through the animation.
     frequencies[0] = bin_idx
     line1 =plt.bar(pos, frequencies, width, color='r')
     plt.draw()
     elapsed = time.time() - t
     print elapsed

The code works, but the outputs shows how after some iterations it just becomes way slower than at the beginning. Is there a way to speed things up, we want to update this in real time, and the process in which it runs is pretty fast.

Also, it is important to notice, that I do not want a post processing animation, we want real time updates, so the histogram animation example was not working for this particular process.

Thanks

Upvotes: 3

Views: 4478

Answers (2)

unutbu
unutbu

Reputation: 880259

If you have a newer version of Matplotlib there is an animations.FuncAnimation class which can help reduce some of the boiler-plate code. (See this page for an example.) It is pretty fast (~ 52 frames per second):

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

clock = timeit.default_timer

fig, ax = plt.subplots()

alphab = ['A', 'B', 'C', 'D', 'E', 'F']
frequencies = [1, 44, 12, 11, 2, 10]

pos = np.arange(len(alphab))
width = 1.0     # gives histogram aspect to the bar diagram
ax.set_xticks(pos + (width / 2))
ax.set_xticklabels(alphab)

rects = plt.bar(pos, frequencies, width, color='r')
start = clock()

def animate(arg, rects):
    frameno, frequencies = arg
    for rect, f in zip(rects, frequencies):
        rect.set_height(f)
    print("FPS: {:.2f}".format(frameno / (clock() - start))) 

def step():
    for frame, bin_idx in enumerate(np.linspace(0,1000000,100000000), 1):
        #Here we just change the first bin, so it increases through the animation.
        frequencies[0] = bin_idx
        yield frame, frequencies


ani = animation.FuncAnimation(fig, animate, step, interval=10,
                              repeat=False, blit=False, fargs=(rects,))
plt.show()

If you don't have a newer version of Matplotlib, here is the older way to do it. It is slightly slower (~ 45 frames per second):

Don't call plt.bar with each iteration of the loop. Instead, call it just once, save the rects return value, and then call set_height to modify the height of those rects on subsequent iterations of the loop. This trick (and others) is explained in the Matplotlib Animations Cookbook.

import sys
import matplotlib as mpl
mpl.use('TkAgg')  # do this before importing pyplot
import matplotlib.pyplot as plt
import numpy as np
import timeit

clock = timeit.default_timer

fig, ax = plt.subplots()

alphab = ['A', 'B', 'C', 'D', 'E', 'F']
frequencies = [1, 44, 12, 11, 2, 10]

pos = np.arange(len(alphab))
width = 1.0     # gives histogram aspect to the bar diagram
ax.set_xticks(pos + (width / 2))
ax.set_xticklabels(alphab)

def animate():
    start = clock()
    rects = plt.bar(pos, frequencies, width, color='r')
    for frameno, bin_idx in enumerate(np.linspace(0,1000000,100000000), 2):
        #Here we just change the first bin, so it increases through the animation.
        frequencies[0] = bin_idx
        # rects = plt.bar(pos, frequencies, width, color='r')
        for rect, f in zip(rects, frequencies):
            rect.set_height(f)
        fig.canvas.draw()
        print("FPS: {:.2f}".format(frameno / (clock() - start)))         

win = fig.canvas.manager.window
win.after(1, animate)
plt.show()

For comparison, adding plt.clf to your original code, on my machine reaches about 12 frames per second.


Some comments about timing:

You won't get accurate measurements by calculating the very small time differences with each pass through the loop. The time resolution of time.time() -- at least on my computer -- is not great enough. You'll get more accurate measurements by measuring one starting time and calculating the large time difference between the start time and the current time, and then dividing by the number of frames.

I also changed time.time to timeit.default_timer. The two are the same on Unix computers, but timeit.default_timer is set to time.clock on Windows machines. Thus timeit.default_timer chooses the more accurate timer for each platform.

Upvotes: 3

Pascal Bugnion
Pascal Bugnion

Reputation: 4928

I think that your code gets slower because you are not clearing the figure, so you are replotting histograms on top of each other for every iteration.

Adding a plt.clf() call before your line1 = ... clears the existing graph.

Upvotes: 1

Related Questions