NumPyの乱数・シャッフル・ランダム抽出(np.random
)使い方まとめ
Numpyでは、2019年にリリースされたバージョン1.17より新たな乱数生成器が実装されました。しかしそれから3年以上経過した記事執筆時点でも、既に非推奨となっている古い仕様(np.random
直下にある諸関数の使用)が依然として多くの人に用いられ続けています(なお前後比較・変更点は『numpy.randomのGeneratorをためしてみる - Qiita』を参照してください)。
この要因としては、古い内容のまま放置されているある種の権威を持った記事や(例えば「Numpy ランダム」などでググると古い仕様を紹介した記事『Numpyによる乱数生成まとめ - Qiita』かなり上位に表示されます)、SEO対策だけは頑張る質の悪い泡沫プログラミング入門サイトの記事などが、新しい仕様を新規参入者に教えず、その結果さらに新規参入までもが既に非推奨となっているコードの拡散に協力してしまっているということがあるでしょう(また、多くの人は公式ドキュメントを見ようとはしません)。
本記事では、上記のような古い記事や低質な記事の代替となるような、バージョン1.17以降のNumPyの乱数の基礎的な使い方から一般的に使われる関数のAPI実装および使用例についての情報を提供するものです。
基本的な使い方
np.random.default_rng()
で、乱数生成器オブジェクトをインスタンス化したのち、各種メソッドで乱数を発生できます。
import numpy as np
# 乱数生成器オブジェクトの作成
rng = np.random.default_rng()
# 乱数の作成
rfloat = rng.random()
print(rfloat)
以下乱数生成器オブジェクトを変数rng
に格納しているものとして説明します。
シード
np.random.default_rng()
にはシード値を渡すことができます。
NumPyの乱数生成器は擬似乱数といって、本当のランダムではありません。極端にいえば$y=f(x)$のような関数になっており、初期条件$x$が決まれば結果$y$が決まっていますが、通常$x$には実行したときの現在時刻などが入るために、結果がランダムになっています。
私達がサイコロを振るとき普通は適当に投げるので結果はランダムですが、もし完全に同じ位置から同じ角度・速度で投げたら(それが可能だとしたら)、決まった目が出るというようなイメージをしてください。
シード値を渡すと、同じ位置・角度・速度でサイコロを投げることができます。以下を何度か実行してみてください。
import numpy as np
# シードを設定しない場合
rng = np.random.default_rng()
print('↓実行するたび値が変わる')
print('1投目: ', rng.integers(1, 7))
print('2投目: ', rng.integers(1, 7))
print('3投目: ', rng.integers(1, 7))
# シードを設定
rng = np.random.default_rng(12345)
print('↓毎回同じ結果(5と2と5)になる')
print('1投目: ', rng.integers(1, 7))
print('2投目: ', rng.integers(1, 7))
print('3投目: ', rng.integers(1, 7))
なお、たまに勘違いする方がいますが、1投目と2投目が同じ結果になるというわけではありません。何度やっても、1投目同士、2投目同士、…、N投目同士が必ず同じ結果になるということです。次のコードでそれを確かめられます。
import numpy as np
rng1, rng2 = np.random.default_rng(123), np.random.default_rng(123)
# 以下は無限ループになる
while rng1.integers(1, 7) == rng2.integers(1, 7):
pass
乱数値・配列を新規作成
ランダム整数値・配列を作成 -.integers()
rng.integers(low, high=None, size=None, dtype=np.int64, endpoint=False)
引数 | 説明 |
---|---|
low |
結果が取り得る値の最小値(0 のとき省略可能) |
high |
結果が取り得る値の最大値 |
size |
結果の配列の形。省略するとスカラー値 |
dtype |
結果配列のデータ型。デフォルトはint64
|
endpoint |
デフォルトはFalse で右開区間。True にすると閉区間 |
>>> rng = np.random.default_rng(0)
>>> rng.integers(1, 7)
6
>>> rng.integers(1, 7, 10)
array([4, 4, 2, 2, 1, 1, 1, 2, 5, 4], dtype=int64)
>>> rng.integers([10, 20, 30])
array([ 9, 10, 18], dtype=int64)
low
には最小値・high
には最大値を設定しますが、ここで、結果が取り得る値はlow
以上high
未満になることに注意してください(range()
関数のstart
/stop
引数をイメージしてください)。
rng.integers(1, 7)
# 結果は{1, 2, 3, 4, 5, 6}のいずれかで、`7`は出ない
low=0
の場合は省略できます。
rng.integers(0, 7)
rng.integers(7)
# どちらも結果は{0, 1, 2, 3, 4, 5, 6}のいずれか
結果が取り得る値をlow
以上high
以下にするには、endpoint
引数をTrue
に指定します。
rng.integers(1, 7)
rng.integers(1, 7, endpoint=False)
# どちらも結果は{1, 2, 3, 4, 5, 6}のいずれかで、`7`は出ない
rng.integers(1, 7, endpoint=True)
# 結果は{1, 2, 3, 4, 5, 6, 7}のいずれか
なおlow
/high
には小数を渡すこともできます。この場合小数点以下は切り捨てられて解釈されます。この使い方は推奨しません。また浮動小数の精度には注意してください。
rng.integers(1, 7.9)
# 結果は{1, 2, 3, 4, 5, 6}のいずれかで、`7`は出ない
rng.integers(5, (0.7+0.1)*10)
# 結果は{1, 2, 3, 4, 5, 6}のいずれかで、`7`は出ない
size
引数を省略するかNone
を指定すると(ここまでの例のように)結果はスカラー値になります。
size
引数に整数を渡すと、その長さの1次元配列が返却されます。タプルを渡すと2次元以上の配列も作成できます。
rng.integers(1, 7, 10).shape # -> (10,)
rng.integers(1, 7, (3, 5)).shape # -> (3, 5)
配列の要素ごとに取りうる値の区間を変更したい場合は、low
/high
引数をリストにします。
rng.integers([0, 10, 20], [10, 20, 30])
rng.integers([0, 10, 20], [10, 20, 30], size=3)
# どちらも以下にほぼ等しい
np.array([rng.integers(0, 10), rng.integers(10, 20), rng.integers(20, 30)])
low
の長さ/high
の長さ/size
の値が一致していなくても、可能であれば適宜ブロードキャストされます。
rng.integers([0, 10, 20], [10, 20, 30], size=(2, 3))
# 以下にほぼ等しい
np.array([[rng.integers(0, 10), rng.integers(10, 20), rng.integers(20, 30)],
[rng.integers(0, 10), rng.integers(10, 20), rng.integers(20, 30)]])
rng.integers(low=[0, 10], high=[[20], [30]], size=(3, 2, 2))
# どのような結果になるか考えてみましょう
low
/high
/size
の値の組み合わせが適切でない場合はエラーが出ます。
rng.integers([0, 10, 20], [10, 20, 30], size=4)
# raise ValueError
ランダム小数値・配列を作成 -.random()
rng.random(size=None, dtype=np.float64, out=None)
引数 | 説明 |
---|---|
size |
結果の配列の形。省略するとスカラー値 |
dtype |
結果配列のデータ型。デフォルトはfloat64
|
out |
既存の配列に要素を代入する場合に使用 |
>>> rng = np.random.default_rng(0)
>>> rng.random()
0.6369616873214543
>>> rng.random(3)
array([0.26978671, 0.04097352, 0.01652764])
>>> rng.random((2, 5))
array([[0.81327024, 0.91275558, 0.60663578, 0.72949656, 0.54362499],
[0.93507242, 0.81585355, 0.0027385 , 0.85740428, 0.03358558]])
.integers()
と同様に、size
引数を省略するかNone
を指定すると結果はスカラー値になり、size
引数に整数を渡すとその長さの1次元配列が返却されます。タプルを渡すと2次元以上の配列も作成できます。
.integers()
と異なりlow
/high
引数は存在せず、結果が取り得る値は必ず0
以上1
未満になります。
最小値low
と最大値high
を指定したい場合は、変換式$(high - low) * random() + low$を適用します。
low, high = 1, 7
arr = rng.random(10)
(high - low) * arr + low
# -> 1以上7未満の値を10個並べた配列
ランダムなバイト文字列を作成 -.bytes()
rng.bytes(length)
length
には文字列の長さを指定します。
>>> rng = np.random.default_rng(0)
>>> rng.bytes(0)
b''
>>> rng.bytes(1)
b'\xcf'
>>> rng.bytes(10)
b'!\xd7\xd9\x82\xf8\xbd\x10E\xb8\xe8'
正規分布に基づいて乱数値・配列を作成 -.normal()
rng.normal(loc=0.0, scale=1.0, size=None)
引数 | 説明 |
---|---|
loc |
平均値$μ$ |
scale |
標準偏差$σ$ |
size |
結果の配列の形。省略するとスカラー値 |
loc
/scale
をともに省略すると標準正規分布に基づくようになります。
標準正規分布の場合は.standard_normal()
を用いることもできます。
import matplotlib.pyplot as plt
arr = rng.normal(size=100000)
plt.hist(arr, bins=100)
その他の確率分布に基づいて乱数値・配列を作成
rng.binomial(n, p, size=None) # {n: 試行回数, p: 発生確率}
# レア封入率3%の100連ガチャを10万人が引いたときの、各プレイヤーが引いたレアの枚数
arr = rng.binomial(100, 0.03, 100000)
(arr == 0).sum() # -> 約4700人が一枚も引けなかった
rng.poisson(lam=1.0, size=None) # {lam: 平均発生回数λ}
rng.chisquare(df, size=None) # {df: 自由度}
等
既存の配列をシャッフル・ランダム並び替え -.shuffle()
/.permutation()
rng.shuffle(x, axis=0)
rng.permutation(x, axis=0)
引数 | 説明 |
---|---|
x |
シャッフル元の配列 |
axis |
a が多次元配列の場合にシャッフルする軸 |
-.shuffle()
は既存の配列をシャッフルします。引数として与えた配列x
を変更します。
-.permutation()
は引数として与えた配列x
を並び替えた新しい配列を返します。
>>> rng = np.random.default_rng(0)
>>> arr = np.array(['a', 'b', 'c', 'd', 'e', 'f', 'g'])
>>> rng.permutation(arr)
array(['c', 'e', 'd', 'g', 'f', 'a', 'b'], dtype='<U1')
>>> arr
array(['a', 'b', 'c', 'd', 'e', 'f', 'g'], dtype='<U1')
>>> rng.shuffle(arr)
>>> arr
array(['f', 'c', 'e', 'g', 'b', 'a', 'd'], dtype='<U1')
axis
引数はシャッフルする軸を指定します。
>>> rng = np.random.default_rng(0)
>>> arr = np.array([[11, 12, 13],
... [21, 22, 23],
... [31, 32, 33]])
# 行(axis=0)並び替え
>>> rng.permutation(arr, axis=0)
array([[31, 32, 33],
[11, 12, 13],
[21, 22, 23]])
# 列(axis=1)並び替え
>>> rng.permutation(arr, axis=1)
array([[13, 12, 11],
[23, 22, 21],
[33, 32, 31]])
多次元配列の各要素をシャッフル・ランダム並び替え -.permuted()
rng.permuted(x, axis=None, out=None)
引数 | 説明 |
---|---|
x |
シャッフル元の配列 |
axis |
シャッフルする軸 |
out |
既存の配列に要素を代入する場合に使用 |
- 前項の
.permutation()
は多次元配列の軸を並び替えます。
-.permuted()
は多次元配列の軸内の要素をシャッフルします。
>>> rng = np.random.default_rng(0)
>>> arr = np.array([[11, 12, 13, 14, 15],
... [21, 22, 23, 24, 25],
... [31, 32, 33, 34, 35]])
#`.permutation()`は列`[[13], [23], [33]]`が維持されている
>>> rng.permutation(arr, axis=1)
array([[13, 15, 14, 11, 12],
[23, 25, 24, 21, 22],
[33, 35, 34, 31, 32]])
#`.permuted()`は各行の要素をそれぞれシャッフルする
>>> rng.permutation(arr, axis=1)
array([[15, 12, 13, 11, 14],
[21, 23, 24, 25, 22],
[34, 33, 31, 32, 35]])
ランダムサンプリング抽出・ガチャ・くじ引き -.choice()
rng.choice(a, size=None, replace=True, p=None, axis=0, shuffle=True)
引数 | 説明 |
---|---|
a |
配列(母集団) |
size |
結果の配列の形。省略するとスカラー値 |
replace |
デフォルトはTrue で重複あり。False にすると重複なし |
p |
a に対応する離散確率分布 |
axis |
a が多次元配列の場合にサンプリングする軸 |
shuffle |
組み合わせが欲しい(結果の順番を気にしない)場合はFalse
|
配列a
から任意の数size
だけ要素を取り出します。
>>> rng = np.random.default_rng(0)
>>> arr = np.array(['a', 'b', 'c', 'd', 'e', 'f', 'g'])
>>> rng.choice(arr, 3)
array(['f', 'e', 'd'], dtype='<U1')
>>> rng.choice(arr, 10)
array(['b', 'c', 'a', 'a', 'a', 'b', 'f', 'e', 'g', 'd'], dtype='<U1')
引数p
に確率分布の配列を与えると、a
から抽出される確率に重みをつけることができます。
なおp
の長さがa
と同じでない場合や、p
の総和が1.0
(全確率)でない場合はエラーが出ます。
>>> rng = np.random.default_rng(0)
>>> arr = np.array(['N', 'R', 'SR', 'SSR'])
>>> s = rng.choice(arr, 10000, p=[0.5, 0.3, 0.15, 0.05])
>>> np.unique(s, return_counts=True)
(array(['N', 'R', 'SR', 'SSR'], dtype='<U3'),
array([4990, 2989, 1523, 498], dtype=int64))
デフォルトのreplace=True
では、重複ありでサンプリングします(いわゆる『袋からボールを取り出した後、ボールを袋に戻してから2個目を取り出す』)が、replace=False
に設定すると重複なしでサンプリングします。
なおreplace=False
の場合はsize
がlen(a)
より多いとエラーが出ます。size
とlen(a)
が等しいときは並び替えとなるので.permutation(x)
と同じ操作をすることになります。
この重複なし(replace=False
)抽出の場合、結果配列の並び順が気にならないときがあります(高校数学でいう「順列」ではなく「組み合わせ」を求める場合)。例えばくじが複数回引かれるとき、1人目と2人目がそれぞれ引く場合は1つ目と2つ目の結果を区別する必要があるのに対して、同じ人が複数回引く場合は1つ目と2つ目を区別する必要はありません。
この場合はshuffle=False
を設定すると、処理速度が速くなります。
>>> rng = np.random.default_rng(0)
>>> arr = np.array(['a', 'b', 'c', 'd', 'e', 'f', 'g'])
# 重複あり(replace=True)
>>> rng.choice(arr, 7)
array(['f', 'e', 'd', 'b', 'c', 'a', 'a'], dtype='<U1')
# 重複なし(replace=False)
>>> rng.choice(arr, 7, replace=False)
array(['a', 'g', 'b', 'c', 'd', 'f', 'e'], dtype='<U1')
# 重複なし、組み合わせ抽出(速い)
>>> rng.choice(arr, 7, replace=False, shuffle=False)
array(['a', 'b', 'c', 'd', 'e', 'f', 'g'], dtype='<U1')
第一引数a
に整数を設定するとnp.arange(a)
が元の配列と解釈されます(そのためreplace=True
の場合は.integers(a)
と同じ操作をすることになります)。
>>> rng = np.random.default_rng(0)
>>> rng.choice(10, 7, replace=False)
array([9, 0, 1, 5, 2, 4, 3], dtype=int64)
多次元配列の場合はaxis
引数で抽出する軸を設定します。
>>> rng = np.random.default_rng(0)
>>> arr = np.array([[11, 12, 13],
... [21, 22, 23],
... [31, 32, 33]])
# 行(axis=0)サンプリング
>>> rng.choice(arr, 1, axis=0)
array([[31, 32, 33]])
# 列(axis=1)サンプリング
>>> rng.choice(arr, 1, axis=1)
array([[12],
[22],
[32]])
おまけ
.perumutation()
/.choice(replace=False)
を高速に
配列のサイズがおよそ100未満の場合はrng.permutation(x)
よりもx[rng.random(len(x)).argsort()]
のほうが高速に処理できます。
>>> rng = np.random.default_rng(0)
>>> arr = np.array(['a', 'b', 'c', 'd', 'e', 'f', 'g'])
# 一般的な方法
>>> rng.permutation(arr)
array(['c', 'e', 'd', 'g', 'f', 'a', 'b'], dtype='<U1')
# 速い方法
>>> idx = rng.random(len(arr)).argsort()
... idx
array([4, 2, 3, 0, 6, 1, 5], dtype=int64)
>>> arr[idx]
array(['e', 'c', 'd', 'a', 'g', 'b', 'f'], dtype='<U1')
この方法は.perumutation()
を複数回行う場合に特に有用です。
>>> rng = np.random.default_rng(0)
>>> arr = np.array(['a', 'b', 'c', 'd', 'e', 'f', 'g'])
# 一般的な方法
>>> np.vstack([rng.permutation(arr) for _ in range(4)])
array([['c', 'e', 'd', 'g', 'f', 'a', 'b'],
['f', 'c', 'e', 'g', 'b', 'a', 'd'],
['c', 'b', 'd', 'g', 'a', 'e', 'f'],
['e', 'a', 'g', 'c', 'f', 'b', 'd']], dtype='<U1')
# 速い方法
>>> arr[rng.random((4, len(arr))).argsort()]
array([['c', 'd', 'a', 'b', 'g', 'f', 'e'],
['a', 'g', 'e', 'd', 'f', 'c', 'b'],
['a', 'd', 'e', 'c', 'b', 'f', 'g'],
['c', 'e', 'a', 'f', 'b', 'd', 'g']], dtype='<U1')
.choice(replace=False)
は以下のようにします。
>>> rng = np.random.default_rng(0)
>>> arr = np.array(['a', 'b', 'c', 'd', 'e', 'f', 'g'])
# 一般的な方法
>>> np.vstack([rng.choice(arr, 3, replace=False) for _ in range(4)])
array([['d', 'g', 'e'],
['g', 'f', 'a'],
['d', 'g', 'f'],
['d', 'g', 'f']], dtype='<U1')
# 速い方法
>>> arr[rng.random((4, len(arr))).argsort()[:, :3]]
array([['b', 'd', 'f'],
['d', 'e', 'b'],
['b', 'a', 'f'],
['b', 'e', 'a']], dtype='<U1')