0
0

楕円形に分布する乱数を生成する

Last updated at Posted at 2024-08-03

他人に PCA(主成分分析)などを説明する際に、いつも「楕円形に分布する乱数を作りたいなぁ」と思う。
あるあるですね。

でもその度に毎回

  • 各軸がガウス分布に従う2次元の変数だから、numpy.random の multivariate_normal か
  • cov ってなんだっけ・・・、あ、共分散行列か
  • 作りたい形の共分散行列ってどうやって作るんだよ・・・
  • (適当な値の cov をつっこむ)"covariance is not symmetric positive-semidefinite." いやまぁそうだろうけどね

となって、途方に暮れる。

なので、ちゃんと作って見た。

関数

from numpy.random import Generator, default_rng
from numpy.typing import NDArray

def generate_random_ellipse(
    corrcoef: float,
    degree: float,
    size: int,
    seed: int = 12,
) -> NDArray:
    """楕円形に分布する2次元の乱数を生成する。

    Args:
        corrcoef (float):
            生成する乱数の、角度を回転させる前の相関係数。
            楕円の平べったさになる(回転した後の相関係数はこの値とは異なる)。
            0 だと真ん丸で、1 だと平べったい。
        degree (float):
            楕円の長径の x 軸との角度。
        size (float):
            乱数の個数。
        seed (int):
            乱数シード。

    Returns:
        NDArray: (size, 2) の行列
    """
    rng: Generator = default_rng(seed)
    x: NDArray = rng.normal(loc=0, scale=1, size=size)
    y: NDArray = rng.normal(loc=0, scale=1, size=size)
    z: NDArray = corrcoef * x + (1 - (corrcoef**2)) ** 0.5 * y
    d: NDArray = np.concatenate([x.reshape(1, -1), z.reshape(1, -1)], axis=0)
    rad: float = np.deg2rad(degree - 45)
    m: NDArray = np.array(
        [[np.cos(rad), -np.sin(rad)], [np.sin(rad), np.cos(rad)]]
    )
    return np.dot(m, d).T

相関のある2つの疑似乱数の生成は、こちらを参照させていただきました。
上記で楕円形の乱数を発生させて、それを回転させているだけ。

使ってみる

例えばこんな感じで生成します。

x: NDArray = generate_random_ellipse(corrcoef=0.8, degree=70, size=1000)

するとこんなのが出来ます。

20240803_141256.png

もういつでも楕円形に分布する乱数が作れますね!

ちなみに

作った乱数の相関係数は、もちろん指定した値とは変わってしまいます。

>>> np.corrcoef(x, rowvar=False)

array([[1.        , 0.63088707],
       [0.63088707, 1.        ]])

そしてこの乱数の共分散行列は以下。

>>> np.cov(x, rowvar=False)

array([[0.38510963, 0.49677686],
       [0.49677686, 1.61003414]])

これを numpy.random の multivariate_normal にあげれば、同様の乱数が作れます。

rng: Generator = default_rng(12)
x2: NDArray = rng.multivariate_normal(
    mean=np.array([0, 0]),
    cov=np.array([[0.38510963, 0.49677686], [0.49677686, 1.61003414]]),
    size=1000,
)

20240803_141852.png

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0