Chapter 16, Basic
Random numbers with Generator
Shuffling data, sampling it, generating a synthetic set for training: all of this requires **controlled** randomness. And NumPy now has a modern way to do it, different from what many tutorials still teach.
Code for this chapter: básico/cap16_aleatorios.py
The modern way: `default_rng`
You create a generator with a seed (seed) and request numbers from it. The same seed produces always the same sequence, and that is what makes a result reproducible:
import numpy as np
rng = np.random.default_rng(42)
print(rng.random(3))
print(rng.integers(0, 10, size=5))
print(rng.normal(loc=0, scale=1, size=3))
print(rng.choice([10, 20, 30], size=2, replace=False))
[0.77395605 0.43887844 0.85859792]
[0 6 2 0 5]
[ 0.1278404 -0.31624259 -0.01680116]
[30 20]
a = np.random.default_rng(7).random(3)
b = np.random.default_rng(7).random(3)
print(np.array_equal(a, b))
True
| Method | Generates |
|---|---|
rng.random(n) | Uniform decimals in [0, 1) |
rng.integers(a, b, size) | Integers in [a, b) |
rng.normal(media, desvio, size) | Normal distribution (the bell curve) |
rng.uniform(a, b, size) | Uniform decimals in [a, b) |
rng.choice(opcoes, size, replace, p) | Drawing existing values, with or without replacement |
rng.shuffle(a), rng.permutation(a) | Shuffle |
rng.binomial, rng.poisson, rng.exponential | Other distributions |
Why not `np.random.seed`
The old API (np.random.seed(42) followed by np.random.rand(3)) still works and appears in a lot of material, including courses, but uses a global state hidden in the module:
np.random.seed(42)
print(np.random.rand(3))
[0.37454012 0.95071431 0.73199394]
The problem with global state is that any code, anywhere, can consume numbers from it and change its sequence without you noticing, which breaks reproducibility and makes tests fragile. With the Generator, the state resides in a controlled object. And, therefore, functions that use randomness must receive the generator as a parameter:
def sorteio(n, rng):
return rng.integers(1, 7, size=n)
print(sorteio(5, np.random.default_rng(1)).tolist())
[3, 4, 5, 6, 1]
With this signature, the function test passes a generator with a fixed seed and checks for an exact result, while production passes any generator.
The guarantee of reproduction
The same seed reproduces the same numbers in the same version of NumPy. Between versions, the guarantee of the
Generatoris weaker than that of the old API, which the project froze. For an experiment that needs to be reproduced years later, also record the version of the library.
Shuffle: in place or in copy
The rng.shuffle shuffles the array itself and returns None. Writing x = rng.shuffle(x) discards the array. To get a shuffled version and keep the original, use permutation:
vetor = np.arange(1, 6)
resultado = rng.shuffle(vetor)
print(resultado, sorted(vetor.tolist()))
original = np.arange(1, 6)
embaralhado = rng.permutation(original)
print(original, sorted(embaralhado.tolist()))
None [1, 2, 3, 4, 5]
[1 2 3 4 5] [1, 2, 3, 4, 5]
Generate a synthetic dataset
Synthetic data with known characteristics allows testing an analysis knowing the answer. Here, a thousand heights with a mean of 170 and a standard deviation of 10: the sample mean is close to 170, but not exactly at it, because it is a sample:
alturas = rng.normal(170, 10, size=1000)
print(abs(alturas.mean() - 170) < 1, abs(alturas.std() - 10) < 1)
sorteados = rng.choice(["cara", "coroa"], size=1000, p=[0.9, 0.1])
print((sorteados == "cara").mean() > 0.8)
True True
True
Exercise 1
Simulate data
Write simular_dados(n, rng) that rolls a 6-sided die n times and returns how many times each face came up (an array with 6 counts), with np.bincount.