Pythonを用いたデータサイエンスや機械学習の現場において、乱数生成はシミュレーションやサンプリング、テストデータの作成など欠かせない要素となっています。
中でも整数の乱数を生成する操作は、インデックスのランダム抽出や離散的な事象のモデル化において頻繁に利用されます。
これまでNumPyではnumpy.random.randintが広く使われてきましたが、現在の最新バージョンではより高速で柔軟なGenerator(ジェネレータ)クラスの使用が推奨されています。
この記事では、従来のrandintの使い方から、最新の推奨手法への移行、そしてパフォーマンスを最大限に引き出すための実践的なテクニックまでを詳しく解説します。
NumPyを利用するすべてのエンジニアが知っておくべき、2026年現在のスタンダードな乱数生成手法をマスターしていきましょう。
NumPyにおける乱数生成の進化と新旧APIの違い
NumPyの乱数生成機能は、バージョン1.17を境に大きな転換期を迎えました。
以前から存在するnumpy.random.randintなどの関数は「Legacy(レガシー)API」と呼ばれ、現在はメンテナンスモードに入っています。
レガシーAPIは、グローバルな乱数生成器の状態(Random State)を共有するため、マルチスレッド環境での並列実行時に予期せぬ挙動やパフォーマンス低下を引き起こす可能性がありました。
これに対し、新しく導入された「Generator API」は、明示的に乱数生成オブジェクトを作成して利用するスタイルを採用しています。
新しいAPIでは、内部アルゴリズムとして「PCG64」が採用されており、従来の「Mersenne Twister(メルセンヌ・ツイスタ)」よりも統計的な性質が良く、動作も高速です。
今後の新規開発においては、numpy.random.default_rng()を使用してGeneratorインスタンスを取得し、そのメソッドを通じて乱数を得る方法が標準となります。
従来のnumpy.random.randintによる整数乱数生成
まずは、多くの既存コードで使用されているnumpy.random.randintの基本的な使い方をおさらいしておきましょう。
この関数は、指定した範囲内から一様分布に従う整数を生成します。
基本的な構文はnumpy.random.randint(low, high=None, size=None, dtype=int)となります。
lowは生成する範囲の最小値を指定し、highは最大値を指定しますが、最大値は生成される値に含まれない(半開区間)ことに注意が必要です。
import numpy as np
# 0から9までの整数を1つ生成
val = np.random.randint(0, 10)
print(f"生成された値: {val}")
# 1から100までの整数を5個持つ配列を生成
arr = np.random.randint(1, 101, size=5)
print(f"生成された配列: {arr}")
生成された値: 7
生成された配列: [12 85 43 9 66]
もしhighを指定せずlowだけを指定した場合、0からlow未満の範囲で数値が生成されます。
例えばnp.random.randint(5)と記述すると、0から4までの整数が返されます。
size引数には、スカラー値だけでなくタプルを指定することで、多次元配列を一度に生成することも可能です。
randintの引数と戻り値の詳細
randintには、生成するデータの型を指定するdtype引数も存在します。
デフォルトではPythonの標準的な整数型が使用されますが、メモリを節約したい場合にはint8やuint16などを指定することができます。
大規模なデータを扱う際には、適切なdtypeを選択することでメモリ使用量を大幅に削減することが可能です。
以下の表は、randintで使用可能な主要な引数の一覧です。
| 引数名 | 役割 | 備考 |
|---|---|---|
| low | 生成範囲の下限 | この値自体は含まれる |
| high | 生成範囲の上限 | この値は含まれない(オプション) |
| size | 出力の形状 | 整数またはタプルで指定 |
| dtype | データの型 | int64, int32, uint8など |
最新の推奨手法:Generatorとintegersメソッド
2026年現在の開発現場において、もっとも推奨される方法はnumpy.random.Generator.integersメソッドを利用することです。
このメソッドを利用するためには、まずnp.random.default_rng()を呼び出してGeneratorオブジェクトを作成する必要があります。
default_rng()にはシード値を渡すことができ、再現性が必要な場合には特定の数値を指定します。
新しいAPIでは、従来のrandintよりも機能が強化されており、例えば範囲の終端(high)を結果に含めるかどうかを制御するendpointパラメータが追加されています。
import numpy as np
# Generatorのインスタンスを作成
rng = np.random.default_rng(seed=42)
# 0から10までの整数を5個生成(10は含まない)
arr_exclusive = rng.integers(0, 10, size=5)
print(f"終端を含まない場合: {arr_exclusive}")
# 0から10までの整数を5個生成(10も含む可能性がある)
arr_inclusive = rng.integers(0, 10, size=5, endpoint=True)
print(f"終端を含む場合: {arr_inclusive}")
終端を含まない場合: [0 7 6 4 4]
終端を含む場合: [8 0 6 2 10]
endpoint=Trueを指定できるようになったことで、数学的な閉区間([low, high])での乱数生成が直感的に行えるようになりました。
これは、サイコロの目(1から6)をシミュレートする場合などに、rng.integers(1, 6, endpoint=True)と書けるため非常に便利です。
なぜGeneratorへ移行すべきなのか
従来のnp.random.seed()によるシード固定は、プログラム全体に影響を及ぼすグローバルな設定でした。
そのため、複数のライブラリが内部で乱数を使用している場合、シードが競合したり、意図しない場所で乱数の生成順序が変わってしまうリスクがありました。
Generator APIを使用すれば、特定のオブジェクト内だけで状態が完結するため、コードのモジュール性と予測可能性が向上します。
また、内部で使われているPCG64アルゴリズムは、統計的な検定(BigCrushなど)においてメルセンヌ・ツイスタよりも優れた成績を収めています。
さらに、Generatorクラスは大規模な配列生成において、Legacy APIよりも優れたスループットを発揮するように設計されています。
効率的な整数乱数生成のための実践テクニック
大量の整数データを生成する場合、単純にループを回すのではなく、NumPyのベクトル演算機能を最大限に活用することが重要です。
size引数に多次元の形状を渡すことで、C言語レベルで最適化されたループ処理が行われ、Pythonのループよりも圧倒的に速く処理が完了します。
また、生成する整数の範囲が小さい場合(例えば0か1のみなど)、dtypeをnp.int8やboolに指定することで、メモリ転送速度を向上させることが可能です。
# 1000x1000の巨大な行列をint8型で生成
huge_matrix = rng.integers(0, 2, size=(1000, 1000), dtype=np.int8)
print(f"行列の形状: {huge_matrix.shape}")
print(f"メモリ使用量: {huge_matrix.nbytes} バイト")
行列の形状: (1000, 1000)
メモリ使用量: 1000000 バイト
もしdtype=np.int64(デフォルトに近い設定)を使用した場合、メモリ使用量は8,000,000バイトとなり、8倍の差が生じます。
このように、データの特性に合わせた型選択は、大規模計算において非常に重要な最適化手法となります。
ブロードキャストを活用した範囲の指定
rng.integersの面白い特性として、lowやhighに配列を渡すことができる点が挙げられます。
これにより、要素ごとに異なる範囲から乱数を生成することが一行で記述できます。
例えば、「1つ目の要素は0-10の間、2つ目は10-100の間、3つ目は100-1000の間」といった生成が可能です。
lows = [0, 10, 100]
highs = [10, 100, 1000]
# それぞれ異なる範囲から1つずつ生成
varied_samples = rng.integers(lows, highs)
print(f"異なる範囲からの生成結果: {varied_samples}")
異なる範囲からの生成結果: [ 3 42 812]
このテクニックは、パラメータ探索や、個体ごとに異なる制約を持つエージェントベースのシミュレーションなどで極めて有効です。
パフォーマンス比較:Legacy API vs Generator API
実際に、旧来のrandintと新しいGeneratorのintegersでどの程度の速度差があるのかを確認してみましょう。
一般的に、単一の数値を生成する場合には大きな差は感じられませんが、数百万個単位の配列を生成する際にその真価が発揮されます。
以下は、1,000万個の整数を生成する際の処理時間を比較する概念的な例です。
| 手法 | アルゴリズム | 処理速度(相対的な目安) |
|---|---|---|
| np.random.randint | MT19937 | 1.0 (基準) |
| Generator.integers | PCG64 | 約1.2x 〜 1.5x 高速 |
最新のハードウェア環境(特にCPUのベクトル演算命令が強力な場合)では、PCG64の方がメモリアクセスパターンが効率的であり、計算速度の向上が期待できます。
また、ビット生成の品質が高いため、モンテカルロ法などの精密な統計計算を行う際にも、Generator APIを使用することが推奨されます。
よくあるエラーとトラブルシューティング
NumPyの乱数生成でよく遭遇する問題の一つに、「範囲指定のミス」があります。
lowがhigh以上の値になると、ValueErrorが発生します。
動的に範囲が決まるプログラムを書く場合は、必ずlow < highであるか、あるいはendpoint=Trueを考慮した条件チェックを入れるようにしましょう。
また、古いコードをコピー&ペーストした際に、np.random.seed()とdefault_rng()を混ぜてしまうケースも見受けられます。
これらは独立した乱数ストリームを持っているため、一方のシードを固定しても他方の結果は固定されません。
プロジェクト全体でどちらのAPIを使用するか統一することが、デバッグのしやすさに直結します。
まとめ
本記事では、NumPyにおける整数乱数生成の仕組みと、その最新手法について解説してきました。
従来のnumpy.random.randintは馴染み深い関数ですが、2026年現在のスタンダードはnumpy.random.default_rng().integersです。
新しいGenerator APIは、速度面だけでなく、安全性や機能性の面でも従来のAPIを凌駕しています。
特にendpoint引数による柔軟な範囲指定や、PCG64アルゴリズムによる高品質な乱数生成は、データ分析の信頼性を高める上で非常に重要です。
効率的なデータ生成のために、適切なdtypeの選択や多次元配列の一括生成を活用し、パフォーマンスを最大化させましょう。
これからNumPyを使ったプログラムを記述する際は、ぜひ最新のGeneratorクラスを活用して、クリーンで高速なコードを目指してください。
