Skip to content

    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:

    básico/cap16_aleatorios.pylinhas 10 a 16
    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))
    
    Output
    [0.77395605 0.43887844 0.85859792]
    [0 6 2 0 5]
    [ 0.1278404  -0.31624259 -0.01680116]
    [30 20]
    
    básico/cap16_aleatorios.pylinhas 18 a 20
    a = np.random.default_rng(7).random(3)
    b = np.random.default_rng(7).random(3)
    print(np.array_equal(a, b))
    
    Output
    True
    
    MethodGenerates
    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.exponentialOther 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:

    básico/cap16_aleatorios.pylinhas 25 a 26
    np.random.seed(42)
    print(np.random.rand(3))
    
    Output
    [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:

    básico/cap16_aleatorios.pylinhas 28 a 32
    def sorteio(n, rng):
        return rng.integers(1, 7, size=n)
    
    
    print(sorteio(5, np.random.default_rng(1)).tolist())
    
    Output
    [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 Generator is 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:

    básico/cap16_aleatorios.pylinhas 37 a 43
    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()))
    
    Output
    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:

    básico/cap16_aleatorios.pylinhas 48 a 52
    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)
    
    Output
    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.