Génération de Nombres Aléatoires avec NumPy
NumPy est une bibliothèque fondamentale en Python, indispensable pour le calcul scientifique, l'analyse de données et l'apprentissage automatique. Une de ses capacités essentielles est la génération de nombres et de tableaux de données aléatoires, qui trouve des applications dans la simulation, les statistiques, la cryptographie et bien d'autres domaines. Ce guide explore les méthodes clés offertes par NumPy pour créer et manipuler des données aléatoires, en privilégiant les approches modernes pour une meilleure gestion de la reproductibilité.
Création de Données Aléatoires Fondamentales
NumPy propose des fonctions intuitives pour générer des nombres aléatoires suivant des distributions de base.
Nombres Flottants Uniformément Distribués
Pour générer des nombres décimaux (flottants) dans un intervalle donné, les méthodes de la classe Generator sont à privilégier. Elles offrent flexibilité et contrôle.
import numpy as np
# Initialisation d'un générateur de nombres aléatoires avec une graine fixe
# La graine (seed) assure la reproductibilité des résultats.
gen = np.random.default_rng(42)
# Génération d'un nombre flottant unique dans l'intervalle [0.0, 1.0)
nombre_unique = gen.random()
print(f"Nombre flottant unique [0,1): {nombre_unique:.4f}")
# Création d'un tableau 1D de 5 nombres aléatoires dans [0.0, 1.0)
tableau_1d = gen.random(5)
print(f"Tableau 1D de 5 éléments [0,1):\n{tableau_1d}")
# Génération d'une matrice 2x3 de nombres aléatoires dans [0.0, 1.0)
matrice_2x3 = gen.random((2, 3))
print(f"Matrice 2x3 [0,1):\n{matrice_2x3}")
# Génération de nombres dans un intervalle [a, b) arbitraire, par exemple [5, 15)
nombre_intervalle = gen.uniform(5, 15)
print(f"Nombre flottant unique [5,15): {nombre_intervalle:.4f}")
tableau_intervalle = gen.uniform(5, 15, size=4)
print(f"Tableau 1D de 4 éléments [5,15):\n{tableau_intervalle}")
Nombres Entiers Aléatoires
La fonction integers est conçue pour produire des nombres entiers aléatoires dans un intervalle spécifié.
import numpy as np
gen = np.random.default_rng(42)
# Génération d'un entier unique entre 1 (inclus) et 10 (exclu)
entier_unique = gen.integers(1, 10)
print(f"Entier unique [1,9]: {entier_unique}")
# Génération d'un tableau de 5 entiers entre 1 et 10
tableau_entiers = gen.integers(1, 11, size=5)
print(f"Tableau de 5 entiers [1,10]: {tableau_entiers}")
# Création d'une matrice 3x2 d'entiers entre 1 et 10
matrice_entiers = gen.integers(1, 11, size=(3, 2))
print(f"Matrice 3x2 d'entiers [1,10]:\n{matrice_entiers}")
Échantillonnage à Partir de Distributions Communes
Au-delà des distributions uniformes, NumPy permet de générer des échantillons suivant de nombreuses distributions statistiques standard.
Distribution Normale (Gaussienne)
La distribution normale, ou gaussienne, est fondamentale en staitstiques. NumPy offre des fonctions pour échantillonner à partir de cette distribution avec des paramètres personnalisables.
import numpy as np
gen = np.random.default_rng(42)
# Échantillon unique de la distribution normale standard N(0,1) (moyenne 0, écart-type 1)
echantillon_std = gen.normal()
print(f"Échantillon N(0,1) unique: {echantillon_std:.4f}")
# Échantillon unique d'une distribution normale avec une moyenne (loc) de 10 et un écart-type (scale) de 3
echantillon_personnalise = gen.normal(loc=10, scale=3)
print(f"Échantillon N(10,3) unique: {echantillon_personnalise:.4f}")
# Génération d'un tableau de 50 échantillons N(0,1)
tableau_std = gen.normal(loc=0, scale=1, size=50)
print(f"Moyenne des 50 échantillons N(0,1): {tableau_std.mean():.4f}")
print(f"Écart-type des 50 échantillons N(0,1): {tableau_std.std():.4f}")
# La fonction standard_normal est un raccourci pour N(0,1)
matrice_std = gen.standard_normal((2, 2))
print(f"Matrice 2x2 d'échantillons N(0,1):\n{matrice_std}")
Autres Distributions Statistiques
NumPy prend en charge une grande variété d'autres distributions utiles pour la modélisation.
import numpy as np
gen = np.random.default_rng(42)
# Distribution Exponentielle (souvent utilisée pour les temps d'attente)
# Paramètre 'scale' est l'inverse du taux lambda (1/lambda)
echantillons_exp = gen.exponential(scale=3.0, size=5) # λ = 1/3
print(f"5 échantillons Exponentiels (scale=3.0):\n{echantillons_exp}")
# Distribution de Poisson (modélise le nombre d'événements dans un intervalle fixe)
# Paramètre 'lam' est la moyenne des occurrences
echantillons_poisson = gen.poisson(lam=5, size=5)
print(f"5 échantillons de Poisson (lambda=5):\n{echantillons_poisson}")
# Distribution Binomiale (nombre de succès dans une série d'essais de Bernoulli)
# 'n' est le nombre d'essais, 'p' la probabilité de succès
echantillons_binom = gen.binomial(n=20, p=0.7, size=5)
print(f"5 échantillons Binomiaux (n=20, p=0.7):\n{echantillons_binom}")
Manipulation Aléatoire de Tableaux Existants
NumPy offre également des outils pour réorganiser ou sélectionner aléatoirement des éléments à partir de tableaux déjà définis.
Sélectionner des Éléments au Hasard
La fonction choice permet de choisir des éléments d'un tableau, avec ou sans remplacement, et avec des probabilités spécifiées.
import numpy as np
gen = np.random.default_rng(42)
# Tableau d'éléments à partir duquel choisir
couleurs = np.array(['rouge', 'vert', 'bleu', 'jaune', 'orange'])
print(f"Tableau d'origine: {couleurs}")
# Sélectionner un élément unique au hasard
selection_unique = gen.choice(couleurs)
print(f"Un élément choisi: {selection_unique}")
# Sélectionner 3 éléments avec remplacement (par défaut)
selection_avec_remplacement = gen.choice(couleurs, size=3)
print(f"3 éléments (avec remplacement): {selection_avec_remplacement}")
# Sélectionner 3 éléments sans remplacement
selection_sans_remplacement = gen.choice(couleurs, size=3, replace=False)
print(f"3 éléments (sans remplacement): {selection_sans_remplacement}")
# Sélection avec des probabilités pondérées
# Exemple: le rouge a 50% de chance, les autres 12.5% chacun
poids = [0.5, 0.125, 0.125, 0.125, 0.125]
selection_ponderee = gen.choice(couleurs, size=2, p=poids)
print(f"2 éléments (pondérés): {selection_ponderee}")
Permutation et Mélange d'Éléments
Pour réordonner les éléments d'un tableau de manière aléatoire, NumPy propose shuffle et permutation.
import numpy as np
gen = np.random.default_rng(42)
# Mélange (brassage) d'un tableau en place (modifie l'original)
sequence_initiale = np.array([10, 20, 30, 40, 50])
print(f"Séquence originale pour mélange: {sequence_initiale}")
gen.shuffle(sequence_initiale)
print(f"Séquence après mélange (modifiée): {sequence_initiale}")
# Retourne une nouvelle séquence mélangée sans altérer l'original
mots = np.array(['un', 'deux', 'trois', 'quatre'])
permutation_nouvelle = gen.permutation(mots)
print(f"Mots originaux après permutation: {mots}")
print(f"Nouvelle permutation des mots: {permutation_nouvelle}")
Reproductibilité des Séquences Aléatoires : La Graine
Pour garantir que les résultats des opérations aléatoires peuvent être reproduits à chaque exécution, il est essentiel de fixer une "graine" (seed). L'aproche moderne utilise un objet Generator.
import numpy as np
# Création de deux générateurs avec la même graine
gen_a = np.random.default_rng(12345)
gen_b = np.random.default_rng(12345)
# Les séquences générées seront identiques
print(f"Séquence de gen_a: {gen_a.random(4)}")
print(f"Séquence de gen_b: {gen_b.random(4)}")
# Un générateur avec une graine différente produira une séquence différente
gen_c = np.random.default_rng(98765)
print(f"Séquence de gen_c: {gen_c.random(4)}")
L'utilisation de np.random.default_rng(seed) est la méthode recommandée car elle encapsule l'état du générateur aléatoire dans un objet dédié, ce qui prévient les interférences avec d'autres opérations aléatoires globales.
Cas d'Usage Concrets
La génération de nombres aléatoires est une pierre angulaire dans de nombreux scénarios techniques.
1. Création de Données Synthétiques pour la Simulation
Générer des ensembles de données artificiels pour tester des algorithmes, simuler des scénarios ou créer des exemples.
import numpy as np
gen = np.random.default_rng(50)
nombre_observations = 2000
# Génération de poids corporels (distribution normale)
# Moyenne 75 kg, écart-type 8 kg
poids_kg = gen.normal(loc=75, scale=8, size=nombre_observations)
# Génération d'âges (distribution uniforme)
# Entre 18 et 80 ans (81 exclu)
ages_ans = gen.integers(low=18, high=81, size=nombre_observations)
# Génération de scores d'examen (distribution triangulaire, si applicable)
# Minimum 0, mode 70, maximum 100
scores_exam = gen.triangular(left=0, mode=70, right=100, size=nombre_observations)
print(f"Statistiques des données synthétiques générées:")
print(f" Poids moyen: {poids_kg.mean():.1f} kg")
print(f" Âge moyen: {ages_ans.mean():.1f} ans")
print(f" Score moyen: {scores_exam.mean():.1f}")
2. Échantillonnnage pour l'Analyse ou l'Apprentissage Automatique
Sélectionner un sous-ensemble représentatif d'une grande population pour l'analyse.
import numpy as np
gen = np.random.default_rng(50)
# Simuler un grand ensemble d'IDs d'articles (1 million d'articles)
ids_articles = np.arange(1, 1_000_001)
# Échantillonner 10 000 IDs d'articles uniques sans remplacement
indices_selectionnes = gen.choice(len(ids_articles), size=10_000, replace=False)
echantillon_ids = ids_articles[indices_selectionnes]
print(f"Taille de l'ensemble d'IDs original: {len(ids_articles)}")
print(f"Taille de l'échantillon extrait: {len(echantillon_ids)}")
print(f"Premiers 10 IDs de l'échantillon: {echantillon_ids[:10]}...")
3. Simulation de Monte Carlo pour l'Estimation
Utiliser des échantillons aléatoires pour estimer des quantités numériques, comme la valeur de Pi.
import numpy as np
def estimer_pi_monte_carlo(nb_points=1_000_000, generateur=None):
if generateur is None:
generateur = np.random.default_rng()
# Générer des coordonnées X et Y uniformément dans le carré [-1, 1] x [-1, 1]
x_coords = generateur.uniform(-1, 1, nb_points)
y_coords = generateur.uniform(-1, 1, nb_points)
# Compter les points qui tombent à l'intérieur du cercle unitaire (x^2 + y^2 <= 1)
points_dans_cercle = np.sum(x_coords**2 + y_coords**2 <= 1)
# L'estimation de Pi est 4 * (points dans le cercle / total des points)
estimation = 4 * points_dans_cercle / nb_points
return estimation
gen_pi = np.random.default_rng(2023)
pi_estime = estimer_pi_monte_carlo(nb_points=5_000_000, generateur=gen_pi)
print(f"Estimation de Pi par Monte Carlo: {pi_estime:.6f}")
print(f"Valeur réelle de Pi (NumPy): {np.pi:.6f}")
print(f"Erreur absolue: {abs(pi_estime - np.pi):.6f}")
4. Augmentation de Données par Ajout de Bruit
Introduire du bruit aléatoire dans des données existantes pour augmenter la robustesse des modèles d'apprentissage automatique ou simuler des conditions réelles.
import numpy as np
def ajouter_bruit_aleatoire(donnees_initiales, intensite_bruit=0.1, generateur=None):
if generateur is None:
generateur = np.random.default_rng()
# Générer du bruit suivant une distribution normale centrée sur 0, avec un écart-type donné
bruit = generateur.normal(loc=0, scale=intensite_bruit, size=donnees_initiales.shape)
return donnees_initiales + bruit
gen_bruit = np.random.default_rng(10)
# Un ensemble de données simulé
signal_propre = np.linspace(0, 1, 10) + np.sin(np.linspace(0, 10, 10))
signal_bruit = ajouter_bruit_aleatoire(signal_propre, intensite_bruit=0.2, generateur=gen_bruit)
print(f"Signal original:\n{signal_propre}")
print(f"Signal avec bruit ajouté:\n{signal_bruit}")