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()
