Travaux pratiques - Introduction à Python

Références externes utiles :

Objectif de cette séance

Cette première séance de travaux pratiques a pour objectif de vous faire découvrir (ou rappeler) les bases du langage Python, les entrées/sorties sur fichiers, le calcul numérique avec NumPy et la visualisation avec Matplotlib. Ces outils seront employés dans toutes les séances suivantes.

Si vous maîtrisez déjà Python et NumPy, parcourez rapidement les sections « Entrées et sorties » et « Calculs avec NumPy » en vous assurant de connaître les fonctions loadtxt / genfromtxt, les opérations vectorisées et le broadcasting, puis consacrez davantage de temps à la partie Matplotlib.

Accès à JupyterLab

Si vous êtes auditeur du Cnam, vous pouvez accéder à un serveur JupyterLab en ligne via Moodle (Mes enseignements > RCP216 > Lien vers JupyterHub). Téléchargez le cahier Jupyter de ce TP (clic droit > Enregistrer sous) puis importez-le dans JupyterHub via le bouton de téléversement.

Sur votre machine personnelle, nous recommandons d’installer la distribution Anaconda, qui inclut Python 3, Jupyter, NumPy et Matplotlib.

Bases du langage Python

Parmi les caractéristiques notables du langage Python :

  • langage de programmation objet où tout (ou presque) est objet ;

  • typage fort (la conformité aux types est vérifiée) et dynamique (le type est déterminé à l’exécution, pas de déclaration explicite nécessaire) ;

  • gestion automatique de la mémoire (ramasse-miettes) ;

  • sensible à l’indentation : les structures de contrôle ne délimitent pas leurs blocs avec {}, mais uniquement par l’indentation (convention : multiples de 4 espaces) ;

  • sensible à la casse.

Types de base et collections

Les types immuables courants sont bool, int, float, str et tuple. Les types modifiables courants sont list et dict.

entier = 1
reel   = 3.14
chaine = "pi"
v      = (entier, reel, chaine)   # tuple
print(type(v))                    # <class 'tuple'>

Les listes sont des tableaux unidimensionnels modifiables dont les éléments peuvent être de types différents. L’indexation commence à 0 ; les indices négatifs partent de la fin. L’opérateur : permet le slicing :

liste = [1, 3.14, 'pi', True]
print(liste[0])     # 1
print(liste[-1])    # True
print(liste[:2])    # [1, 3.14]
liste.append(42)    # ajout en fin de liste
print(len(liste))   # 5

Les dictionnaires sont des tableaux associatifs {clé : valeur} :

cours = {'RCP208': 2016, 'RCP209': 2017, 'RCP216': 2024}
print(cours['RCP216'])   # 2024
print(cours.keys())      # dict_keys(['RCP208', 'RCP209', 'RCP216'])

Structures de contrôle

Les boucles for parcourent tout itérable ; les tests de conditions utilisent if / elif / else :

for i in range(6):
    if i % 2 == 0:
        continue       # passe à l'itération suivante
    print(i, ': impair')
# 1 : impair
# 3 : impair
# 5 : impair

La compréhension de liste permet de construire une liste de façon concise :

carres_impairs = [i**2 for i in range(1, 14) if i % 2 != 0]
print(carres_impairs)
# [1, 9, 25, 49, 81, 121, 169]

Question

Écrivez une compréhension de liste qui retourne tous les multiples de 3 compris entre 1 et 50.

Question

À partir des listes suivantes de mois et de nombres de jours correspondants, construisez un dictionnaire qui associe à chaque mois son nombre de jours (années non bissextiles), en utilisant une compréhension de dictionnaire.

mois    = ['janvier','février','mars','avril','mai','juin',
           'juillet','août','septembre','octobre','novembre','décembre']
nbJours = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]

NumPy

NumPy est la bibliothèque de référence pour le calcul numérique en Python. Son type central est le tableau multidimensionnel ndarray, composé d’éléments de même type, et sur lequel les opérations sont exécutées de façon vectorisée (sans boucle Python explicite), ce qui les rend très efficaces.

Création de tableaux :

import numpy as np

# À partir d'une liste
v = np.array([1.0, 2.5, 3.7, 4.2])
print(v.dtype)    # float64
print(v.shape)    # (4,)

# Matrice 2×3
M = np.array([[1, 2, 3],
              [4, 5, 6]], dtype=float)
print(M.shape)    # (2, 3)
print(M.ndim)     # 2

Tableaux utilitaires fréquemment utilisés :

print(np.zeros((3, 4)))            # matrice de zéros 3×4
print(np.ones((2, 5)))             # matrice de uns 2×5
print(np.eye(4))                   # matrice identité 4×4
print(np.arange(0, 10, 2))         # [0, 2, 4, 6, 8]
print(np.linspace(0, 1, 5))        # [0.0, 0.25, 0.5, 0.75, 1.0]
print(np.random.randn(3, 3))       # matrice 3×3 de valeurs aléatoires (loi normale)

Fonctions

def normaliser(valeurs, centrer=True):
    """Normalise une liste de valeurs numériques.
    Si centrer=True (défaut), centre aussi sur la moyenne."""
    import numpy as np
    arr = np.array(valeurs, dtype=float)
    if centrer:
        arr = arr - arr.mean()
    return arr / arr.std()

print(normaliser([2, 4, 6, 8]))

Les fonctions lambda (anonymes) sont utiles pour des expressions courtes :

maximum = lambda x, y: x if x > y else y
print(maximum(3, 7))   # 7

Question

Écrivez une fonction description(tableau) qui, à partir d’un tableau NumPy unidimensionnel, affiche son nombre d’éléments, sa moyenne, son minimum et son maximum. Testez-la sur [4, 7, 2, 9, 1, 5].

Entrées et sorties

Lors des TP suivants nous verrons la lecture et l’écriture via Spark, Polars, etc. Il est toutefois utile de connaître également les mécanismes natifs Python pour les fichiers texte et les fichiers CSV.

Fichiers texte

La fonction native open ouvre un flux depuis un fichier. L’instruction with gère automatiquement la fermeture :

# Écriture
with open('notes.txt', 'w') as f:
    f.write('RCP216 - Séance 1\n')
    f.write('Introduction à Python\n')

# Lecture complète
with open('notes.txt') as f:
    contenu = f.read()
print(contenu)

# Lecture ligne par ligne (économique en mémoire pour les grands fichiers)
with open('notes.txt') as f:
    for ligne in f:
        print(ligne.strip())

Le mode 'a' (append) ajoute des lignes sans écraser le fichier.

Question

Créez un fichier nombres.txt contenant les carrés des entiers de 1 à 10 (un par ligne), puis relisez ce fichier et affichez la somme des valeurs.

Fichiers CSV avec NumPy

Téléchargez ce fichier :

wget http://cedric.cnam.fr/vertigo/Cours/RCP216/docs/data.csv

La fonction numpy.loadtxt lit directement un fichier CSV en tableau NumPy :

import numpy as np

# Supposons un fichier data.csv avec des valeurs numériques séparées par des virgules
# et une ligne d'en-tête
data = np.loadtxt('data.csv', delimiter=',', skiprows=1)
print(data.shape)

Pour des fichiers contenant des colonnes de types mixtes (numériques et textuelles), on utilise plutôt numpy.genfromtxt.

Nous nous servirons de ces fonctions sur les données mammals.csv, qui décrivent les habitudes de sommeil de différentes espèces de mammifères. Téléchargez ce fichier :

wget http://cedric.cnam.fr/~crucianm/src/mammals.csv

Examinez le contenu du fichier (en-tête, colonnes, séparateur). Chargez ensuite les colonnes 3, 5 et 9 (durée de sommeil sans rêve, durée totale de sommeil, et index d’exposition) dans un tableau NumPy, et la colonne 0 (noms des espèces) dans un tableau de chaînes :

mammals = np.loadtxt('mammals.csv', delimiter=';', usecols=[3, 5, 9], skiprows=1)
noms    = np.genfromtxt('mammals.csv', dtype='str', delimiter=';',
                        usecols=[0], skip_header=1)
print(mammals.shape)    # (nb_espèces, 3)
print(noms[:5])

Question

Combien y a-t-il d’espèces dans ce fichier ? Quelle est la durée moyenne de sommeil total (colonne 5) ? Quelle est l’espèce qui présente l’index d’exposition le plus élevé ?

Calculs avec NumPy

Indexation et slicing

M = np.array([[10, 20, 30],
              [40, 50, 60],
              [70, 80, 90]])

print(M[1, 2])      # 60  (ligne 1, colonne 2)
print(M[:, 1])      # [20, 50, 80]  (toute la colonne 1)
print(M[0, :])      # [10, 20, 30]  (toute la ligne 0)
print(M[1:, :2])    # sous-matrice lignes 1-2, colonnes 0-1

L”indexation booléenne est très puissante pour filtrer des données :

v = np.array([3.1, 0.5, 8.2, 1.7, 6.4])
print(v[v > 3])       # [3.1  8.2  6.4]
print(v[v > 3].mean()) # moyenne des valeurs > 3

Opérations vectorisées et broadcasting

Les opérations arithmétiques s’appliquent élément par élément sans boucle :

a = np.array([1.0, 2.0, 3.0])
b = np.array([4.0, 5.0, 6.0])
print(a + b)       # [5.  7.  9.]
print(a * b)       # [4. 10. 18.]
print(np.sqrt(a))  # [1.   1.41 1.73]

Le broadcasting permet d’effectuer des opérations entre tableaux de formes compatibles sans avoir à les copier explicitement :

M = np.ones((3, 4))
v = np.array([1, 2, 3, 4])  # forme (4,)
print(M + v)   # v est "diffusé" sur chaque ligne de M

L’application directe pour centrer et réduire un ensemble de données (soustraction de la moyenne et division par l’écart-type, colonne par colonne) s’écrit :

X = np.random.randn(100, 5)          # 100 observations, 5 variables
X_norm = (X - X.mean(axis=0)) / X.std(axis=0)
print(X_norm.mean(axis=0).round(10))  # proche de zéro
print(X_norm.std(axis=0).round(10))   # proche de 1

Question

Créez un tableau NumPy A de forme (50, 3) contenant des valeurs aléatoires issues d’une loi normale. Calculez, sans boucle Python, les valeurs suivantes :

  • la valeur maximale de chaque colonne ;

  • la norme euclidienne de chaque ligne (racine de la somme des carrés) ;

  • le nombre de lignes pour lesquelles toutes les valeurs sont positives.

Calculs matriciels

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

print(A @ B)            # produit matriciel (Python 3.5+)
print(A.T)              # transposée
print(np.linalg.det(A)) # déterminant
print(np.linalg.inv(A)) # inverse (si elle existe)

# Valeurs et vecteurs propres
valeurs_propres, vecteurs_propres = np.linalg.eig(A)
print(valeurs_propres)

Question

Soient deux vecteurs u = [1, 2, 3] et v = [4, 5, 6]. Sans boucle, calculez leur produit scalaire et leur distance euclidienne.

Chargement et manipulation des données mammals

Reprenons le fichier mammals.csv chargé dans la section précédente. La matrice mammals contient trois variables : durée de sommeil sans rêve (col. 0), durée totale de sommeil (col. 1) et indice d’exposition (col. 2).

Question

  1. Calculez la matrice de corrélation entre les trois variables avec np.corrcoef (transposez d’abord : np.corrcoef(mammals.T)). Quelles variables sont les plus corrélées ?

  2. Centrez et réduisez les données (moyenne 0, écart-type 1 par colonne). Vérifiez les résultats.

Visualisation avec Matplotlib

Matplotlib est la bibliothèque de visualisation de référence en Python. Le module pyplot en est l’interface la plus courante. Dans Jupyter, l’instruction %matplotlib inline (ou %matplotlib widget pour des graphiques interactifs) active l’affichage des figures dans le carnet.

import matplotlib.pyplot as plt
%matplotlib inline

Courbes (plot)

x = np.linspace(0, 2 * np.pi, 200)
plt.figure(figsize=(8, 3))
plt.plot(x, np.sin(x), label='sin(x)')
plt.plot(x, np.cos(x), label='cos(x)', linestyle='--')
plt.xlabel('x')
plt.ylabel('y')
plt.title('Fonctions trigonométriques')
plt.legend()
plt.tight_layout()
plt.show()

Question

Tracez sur le même graphique les séries temporelles des deux colonnes de mammals correspondant aux durées de sommeil (colonnes 0 et 1 du tableau mammals). Ajoutez une légende, des titres d’axes et un titre de graphique. Que constatez-vous sur les échelles ?

Nuages de points (scatter plot)

Pour visualiser la relation entre deux variables :

plt.figure(figsize=(5, 5))
plt.scatter(mammals[:, 0], mammals[:, 1],
            c=mammals[:, 2],   # couleur selon l'index d'exposition
            cmap='viridis', alpha=0.7)
plt.colorbar(label="Index d'exposition")
plt.xlabel('Sommeil sans rêve (h)')
plt.ylabel('Sommeil total (h)')
plt.title('Mammifères : sommeil total vs sans rêve')
plt.show()

L’argument c= accepte un tableau de valeurs numériques qui seront mappées sur une palette de couleurs (colormap). Cela permet d’afficher une troisième variable sans passer en 3D.

Question

Reproduisez le même nuage de points mais en ajoutant, pour chaque point, le nom de l’espèce correspondante (utilisez plt.annotate). Pour ne pas surcharger le graphique, affichez seulement une espèce sur deux.

Histogrammes

plt.figure(figsize=(6, 3))
plt.hist(mammals[:, 1], bins=10, edgecolor='black')
plt.xlabel('Durée totale de sommeil (h/jour)')
plt.ylabel('Nombre d\'espèces')
plt.title('Distribution de la durée de sommeil')
plt.show()

Question

Affichez côte à côte (plt.subplot) l’histogramme des trois colonnes du tableau mammals. Ajoutez un titre à chaque sous-graphique.

Visualisation 3D

from mpl_toolkits.mplot3d import Axes3D

fig = plt.figure(figsize=(7, 6))
ax  = fig.add_subplot(111, projection='3d')
ax.scatter(mammals[:, 0], mammals[:, 1], mammals[:, 2],
           c=mammals[:, 2], cmap='plasma')
ax.set_xlabel('Sommeil sans rêve (h)')
ax.set_ylabel('Sommeil total (h)')
ax.set_zlabel("Indice d'exposition")
plt.title('Mammifères — vue 3D')
plt.show()

Dans Jupyter, ajoutez %matplotlib widget avant la cellule pour obtenir une vue interactive que vous pouvez faire pivoter avec la souris (bouton gauche maintenu appuyé).

Question

Reprenez cette visualisation 3D et affichez le nom de chaque espèce à côté de son point (méthode ax.text). Pour la lisibilité, n’affichez qu’une espèce sur deux.

Exercice de synthèse

Nous terminons cette séance par un exercice qui mobilise l’ensemble des outils vus.

Téléchargez le jeu de données Iris (150 iris décrits par 4 variables botaniques et une étiquette de classe) :

wget https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data

Le fichier ne contient pas d’en-tête. Les quatre premières colonnes sont numériques (longueur/largeur des sépales et pétales en cm) ; la cinquième est le nom de l’espèce.

Question 1 : Chargement et description

Chargez les colonnes numériques dans un tableau NumPy X et les étiquettes dans un tableau de chaînes y. Affichez le nombre d’observations, le nombre de variables, la moyenne et l’écart-type de chaque variable, et le nombre d’observations par classe.

Question 2 : Visualisation

Affichez une matrice de nuages de points (scatter matrix) des 4 variables, en colorant les points selon l’espèce. Vous pouvez utiliser plt.subplot et une boucle. Quelles paires de variables semblent les plus discriminantes ?

Question 3 : Calcul

Centrez et réduisez le tableau X. Calculez ensuite, pour chaque paire de variables, le coefficient de corrélation de Pearson. Quelle paire présente la plus forte corrélation ?