SciPy: распределение фон Мизеса по полукругу?
Я пытаюсь найти лучший способ определить распределение фон-Мизеса, заключенное в полукруг (я использую его для рисования ненаправленных линий с разной концентрацией). В настоящее время я использую sciPy vonmises.rvs (). По сути, я хочу иметь возможность ввести, скажем, среднюю ориентацию числа pi / 2 и усечь распределение до не более чем pi / 2 с каждой стороны.
Я мог бы использовать усеченное нормальное распределение, но потеряю обертку фон-мизеса (скажем, если мне нужна средняя ориентация 0)
Я видел это в исследовательских работах, посвященных отображению ориентации волокон, но я не могу понять, как это реализовать (на python). Я немного не понимаю, с чего начать.
Если мой фон Мезис определяется как (из numpy.vonmises):
np.exp(kappa*np.cos(x-mu))/(2*np.pi*i0(kappa))
с участием:
mu, kappa = 0, 4.0
x = np.linspace(-np.pi, np.pi, num=51)
Как мне изменить его, чтобы вместо этого использовать обертку вокруг полукруга?
Может ли кто-нибудь, имеющий некоторый опыт работы с этим, дать совет?
Ответы
Полезно иметь прямую численную обратную выборку CDF, он должен отлично работать для распределения с ограниченной областью. Вот пример кода, построение таблиц PDF и CDF и выборка с использованием метода обратного CDF. Конечно, можно оптимизировать и векторизовать
Код, Python 3.8, x64 Windows 10
import numpy as np
import matplotlib.pyplot as plt
import scipy.integrate as integrate
def PDF(x, μ, κ):
return np.exp(κ*np.cos(x - μ))
N = 201
μ = np.pi/2.0
κ = 4.0
xlo = μ - np.pi/2.0
xhi = μ + np.pi/2.0
# PDF normaliztion
I = integrate.quad(lambda x: PDF(x, μ, κ), xlo, xhi)
print(I)
I = I[0]
x = np.linspace(xlo, xhi, N, dtype=np.float64)
step = (xhi-xlo)/(N-1)
p = PDF(x, μ, κ)/I # PDF table
# making CDF table
c = np.zeros(N, dtype=np.float64)
for k in range(1, N):
c[k] = integrate.quad(lambda x: PDF(x, μ, κ), xlo, x[k])[0] / I
c[N-1] = 1.0 # so random() in [0...1) range would work right
#%%
# sampling from tabular CDF via insverse CDF method
def InvCDFsample(c, x, gen):
r = gen.random()
i = np.searchsorted(c, r, side='right')
q = (r - c[i-1]) / (c[i] - c[i-1])
return (1.0 - q) * x[i-1] + q * x[i]
# sampling test
RNG = np.random.default_rng()
s = np.empty(20000)
for k in range(0, len(s)):
s[k] = InvCDFsample(c, x, RNG)
# plotting PDF, CDF and sampling density
plt.plot(x, p, 'b^') # PDF
plt.plot(x, c, 'r.') # CDF
n, bins, patches = plt.hist(s, x, density = True, color ='green', alpha = 0.7)
plt.show()
и график с PDF, CDF и гистограммой выборки

Вы можете отбросить значения за пределами желаемого диапазона с помощью фильтрации numpy ( theta=theta[(theta>=0)&(theta<=np.pi)]
сокращая массив образцов). Итак, вы можете сначала увеличить количество сгенерированных выборок, затем отфильтровать, а затем взять подмассив желаемого размера.
Или вы можете добавить / вычесть число пи, чтобы поместить их все в этот диапазон (через theta = np.where(theta < 0, theta + np.pi, np.where(theta > np.pi, theta - np.pi, theta))
). Как отмечает @SeverinPappadeux, такое изменение распределения, вероятно, нежелательно.
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
import numpy as np
from scipy.stats import vonmises
mu = np.pi / 2
kappa = 4
orig_theta = vonmises.rvs(kappa, loc=mu, size=(10000))
fig, axes = plt.subplots(ncols=2, sharex=True, sharey=True, figsize=(12, 4))
for ax in axes:
theta = orig_theta.copy()
if ax == axes[0]:
ax.set_title(f"$Von Mises, \\mu={mu:.2f}, \\kappa={kappa}$")
else:
theta = theta[(theta >= 0) & (theta <= np.pi)]
print(len(theta))
ax.set_title(f"$Von Mises, angles\\ filtered\\ ({100 * len(theta) / (len(orig_theta)):.2f}\\ \\%)$")
segs = np.zeros((len(theta), 2, 2))
segs[:, 1, 0] = np.cos(theta)
segs[:, 1, 1] = np.sin(theta)
line_segments = LineCollection(segs, linewidths=.1, colors='blue', alpha=0.5)
ax.add_collection(line_segments)
ax.autoscale()
ax.set_aspect('equal')
plt.show()
