Random Number Generation

Reviewed & published by Brayan K

Random number generation in modern NumPy is handled by a Generator object, created with np.random.default_rng(), that produces random floats, integers, and samples from statistical distributions on demand.

Part of the free NumPy course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

In this lesson you'll create a generator, draw random floats and integers, sample from a normal distribution, pick and shuffle elements, and lock in reproducible results with a fixed seed.

What You'll Learn in This Lesson

1 The Modern Generator API

Modern NumPy creates randomness through a Generator object. You build one with np.random.default_rng(seed) and then call methods on it. Passing a fixed integer seed makes the sequence reproducible — the same seed always yields the same numbers.

import numpy as np

# Create a generator with a fixed seed for reproducibility
rng = np.random.default_rng(42)

# Random floats in [0, 1)
print(rng.random(3))
# [0.77395605 0.43887844 0.85859792]

# Run again with the SAME seed -> identical numbers
rng2 = np.random.default_rng(42)
print(rng2.random(3))
# [0.77395605 0.43887844 0.85859792]

Expected output: with seed 42, both calls print the identical array [0.77395605 0.43887844 0.85859792]. Change the seed and the numbers change.

2 Random Integers with rng.integers

For whole numbers use rng.integers(low, high, size). Like Python's range, the high value is excluded by default. The size argument controls how many you get and can even be a tuple for a 2D grid.

import numpy as np

rng = np.random.default_rng(0)

# Five integers from 1 up to (but not including) 7 -> dice rolls
rolls = rng.integers(1, 7, size=5)
print(rolls)        # e.g. [5 6 1 4 4]  (reproducible with seed 0)

# A 2x3 grid of integers from 0 to 9
grid = rng.integers(0, 10, size=(2, 3))
print(grid)
# [[8 0 1]
#  [3 3 7]]

Expected output: five values in the range 1–6 and a 2×3 grid of values 0–9. With seed 0 these are reproducible; the exact numbers stay fixed as long as the seed does.

3 Sampling a Normal Distribution

Real-world data often follows a bell curve. rng.normal(mean, std, size) draws samples from a normal (Gaussian) distribution with the mean and standard deviation you choose. Drawing many samples, the average will sit close to the requested mean.

import numpy as np

rng = np.random.default_rng(7)

# 1000 samples centered at 100 with spread of 15 (like IQ scores)
samples = rng.normal(100, 15, size=1000)

print(samples.shape)        # (1000,)
print(round(samples.mean(), 1))   # close to 100.0  (e.g. 100.4)
print(round(samples.std(), 1))    # close to 15.0   (e.g. 14.8)

Expected output: shape (1000,), a mean near 100.0 and a standard deviation near 15.0. The exact decimals depend on the seed, but they cluster around those targets.

4 choice, shuffle, and the Legacy API

rng.choice picks random elements from an array, with or without replacement. rng.shuffle reorders an array in place. You may still see the older np.random.seed and np.random.rand in tutorials — they work, but the Generator API is preferred for new code.

import numpy as np

rng = np.random.default_rng(3)

colors = np.array(["red", "green", "blue", "yellow"])

# Pick 2 colors without repeats
print(rng.choice(colors, size=2, replace=False))   # e.g. ['blue' 'red']

# Shuffle a deck of numbers IN PLACE
deck = np.arange(1, 6)
rng.shuffle(deck)
print(deck)        # e.g. [3 1 5 2 4]

# Legacy API (still works, but prefer default_rng)
np.random.seed(42)
print(np.random.rand(2))   # [0.37454012 0.95071431]

Expected output: two distinct colors, a shuffled 1–5 deck, and from the legacy seed-42 call the fixed pair [0.37454012 0.95071431].

🎯 YOUR TURN: Fill in the Blanks

Replace each ___ so the program creates a seeded generator and rolls four dice.

import numpy as np

# Create a reproducible generator with seed 99
rng = np.random.___(99)        # fill in the function name

# Four dice: integers 1 through 6 (high value 7 is excluded)
dice = rng.integers(1, ___, size=4)   # fill in the high value

print(dice)        # four numbers, each between 1 and 6
print(dice.min() >= 1 and dice.max() <= 6)   # Expected output: True

Expected output: four numbers from 1 to 6 and True. (Answers: default_rng, 7.)

⚠️ Common Errors & Quick Reference

❌ Results are different every run when I wanted them fixed

You created the generator without a seed.

✅ Fix: pass a fixed integer, e.g. np.random.default_rng(42), for reproducible output.

❌ rng.integers gave a number equal to high

It never should — high is excluded by default, so this usually means an off-by-one expectation.

✅ Fix: to include the top value, pass endpoint=True, or set high one above your target.

TaskCode
Make a seeded generatornp.random.default_rng(42)
Random floats [0, 1)rng.random(5)
Random integersrng.integers(0, 10, size=5)
Normal distributionrng.normal(0, 1, size=5)
Pick random elementsrng.choice(arr, size=2)
Shuffle in placerng.shuffle(arr)

🏆 Mini Challenge: Simulate Coin Flips

Flip a fair coin 1000 times and count the heads. With enough flips the proportion should sit near 0.5.

import numpy as np

rng = np.random.default_rng(2024)

# 0 = tails, 1 = heads
flips = rng.integers(0, 2, size=1000)

heads = flips.sum()
print("Heads:", heads)               # e.g. Heads: 503
print("Proportion:", heads / 1000)   # close to 0.5

❓ Frequently Asked Questions

Lesson complete — randomness, under control!

You can build a seeded generator with default_rng, draw floats, integers, and normal samples, pick and shuffle elements, and guarantee reproducible results with a fixed seed.

Practice quiz

How do you create a generator in modern NumPy?

  • np.random.default_rng(seed)
  • np.random.make()
  • np.Generator()
  • np.rng()

Answer: np.random.default_rng(seed). np.random.default_rng(seed) is the recommended way to create a Generator.

How do you make random results reproducible?

  • Call shuffle twice
  • Pass a fixed integer seed to default_rng
  • Use a larger array
  • Never call random()

Answer: Pass a fixed integer seed to default_rng. A fixed seed always produces the same sequence, essential for reproducibility.

What does rng.random() return?

  • Integers
  • Booleans
  • Floats in the half-open interval [0, 1)
  • Strings

Answer: Floats in the half-open interval [0, 1). rng.random returns floats uniformly distributed in [0, 1).

By default, is the high value excluded in rng.integers(low, high)?

  • No, it is always included
  • Only for size>1
  • Only for negative numbers
  • Yes, high is excluded by default

Answer: Yes, high is excluded by default. Like Python's range, rng.integers excludes high by default unless endpoint=True.

Which method draws samples from a normal distribution?

  • rng.normal(mean, std, size)
  • rng.integers
  • rng.random
  • rng.choice

Answer: rng.normal(mean, std, size). rng.normal(mean, std, size) draws Gaussian samples with the given mean and spread.

What does rng.shuffle(arr) do?

  • Returns a sorted copy
  • Reorders the array in place and returns None
  • Returns a new shuffled array
  • Picks one element

Answer: Reorders the array in place and returns None. rng.shuffle reorders the array in place and returns None.

Which method picks random elements from an array?

  • rng.normal
  • rng.integers
  • rng.choice
  • rng.shuffle

Answer: rng.choice. rng.choice selects random elements, with or without replacement.

Which is the legacy global API you may still see?

  • rng.integers
  • default_rng
  • rng.normal
  • np.random.seed and np.random.rand

Answer: np.random.seed and np.random.rand. np.random.seed/np.random.rand are the older global functions; default_rng is preferred.

Two generators created with the same seed will...

  • Produce the same sequence of numbers
  • Produce opposite numbers
  • Always differ
  • Raise an error

Answer: Produce the same sequence of numbers. The same seed yields the identical sequence, which is what makes results reproducible.

How do you include the top value in rng.integers?

  • Set size larger
  • Pass endpoint=True
  • Use rng.random instead
  • Subtract one from low

Answer: Pass endpoint=True. Pass endpoint=True (or set high one above your target) to include the top value.

Continue this course

Frequently asked questions

How do I generate random numbers in modern NumPy?

Create a Generator with rng = np.random.default_rng(seed), then call methods on it such as rng.random() for floats in [0, 1), rng.integers(low, high, size) for integers, and rng.normal(mean, std, size) for normally distributed values. This Generator API is the recommended approach in NumPy 1.17 and later.

How do I make random results reproducible?

Pass a fixed integer seed when you create the generator, like np.random.default_rng(42). The same seed always produces the same sequence of random numbers, which is essential for debugging, testing, and reproducible experiments. Use a different seed (or no seed) when you want different results each run.

Should I use np.random.seed and np.random.rand?

Those are the legacy global functions. They still work and you will see them in older code, but NumPy now recommends the Generator API via np.random.default_rng because it avoids hidden global state and produces higher-quality random numbers. Prefer default_rng for new code.

What is the difference between rng.random and rng.integers?

rng.random returns floating-point numbers uniformly distributed in the half-open interval [0, 1). rng.integers returns whole numbers from a range you specify with low and high. By default rng.integers excludes the high value, matching Python's range behavior, unless you pass endpoint=True.