scipy.stats.vonmises_fisher#

scipy.stats.vonmises_fisher = <scipy.stats._multivariate.vonmises_fisher_gen object>[source]#

A von Mises-Fisher variable.

The mu keyword specifies the mean direction vector. The kappa keyword specifies the concentration parameter.

Parameters:
muarray_like

Mean direction of the distribution. Must be a one-dimensional unit vector of norm 1.

kappafloat

Concentration parameter. Must be positive.

seed{None, int, np.random.RandomState, np.random.Generator}, optional

Used for drawing random variates. If seed is None, the RandomState singleton is used. If seed is an int, a new RandomState instance is used, seeded with seed. If seed is already a RandomState or Generator instance, then that object is used. Default is None.

See also

scipy.stats.vonmises

Von-Mises Fisher distribution in 2D on a circle

uniform_direction

uniform distribution on the surface of a hypersphere

Notes

The von Mises-Fisher distribution is a directional distribution on the surface of the unit hypersphere. The probability density function of a unit vector \(\mathbf{x}\) is

\[f(\mathbf{x}) = \frac{\kappa^{d/2-1}}{(2\pi)^{d/2}I_{d/2-1}(\kappa)} \exp\left(\kappa \mathbf{\mu}^T\mathbf{x}\right),\]

where \(\mathbf{\mu}\) is the mean direction, \(\kappa\) the concentration parameter, \(d\) the dimension and \(I\) the modified Bessel function of the first kind. As \(\mu\) represents a direction, it must be a unit vector or in other words, a point on the hypersphere: \(\mathbf{\mu}\in S^{d-1}\). \(\kappa\) is a concentration parameter, which means that it must be positive (\(\kappa>0\)) and that the distribution becomes more narrow with increasing \(\kappa\). In that sense, the reciprocal value \(1/\kappa\) resembles the variance parameter of the normal distribution.

The von Mises-Fisher distribution often serves as an analogue of the normal distribution on the sphere. Intuitively, for unit vectors, a useful distance measure is given by the angle \(\alpha\) between them. This is exactly what the scalar product \(\mathbf{\mu}^T\mathbf{x}=\cos(\alpha)\) in the von Mises-Fisher probability density function describes: the angle between the mean direction \(\mathbf{\mu}\) and the vector \(\mathbf{x}\). The larger the angle between them, the smaller the probability to observe \(\mathbf{x}\) for this particular mean direction \(\mathbf{\mu}\).

In dimensions 2 and 3, specialized algorithms are used for fast sampling [2], [3]. For dimensions of 4 or higher the rejection sampling algorithm described in [4] is utilized. This implementation is partially based on the geomstats package [5], [6].

Added in version 1.11.

References

[1]

Von Mises-Fisher distribution, Wikipedia, https://en.wikipedia.org/wiki/Von_Mises%E2%80%93Fisher_distribution

[2]

Mardia, K., and Jupp, P. Directional statistics. Wiley, 2000.

[3]

J. Wenzel. Numerically stable sampling of the von Mises Fisher distribution on S2. https://www.mitsuba-renderer.org/~wenzel/files/vmf.pdf

[4]

Wood, A. Simulation of the von mises fisher distribution. Communications in statistics-simulation and computation 23, 1 (1994), 157-164. https://doi.org/10.1080/03610919408813161

[5]

geomstats, Github. MIT License. Accessed: 06.01.2023. geomstats/geomstats

[6]

Miolane, N. et al. Geomstats: A Python Package for Riemannian Geometry in Machine Learning. Journal of Machine Learning Research 21 (2020). http://jmlr.org/papers/v21/19-027.html

Examples

Visualization of the probability density

Plot the probability density in three dimensions for increasing concentration parameter. The density is calculated by the pdf method.

>>> import numpy as np
>>> import matplotlib.pyplot as plt
>>> from scipy.stats import vonmises_fisher
>>> from matplotlib.colors import Normalize
>>> n_grid = 100
>>> u = np.linspace(0, np.pi, n_grid)
>>> v = np.linspace(0, 2 * np.pi, n_grid)
>>> u_grid, v_grid = np.meshgrid(u, v)
>>> vertices = np.stack([np.cos(v_grid) * np.sin(u_grid),
...                      np.sin(v_grid) * np.sin(u_grid),
...                      np.cos(u_grid)],
...                     axis=2)
>>> x = np.outer(np.cos(v), np.sin(u))
>>> y = np.outer(np.sin(v), np.sin(u))
>>> z = np.outer(np.ones_like(u), np.cos(u))
>>> def plot_vmf_density(ax, x, y, z, vertices, mu, kappa):
...     vmf = vonmises_fisher(mu, kappa)
...     pdf_values = vmf.pdf(vertices)
...     pdfnorm = Normalize(vmin=pdf_values.min(), vmax=pdf_values.max())
...     ax.plot_surface(x, y, z, rstride=1, cstride=1,
...                     facecolors=plt.cm.viridis(pdfnorm(pdf_values)),
...                     linewidth=0)
...     ax.set_aspect('equal')
...     ax.view_init(azim=-130, elev=0)
...     ax.axis('off')
...     ax.set_title(rf"$\kappa={kappa}$")
>>> fig, axes = plt.subplots(nrows=1, ncols=3, figsize=(9, 4),
...                          subplot_kw={"projection": "3d"})
>>> left, middle, right = axes
>>> mu = np.array([-np.sqrt(0.5), -np.sqrt(0.5), 0])
>>> plot_vmf_density(left, x, y, z, vertices, mu, 5)
>>> plot_vmf_density(middle, x, y, z, vertices, mu, 20)
>>> plot_vmf_density(right, x, y, z, vertices, mu, 100)
>>> plt.subplots_adjust(top=1, bottom=0.0, left=0.0, right=1.0, wspace=0.)
>>> plt.show()
../../_images/scipy-stats-vonmises_fisher-1_00_00.png

As we increase the concentration parameter, the points are getting more clustered together around the mean direction.

Sampling

Draw 5 samples from the distribution using the rvs method resulting in a 5x3 array.

>>> rng = np.random.default_rng()
>>> mu = np.array([0, 0, 1])
>>> samples = vonmises_fisher(mu, 20).rvs(5, random_state=rng)
>>> samples
array([[ 0.3884594 , -0.32482588,  0.86231516],
       [ 0.00611366, -0.09878289,  0.99509023],
       [-0.04154772, -0.01637135,  0.99900239],
       [-0.14613735,  0.12553507,  0.98126695],
       [-0.04429884, -0.23474054,  0.97104814]])

These samples are unit vectors on the sphere \(S^2\). To verify, let us calculate their euclidean norms:

>>> np.linalg.norm(samples, axis=1)
array([1., 1., 1., 1., 1.])

Plot 20 observations drawn from the von Mises-Fisher distribution for increasing concentration parameter \(\kappa\). The red dot highlights the mean direction \(\mu\).

>>> def plot_vmf_samples(ax, x, y, z, mu, kappa):
...     vmf = vonmises_fisher(mu, kappa)
...     samples = vmf.rvs(20)
...     ax.plot_surface(x, y, z, rstride=1, cstride=1, linewidth=0,
...                     alpha=0.2)
...     ax.scatter(samples[:, 0], samples[:, 1], samples[:, 2], c='k', s=5)
...     ax.scatter(mu[0], mu[1], mu[2], c='r', s=30)
...     ax.set_aspect('equal')
...     ax.view_init(azim=-130, elev=0)
...     ax.axis('off')
...     ax.set_title(rf"$\kappa={kappa}$")
>>> mu = np.array([-np.sqrt(0.5), -np.sqrt(0.5), 0])
>>> fig, axes = plt.subplots(nrows=1, ncols=3,
...                          subplot_kw={"projection": "3d"},
...                          figsize=(9, 4))
>>> left, middle, right = axes
>>> plot_vmf_samples(left, x, y, z, mu, 5)
>>> plot_vmf_samples(middle, x, y, z, mu, 20)
>>> plot_vmf_samples(right, x, y, z, mu, 100)
>>> plt.subplots_adjust(top=1, bottom=0.0, left=0.0,
...                     right=1.0, wspace=0.)
>>> plt.show()
../../_images/scipy-stats-vonmises_fisher-1_01_00.png

The plots show that with increasing concentration \(\kappa\) the resulting samples are centered more closely around the mean direction.

Fitting the distribution parameters

The distribution can be fitted to data using the fit method returning the estimated parameters. As a toy example let’s fit the distribution to samples drawn from a known von Mises-Fisher distribution.

>>> mu, kappa = np.array([0, 0, 1]), 20
>>> samples = vonmises_fisher(mu, kappa).rvs(1000, random_state=rng)
>>> mu_fit, kappa_fit = vonmises_fisher.fit(samples)
>>> mu_fit, kappa_fit
(array([0.01126519, 0.01044501, 0.99988199]), 19.306398751730995)

We see that the estimated parameters mu_fit and kappa_fit are very close to the ground truth parameters.

Methods

pdf(x, mu=None, kappa=1)

Probability density function.

logpdf(x, mu=None, kappa=1)

Log of the probability density function.

rvs(mu=None, kappa=1, size=1, random_state=None)

Draw random samples from a von Mises-Fisher distribution.

entropy(mu=None, kappa=1)

Compute the differential entropy of the von Mises-Fisher distribution.

fit(data)

Fit a von Mises-Fisher distribution to data.