Aleatório, mas repetível
A simulação Monte Carlo, o bootstrap e muitos testes precisam de números aleatórios, mas um resultado que ninguém consegue reproduzir não vale nada: não dá para depurá-lo, comparar duas versões de um modelo nem mostrá-lo a um colega. Os números que um computador produz não são realmente aleatórios. Um gerador de números pseudoaleatórios é um algoritmo determinístico que, a partir de um valor inicial chamado semente (seed), produz uma longa sequência que se comporta como sorteios aleatórios. A mesma semente dá a mesma sequência, sempre e em qualquer máquina.
import numpy as np
rng = np.random.default_rng(42) # a Generator created from the seed 42
print(rng.random(3).round(4).tolist()) # [0.774, 0.4389, 0.8586]
rng = np.random.default_rng(42) # the same seed again: the same numbers
print(rng.random(3).round(4).tolist()) # [0.774, 0.4389, 0.8586]
rng = np.random.default_rng(43) # another seed: other numbers
print(rng.random(3).round(4).tolist()) # [0.6523, 0.0438, 0.02]Use um Generator, não estado global
O código antigo chama np.random.seed(42) uma vez e depois np.random.normal(...) em qualquer lugar: todo o código compartilha um único gerador global oculto. Qualquer biblioteca, ou qualquer linha nova que sorteie um número, desloca a sequência de todos os outros, e os resultados mudam por razões sem relação com o seu modelo. A forma moderna é criar um objeto Generator com np.random.default_rng(seed) e passá-lo explicitamente. Seus métodos random, normal, choice, integers fazem todos os sorteios, e dois geradores nunca interferem.
def simulate_returns(seed, n, mu, sigma):
rng = np.random.default_rng(seed) # a private generator, not the global one
return rng.normal(mu, sigma, size=n)
first = simulate_returns(7, 5, 0.0005, 0.01)
second = simulate_returns(7, 5, 0.0005, 0.01)
print(first.round(5).tolist()) # [0.00051, 0.00349, -0.00224, -0.00841, -0.00405]
print(np.array_equal(first, second)) # True repeatableReamostragem e fluxos independentes
Um bootstrap reamostra o histórico para ver quanto uma estatística poderia ter variado: sorteiam-se tantos retornos quanto se tem, com reposição, com rng.choice(returns, size=len(returns), replace=True). Alguns dias aparecem duas vezes, outros nenhuma. Quando muitas simulações rodam em paralelo, dar a cada processo as sementes 1, 2, 3 é arriscado porque sementes vizinhas não têm independência garantida. np.random.SeedSequence(seed).spawn(k) deriva k sequências filhas projetadas para não se sobrepor; construa um default_rng(child) de cada uma.
data = np.array([1, 2, 3, 4, 5])
rng = np.random.default_rng(1)
print(rng.choice(data, size=len(data), replace=True).tolist()) # [3, 3, 4, 5, 1] 3 appears twice, 2 not at all
# Three independent streams from one seed, for three parallel simulations
children = np.random.SeedSequence(2026).spawn(3)
streams = [np.random.default_rng(child) for child in children]
print([stream.random(2).round(4).tolist() for stream in streams])
# [[0.3827, 0.0204], [0.6711, 0.158], [0.5508, 0.2339]]rng = np.random.default_rng(42)
print(rng.integers(1, 7, size=8).tolist()) # [1, 5, 4, 3, 3, 6, 1, 5] eight dice rolls, repeatable
rng = np.random.default_rng(42)
print(rng.uniform(-0.01, 0.01, 3).round(5).tolist()) # [0.00548, -0.00122, 0.00717]rng = np.random.default_rng(seed): a mesma semente dá sempre os mesmos números; use seus métodosrandom,normal,choice,integers.- Passe o gerador em vez de usar o estado global oculto
np.random.seed. choice(..., replace=True)é um bootstrap;SeedSequence(seed).spawn(k)dákfluxos independentes; registre a semente usada.
A seguir: a amostragem de distribuições, transformando números aleatórios uniformes em sorteios normais, exponenciais e discretos.
Lab: o que é avaliado
- Criar um gerador repetível com
make_rng25 pts - Sortear retornos reproduzíveis com
simulate_returns25 pts - Reamostrar o histórico com
bootstrap_returns25 pts - Dividir uma semente em fluxos independentes com
independent_streams25 pts