2

I have the following animated subplots that simulate histograms of four different distributions:

import numpy
from matplotlib.pylab import *
import matplotlib.animation as animation

n = 100

# generate 4 random variables from the random, gamma, exponential, and uniform distributions
x1 = np.random.normal(-2.5, 1, 10000)
x2 = np.random.gamma(2, 1.5, 10000)
x3 = np.random.exponential(2, 10000)+7
x4 = np.random.uniform(14,20, 10000)

fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2)

def updateData(curr):

    if curr == n: 
        a.event_source.stop()

    ax1.hist(x1[:curr], normed=True, bins=20, alpha=0.5)
    ax2.hist(x2[:curr], normed=True, bins=20, alpha=0.5)
    ax3.hist(x3[:curr], normed=True, bins=20, alpha=0.5)
    ax4.hist(x4[:curr], normed=True, bins=20, alpha=0.5)

simulation = animation.FuncAnimation(fig, updateData, interval=20, repeat=False)

plt.show()

It works, but for some reason the normed=True is being ignored for the y-axis scaling. If I take these plots out of the animation, they scale properly. How do I get proper scaling in the animation?

EDIT

Instead of having a scale like this (outside of animation):no animation

I get (inside of animation): animation

horcle_buzz
  • 2,101
  • 3
  • 30
  • 59

3 Answers3

7

The normed = True argument to the histogram makes the histogram plot the density of the distribution. From the documentation:

normed : boolean, optional
If True, the first element of the return tuple will be the counts normalized to form a probability density, i.e., n/(len(x)`dbin), i.e., the integral of the histogram will sum to 1. If stacked is also True, the sum of the histograms is normalized to 1. Default is False

This means that the hight of the histogram bar depends on the bin width. If only one data point is plotted as is the case at the beginning of the animation the bar height will be 1./binwidth. If the bin width is smaller than zero, the bar height might become very large.

It's therefore a good idea to fix the bins and use them throughout the animation.
It's also reasonable to clear the axes such that there are not 100 different histograms being plotted.

import numpy as np
from matplotlib.pylab import *
import matplotlib.animation as animation

# generate 4 random variables from the random, gamma, exponential, and uniform distribution
x1 = np.random.normal(-2.5, 1, 10000)
x2 = np.random.gamma(2, 1.5, 10000)
x3 = np.random.exponential(2, 10000)+7
x4 = np.random.uniform(14,20, 10000)

fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2)

def updateData(curr):
    if curr <=2: return
    for ax in (ax1, ax2, ax3, ax4):
        ax.clear()
    ax1.hist(x1[:curr], normed=True, bins=np.linspace(-6,1, num=21), alpha=0.5)
    ax2.hist(x2[:curr], normed=True, bins=np.linspace(0,15,num=21), alpha=0.5)
    ax3.hist(x3[:curr], normed=True, bins=np.linspace(7,20,num=21), alpha=0.5)
    ax4.hist(x4[:curr], normed=True, bins=np.linspace(14,20,num=21), alpha=0.5)

simulation = animation.FuncAnimation(fig, updateData, interval=50, repeat=False)

plt.show()

enter image description here

ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
  • I guess skipping reading the documentation was worthy of the downvote! ^_^ I didn't really understand what you meant by "100 different histograms being plotted," until I realized that each call to updateData created a new histogram. – horcle_buzz Mar 20 '17 at 20:55
  • Could you explain the importance of clearing the axis please? – ZakS May 10 '18 at 21:07
  • 1
    @Zaks As pointed out in the answer, if you don't clear the axes, you will end up with 100 individual histograms in the axes. (Note that this is different if you use blitting - in which case you do not need to clear the axes, unless you want to save/export the animation as movie). – ImportanceOfBeingErnest May 10 '18 at 22:29
  • @ImportanceOfBeingErnest ok, I am understanding that funcAnimate is basically looping and recreating the histogram in each loop, but keeping the previous info. Which makes me ask: In your function UpdateData, is (curr) the nth loop, so you basically are cutting the FuncAnimate at 2? very grateful, thank you. – ZakS May 13 '18 at 07:04
  • @ZakS `curr` is the frame number, it will be `0,1,2,3,4,5,....`. You may use the `frames` argument to set the number of frames (which I didn't do in the above code). – ImportanceOfBeingErnest May 13 '18 at 10:19
  • @ImportanceOfBeingErnest Is there a way to set counts in y-axis to be the same value so that y labels don't flicker throughout the animation? To make it more clear, how do I set the y_range to be at max(counts). Is there an easy way to do this instead of binning the data beforehand? Thanks :) – AshlinJP Apr 09 '20 at 22:10
  • 1
    @AshlinJP You cannot know a value before it's being computed (That's more a philosophical fact!). But you can compute all histograms before, and then know to which value to set `ax.set_ylim(0, maxvalue)`. – ImportanceOfBeingErnest Apr 09 '20 at 22:46
  • Note that the argument `normed` has been substituted with `density` (after being deprecated) from `matplotlib.pyplot.hist`. – matmar Sep 17 '20 at 23:14
1

Yeah!! I also faced the same problem, if you are getting such kind of problem don't forget to clear the axis before displaying each frame of the animation.

use plt.cla() or ax.clear()(in your case) for each axis

before doing the plot in the function defined for animation

-1

Got it!

My iterating over n was the culprit. This does what I expected:

def updateData(curr):

    curr2=100+curr*5 

    #if curr == n: 
    #    a.event_source.stop()

    ax1.hist(x1[:curr2], normed=True, bins=20, alpha=0.5)
    ax2.hist(x2[:curr2], normed=True, bins=20, alpha=0.5)
    ax3.hist(x3[:curr2], normed=True, bins=20, alpha=0.5)
    ax4.hist(x4[:curr2], normed=True, bins=20, alpha=0.5)

simulation = animation.FuncAnimation(fig, updateData, frames=900, interval=10)

plt.show()
horcle_buzz
  • 2,101
  • 3
  • 30
  • 59