Capítulo 16, Básico
Números aleatórios com Generator
Embaralhar dados, sorteá-los, gerar um conjunto sintético para treinar: tudo isso exige aleatoriedade **controlada**. E o NumPy tem hoje uma forma moderna de fazê-lo, diferente da que muitos tutoriais ainda ensinam.
Código deste capítulo: básico/cap16_aleatorios.py
A forma moderna: `default_rng`
Você cria um gerador com uma semente (seed) e pede números a ele. A mesma semente produz sempre a mesma sequência, e é isso que torna um resultado reproduzível:
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
| Método | Gera |
|---|---|
rng.random(n) | Decimais uniformes em [0, 1) |
rng.integers(a, b, size) | Inteiros em [a, b) |
rng.normal(media, desvio, size) | Distribuição normal (a curva do sino) |
rng.uniform(a, b, size) | Decimais uniformes em [a, b) |
rng.choice(opcoes, size, replace, p) | Sorteio de valores existentes, com ou sem reposição |
rng.shuffle(a), rng.permutation(a) | Embaralhar |
rng.binomial, rng.poisson, rng.exponential | Outras distribuições |
Por que não `np.random.seed`
A API antiga (np.random.seed(42) seguida de np.random.rand(3)) ainda funciona e aparece em muito material, inclusive em cursos, mas usa um estado global escondido no módulo:
np.random.seed(42)
print(np.random.rand(3))
[0.37454012 0.95071431 0.73199394]
O problema do estado global é que qualquer código, em qualquer lugar, pode consumir números dele e mudar a sua sequência sem você perceber, o que quebra a reprodutibilidade e torna os testes frágeis. Com o Generator, o estado mora em um objeto que você controla. E, por isso, funções que usam aleatoriedade devem receber o gerador como parâmetro:
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]
Com essa assinatura, o teste da função passa um gerador com semente fixa e verifica um resultado exato, e a produção passa um gerador qualquer.
A garantia de reprodução
A mesma semente reproduz os mesmos números na mesma versão do NumPy. Entre versões, a garantia do
Generatoré mais fraca do que a da API antiga, que o projeto congelou. Para um experimento que precisa ser reproduzido anos depois, registre também a versão da biblioteca.
Embaralhar: no lugar ou em cópia
O rng.shuffle embaralha o próprio array e devolve None. Escrever x = rng.shuffle(x) joga fora o array. Para obter um embaralhado e manter o 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]
Gerar um conjunto sintético
Dados sintéticos com características conhecidas permitem testar uma análise sabendo a resposta. Aqui, mil alturas com média 170 e desvio 10: a média amostral fica perto de 170, mas não exatamente nela, porque é uma amostra:
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
Exercício 1
Simular dados
Escreva simular_dados(n, rng) que jogue um dado de 6 faces n vezes e devolva quantas vezes saiu cada face (um array com 6 contagens), com np.bincount.