Skip to content

Latest commit

 

History

History
441 lines (308 loc) · 11.9 KB

File metadata and controls

441 lines (308 loc) · 11.9 KB

🔝 Retour au Sommaire

13.1.1 Arrays et opérations vectorisées

Introduction à NumPy

NumPy (Numerical Python) est la bibliothèque fondamentale pour le calcul scientifique en Python. Elle offre des structures de données efficaces pour manipuler de grandes quantités de données numériques et effectuer des calculs mathématiques à grande vitesse.

Installation de NumPy

Avant de commencer, assurez-vous d'avoir NumPy installé :

pip install numpy

Importation de NumPy

Par convention, NumPy est importé avec l'alias np :

import numpy as np

Qu'est-ce qu'un Array ?

Un array (ou tableau) NumPy est une structure de données qui permet de stocker une collection d'éléments, généralement des nombres, de manière organisée et efficace.

Différence entre liste Python et array NumPy

Prenons un exemple simple pour comprendre la différence :

# Liste Python classique
liste_python = [1, 2, 3, 4, 5]

# Array NumPy
array_numpy = np.array([1, 2, 3, 4, 5])

print("Liste Python:", liste_python)  
print("Array NumPy:", array_numpy)  

Pourquoi utiliser des arrays NumPy ?

  1. Performance : Les opérations sur les arrays sont beaucoup plus rapides
  2. Fonctionnalités mathématiques : NumPy offre de nombreuses fonctions mathématiques
  3. Opérations vectorisées : Possibilité d'effectuer des opérations sur tous les éléments en une seule instruction

Création d'arrays

Méthode 1 : À partir d'une liste Python

# Array 1D (une dimension - vecteur)
arr_1d = np.array([1, 2, 3, 4, 5])  
print("Array 1D:", arr_1d)  

# Array 2D (deux dimensions - matrice)
arr_2d = np.array([[1, 2, 3],
                   [4, 5, 6]])
print("Array 2D:\n", arr_2d)

# Array 3D (trois dimensions)
arr_3d = np.array([[[1, 2], [3, 4]],
                   [[5, 6], [7, 8]]])
print("Array 3D:\n", arr_3d)

Méthode 2 : Fonctions de création intégrées

NumPy propose plusieurs fonctions pratiques pour créer des arrays :

# Array de zéros
zeros = np.zeros(5)  # [0. 0. 0. 0. 0.]  
print("Zeros:", zeros)  

# Array de uns
ones = np.ones((3, 3))  # Matrice 3x3 remplie de 1  
print("Ones:\n", ones)  

# Array avec une séquence de nombres
arange = np.arange(0, 10, 2)  # De 0 à 10 (exclus), par pas de 2  
print("Arange:", arange)  # [0 2 4 6 8]  

# Array avec des valeurs espacées linéairement
linspace = np.linspace(0, 1, 5)  # 5 valeurs entre 0 et 1  
print("Linspace:", linspace)  # [0.   0.25 0.5  0.75 1.  ]  

# Matrice identité
identity = np.eye(3)  # Matrice identité 3x3  
print("Identity:\n", identity)  

# Array avec des valeurs aléatoires
random_arr = np.random.random((2, 3))  # Matrice 2x3 de valeurs aléatoires entre 0 et 1  
print("Random:\n", random_arr)  

Propriétés des arrays

Les arrays NumPy possèdent plusieurs attributs importants :

arr = np.array([[1, 2, 3, 4],
                [5, 6, 7, 8]])

# Forme (dimensions) de l'array
print("Shape:", arr.shape)  # (2, 4) - 2 lignes, 4 colonnes

# Nombre de dimensions
print("Ndim:", arr.ndim)  # 2

# Nombre total d'éléments
print("Size:", arr.size)  # 8

# Type des éléments
print("Dtype:", arr.dtype)  # int64 (peut varier selon le système)

Opérations vectorisées

Les opérations vectorisées sont la caractéristique la plus puissante de NumPy. Elles permettent d'effectuer des opérations sur tous les éléments d'un array sans utiliser de boucles explicites.

Opérations arithmétiques de base

arr = np.array([1, 2, 3, 4, 5])

# Addition
print("Addition (+5):", arr + 5)  # [6 7 8 9 10]

# Soustraction
print("Soustraction (-2):", arr - 2)  # [-1  0  1  2  3]

# Multiplication
print("Multiplication (×3):", arr * 3)  # [3 6 9 12 15]

# Division
print("Division (÷2):", arr / 2)  # [0.5 1.  1.5 2.  2.5]

# Puissance
print("Puissance (²):", arr ** 2)  # [1 4 9 16 25]

Comparaison : Avec et sans vectorisation

Sans vectorisation (liste Python classique) :

liste = [1, 2, 3, 4, 5]  
resultat = []  

# Il faut utiliser une boucle
for element in liste:
    resultat.append(element * 2)

print("Résultat avec boucle:", resultat)

Avec vectorisation (NumPy) :

arr = np.array([1, 2, 3, 4, 5])

# Une seule ligne suffit !
resultat = arr * 2  
print("Résultat vectorisé:", resultat)  

Opérations entre arrays

Vous pouvez effectuer des opérations entre deux arrays de même taille :

arr1 = np.array([1, 2, 3, 4])  
arr2 = np.array([10, 20, 30, 40])  

# Addition élément par élément
print("Addition:", arr1 + arr2)  # [11 22 33 44]

# Multiplication élément par élément
print("Multiplication:", arr1 * arr2)  # [10 40 90 160]

# Division élément par élément
print("Division:", arr2 / arr1)  # [10. 10. 10. 10.]

Fonctions mathématiques universelles

NumPy fournit de nombreuses fonctions mathématiques qui s'appliquent élément par élément :

arr = np.array([1, 4, 9, 16, 25])

# Racine carrée
print("Racine carrée:", np.sqrt(arr))  # [1. 2. 3. 4. 5.]

# Exponentielle
arr2 = np.array([0, 1, 2])  
print("Exponentielle:", np.exp(arr2))  # [1.         2.71828183 7.3890561 ]  

# Logarithme naturel
print("Logarithme:", np.log(arr))  # [0.         1.38629436 2.19722458 2.77258872 3.21887582]

# Fonctions trigonométriques
angles = np.array([0, np.pi/2, np.pi])  
print("Sinus:", np.sin(angles))  # [0.0000000e+00 1.0000000e+00 1.2246468e-16]  

Opérations d'agrégation

Les opérations d'agrégation permettent de calculer des statistiques sur les arrays :

arr = np.array([3, 7, 1, 9, 2, 8])

# Somme de tous les éléments
print("Somme:", np.sum(arr))  # 30
# ou
print("Somme:", arr.sum())  # 30

# Moyenne
print("Moyenne:", np.mean(arr))  # 5.0

# Minimum et maximum
print("Minimum:", np.min(arr))  # 1  
print("Maximum:", np.max(arr))  # 9  

# Écart-type
print("Écart-type:", np.std(arr))  # 3.08...

# Médiane
print("Médiane:", np.median(arr))  # 5.0

Agrégations sur des arrays multidimensionnels

Pour les arrays 2D, vous pouvez spécifier l'axe d'agrégation :

arr_2d = np.array([[1, 2, 3],
                   [4, 5, 6],
                   [7, 8, 9]])

# Somme totale
print("Somme totale:", np.sum(arr_2d))  # 45

# Somme par colonne (axe 0)
print("Somme par colonne:", np.sum(arr_2d, axis=0))  # [12 15 18]

# Somme par ligne (axe 1)
print("Somme par ligne:", np.sum(arr_2d, axis=1))  # [6 15 24]

# Moyenne par colonne
print("Moyenne par colonne:", np.mean(arr_2d, axis=0))  # [4. 5. 6.]

Broadcasting (Diffusion)

Le broadcasting est un mécanisme puissant qui permet d'effectuer des opérations entre arrays de formes différentes :

# Array 2D
matrice = np.array([[1, 2, 3],
                    [4, 5, 6],
                    [7, 8, 9]])

# Array 1D
vecteur = np.array([10, 20, 30])

# Broadcasting : le vecteur est "diffusé" sur chaque ligne
resultat = matrice + vecteur  
print("Résultat du broadcasting:\n", resultat)  
# [[11 22 33]
#  [14 25 36]
#  [17 28 39]]

Exemple pratique : normalisation de données

Le broadcasting est très utile pour normaliser des données :

# Données (par exemple, notes d'étudiants sur différents examens)
notes = np.array([[85, 90, 78],
                  [92, 88, 95],
                  [78, 85, 88]])

# Calculer la moyenne de chaque examen (colonne)
moyennes = np.mean(notes, axis=0)  
print("Moyennes par examen:", moyennes)  # [85. 87.66... 87.]  

# Soustraire la moyenne à chaque note (centrage)
notes_centrees = notes - moyennes  
print("Notes centrées:\n", notes_centrees)  

Exemples pratiques d'opérations vectorisées

Exemple 1 : Conversion de températures

# Températures en Celsius
celsius = np.array([0, 10, 20, 30, 40])

# Conversion en Fahrenheit : F = C × 9/5 + 32
fahrenheit = celsius * 9/5 + 32  
print("Celsius:", celsius)  
print("Fahrenheit:", fahrenheit)  # [32. 50. 68. 86. 104.]  

Exemple 2 : Calcul de distances

# Points sur un axe
points = np.array([0, 5, 10, 15, 20])

# Calculer la distance de chaque point par rapport à l'origine
distances = np.abs(points - 0)  
print("Distances:", distances)  # [0 5 10 15 20]  

# Calculer la distance de chaque point par rapport au point 10
distances_depuis_10 = np.abs(points - 10)  
print("Distances depuis 10:", distances_depuis_10)  # [10 5 0 5 10]  

Exemple 3 : Application d'une remise

# Prix de produits
prix = np.array([19.99, 49.99, 99.99, 149.99])

# Appliquer une remise de 20%
prix_reduits = prix * 0.8  
print("Prix originaux:", prix)  
print("Prix avec remise:", prix_reduits)  

# Arrondir à 2 décimales
prix_reduits_arrondis = np.round(prix_reduits, 2)  
print("Prix arrondis:", prix_reduits_arrondis)  

Conditions et masques booléens

Vous pouvez utiliser des conditions pour filtrer ou modifier des arrays :

arr = np.array([1, 5, 10, 15, 20, 25])

# Créer un masque booléen
masque = arr > 10  
print("Masque (valeurs > 10):", masque)  # [False False False True True True]  

# Utiliser le masque pour filtrer
valeurs_sup_10 = arr[masque]  
print("Valeurs > 10:", valeurs_sup_10)  # [15 20 25]  

# Écriture concise
print("Valeurs > 10 (concis):", arr[arr > 10])  # [15 20 25]

# Modifier les valeurs selon une condition
arr_copie = arr.copy()  
arr_copie[arr_copie < 10] = 0  # Mettre à 0 les valeurs < 10  
print("Array modifié:", arr_copie)  # [0 0 10 15 20 25]  

Conditions multiples

arr = np.array([1, 5, 10, 15, 20, 25, 30])

# Valeurs entre 10 et 20 (inclus)
masque = (arr >= 10) & (arr <= 20)  
print("Valeurs entre 10 et 20:", arr[masque])  # [10 15 20]  

# Valeurs < 10 OU > 20
masque2 = (arr < 10) | (arr > 20)  
print("Valeurs < 10 ou > 20:", arr[masque2])  # [1 5 25 30]  

Pourquoi les opérations vectorisées sont-elles plus rapides ?

Les opérations vectorisées sont optimisées et exécutées en code C compilé, ce qui les rend beaucoup plus rapides que les boucles Python :

import time

# Avec une liste Python et une boucle
liste = list(range(1000000))  
debut = time.time()  
resultat_liste = [x * 2 for x in liste]  
temps_liste = time.time() - debut  

# Avec NumPy (vectorisé)
arr = np.array(liste)  
debut = time.time()  
resultat_numpy = arr * 2  
temps_numpy = time.time() - debut  

print(f"Temps avec liste Python: {temps_liste:.4f} secondes")  
print(f"Temps avec NumPy: {temps_numpy:.4f} secondes")  
print(f"NumPy est {temps_liste/temps_numpy:.1f}x plus rapide!")  

Bonnes pratiques

  1. Éviter les boucles : Privilégiez toujours les opérations vectorisées
  2. Utiliser les fonctions NumPy : np.sum(), np.mean(), etc. sont optimisées
  3. Choisir le bon type de données : Spécifiez dtype si nécessaire pour économiser de la mémoire
  4. Préallouer les arrays : Créez l'array avec sa taille finale plutôt que de l'agrandir progressivement
# ❌ Mauvaise pratique : agrandir l'array progressivement
arr = np.array([])  
for i in range(1000):  
    arr = np.append(arr, i)  # Très lent !

# ✅ Bonne pratique : préallouer
arr = np.zeros(1000)  
for i in range(1000):  
    arr[i] = i

# ✅ Encore mieux : utiliser arange
arr = np.arange(1000)

Résumé

Les arrays NumPy et les opérations vectorisées sont des outils puissants qui permettent de :

  • Manipuler efficacement de grandes quantités de données numériques
  • Effectuer des calculs mathématiques complexes en une seule ligne
  • Améliorer considérablement les performances par rapport aux listes Python
  • Écrire du code plus concis et lisible

Les opérations vectorisées éliminent le besoin de boucles explicites et tirent parti de l'optimisation en C de NumPy pour obtenir des performances exceptionnelles.

Dans les prochaines sections, nous explorerons l'indexation et le slicing avancés qui vous permettront de manipuler encore plus finement vos arrays NumPy.

⏭️ Indexation et slicing avancés