Manipulation et Visualisation de Données
Avant de pouvoir entraîner des modèles de Machine Learning, il est essentiel de savoir manipuler et visualiser les données. Comme nous l'avons vu précédemment, une grande partie du travail d'un Data Scientist consiste à préparer les données pour les rendre exploitables par les algorithmes.
Dans cette partie, nous allons approfondir notre exploration des librairies Python qui sont indispensables pour ces tâches : NumPy, pandas et matplotlib.
Objectifs de cette Partie
- NumPy :
- Comprendre les tableaux multidimensionnels (ndarrays).
- Maîtriser la création, l'indexation, le slicing et la manipulation de forme de ces tableaux.
- Apprendre à utiliser les opérations et fonctions mathématiques/statistiques de NumPy.
- pandas :
- Comprendre les Series et les DataFrames.
- Maîtriser la création, l'indexation et la sélection de données dans ces structures.
- Apprendre à nettoyer les données (gestion des valeurs manquantes, conversion de types).
- Découvrir les techniques de manipulation de données (filtrage, ajout/suppression de colonnes, tri, agrégation).
- matplotlib :
- Comprendre l'importance de la visualisation en Data Science.
- Maîtriser les concepts de base (figures, axes, types de graphiques courants).
- Apprendre à personnaliser les graphiques (titres, légendes, labels, couleurs, styles).
- Découvrir l'intégration avec pandas.
Pourquoi cette Partie est-elle Importante ?
- Préparation des données : La manipulation et le nettoyage des données sont des étapes cruciales avant tout travail de modélisation.
- Exploration des données : La visualisation permet de comprendre les données, d'identifier des tendances, des anomalies, des relations entre variables.
- Communication des résultats : Les graphiques sont essentiels pour communiquer les résultats de manière claire et concise.
Même si vous avez déjà une certaine familiarité avec ces librairies, cette partie vous permettra de consolider vos connaissances et de découvrir des aspects plus avancés.
L'Importance du Prétraitement des Données : Le Rôle Clé de NumPy et pandas
Avant même de commencer à construire des modèles de Machine Learning, il y a une étape cruciale, souvent sous-estimée, mais qui représente une part importante du travail d'un Data Scientist : le prétraitement des données. C'est là que NumPy et pandas entrent en jeu de manière essentielle.
Pourquoi le Prétraitement est-il si Important ?
Les données du monde réel sont rarement "propres" et prêtes à l'emploi. Elles sont souvent :
- Incomplètes : Contiennent des valeurs manquantes (NaN).
- Bruyantes : Contiennent des erreurs ou des valeurs aberrantes.
- Hétérogènes : Proviennent de différentes sources, avec différents formats.
- Non structurées : Ne sont pas organisées en tableaux (ex : texte, images, sons).
- Redondantes: Contiennent des informations dupliquées ou inutiles.
Si vous essayez d'entraîner un modèle de ML sur des données brutes, vous obtiendrez probablement des résultats médiocres, voire complètement faux. Le prétraitement des données vise à transformer les données brutes en un format propre, cohérent et exploitable par les algorithmes de ML.
Les Tâches Courantes du Prétraitement
Voici quelques exemples de tâches de prétraitement courantes :
- Nettoyage des données :
- Gestion des valeurs manquantes (suppression, imputation).
- Détection et correction des erreurs.
- Suppression des doublons.
- Transformation des données :
- Normalisation et standardisation (mise à l'échelle des données).
- Encodage des variables catégorielles (transformation de texte en nombres).
- Création de nouvelles variables (feature engineering).
- Réduction de dimensionnalité :
- Sélection des variables les plus importantes (feature selection).
- Extraction de nouvelles variables (ex : Analyse en Composantes Principales - PCA).
- Agrégation de données:
- Combiner plusieurs sources,
- Créer des résumés statistiques.
Le Rôle de NumPy et pandas dans le Prétraitement
NumPy et pandas sont des outils indispensables pour le prétraitement des données :
-
NumPy :
- Permet de manipuler efficacement les données numériques sous forme de tableaux.
- Offre des fonctions mathématiques pour la normalisation, la standardisation, etc.
- Facilite la manipulation des tableaux multidimensionnels (ex : images).
-
pandas :
- Permet de lire et d'écrire des données dans différents formats (CSV, Excel, JSON, etc.).
- Offre des outils puissants pour filtrer, transformer et agréger les données.
- Facilite la gestion des valeurs manquantes.
- Permet de manipuler des données structurées avec des étiquettes (DataFrames).
En résumé, une grande partie du travail d'un Data Scientist consiste à utiliser NumPy et pandas pour transformer des données brutes et désordonnées en un format propre et structuré, prêt à être utilisé par les algorithmes de Machine Learning.
La qualité de vos données d'entrée a un impact direct sur la qualité de votre modèle de ML. Un bon prétraitement est essentiel pour obtenir de bons résultats.
NumPy : Le Calcul Numérique Efficace
Comme nous l'avons mentionné, NumPy (Numerical Python) est la librairie fondamentale pour le calcul scientifique et le Machine Learning en Python. Elle fournit :
- Tableaux Multidimensionnels (ndarray) : Des structures de données optimisées pour stocker et manipuler des données numériques, bien plus efficaces que les listes Python standard.
- Fonctions Mathématiques de Haut Niveau : Un large éventail de fonctions mathématiques optimisées pour opérer sur ces tableaux (opérations élément par élément, algèbre linéaire, transformations de Fourier, etc.).
Pourquoi utiliser NumPy plutôt que les listes Python ?
- Performance : Les opérations NumPy sont implémentées en C, ce qui les rend beaucoup plus rapides que les boucles Python équivalentes.
- Compacité : Les tableaux NumPy prennent moins de place en mémoire que les listes Python.
- Fonctionnalité : NumPy offre des fonctions de manipulation de tableaux très puissantes (redimensionnement, slicing, broadcasting, etc.).
Tableaux Multidimensionnels (ndarrays)
Le cœur de NumPy est la classe ndarray (n-dimensional array). Un ndarray est un tableau homogène (tous les éléments ont le même type) de dimensions quelconques.
Création de Tableaux
Il existe plusieurs façons de créer des tableaux NumPy :
import numpy as np
# À partir d'une liste Python
a = np.array([1, 2, 3]) # Vecteur (1D)
print(f"Vecteur a :\n{a}")
b = np.array([[1, 2, 3], [4, 5, 6]]) # Matrice (2D)
print(f"Matrice b :\n{b}")
# Avec des fonctions prédéfinies
zeros = np.zeros((2, 3)) # Tableau de zéros de forme (2, 3)
print(f"\nTableau de zéros :\n{zeros}")
ones = np.ones((3, 2)) # Tableau de uns de forme (3, 2)
print(f"Tableau de uns :\n{ones}")
eye = np.eye(3) # Matrice identité 3x3
print(f"Matrice identité :\n{eye}")
random = np.random.rand(2, 4) # Tableau de nombres aléatoires (uniforme [0, 1[)
print(f"Tableau aléatoire :\n{random}")
range_array = np.arange(10) # Similaire à range, mais retourne un ndarray
print(f"Tableau range :\n{range_array}")
linspace_array = np.linspace(0, 1, 5) # 5 nombres équitablement espacés entre 0 et 1
print(f"Tableau linspace :\n{linspace_array}")
Output
Vecteur a :
[1 2 3]
Matrice b :
[[1 2 3]
[4 5 6]]
Tableau de zéros :
[[0. 0. 0.]
[0. 0. 0.]]
Tableau de uns :
[[1. 1.]
[1. 1.]
[1. 1.]]
Matrice identité :
[[1. 0. 0.]
[0. 1. 0.]
[0. 0. 1.]]
Tableau aléatoire :
[[0.54265228 0.57372315 0.40373677 0.83450694]
[0.79164131 0.9424338 0.79528035 0.0391037 ]]
Tableau range :
[0 1 2 3 4 5 6 7 8 9]
Tableau linspace :
[0. 0.25 0.5 0.75 1. ]
Attributs des Tableaux
Les tableaux NumPy ont plusieurs attributs utiles :
a = np.array([[1, 2, 3], [4, 5, 6]])
print(f"Forme (shape) : {a.shape}") # (2, 3) : 2 lignes, 3 colonnes
print(f"Nombre de dimensions (ndim) : {a.ndim}") # 2
print(f"Nombre total d'éléments (size) : {a.size}") # 6
print(f"Type des éléments (dtype) : {a.dtype}") # int64 (ou int32, etc.)
Indexation et Slicing
L'indexation et le slicing des tableaux NumPy sont similaires à ceux des listes Python, mais avec des fonctionnalités supplémentaires :
a = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])
# Accès à un élément
print(f"Élément a[0, 1] : {a[0, 1]}") # 2 (première ligne, deuxième colonne)
# Slicing de lignes
print(f"Première ligne : {a[0, :]}") # [1 2 3 4]
print(f"Deuxième et troisième lignes :\n{a[1:, :]}")
# Slicing de colonnes
print(f"Première colonne : {a[:, 0]}") # [1 5 9]
print(f"Deux dernières colonnes :\n{a[:, 2:]}")
# Slicing avec des pas
print(f"Une ligne sur deux :\n{a[::2, :]}")
# Indexation booléenne
b = a > 5
print(f"\nTableau booléen b :\n{b}")
print(f"Éléments de a supérieurs à 5 : {a[b]}")
Output
Élément a[0, 1] :
2
Première ligne :
[1 2 3 4]
Deuxième et troisième lignes :
[[ 5 6 7 8]
[ 9 10 11 12]]
Première colonne :
[1 5 9]
Deux dernières colonnes :
[[ 3 4]
[ 7 8]
[11 12]]
Une ligne sur deux :
[[ 1 2 3 4]
[ 9 10 11 12]]
Tableau booléen b :
[[False False False False]
[False True True True]
[ True True True True]]
Éléments de a supérieurs à 5 :
[ 6 7 8 9 10 11 12]
Manipulation de Forme
On peut modifier la forme d'un tableau sans changer ses données :
a = np.arange(12) # [ 0 1 2 3 4 5 6 7 8 9 10 11]
# Reshape
b = a.reshape(3, 4) # Matrice 3x4
print(f"Tableau b (reshape) :\n{b}")
c = a.reshape(2, 2, 3) # Tenseur 2x2x3
print(f"Tableau c (reshape) :\n{c}")
# Aplatissement
d = b.flatten() # Vecteur
print(f"Tableau d (flatten) : {d}")
e = b.ravel() # Similaire à flatten, mais peut retourner une vue plutôt qu'une copie
print(f"Tableau e (ravel) : {e}")
# Transposition
f = b.T # Transposée de b (échange lignes et colonnes)
print(f"Tableau f (transposée) :\n{f}")
Output
Tableau b (reshape) :
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
Tableau c (reshape) :
[[[ 0 1 2]
[ 3 4 5]]
[[ 6 7 8]
[ 9 10 11]]]
Tableau d (flatten) :
[ 0 1 2 3 4 5 6 7 8 9 10 11]
Tableau e (ravel) :
[ 0 1 2 3 4 5 6 7 8 9 10 11]
Tableau f (transposée) :
[[ 0 4 8]
[ 1 5 9]
[ 2 6 10]
[ 3 7 11]]
Opérations et Fonctions Mathématiques
NumPy offre un large éventail d'opérations et de fonctions mathématiques optimisées pour les tableaux :
a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6], [7, 8]])
# Opérations élément par élément
print(f"Addition :\n{a + b}")
print(f"Soustraction :\n{a - b}")
print(f"Multiplication :\n{a * b}")
print(f"Division :\n{a / b}")
print(f"Puissance :\n{a**2}")
# Fonctions mathématiques
print(f"Somme des éléments : {np.sum(a)}")
print(f"Moyenne des éléments : {np.mean(a)}")
print(f"Écart-type : {np.std(a)}")
print(f"Minimum : {np.min(a)}")
print(f"Maximum : {np.max(a)}")
print(f"Sinus : {np.sin(a)}")
print(f"Cosinus : {np.cos(a)}")
print(f"Exponentielle : {np.exp(a)}")
print(f"Logarithme : {np.log(a)}")
# Algèbre linéaire
print(f"Produit matriciel :\n{np.dot(a, b)}") # ou a @ b
print(f"Inverse de a:\n{np.linalg.inv(a)}") # L'inverse d'une matrice
print(f"Déterminant de a: {np.linalg.det(a)}")
Output
Addition :
[[ 6 8]
[10 12]]
Soustraction :
[[-4 -4]
[-4 -4]]
Multiplication :
[[ 5 12]
[21 32]]
Division :
[[0.2 0.33333333]
[0.42857143 0.5 ]]
Puissance :
[[ 1 4]
[ 9 16]]
Somme des éléments : 10
Moyenne des éléments : 2.5
Écart-type : 1.118033988749895
Minimum : 1
Maximum : 4
Sinus :
[[ 0.84147098 0.90929743]
[ 0.14112001 -0.7568025 ]]
Cosinus :
[[ 0.54030231 -0.41614684]
[-0.9899925 -0.65364362]]
Exponentielle :
[[ 2.71828183 7.3890561 ]
[20.08553692 54.59815003]]
Logarithme :
[[0. 0.69314718]
[1.09861229 1.38629436]]
Produit matriciel :
[[19 22]
[43 50]]
Inverse de a:
[[-2. 1. ]
[ 1.5 -0.5]]
Déterminant de a: -2.0000000000000004
Broadcasting (Diffusion)
Le broadcasting est un mécanisme puissant de NumPy qui permet d'effectuer des opérations arithmétiques entre des tableaux (ndarrays) de formes différentes, sous certaines conditions. C'est une forme d'extension implicite qui évite d'avoir à créer des copies inutiles des données.
Principe de Base
L'idée générale du broadcasting est la suivante : si vous essayez d'effectuer une opération entre deux tableaux qui n'ont pas exactement la même forme, NumPy va essayer d'"étendre" (broadcast) le plus petit tableau pour qu'il ait une forme compatible avec le plus grand tableau, sans pour autant faire de copie en mémoire.
Règles du Broadcasting
Pour que le broadcasting fonctionne, les dimensions des deux tableaux doivent être compatibles. Deux dimensions sont compatibles si :
- Elles sont égales.
- L'une d'elles est égale à 1.
Si ces conditions ne sont pas remplies, une erreur ValueError: operands could not be broadcast together sera levée.
Exemples
Exemple 1 : Addition d'un vecteur à une matrice
import numpy as np
a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
b = np.array([1, 0, 1])
# Ajouter b à chaque ligne de a
c = a + b
print(c)
Sortie :
[[ 2 2 4]
[ 5 5 7]
[ 8 8 10]]
Dans cet exemple, a est une matrice (3x3) et b est un vecteur (3). La forme de b est "étendue" pour devenir (1x3), puis (3x3) en dupliquant les lignes, avant que l'addition ne soit effectuée. C'est comme si on avait fait :
b_expanded = np.array([[1, 0, 1], [1, 0, 1], [1, 0, 1]])
c = a + b_expanded
Mais avec le broadcasting, cette duplication est virtuelle, pas réelle (pas de copie en mémoire).
Exemple 2 : Multiplication d'une matrice par un scalaire
a = np.array([[1, 2, 3], [4, 5, 6]])
b = 2
# Multiplier chaque élément de a par 2
c = a * b
print(c)
Sortie :
[[ 2 4 6]
[ 8 10 12]]
Ici, b est un scalaire (une seule valeur). Il est "broadcasté" pour devenir une matrice de la même forme que a, remplie de 2.
Exemple 3 : Broadcasting le long de différentes dimensions
a = np.array([[1, 2, 3]]) # Forme (1, 3)
b = np.array([[4], [5]]) # Forme (2, 1)
c = a + b
print(c)
Sortie:
[[5 6 7]
[6 7 8]]
Dans ce cas:
aa pour forme (1, 3). La première dimension est étendue pour devenir (2,3).ba pour forme (2, 1). La deuxième dimension est étendue pour devenir (2,3).- Le calcul est possible.
Exemple 4 : Broadcasting impossible
a = np.array([[1, 2, 3], [4, 5, 6]]) # Forme (2, 3)
b = np.array([1, 2]) # Forme (2,)
# Essayer d'ajouter a et b
# c = a + b # ValueError: operands could not be broadcast together
Ici, les formes ne sont pas compatibles. La dernière dimension de a est 3, et la dernière dimension de b est 2. Aucune des deux n'est égale à 1. Le broadcasting est impossible.
Pourquoi le Broadcasting est-il Utile ?
- Concision du code : Évite d'écrire des boucles explicites pour effectuer des opérations sur des tableaux de formes différentes.
- Performance : Les opérations broadcastées sont optimisées en C, ce qui les rend beaucoup plus rapides que les boucles Python équivalentes.
- Lisibilité : Le code est souvent plus clair et plus facile à comprendre.
Le broadcasting est un concept puissant, mais il peut être un peu déroutant au début. Il est important de bien comprendre les règles pour éviter les erreurs.
Exercices NumPy
Voici quelques exercices vous permettant de vous entrainer avec NumPy.
Exercice 1 : Création et Manipulation
- Créez un tableau NumPy
aà partir de la liste[10, 20, 30, 40, 50]. - Créez un tableau
bde forme (3, 3) rempli de zéros. - Créez un tableau
cde forme (2, 4) rempli de nombres aléatoires entiers entre 1 et 10. - Affichez la forme, le nombre de dimensions et le type des éléments de
c. - Transformez
cen un vecteur de taille 8. - Créez un tableau
dqui contient les éléments decsupérieurs à 5.
Solution Exercice 1
import numpy as np
# 1.
a = np.array([10, 20, 30, 40, 50])
# 2.
b = np.zeros((3, 3))
# 3.
c = np.random.randint(1, 11, size=(2, 4))
# 4.
print(f"Forme de c : {c.shape}")
print(f"Nombre de dimensions de c : {c.ndim}")
print(f"Type des éléments de c : {c.dtype}")
# 5.
c_reshaped = c.reshape(8) # Ou c.flatten() ou c.ravel()
# 6.
d = c[c > 5]
Exercice 2: Opération
Soit les matrices : et
- Créez les tableaux NumPy et correspondant à ces matrices.
- Calculez la somme, la différence, le produit élément par élément et le produit matriciel de et .
- Calculez la transposée de .
- Calculez la somme de tous les éléments de .
- Calculez la moyenne des éléments de chaque ligne de .
Solution Exercice 2
import numpy as np
# 1.
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])
# 2.
somme = A + B
difference = A - B
produit_element = A * B
produit_matriciel = np.dot(A, B) # Ou A @ B
# 3.
transposee_A = A.T
# 4.
somme_elements_A = np.sum(A)
#5.
moyenne_ligne_B = np.mean(B, axis = 1)
pandas : Manipulation et Analyse de Données Structurées
pandas est la librairie de référence pour manipuler et analyser des données structurées en Python. Elle est construite sur NumPy et introduit deux structures de données clés :
- Series : Un tableau unidimensionnel étiqueté, similaire à une colonne d'un tableur ou une série temporelle.
- DataFrame : Un tableau bidimensionnel étiqueté, similaire à une feuille de calcul ou une table de base de données.
Pourquoi utiliser pandas ?
- Facilité d'utilisation : pandas offre une interface intuitive et de haut niveau pour lire, écrire, filtrer, transformer, agréger et visualiser des données.
- Gestion des données manquantes : pandas facilite la gestion des données manquantes (représentées par
NaN- Not a Number). - Alignement des données : Les opérations entre Series et DataFrames alignent automatiquement les données en fonction de leurs étiquettes (index).
- Intégration avec NumPy et matplotlib : pandas s'intègre parfaitement avec NumPy (les DataFrames sont construits sur des tableaux NumPy) et matplotlib (pour la visualisation).
- Flexibilité : pandas peut gérer une grande variété de types de données (nombres, chaînes de caractères, dates, etc.) et de structures de données.
Series
Une Series est un tableau unidimensionnel étiqueté. Elle peut contenir n'importe quel type de données (entiers, flottants, chaînes de caractères, objets Python, etc.). Les étiquettes sont appelées index. Ce type de donnée est très utilisé dans l'entrainement des modèles car on va pouvoir mettre des étiquettes sur les données pour pouvoir identifier, par exemple, des photos etc.
Création de Series
import pandas as pd
# À partir d'une liste
s = pd.Series([10, 20, 30, 40], index=['a', 'b', 'c', 'd'])
print(f"Series s :\n{s}")
# À partir d'un dictionnaire
data = {'a': 10, 'b': 20, 'c': 30, 'd': 40}
s = pd.Series(data)
print(f"\nSeries s (à partir d'un dictionnaire) :\n{s}")
# Avec un index par défaut (entiers de 0 à n-1)
s = pd.Series([10, 20, 30, 40])
print(f"\nSeries s (index par défaut) :\n{s}")
Output
Series s :
a 10
b 20
c 30
d 40
dtype: int64
Series s (à partir d'un dictionnaire) :
a 10
b 20
c 30
d 40
dtype: int64
Series s (index par défaut) :
0 10
1 20
2 30
3 40
dtype: int64
Accès aux Données
s = pd.Series([10, 20, 30, 40], index=['a', 'b', 'c', 'd'])
# Par position (comme une liste)
print(f"Premier élément : {s[0]}")
print(f"Deuxième et troisième éléments : {s[1:3]}")
# Par étiquette (index)
print(f"Élément d'index 'b' : {s['b']}")
print(f"Éléments d'index 'a' et 'c' : {s[['a', 'c']]}")
# Avec .loc (par étiquette) et .iloc (par position)
print(f"Élément d'index 'c' (loc) : {s.loc['c']}")
print(f"Troisième élément (iloc) : {s.iloc[2]}")
Output
Premier élément : 10
Deuxième et troisième éléments :
b 20
c 30
dtype: int64
Élément d'index 'b' : 20
Éléments d'index 'a' et 'c' :
a 10
c 30
dtype: int64
Élément d'index 'c' (loc) : 30
Troisième élément (iloc) : 30
Opérations sur les Series
s1 = pd.Series([1, 2, 3], index=['a', 'b', 'c'])
s2 = pd.Series([4, 5, 6], index=['b', 'c', 'd'])
# Addition (alignement automatique sur l'index)
print(f"s1 + s2 :\n{s1 + s2}")
# Opérations avec des scalaires (broadcasting)
print(f"s1 * 2 :\n{s1 * 2}")
# Fonctions NumPy
import numpy as np
print(f"np.exp(s1) :\n{np.exp(s1)}")
Output
s1 + s2 :
a NaN
b 6.0
c 8.0
d NaN
dtype: float64
s1 * 2 :
a 2
b 4
c 6
dtype: int64
np.exp(s1) :
a 2.718282
b 7.389056
c 20.085537
dtype: float64
DataFrames
Un DataFrame est un tableau bidimensionnel étiqueté. Il peut être vu comme un dictionnaire de Series partageant le même index. Chaque colonne d'un DataFrame peut contenir un type de données différent. Sera aussi très utilisé dans les données que nous donnons au modèle pour l'entrainement.
Création de DataFrames
# À partir d'un dictionnaire de listes ou de Series
data = {'Nom': ['Alice', 'Bob', 'Charlie'],
'Âge': [25, 30, 28],
'Ville': ['Paris', 'Londres', 'New York']}
df = pd.DataFrame(data)
print(f"DataFrame df :\n{df}")
# À partir d'une liste de dictionnaires
data = [{'Nom': 'Alice', 'Âge': 25, 'Ville': 'Paris'},
{'Nom': 'Bob', 'Âge': 30, 'Ville': 'Londres'},
{'Nom': 'Charlie', 'Âge': 28, 'Ville': 'New York'}]
df = pd.DataFrame(data)
print(f"\nDataFrame df (à partir d'une liste de dictionnaires) :\n{df}")
# À partir d'un tableau NumPy
import numpy as np
array = np.array([[1, 2, 3], [4, 5, 6]])
df = pd.DataFrame(array, columns=['A', 'B', 'C'])
print(f"\nDataFrame df (à partir d'un tableau NumPy) :\n{df}")
# Avec un index personnalisé
df = pd.DataFrame(data, index=['id1', 'id2', 'id3'])
print(f"\nDataFrame df (index personnalisé) :\n{df}")
Output
DataFrame df :
Nom Âge Ville
0 Alice 25 Paris
1 Bob 30 Londres
2 Charlie 28 New York
DataFrame df (à partir d'une liste de dictionnaires) :
Nom Âge Ville
0 Alice 25 Paris
1 Bob 30 Londres
2 Charlie 28 New York
DataFrame df (à partir d'un tableau NumPy) :
A B C
0 1 2 3
1 4 5 6
DataFrame df (index personnalisé) :
Nom Âge Ville
id1 Alice 25 Paris
id2 Bob 30 Londres
id3 Charlie 28 New York
Accès aux Données
data = {'Nom': ['Alice', 'Bob', 'Charlie'],
'Âge': [25, 30, 28],
'Ville': ['Paris', 'Londres', 'New York']}
df = pd.DataFrame(data)
# Accès aux colonnes
print(f"Colonne 'Nom' :\n{df['Nom']}") # Retourne une Series
print(f"\nColonnes 'Nom' et 'Âge' :\n{df[['Nom', 'Âge']]}") # Retourne un DataFrame
# Accès aux lignes (avec .loc et .iloc)
print(f"\nPremière ligne (iloc) :\n{df.iloc[0]}") # Retourne une Series
print(f"Lignes d'index 0 et 2 (iloc) :\n{df.iloc[[0, 2]]}") # Retourne un DataFrame
print(f"\nLigne d'index 'Bob' (loc - si l'index est 'Nom') :\n{df.loc[df['Nom'] == 'Bob']}")
# Accès à un élément spécifique
print(f"\nÂge de Bob (loc) : {df.loc[df['Nom'] == 'Bob', 'Âge']}")
Output
Colonne 'Nom' :
0 Alice
1 Bob
2 Charlie
Name: Nom, dtype: object
Colonnes 'Nom' et 'Âge' :
Nom Âge
0 Alice 25
1 Bob 30
2 Charlie 28
Première ligne (iloc) :
Nom Alice
Âge 25
Ville Paris
Name: 0, dtype: object
Lignes d'index 0 et 2 (iloc) :
Nom Âge Ville
0 Alice 25 Paris
2 Charlie 28 New York
Ligne d'index 'Bob' (loc - si l'index est 'Nom') :
Nom Âge Ville
1 Bob 30 Londres
Âge de Bob (loc) : 1 30
Name: Âge, dtype: int64
Manipulation de Données
# Création du DataFrame (exemple)
data = {'Nom': ['Alice', 'Bob', 'Charlie'],
'Âge': [25, 30, 28],
'Ville': ['Paris', 'Lyon', 'Marseille']}
df = pd.DataFrame(data)
# Ajout d'une colonne
df['Salaire'] = [50000, 60000, 55000]
print(f"\nDataFrame avec colonne 'Salaire' :\n{df}")
# Suppression d'une colonne
df = df.drop('Salaire', axis=1) # axis=1 pour les colonnes, axis=0 pour les lignes
print(f"\nDataFrame sans colonne 'Salaire' :\n{df}")
# Filtrage des données
print(f"\nLignes où l'âge est supérieur à 28 :\n{df[df['Âge'] > 28]}")
# Tri des données
print(f"\nDataFrame trié par âge (décroissant) :\n{df.sort_values('Âge', ascending=False)}")
# Agrégation de données
print(f"\nÂge moyen : {df['Âge'].mean()}")
print(f"Nombre de personnes par ville :\n{df['Ville'].value_counts()}")
Output
DataFrame avec colonne 'Salaire' :
Nom Âge Ville Salaire
0 Alice 25 Paris 50000
1 Bob 30 Lyon 60000
2 Charlie 28 Marseille 55000
DataFrame sans colonne 'Salaire' :
Nom Âge Ville
0 Alice 25 Paris
1 Bob 30 Lyon
2 Charlie 28 Marseille
Lignes où l'âge est supérieur à 28 :
Nom Âge Ville
1 Bob 30 Lyon
DataFrame trié par âge (décroissant) :
Nom Âge Ville
1 Bob 30 Lyon
2 Charlie 28 Marseille
0 Alice 25 Paris
Âge moyen : 27.666666666666668
Nombre de personnes par ville :
Ville
Paris 1
Lyon 1
Marseille 1
Name: count, dtype: int64
Gestion des Données Manquantes
# Création d'un DataFrame avec des données manquantes
data = {'A': [1, 2, np.nan, 4],
'B': [5, np.nan, np.nan, 8],
'C': [9, 10, 11, 12]}
df = pd.DataFrame(data)
print(f"\nDataFrame avec données manquantes :\n{df}")
# Détection des données manquantes
print(f"\nDonnées manquantes (isnull) :\n{df.isnull()}")
# Suppression des lignes contenant des données manquantes
print(f"\nDataFrame sans les lignes avec NaN :\n{df.dropna()}")
# Remplacement des données manquantes (imputation)
print(f"\nDataFrame avec NaN remplacés par 0 :\n{df.fillna(0)}")
print(f"DataFrame avec NaN remplacés par la moyenne de la colonne :\n{df.fillna(df.mean())}")
Output
DataFrame avec données manquantes :
A B C
0 1.0 5.0 9
1 2.0 NaN 10
2 NaN NaN 11
3 4.0 8.0 12
Données manquantes (isnull) :
A B C
0 False False False
1 False True False
2 True True False
3 False False False
DataFrame sans les lignes avec NaN :
A B C
0 1.0 5.0 9
3 4.0 8.0 12
DataFrame avec NaN remplacés par 0 :
A B C
0 1.0 5.0 9
1 2.0 0.0 10
2 0.0 0.0 11
3 4.0 8.0 12
DataFrame avec NaN remplacés par la moyenne de la colonne :
A B C
0 1.000000 5.0 9
1 2.000000 6.5 10
2 2.333333 6.5 11
3 4.000000 8.0 12
Lecture et Écriture de Fichiers
pandas peut lire et écrire des données dans une variété de formats :
# Lecture d'un fichier CSV
df = pd.read_csv('mon_fichier.csv')
# Écriture dans un fichier CSV
df.to_csv('mon_fichier.csv', index=False) # index=False pour ne pas écrire l'index
# Autres formats : Excel (read_excel, to_excel), JSON (read_json, to_json), SQL (read_sql, to_sql), etc.
Exercices Pandas
Exercice 1 : Création et Manipulation de Séries
- Créez une série Pandas
s1à partir de la liste[1, 3, 5, np.nan, 6, 8]. - Créez une série
s2avec les valeurs[10, 20, 30]et l'index['a', 'b', 'c']. - Affichez les éléments de
s1d'index 1 et 3. - Additionnez
s1ets2. Expliquez le résultat.
Solution Exercice 1
import pandas as pd
import numpy as np
# 1.
s1 = pd.Series([1, 3, 5, np.nan, 6, 8])
# 2.
s2 = pd.Series([10, 20, 30], index=['a', 'b', 'c'])
# 3.
print(s1[1])
print(s1[3])
# 4.
print(s1 + s2) # Les valeurs sont additionnées là où les index correspondent. NaN + nombre = NaN.
Exercice 2 : Création et Manipulation de DataFrames
- Créez un DataFrame
dfà partir du dictionnaire suivant :
data = {'Nom': ['Alice', 'Bob', 'Charlie', 'David'],
'Âge': [25, 32, 18, 47],
'Ville': ['Paris', 'Londres', 'Paris', 'Berlin']}
- Affichez les colonnes 'Nom' et 'Ville'.
- Affichez les lignes correspondant aux personnes habitant à Paris.
- Ajoutez une colonne 'Score' au DataFrame, avec les valeurs
[85, 92, 78, 64]. - Calculez l'âge moyen des personnes dans le DataFrame.
- Supprimer la colonne 'Score'.
- Remplacez les valeurs manquantes (s'il y en a) par la moyenne de la colonne.
Solution Exercice 2
import pandas as pd
# 1.
data = {'Nom': ['Alice', 'Bob', 'Charlie', 'David'],
'Âge': [25, 32, 18, 47],
'Ville': ['Paris', 'Londres', 'Paris', 'Berlin']}
df = pd.DataFrame(data)
# 2.
print(df[['Nom', 'Ville']])
# 3.
print(df[df['Ville'] == 'Paris'])
# 4.
df['Score'] = [85, 92, 78, 64]
# 5.
print(df['Âge'].mean())
# 6.
df = df.drop('Score', axis = 1)
# 7.
df = df.fillna(df.mean())
matplotlib : Visualisation de Données
matplotlib est la librairie de base pour la création de graphiques en Python. Elle permet de générer une grande variété de visualisations, des plus simples (courbes, histogrammes, nuages de points) aux plus complexes (graphiques 3D, visualisations interactives).
Pourquoi la Visualisation est-elle Importante ?
- Exploration des données : Les graphiques permettent de visualiser rapidement les distributions, les tendances, les corrélations, les anomalies dans les données.
- Compréhension des modèles : La visualisation peut aider à comprendre comment un modèle fonctionne, à identifier ses forces et ses faiblesses.
- Communication des résultats : Les graphiques sont essentiels pour présenter les résultats de manière claire et concise, que ce soit dans un rapport, une présentation ou un article scientifique.
- Suivi et optimisation des modèles de Machine Learning : Essentiel pour visualiser les courbes d'apprentissage (ex: loss, accuracy, précision, recall), les courbes de coût (ex: log loss), et d'autres métriques durant l'entraînement d'un modèle, permettant d'identifier des problèmes comme le sur-apprentissage ou le sous-apprentissage et d'ajuster les hyperparamètres en conséquence.
Concepts de Base
Figures et Axes
L'élément de base de matplotlib est la figure. C'est la fenêtre ou la page sur laquelle les graphiques sont dessinés. Une figure peut contenir un ou plusieurs axes (subplots). Chaque axe représente un graphique individuel, avec ses propres coordonnées, titres, labels, etc.
import matplotlib.pyplot as plt
# Création d'une figure et d'un axe
fig, ax = plt.subplots() # Crée une figure et un axe (subplot)
Ce code crée une figure vide et un axe vide.
Types de Graphiques Courants
matplotlib offre une grande variété de types de graphiques. Voici quelques-uns des plus courants :
plot(): Courbes (lignes).scatter(): Nuages de points.hist(): Histogrammes.bar(): Diagrammes à barres.pie(): Diagrammes circulaires ("camemberts").imshow(): Affichage d'images.contour()etcontourf(): Courbes de niveau.boxplot(): Boîtes à moustaches.
import matplotlib.pyplot as plt
import numpy as np
# Courbe
x = np.linspace(0, 10, 100)
y = np.sin(x)
fig, ax = plt.subplots()
ax.plot(x, y)
# Nuage de points
x = np.random.rand(50)
y = np.random.rand(50)
fig, ax = plt.subplots()
ax.scatter(x, y)
# Histogramme
data = np.random.randn(1000)
fig, ax = plt.subplots()
ax.hist(data, bins=30) # bins : Nombre de classes
# Diagramme à barres
categories = ['A', 'B', 'C', 'D']
valeurs = [25, 40, 15, 30]
fig, ax = plt.subplots()
ax.bar(categories, valeurs)


Personnalisation des Graphiques
matplotlib permet de personnaliser tous les aspects des graphiques :
- Titres :
set_title() - Labels des axes :
set_xlabel(),set_ylabel() - Légendes :
legend() - Couleurs : Spécifiées par des noms ('red', 'blue', etc.), des codes hexadécimaux ('#FF0000', etc.) ou des tuples RGB ((1, 0, 0), etc.).
- Styles de lignes : pointillés ('--'), tirets ('-'), etc.
- Marqueurs : Cercles ('o'), croix ('x'), carrés ('s'), etc.
- Grilles :
grid() - Limites des axes :
set_xlim(),set_ylim()
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 10, 100)
y = np.sin(x)
fig, ax = plt.subplots()
ax.plot(x, y, color='red', linestyle='--', marker='o', label='sin(x)')
ax.set_title('Courbe de sin(x)')
ax.set_xlabel('x')
ax.set_ylabel('sin(x)')
ax.legend()
ax.grid(True)
ax.set_xlim(0, 10)
ax.set_ylim(-1.2, 1.2)

Intégration avec pandas
pandas s'intègre parfaitement avec matplotlib. Vous pouvez directement utiliser les méthodes de visualisation de pandas, qui utilisent matplotlib en arrière-plan.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# Création d'un DataFrame
data = {'A': np.random.randn(100),
'B': np.random.randn(100) + 1,
'C': np.random.randn(100) - 1}
df = pd.DataFrame(data)
# Courbe (à partir d'une Series)
df['A'].plot()
plt.show()
# Histogramme (à partir d'une Series)
df['B'].hist()
plt.show()
# Nuage de points (à partir de deux colonnes d'un DataFrame)
df.plot.scatter(x='A', y='B')
plt.show()
# Boîte à moustaches (pour toutes les colonnes numériques)
df.boxplot()
plt.show()




L'avantage d'utiliser les méthodes de visualisation de pandas est qu'elles gèrent automatiquement les étiquettes des axes, les légendes, etc., en fonction des noms des colonnes du DataFrame.
Deux interfaces
Il est important de noter qu'il existe deux interfaces principales pour utiliser matplotlib :
-
L'interface orientée objet (OO) : C'est l'approche recommandée, que nous avons utilisée dans les exemples ci-dessus. On crée explicitement des objets
FigureetAxes, et on appelle des méthodes sur ces objets pour construire le graphique. -
L'interface
pyplot(style MATLAB) : C'est une interface plus implicite, qui utilise des fonctions globales depyplotpour modifier l'état "courant" de la figure et des axes. C'est moins flexible et moins explicite, mais parfois plus rapide pour des graphiques simples.
# Exemple avec l'interface pyplot
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 10, 100)
y = np.sin(x)
plt.plot(x, y)
plt.title('Courbe de sin(x)')
plt.xlabel('x')
plt.ylabel('sin(x)')
plt.show()
Exercices Pratiques Rapides : Manipulation et Visualisation avec NumPy, pandas et matplotlib
Voici deux exercices pour mettre en pratique les concepts que nous venons de voir.
Exercice 1 : Analyse de Données Météorologiques
Données :
Vous disposez des températures moyennes mensuelles (°C) relevées dans une ville pendant un an :
temperatures = [
-2, # Janvier
0, # Février
5, # Mars
10, # Avril
15, # Mai
20, # Juin
25, # Juillet
24, # Août
20, # Septembre
14, # Octobre
8, # Novembre
2 # Décembre
]
mois = [
"Janvier",
"Février",
"Mars",
"Avril",
"Mai",
"Juin",
"Juillet",
"Août",
"Septembre",
"Octobre",
"Novembre",
"Décembre",
]
Consignes :
- Créez un tableau NumPy
temperatures_npà partir de la listetemperatures. - Créez une Series pandas
temperatures_pdà partir des données, en utilisantmoiscomme index. - Calculez la température moyenne annuelle avec NumPy, puis avec pandas.
- Trouvez le mois le plus chaud et le mois le plus froid avec pandas.
- Créez un DataFrame pandas
meteoavec deux colonnes : 'Mois' et 'Température'. - Ajoutez une colonne 'Température_F' au DataFrame, contenant les températures en Fahrenheit (F = (9/5)C + 32).
- Tracez un graphique à barres des températures mensuelles (en °C) avec pandas.
- Tracez une courbe des températures mensuelles (en °C) avec matplotlib (en utilisant le DataFrame). Ajoutez un titre, des labels aux axes et une grille.
Solution Exercice 1
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
temperatures = [
-2, # Janvier
0, # Février
5, # Mars
10, # Avril
15, # Mai
20, # Juin
25, # Juillet
24, # Août
20, # Septembre
14, # Octobre
8, # Novembre
2 # Décembre
]
mois = [
"Janvier",
"Février",
"Mars",
"Avril",
"Mai",
"Juin",
"Juillet",
"Août",
"Septembre",
"Octobre",
"Novembre",
"Décembre",
]
# 1.
temperatures_np = np.array(temperatures)
# 2.
temperatures_pd = pd.Series(temperatures, index=mois)
# 3.
moyenne_np = np.mean(temperatures_np)
moyenne_pd = temperatures_pd.mean()
print(f"Moyenne NumPy : {moyenne_np:.2f}°C")
print(f"Moyenne pandas : {moyenne_pd:.2f}°C")
# 4.
mois_plus_chaud = temperatures_pd.idxmax()
mois_plus_froid = temperatures_pd.idxmin()
print(f"Mois le plus chaud : {mois_plus_chaud}")
print(f"Mois le plus froid : {mois_plus_froid}")
# 5.
meteo = pd.DataFrame({'Mois': mois, 'Température': temperatures})
# 6.
meteo['Température_F'] = (9/5) * meteo['Température'] + 32
# 7.
meteo.plot.bar(x='Mois', y='Température', title='Températures moyennes mensuelles')
plt.show()
# 8.
fig, ax = plt.subplots()
ax.plot(meteo['Mois'], meteo['Température'], marker='o', linestyle='-')
ax.set_title('Températures moyennes mensuelles')
ax.set_xlabel('Mois')
ax.set_ylabel('Température (°C)')
ax.grid(True)
plt.xticks(rotation=45)
plt.show()
Exercice 2 : Analyse de Ventes
Données :
Vous avez les données de vente d'un produit sur une semaine :
jours = ['Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi', 'Dimanche']
ventes = [150, 180, 200, 160, 220, 250, 190]
prix = [2.5, 2.5, 2.5, 2.5, 2.5, 3, 3] # Prix unitaire variable
Consignes :
- Créez un DataFrame pandas
ventes_dfavec trois colonnes : 'Jour', 'Ventes' et 'Prix'. - Calculez le chiffre d'affaires (ventes * prix) pour chaque jour et ajoutez-le comme une nouvelle colonne 'CA' au DataFrame.
- Calculez le chiffre d'affaires total de la semaine.
- Quel est le jour où le chiffre d'affaires a été le plus élevé ?
- Créez un nuage de points (scatter plot) représentant le chiffre d'affaires en fonction du nombre de ventes. Utilisez matplotlib.
- Tracez un diagramme circulaire ("camembert") représentant la répartition du chiffre d'affaires par jour de la semaine. Utilisez pandas.
Solution Exercice 2
import pandas as pd
import matplotlib.pyplot as plt
jours = ['Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi', 'Dimanche']
ventes = [150, 180, 200, 160, 220, 250, 190]
prix = [2.5, 2.5, 2.5, 2.5, 2.5, 3, 3] # Prix unitaire variable
# 1.
ventes_df = pd.DataFrame({'Jour': jours, 'Ventes': ventes, 'Prix': prix})
# 2.
ventes_df['CA'] = ventes_df['Ventes'] * ventes_df['Prix']
# 3.
ca_total = ventes_df['CA'].sum()
print(f"Chiffre d'affaires total : {ca_total:.2f}€")
# 4.
jour_max_ca = ventes_df.loc[ventes_df['CA'].idxmax(), 'Jour']
print(f"Jour avec le CA le plus élevé : {jour_max_ca}")
# 5.
fig, ax = plt.subplots()
ax.scatter(ventes_df['Ventes'], ventes_df['CA'])
ax.set_title('Chiffre d\'affaires en fonction des ventes')
ax.set_xlabel('Ventes')
ax.set_ylabel('Chiffre d\'affaires (€)')
plt.show()
# 6.
ventes_df.plot.pie(y='CA', labels=ventes_df['Jour'], autopct='%1.1f%%', legend=False)
plt.title('Répartition du chiffre d\'affaires par jour')
plt.show()