Skip to main content

Random Module Overview

The numpy.random module implements pseudo-random number generators (PRNGs) with the ability to draw samples from a variety of probability distributions.

Quick Start

Create a Generator instance with default_rng and call its methods:
import numpy as np

# Create a generator
rng = np.random.default_rng()

# Generate random floats in [0, 1)
rng.random()
# 0.06369197489564249

# Generate array from standard normal distribution
rng.standard_normal(10)
# array([-0.31, -1.89, -0.36, ...])

# Generate random integers
rng.integers(low=0, high=10, size=5)
# array([8, 7, 6, 2, 0])

Module Structure

Generator

Modern interface for all distributions and random operations

Bit Generators

Core RNG algorithms: PCG64, MT19937, Philox, SFC64

Distributions

Draw samples from various probability distributions

Legacy API

RandomState and legacy global functions

Architecture

NumPy’s random number generation uses a two-layer design:
┌─────────────────────────┐
│      Generator          │  ← User Interface
│  (Distributions)        │     (normal, uniform, etc.)
└─────────┬───────────────┘

┌────────┴───────────────┐
│    BitGenerator        │  ← Core RNG Algorithm
│  (PCG64, MT19937)     │     (state management)
└─────────────────────────┘
  • Generator: High-level interface providing distribution methods
  • BitGenerator: Low-level engine producing random bits

default_rng

numpy.random.default_rng(seed=None)
Construct a new Generator with the default BitGenerator (PCG64). Parameters:
  • seed : , optional
    • None: Use OS entropy for random seed (non-reproducible)
    • int: Use integer as seed
    • SeedSequence: Use for advanced seeding
    • BitGenerator/Generator: Use existing state
Returns:
  • Generator - Initialized Generator instance
Examples:
import numpy as np

# Non-reproducible (different each time)
rng = np.random.default_rng()
print(rng.random())  # 0.6596288841243357

# Reproducible with seed
rng1 = np.random.default_rng(42)
rng2 = np.random.default_rng(42)
print(rng1.random() == rng2.random())  # True

# Use large seed for uniqueness
import secrets
seed = secrets.randbits(128)
rng = np.random.default_rng(seed)
print(f"Seeded with: {seed}")

Generator Class

class numpy.random.Generator(bit_generator)
Container for BitGenerators that provides access to distributions. Parameters:
  • bit_generator : BitGenerator - Core RNG engine
Example:
import numpy as np
from numpy.random import Generator, PCG64

# Create with specific BitGenerator
rng = Generator(PCG64(seed=12345))

# Use methods
print(rng.normal(0, 1, size=5))
print(rng.integers(0, 100, size=10))
Use default_rng() instead of constructing Generator directly unless you need a specific BitGenerator.

Seeding for Reproducibility

Basic Seeding

import numpy as np

# Same seed = same sequence
rng1 = np.random.default_rng(42)
rng2 = np.random.default_rng(42)

for i in range(5):
    print(f"{rng1.random():.6f} == {rng2.random():.6f}")

Advanced Seeding

import numpy as np
import secrets

# Use 128-bit entropy for strong uniqueness
entropy = secrets.randbits(128)
print(f"Entropy: 0x{entropy:032x}")

rng = np.random.default_rng(entropy)

# Spawn independent child generators for parallel work
child1, child2 = rng.spawn(2)

print("Parent:", rng.random())
print("Child1:", child1.random())
print("Child2:", child2.random())

Available BitGenerators

BitGeneratorQualitySpeedPeriodParallel
PCG64 (default)ExcellentVery Fast2¹²⁸Yes (spawn)
MT19937GoodFast2¹⁹⁹³⁷No
PhiloxExcellentFast2²⁵⁶Yes (counter)
SFC64GoodVery Fast~2²⁵⁶Limited
from numpy.random import Generator, PCG64, MT19937, Philox, SFC64

# Use different BitGenerators
rng_pcg = Generator(PCG64(42))
rng_mt = Generator(MT19937(42))
rng_philox = Generator(Philox(42))
rng_sfc = Generator(SFC64(42))

print("PCG64:", rng_pcg.random())
print("MT19937:", rng_mt.random())
print("Philox:", rng_philox.random())
print("SFC64:", rng_sfc.random())

Security Warning

The pseudo-random number generators in this module are designed for statistical modeling and simulation, not security or cryptography. Use Python’s secrets module for security purposes.
# DON'T use for security
import numpy as np
rng = np.random.default_rng()
insecure_token = rng.integers(0, 1000000)  # ❌ Predictable

# DO use secrets module
import secrets
secure_token = secrets.randbelow(1000000)  # ✓ Cryptographically secure

Common Patterns

Generate Random Data

import numpy as np

rng = np.random.default_rng(42)

# Uniform random floats
uniform_data = rng.random(1000)

# Normal distribution
normal_data = rng.normal(loc=0, scale=1, size=1000)

# Random integers
integers = rng.integers(0, 100, size=50)

# Random choice from array
arr = np.array(['apple', 'banana', 'cherry'])
rng.choice(arr, size=10, replace=True)

Shuffle and Permutations

import numpy as np

rng = np.random.default_rng()

# Shuffle in-place
arr = np.arange(10)
rng.shuffle(arr)
print(arr)  # [3, 7, 1, 9, ...]

# Get permutation (copy)
perm = rng.permutation(10)
print(perm)  # [5, 2, 8, 1, ...]

Parallel Random Number Generation

import numpy as np
from concurrent.futures import ProcessPoolExecutor

def simulate(rng, n):
    """Run simulation with independent RNG."""
    return rng.normal(size=n).mean()

# Create parent RNG
parent_rng = np.random.default_rng(42)

# Spawn independent children
child_rngs = parent_rng.spawn(4)

# Run in parallel
with ProcessPoolExecutor() as executor:
    results = executor.map(simulate, child_rngs, [10000]*4)
    
print("Results:", list(results))

Migration from Legacy API

If you’re using the old RandomState or global functions:
# Old (legacy)
import numpy as np
np.random.seed(42)
x = np.random.random(10)
y = np.random.normal(0, 1, 100)

# New (recommended)
import numpy as np
rng = np.random.default_rng(42)
x = rng.random(10)
y = rng.normal(0, 1, 100)
See Legacy API for more details.

Performance Tips

  1. Create Generator once: Reuse the same Generator instance
  2. Use vectorized operations: Request arrays, not scalars in loops
  3. Choose right BitGenerator: PCG64 (default) is excellent for most use cases
import numpy as np
import time

rng = np.random.default_rng()

# Slow: Generate in loop
start = time.time()
result = [rng.random() for _ in range(10000)]
loop_time = time.time() - start

# Fast: Vectorized
start = time.time()
result = rng.random(10000)
vec_time = time.time() - start

print(f"Loop: {loop_time:.4f}s, Vectorized: {vec_time:.4f}s")
print(f"Speedup: {loop_time/vec_time:.1f}x")

See Also

Build docs developers (and LLMs) love