Skip to main content

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.
info

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.

info

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 :

  1. Elles sont égales.
  2. 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:

  1. a a pour forme (1, 3). La première dimension est étendue pour devenir (2,3).
  2. b a pour forme (2, 1). La deuxième dimension est étendue pour devenir (2,3).
  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.
info

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

  1. Créez un tableau NumPy a à partir de la liste [10, 20, 30, 40, 50].
  2. Créez un tableau b de forme (3, 3) rempli de zéros.
  3. Créez un tableau c de forme (2, 4) rempli de nombres aléatoires entiers entre 1 et 10.
  4. Affichez la forme, le nombre de dimensions et le type des éléments de c.
  5. Transformez c en un vecteur de taille 8.
  6. Créez un tableau d qui contient les éléments de c supé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 : A=[1234]A = \begin{bmatrix} 1 & 2 \\ 3 & 4 \end{bmatrix} et B=[5678]B = \begin{bmatrix} 5 & 6 \\ 7 & 8 \end{bmatrix}

  1. Créez les tableaux NumPy AA et BB correspondant à ces matrices.
  2. Calculez la somme, la différence, le produit élément par élément et le produit matriciel de AA et BB.
  3. Calculez la transposée de AA.
  4. Calculez la somme de tous les éléments de AA.
  5. Calculez la moyenne des éléments de chaque ligne de BB.
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

  1. Créez une série Pandas s1 à partir de la liste [1, 3, 5, np.nan, 6, 8].
  2. Créez une série s2 avec les valeurs [10, 20, 30] et l'index ['a', 'b', 'c'].
  3. Affichez les éléments de s1 d'index 1 et 3.
  4. Additionnez s1 et s2. 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

  1. 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']}
  1. Affichez les colonnes 'Nom' et 'Ville'.
  2. Affichez les lignes correspondant aux personnes habitant à Paris.
  3. Ajoutez une colonne 'Score' au DataFrame, avec les valeurs [85, 92, 78, 64].
  4. Calculez l'âge moyen des personnes dans le DataFrame.
  5. Supprimer la colonne 'Score'.
  6. 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.

mathEmpty

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() et contourf() : 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)

mathCourbe

mathNuage

mathHisto

mathBarre

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)

mathEmpty

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()

mathCourbe

mathNuage

mathHisto

mathBarre

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 :

  1. 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 Figure et Axes, et on appelle des méthodes sur ces objets pour construire le graphique.

  2. L'interface pyplot (style MATLAB) : C'est une interface plus implicite, qui utilise des fonctions globales de pyplot pour 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 :

  1. Créez un tableau NumPy temperatures_np à partir de la liste temperatures.
  2. Créez une Series pandas temperatures_pd à partir des données, en utilisant mois comme index.
  3. Calculez la température moyenne annuelle avec NumPy, puis avec pandas.
  4. Trouvez le mois le plus chaud et le mois le plus froid avec pandas.
  5. Créez un DataFrame pandas meteo avec deux colonnes : 'Mois' et 'Température'.
  6. Ajoutez une colonne 'Température_F' au DataFrame, contenant les températures en Fahrenheit (F = (9/5)C + 32).
  7. Tracez un graphique à barres des températures mensuelles (en °C) avec pandas.
  8. 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 :

  1. Créez un DataFrame pandas ventes_df avec trois colonnes : 'Jour', 'Ventes' et 'Prix'.
  2. Calculez le chiffre d'affaires (ventes * prix) pour chaque jour et ajoutez-le comme une nouvelle colonne 'CA' au DataFrame.
  3. Calculez le chiffre d'affaires total de la semaine.
  4. Quel est le jour où le chiffre d'affaires a été le plus élevé ?
  5. Créez un nuage de points (scatter plot) représentant le chiffre d'affaires en fonction du nombre de ventes. Utilisez matplotlib.
  6. 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()