Generare dati casuali e simulare esperimenti con NumPy

by theArchitect
SHARE
Generare dati casuali e simulare esperimenti con NumPy
© Guida-HTML5.it

Introduzione

NumPy viene spesso associato alla manipolazione di array numerici, ma offre anche strumenti completi per generare numeri casuali e costruire simulazioni. Questo sotto-argomento è utile in molti contesti: analisi statistica, test di algoritmi, simulazioni scientifiche, machine learning e sviluppo di applicazioni che richiedono dati artificiali.

Generare un numero casuale non significa ottenere un valore davvero imprevedibile in senso assoluto. I computer usano infatti algoritmi deterministici, chiamati generatori pseudo-casuali. Partendo da un valore iniziale, detto seme o seed, il generatore produce una sequenza di valori che appare casuale.

La possibilità di impostare un seed è fondamentale durante lo sviluppo: consente di ripetere un esperimento e ottenere gli stessi dati. In questo modo è più semplice individuare errori, confrontare algoritmi e verificare i risultati di un programma.

Nelle versioni moderne di NumPy è consigliato utilizzare l´oggetto Generator, creato tramite np.random.default_rng(). Rispetto alle funzioni casuali dello stile più vecchio, questo approccio rende il codice più esplicito, isolabile e facilmente testabile.

Codice completo

import numpy as np

# Creiamo un generatore riproducibile.
# Lo stesso seed produce la stessa sequenza di numeri.
rng = np.random.default_rng(42)

# 1. Generazione di numeri interi casuali
dadi = rng.integers(
    low=1,
    high=7,      # high è escluso: i valori possibili sono da 1 a 6
    size=10
)

print("Risultati dei lanci:", dadi)

# 2. Generazione di numeri reali uniformi nell´intervallo [0, 1)
probabilita = rng.random(size=5)

print("Numeri uniformi:", probabilita)

# 3. Generazione da una distribuzione normale
temperature = rng.normal(
    loc=20,      # media
    scale=3,     # deviazione standard
    size=1000
)

print("Media delle temperature:", temperature.mean())
print("Deviazione standard:", temperature.std())

# 4. Estrazione casuale da una collezione
prodotti = np.array(["A", "B", "C", "D"])

campione = rng.choice(
    prodotti,
    size=8,
    replace=True  # Un prodotto può essere estratto più volte
)

print("Prodotti estratti:", campione)

# 5. Permutazione casuale senza modificare l´array originale
valori = np.array([10, 20, 30, 40, 50])
permutati = rng.permutation(valori)

print("Array originale:", valori)
print("Array permutato:", permutati)

# 6. Simulazione di lanci di una moneta
lanci = rng.choice(
    ["testa", "croce"],
    size=1000
)

numero_teste = np.count_nonzero(lanci == "testa")
percentuale_teste = numero_teste / len(lanci) * 100

print("Percentuale di teste:", percentuale_teste)

# 7. Simulazione Monte Carlo per stimare pi greco
punti = rng.uniform(-1, 1, size=(100000, 2))

# Distanza dall´origine al quadrato:
# x^2 + y^2 <= 1 identifica i punti dentro il cerchio
dentro_cerchio = np.sum(punti ** 2, axis=1) <= 1

stima_pi = 4 * np.mean(dentro_cerchio)

print("Stima di pi greco:", stima_pi)

Spiegazione

Il generatore moderno di NumPy

La riga rng = np.random.default_rng(42) crea un generatore casuale usando il seed 42. Il numero scelto è arbitrario: si potrebbe usare qualsiasi intero. L´aspetto importante è che, mantenendo lo stesso seed e le stesse operazioni, la sequenza generata rimane riproducibile.

È preferibile creare un generatore locale invece di modificare casualmente lo stato globale del programma. In un progetto complesso, più moduli potrebbero infatti usare la casualità. Un generatore condiviso o globale renderebbe più difficile capire quale istruzione abbia modificato la sequenza.

Numeri interi e numeri reali

Il metodo integers() genera valori interi. Il limite inferiore è incluso, mentre quello superiore è escluso, proprio come accade negli intervalli di Python. Per esempio, rng.integers(1, 7) può produrre i valori da 1 a 6.

Il metodo random() produce invece numeri reali nell´intervallo tra 0 incluso e 1 escluso. Il parametro size stabilisce il numero o la forma dei valori generati. Per esempio, size=5 crea un array monodimensionale con cinque elementi, mentre size=(3, 4) crea una matrice di tre righe e quattro colonne.

Distribuzioni statistiche

Una distribuzione descrive il modo in cui i valori casuali vengono distribuiti. Con normal() si usa la distribuzione normale, spesso utilizzata per rappresentare misure influenzate da molte piccole variazioni indipendenti.

Il parametro loc indica la media della distribuzione, mentre scale rappresenta la deviazione standard. Una deviazione standard maggiore produce valori più dispersi attorno alla media.

Scelta e permutazione

choice() permette di estrarre elementi da un array. Con replace=True, l´estrazione avviene con reinserimento: lo stesso elemento può comparire più volte. Con replace=False, ogni elemento può essere scelto al massimo una volta, purché la dimensione del campione non superi il numero di elementi disponibili.

permutation() restituisce una versione mescolata dell´array. Nell´esempio l´array originale non viene modificato. Questa caratteristica è utile quando si vuole creare un ordine casuale per dividere dati in gruppi di addestramento e test.

La simulazione Monte Carlo

La simulazione finale usa un metodo Monte Carlo per stimare il valore di pi greco. Vengono generati punti casuali nel quadrato compreso tra -1 e 1. Il cerchio unitario è inscritto in questo quadrato.

Il rapporto tra i punti dentro il cerchio e il numero totale di punti approssima il rapporto tra l´area del cerchio e quella del quadrato. Moltiplicando questo rapporto per 4 si ottiene una stima di pi greco. Aumentando il numero di punti, in genere, la stima diventa più precisa, anche se non è garantito che migliori a ogni singola esecuzione.

Best practice

  • Usa default_rng(): è l´interfaccia moderna consigliata per la generazione casuale in NumPy.
  • Imposta un seed negli esperimenti: durante sviluppo, debugging e test automatici la riproducibilità è essenziale.
  • Separa i generatori: in applicazioni articolate puoi creare generatori distinti per componenti diversi, evitando interferenze tra sequenze casuali.
  • Preferisci la vettorizzazione: genera molti valori con una sola chiamata invece di usare cicli Python per creare un elemento alla volta.
  • Controlla la forma degli array: usa size in modo esplicito per ottenere dimensioni coerenti con il resto dell´elaborazione.
  • Verifica le assunzioni statistiche: una distribuzione normale non è adatta a ogni problema. Scegli la distribuzione in base al fenomeno che stai modellando.
  • Non confondere casualità simulata e sicurezza: per password, token o chiavi crittografiche bisogna utilizzare strumenti progettati per la sicurezza, non il generatore casuale di NumPy.
  • Documenta il seed: se i risultati devono essere riprodotti da altri sviluppatori, indica il seed e la versione delle librerie utilizzate.

Riepilogo

NumPy permette di generare in modo efficiente interi, numeri reali, campioni casuali e dati provenienti da distribuzioni statistiche. L´oggetto Generator rappresenta il punto di partenza consigliato per lavorare con la casualità.

Hai visto come usare integers(), random(), normal(), choice() e permutation(). Hai inoltre applicato questi strumenti a una simulazione Monte Carlo per stimare pi greco.

Il concetto più importante è la riproducibilità: impostando correttamente il seed puoi ottenere lo stesso esperimento più volte. Quando invece vuoi simulare esecuzioni indipendenti, puoi lasciare che NumPy inizializzi automaticamente il generatore oppure usare strategie dedicate alla gestione di più generatori.

Approfondisci con risorse ufficiali

  • Documentazione NumPy: consulta la sezione ufficiale dedicata alla generazione di numeri casuali e all´API di numpy.random.
  • Generator e default_rng: approfondisci il funzionamento dell´interfaccia moderna e del bit generator utilizzato internamente.
  • Distribuzioni statistiche: studia i metodi disponibili per distribuzioni uniformi, normali, binomiali, poissoniane ed esponenziali.
  • Simulazioni Monte Carlo: applica la generazione casuale a problemi di integrazione numerica, stima probabilistica e analisi del rischio.
  • Riproducibilità scientifica: documenta seed, versioni delle dipendenze e parametri dell´esperimento per rendere verificabili i risultati.

SHARE