Génération et Manipulation de Données Aléatoires avec NumPy

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}")

Étiquettes: NumPy génération aléatoire Python Science des Données Statistiques

Publié le 21 août à 15h52