Skip to content

numpy.random.vonmises documentation contains incorrect code example. #5811

Closed

Description

@Ogaday

The numpy.random reference for the von Mises distribution contains an incorrect code sample (pasted below).

mu, kappa = 0.0, 4.0 # mean and dispersion
s = np.random.vonmises(mu, kappa, 1000)

import matplotlib.pyplot as plt
import scipy.special as sps
count, bins, ignored = plt.hist(s, 50, normed=True)
x = np.arange(-np.pi, np.pi, 2*np.pi/50.)
y = -np.exp(kappa*np.cos(x-mu))/(2*np.pi*sps.jn(0,kappa))
plt.plot(x, y/max(y), linewidth=2, color='r')
plt.show()

While this works for the example, line 5 doesn't concur with the definition of the von Mises probability density function, given above the example. According to documentation about the special functions, the modified bessel function of order real order is given by scipy.special.iv(). Using that function instead means the sign doesn't need to be changed.

I noticed the problem when I tried to emulate the code and I found that area under the pdf didn't sum to 1 when I used the trapezium rule and would sometimes even go below the x axis, when neither the exponential function or the bessel function should ever be negative. Compare:

#incorrect, according to documentation

import numpy as np
import scipy.special as sps
import matplotlib.pyplot as plt
tau = np.pi*2

fig, ax = plt.subplots(1,1, figsize = (10,8))

n = 10
xs = np.linspace(0, 4, n)     #create a range of kappas

mu = 0
m = 50
width = tau/float(m)
sums = np.empty(n)    #for storing the integrals under the curves for different kappas

for i in range(n):
    kappa = xs[i]
    x = np.linspace(-np.pi, np.pi, m)
    y = -np.exp(kappa*np.cos(x-mu))/(tau*sps.jn(0,kappa))
    ax.plot(x, y, label="kappa = {}".format(kappa))
    sums[i] = sum([(y[j]+y[j+1])/2*width for j in range(m-1)])
ax.set_axes("tight")
ax.legend()
print "the area under the pdf for different kappas: "
for i in range(n):
    print "kappa =", xs[i], ", integral =", sums[i]
fig.show()
#correct, using the correct bessel function

import numpy as np
import scipy.special as sps
import matplotlib.pyplot as plt
tau = np.pi*2

fig, ax = plt.subplots(1,1, figsize = (10,8))

n = 10
xs = np.linspace(0, 4, n)     #create a range of kappas

mu = 0
m = 50
width = tau/float(m)
sums = np.empty(n)    #for storing the integrals under the curves for different kappas

for i in range(n):
    kappa = xs[i]
    x = np.linspace(-np.pi, np.pi, m)
    y = np.exp(kappa*np.cos(x-mu))/(tau*sps.iv(0,kappa))
    ax.plot(x, y, label="kappa = {}".format(kappa))
    sums[i] = sum([(y[j]+y[j+1])/2*width for j in range(m-1)])
ax.set_axes("tight")
ax.legend()
print "the area under the pdf for different kappas: "
for i in range(n):
    print "kappa =", xs[i], ", integral =", sums[i]
fig.show()

Excuse my use of tau.

Using: Python 2.7.9 :: Anaconda 2.2.0 (64-bit), IPython 3.1.0, numpy 1.9.2

Activity

  1. argriffing commented on Apr 28, 2015

    @argriffing
    Contributor

    For what it's worth, scipy.stats.vonmises uses i0 for the pdf.

    https://github.com/scipy/scipy/blob/maintenance/0.15.x/scipy/stats/_continuous_distns.py#L3956

    The framework for these 'circular' or 'wrapped' distributions could be improved in scipy; I think they are implemented as hacks on top of continuous location-scale distributions on the real line.

  2. Ogaday commented on Apr 28, 2015

    @Ogaday
    Author

    Oh, interesting, thanks for that. There do seem to be an overload options between numpy and scipy and I often don't know which to use.

    I was fairly certain that the sampling method itself was fine, it's the just example code. Also, the links at the bottom of that page are dead (the source, png, pdf links).

  3. argriffing commented on Apr 28, 2015

    @argriffing
    Contributor

    @Ogaday Just so I understand, changing jn(0,kappa) to -i0(kappa) in the example code and updating the plot and fixing the dead links would be enough to close this issue?

  4. Ogaday commented on Apr 29, 2015

    @Ogaday
    Author

    Yes, and the - sign there cancels with the one at the start of the term, so replace jn(0,kappa) with i0(kappa) and delete the - sign after y =.

    Technically, in terms of best practice, I think it should also read:

    x = np.linspace(-np.pi, np.pi, 51)

    Which does the same thing but with linspace instead of arange, as suggested in the docstring of arange, and it includes the end of the interval, which should be defined under the definition of the function. That is a little pedantic though, but I think it's better practice.

  5. argriffing commented on Apr 29, 2015

    @argriffing
    Contributor

    @Ogaday could you check that the new docstring in #5814 works for you locally? I think the plot image in the docs would be automatically generated from this docstring code, and I've opened a separate issue for the problem with the dead links.

  6. Ogaday commented on Apr 30, 2015

    @Ogaday
    Author

    Works great for me locally, thanks. I was also wondering about the normalising thing as well, looks much better now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions