Logo OFPPT

ISMO - Institut Spécialisé dans les Métiers de l'Offshoring

OFPPT - Office de la Formation Profesionnelle et de la Promotion du Travail

Introduction aux Bibliothèques Data Science avec Python

Dernière mise à jour : Novembre 2025

Parcours d'apprentissage

Déploiement et Packaging

Définitions Fondamentales

🚀

Déploiement

Le déploiement d'une application se définit comme la promotion des composants d'une application depuis un environnement vers un autre (ex: du développement vers la production).

📦

Packaging

Le packaging est l'étape cruciale de préparation. Il consiste à regrouper dans un fichier unique tous les scripts et ressources nécessaires pour que l'installation et le déploiement soient automatisés et reproductibles.

Le Packaging en Python

En Python, le packaging permet de partager et réutiliser du code proprement, sans duplication. C'est la différence entre "j'ai un script sur mon ordi" et "j'ai une bibliothèque installable par tout le monde".

Les outils de l'écosystème

  • distutils : L'outil historique (inclus dans la bibliothèque standard), mais limité.
  • setuptools : L'outil standard actuel, plus puissant, recommandé par la PyPA (Python Packaging Authority).
  • wheel : Le format de distribution binaire moderne (plus rapide que les anciens "eggs").

Setuptools

Nous allons voir comment créer un vrai package Python installable, comme les bibliothèques que vous installez avec pip.

1 — Qu’est-ce que setuptools ?

setuptools est le standard pour packager votre code Python. Il sert à :

  • Lui donner un nom et une version.
  • Déclarer ses dépendances.
  • Le rendre installable avec pip install .

2 — Exemple concret : Package "calculatrice"

Nous allons créer un package nommé calculatrice contenant une fonction addition.

👉 Structure du projet
calculatrice/
├── pyproject.toml
├── setup.py
├── LICENSE
├── README.md
└── src/
    └── calculatrice/
        ├── __init__.py
        └── operations.py

3 — Contenu des fichiers

setup.py (Le cœur de setuptools)
from setuptools import setup, find_packages

setup(
    name="calculatrice",
    version="1.0.0",
    packages=find_packages(where="src"),
    package_dir={"": "src"},
    description="Un mini package de calcul",
)
src/calculatrice/__init__.py

Permet d'exposer les fonctions directement.

from .operations import addition
src/calculatrice/operations.py
def addition(a, b):
    return a + b
pyproject.toml (Configuration de build)
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
LICENSE
MIT License (ou autre licence de votre choix)
README.md
Petit package de calcul.

4 — Génération du package (Distribution)

Pour générer les archives de distribution (source et wheel) que vous pourrez partager ou uploader sur PyPI :

1. Installez l'outil de build :

pip install build

2. Lancez la construction :

py -m build

Cela va créer un dossier dist/ contenant vos fichiers .whl (Wheel) et .tar.gz (Source).

5 — Partager et installer manuellement (Sans Internet)

Vous n'êtes pas obligé de mettre votre package sur Internet (PyPI) pour le partager. Vous pouvez simplement envoyer le fichier généré (.whl) à un collègue par clé USB, mail ou dossier partagé.

Comment votre collègue l'installe ?

Il doit récupérer le fichier .whl (qui se trouve dans votre dossier dist/) et lancer :

pip install calculatrice-1.0.0-py3-none-any.whl

Note : Il doit être dans le dossier où se trouve le fichier .whl, ou donner le chemin complet.

6 — Installation en mode développement (Pour vous)

Ouvrez un terminal dans le dossier où se trouve setup.py et lancez :

pip install .

Le point . signifie "installe le package courant".

7 — Utilisation du package

Maintenant, vous pouvez aller n’importe où sur votre machine et faire :

from calculatrice import addition

print(addition(5, 7))
# Résultat : 12

🎉 Bravo ! Vous venez de créer votre propre package Python officiel, reconnu par pip et importable comme n’importe quelle librairie.

Publication sur TestPyPI

Avant de publier votre package sur le "vrai" PyPI (et qu'il soit visible par le monde entier), il est fortement recommandé de tester la procédure sur TestPyPI. C'est un environnement "bac à sable" identique à PyPI, mais dédié aux tests.

1. Création de compte

  • Allez sur test.pypi.org et créez un compte.
  • Important : Vous devez valider votre adresse email via le lien reçu.
  • Notez votre nom d'utilisateur et votre mot de passe (ou créez un token API dans les paramètres du compte).

2. Installation des outils d'envoi

Nous allons utiliser Twine, l'outil officiel pour uploader des packages de manière sécurisée.

pip install twine

3. Upload du package

Une fois votre package généré (vous devez avoir le dossier dist/ avec vos fichiers .whl et .tar.gz), lancez la commande suivante :

twine upload --repository-url https://test.pypi.org/legacy/ dist/*

Entrez votre nom d'utilisateur et votre mot de passe (ou __token__ comme utilisateur et votre token API comme mot de passe).

4. Vérification et Installation

Si tout s'est bien passé, Twine vous donnera un lien vers la page de votre projet sur TestPyPI (ex: https://test.pypi.org/project/calculatrice/).

Pour tester l'installation comme un utilisateur lambda, utilisez cette commande spéciale (car par défaut pip cherche sur le vrai PyPI) :

pip install --index-url https://test.pypi.org/simple/ --no-deps calculatrice

L'option --no-deps est souvent nécessaire sur TestPyPI car les dépendances (comme pandas ou numpy) n'y sont pas forcément présentes (elles sont sur le vrai PyPI).

Publication officielle sur PyPI

PyPI (Python Package Index) est le dépôt officiel. Une fois votre package ici, n'importe qui dans le monde pourra l'installer avec un simple pip install votre-package.

⚠️

Attention : C'est définitif !

Contrairement à TestPyPI, vous ne pouvez pas écraser une version existante. Si vous avez publié la version 1.0.0 et que vous trouvez un bug 5 minutes après, vous devez publier une version 1.0.1. On ne peut pas "supprimer et remplacer" un fichier sur PyPI.

1. Préparer une belle page PyPI

Pour que votre page PyPI soit accueillante, assurez-vous que votre setup.py utilise votre README.md comme description longue.

# Dans setup.py
with open("README.md", "r", encoding="utf-8") as fh:
    long_description = fh.read()

setup(
    # ...
    long_description=long_description,
    long_description_content_type="text/markdown",
    # ...
)

2. Publication (La vraie !)

  1. Créez un compte sur pypi.org (différent de TestPyPI).
  2. Activez l'authentification à deux facteurs (2FA), c'est désormais obligatoire.
  3. Créez un API Token dans les paramètres de votre compte.
  4. Lancez l'upload (sans spécifier d'URL cette fois, Twine utilise PyPI par défaut) :
twine upload dist/*

3. Semantic Versioning (SemVer)

Pour gérer vos mises à jour, suivez la convention MAJOR.MINOR.PATCH (ex: 1.0.0) :

  • MAJOR (1.x.x) : Changements incompatibles (Breaking changes).
  • MINOR (x.1.x) : Nouvelles fonctionnalités rétro-compatibles.
  • PATCH (x.x.1) : Corrections de bugs rétro-compatibles.

Exercice : Comprendre les versions

Si vous définissez votre dépendance comme "requests>=2.0.0,<3.0.0" dans votre setup.py :
1. Est-ce que la version 2.5.0 sera acceptée ?
2. Est-ce que la version 3.0.1 sera acceptée ?

PyInstaller : Exécutables autonomes

PyInstaller est un outil qui convertit (gèle) des applications Python en exécutables autonomes (.exe sous Windows, binaires sous Linux/Mac).

Utilité principale

Permettre à des utilisateurs finaux de lancer votre application sans avoir besoin d'installer Python ni aucune bibliothèque sur leur machine. Tout est inclus.

Comment ça marche ?

PyInstaller analyse votre code, détecte toutes les dépendances (pandas, numpy, etc.), et les regroupe avec un interpréteur Python minimal dans un dossier ou un fichier unique.

1. Installation

pip install pyinstaller

2. Utilisation basique

Placez-vous dans le dossier de votre script principal (ex: mon_app.py) et lancez :

pyinstaller mon_app.py

Cela va créer plusieurs dossiers (build/, dist/) et un fichier .spec.

3. Options courantes

  • --onefile : Crée un seul fichier exécutable (au lieu d'un dossier). C'est l'option la plus populaire.
  • --noconsole (ou -w) : Pour les interfaces graphiques (Tkinter, PyQt). Masque la fenêtre noire de terminal au lancement.
  • --name "MonApp" : Donne un nom spécifique à l'exécutable.
pyinstaller --onefile --noconsole mon_app.py

4. Exploiter le résultat

Une fois la compilation terminée, tout se trouve dans le dossier dist/.

  • Si vous n'avez pas mis --onefile : Vous aurez un dossier dist/mon_app/ contenant l'exécutable et plein de fichiers .dll/.pyd. Vous devez distribuer tout le dossier.
  • Si vous avez mis --onefile : Vous aurez juste un fichier dist/mon_app.exe. C'est ce fichier que vous pouvez envoyer à vos utilisateurs.

Exercice : Créer un exécutable unique

Vous avez un script analyse.py. Quelle commande lancez-vous pour obtenir un seul fichier .exe facile à partager ?

Documentation du Programme

Définitions et Outils

Documentation Logicielle

C'est un texte écrit qui accompagne le logiciel. Elle explique comment le logiciel fonctionne et/ou comment on doit l'employer. Elle est essentielle pour la maintenance et l'utilisation du code.

Sphinx & ReST

Sphinx est un outil qui génère automatiquement de la documentation (HTML, PDF) à partir de fichiers texte et des commentaires du code. Il utilise le format ReStructured Text (ReST).

Installation

pip install sphinx

Workflow de Création

Voici les étapes standards pour initialiser la documentation d'un projet :

  1. Créer un dossier pour la documentation (souvent nommé docs) à la racine du projet.
  2. Initialiser Sphinx en lançant la commande suivante dans ce dossier :
    sphinx-quickstart

    Cela va créer les fichiers de configuration de base : conf.py et index.rst.

index.rst

Représente la structure (le sommaire) de la documentation.

conf.py

Contient toute la configuration : nom du projet, version, auteur, extensions, etc.

Exemple Concret : Projet "Bonjour"

Imaginons un projet contenant un module Bonjour.py que nous voulons documenter automatiquement.

1. Le Code Source (Bonjour.py)

Le code doit contenir des docstrings pour être reconnu par Sphinx.

def bonjour(nom):
    """Cette fonction permet d'afficher le message bonjour
    :param nom: chaine de caractères
    :return: un message de bonjour
    :rtype: chaine de caractères
    """
    return 'bonjour ' + nom

2. Configuration (conf.py)

Il faut dire à Sphinx où trouver le code et activer l'extension autodoc.

import os
import sys
# Indiquer le chemin vers le code source
sys.path.insert(0, os.path.abspath('../src')) 

project = 'MonProjet'
copyright = '2024, Moi'
author = 'Moi'
release = '1.0'

# Activer l'extension autodoc
extensions = [
    'sphinx.ext.autodoc'
]

3. Création de la page (bnj.rst)

Créez un fichier bnj.rst qui va automatiquement aspirer la documentation de votre fonction.

Documentation du module Bonjour
===============================

.. automodule:: Bonjour
   :members:

4. Mise à jour de index.rst

Ajoutez votre nouveau fichier bnj.rst au sommaire principal.

.. toctree::
   :maxdepth: 2
   :caption: Contents:

   bnj.rst

5. Génération finale

Dans le dossier docs, lancez la commande magique :

.\make html

Votre documentation magnifique est maintenant disponible dans docs/build/html/index.html ! 🎉

Module Collections

Introduction aux Collections

Le module collections implémente des types de données de conteneurs spécialisés, qui apportent des alternatives performantes et pratiques aux conteneurs natifs de Python (dict, list, set et tuple).

Vue d'ensemble des conteneurs

Conteneur Utilité
namedtuple Une fonction permettant de créer une sous-classe de tuple avec des champs nommés.
deque Un conteneur ressemblant à une liste mais avec ajout et suppression rapide à chacun des bouts.
ChainMap Permet d’encapsuler de nombreux dictionnaires dans une seule unité.
Counter Permet de compter les occurrences d'objets hachables.
OrderedDict Une sous-classe de dictionnaire permettant de conserver l'ordre des entrées.
defaultdict Une sous-classe de dictionnaire permettant de spécifier une valeur par défaut dans le constructeur.

namedtuple

Les namedtuples assignent un sens à chaque position dans un tuple et permettent un code plus lisible et auto-documenté. Ils peuvent être utilisés partout où les tuples classiques sont utilisés.

Exemple : Point Géométrique

from collections import namedtuple

# Définition du namedtuple
Point = namedtuple('Point', ['x', 'y'])

# Création d'une instance
p = Point(11, y=22)

print(f"Point complet : {p}")
print(f"Coordonnée x : {p.x}")
print(f"Coordonnée y : {p[1]}")  # Accès par index comme un tuple classique

Exemple 2 : Gestion de Produits

Utilisation pour structurer des données métier simples.

from collections import namedtuple

Produit = namedtuple("Produit", ["id", "nom", "prix", "stock", "categorie"])

p = Produit(1, "Clavier", 150, 20, "Informatique")

print(p.nom)
print(p.prix)
print(p.categorie)

Exercice 1 : Gestion d'Étudiants

Créez un namedtuple appelé 'Etudiant' avec les champs 'nom', 'age', et 'note'. Créez ensuite une instance pour un étudiant nommé "Alice", 20 ans, avec une note de 15. Affichez son nom et sa note.

Exercice 2 : Conversion de Dictionnaire

Soit le dictionnaire data = {'marque': 'Toyota', 'modele': 'Corolla', 'annee': 2020}. Convertissez ce dictionnaire en un namedtuple 'Voiture'.

deque (Double Ended Queue)

Une deque (prononcé "deck") est une généralisation des piles et des files. Elle supporte des ajouts et des suppressions efficaces (O(1)) aux deux extrémités, contrairement aux listes qui sont lentes pour les opérations en début de liste (O(n)).

Exemple : File d'attente rapide

from collections import deque

d = deque(['milieu'])
d.append('droite')       # Ajout à la fin
d.appendleft('gauche')   # Ajout au début

print(d)

d.pop()             # Retire à la fin
d.popleft()         # Retire au début

print(d)

Exemple 2 : Extension et Inversion

Ajout multiple d'éléments et inversion de l'ordre.

from collections import deque

d = deque([1, 2, 3])
d.extend([4, 5])        # Ajoute plusieurs éléments à la fin
d.extendleft([0, -1])   # Ajoute plusieurs éléments au début (ordre inversé)

print(d)
# Résultat : deque([-1, 0, 1, 2, 3, 4, 5])

d.reverse()
print(d)

Exercice 1 : Rotation

Créez une deque avec les éléments "A", "B", "C", "D". Utilisez la méthode rotate() pour décaler les éléments de 1 vers la droite, puis affichez le résultat.

Exercice 2 : Historique Limité

Créez une deque avec une taille maximale (maxlen) de 3. Ajoutez successivement les nombres 1, 2, 3, 4. Que contient la deque à la fin ?

ChainMap

ChainMap regroupe plusieurs dictionnaires ou autres correspondances pour créer une vue unique et pouvant être mise à jour. Si vous recherchez une clé, ChainMap la cherche dans le premier dictionnaire, puis le second, etc.

Exemple : Configuration par défaut et utilisateur

from collections import ChainMap

defauts = {'theme': 'light', 'langue': 'en'}
config_user = {'theme': 'dark'}

config = ChainMap(config_user, defauts)

# 'theme' est pris dans config_user (premier trouvé)
print(config['theme'])  
# 'langue' est pris dans defauts (pas dans config_user)
print(config['langue'])

Exemple 2 : Modification de contexte

Les modifications affectent toujours le premier dictionnaire de la chaîne.

from collections import ChainMap

dict1 = {'a': 1, 'b': 2}
dict2 = {'b': 3, 'c': 4}
chain = ChainMap(dict1, dict2)

chain['b'] = 100  # Modifie dict1
chain['z'] = 999  # Ajoute à dict1

print(f"Chain: {chain}")
print(f"Dict1: {dict1}")  # Modifié
print(f"Dict2: {dict2}")  # Inchangé

Exercice 1 : Fusion de contextes

Vous avez deux dictionnaires : d1 = {'a': 1, 'b': 2} et d2 = {'b': 3, 'c': 4}. Créez une ChainMap qui priorise d1. Quelle est la valeur de 'b' ?

Exercice 2 : Ajout dynamique

Créez une ChainMap vide. Ajoutez-y un nouveau dictionnaire enfant au début avec la méthode new_child() contenant {'x': 10}.

Counter

Counter est une sous-classe de dictionnaire pour compter les objets hachables. C'est un outil très puissant pour faire des statistiques rapides sur des données.

Exemple : Compter les lettres

from collections import Counter

texte = "abracadabra"
compteur = Counter(texte)

print(compteur)
# Affiche Counter({'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1})

print(compteur.most_common(2))
# Affiche les 2 éléments les plus fréquents : [('a', 5), ('b', 2)]

Exemple 2 : Opérations Arithmétiques

Combinaison de compteurs avec des opérations mathématiques.

from collections import Counter

c1 = Counter(a=3, b=1)
c2 = Counter(a=1, b=2)

print(f"Addition : {c1 + c2}")       # a:4, b:3
print(f"Soustraction : {c1 - c2}")   # a:2 (b est négatif donc ignoré)
print(f"Minimum : {c1 & c2}")   # min(c1, c2) -> a:1, b:1
print(f"Maximum : {c1 | c2}")          # max(c1, c2) -> a:3, b:2

Exercice 1 : Compter les mots

Soit la liste mots = ['chat', 'chien', 'chat', 'oiseau', 'chat', 'chien']. Utilisez Counter pour savoir combien de fois chaque mot apparaît.

Exercice 2 : Opérations sur les compteurs

Soit c1 = Counter(a=3, b=1) et c2 = Counter(a=1, b=2). Calculez la somme des deux compteurs (c1 + c2).

OrderedDict

OrderedDict est une sous-classe de dictionnaire qui se souvient de l'ordre dans lequel les entrées ont été ajoutées.
Note : Depuis Python 3.7, les dictionnaires standards conservent aussi l'ordre d'insertion, mais OrderedDict offre des méthodes supplémentaires comme move_to_end().

Exemple : Réorganiser

from collections import OrderedDict

d = OrderedDict()
d['a'] = 1
d['b'] = 2
d['c'] = 3

print(d)

# Déplacer 'a' à la fin
d.move_to_end('a')
print(d)

Exemple 2 : Pile vs File (LIFO/FIFO)

Utilisation de popitem pour retirer des éléments.

from collections import OrderedDict

d = OrderedDict(a=1, b=2, c=3)

# popitem(last=True) retire le dernier (LIFO - Pile)
print(f"Retiré (LIFO) : {d.popitem(last=True)}")

# popitem(last=False) retire le premier (FIFO - File)
print(f"Retiré (FIFO) : {d.popitem(last=False)}")

print(f"Reste : {d}")

Exercice 1 : Sensibilité à l'ordre

Contrairement aux dictionnaires classiques, deux OrderedDict ne sont égaux que si l'ordre des éléments est le même.
Créez od1 = OrderedDict([('a', 1), ('b', 2)]) et od2 = OrderedDict([('b', 2), ('a', 1)]). Vérifiez s'ils sont égaux. Comparez avec deux dictionnaires classiques identiques.

Exercice 2 : Déplacer au début

Créez un OrderedDict od = OrderedDict([('a', 1), ('b', 2), ('c', 3)]). Utilisez move_to_end(key, last=False) pour déplacer la clé 'c' au tout début.

defaultdict

Un defaultdict est un dictionnaire qui ne lève jamais d’erreur quand une clé n’existe pas. Il crée automatiquement une valeur par défaut quand vous accédez à une clé absente.

Le problème avec un dict normal

Avec un dictionnaire classique, accéder à une clé inexistante provoque une erreur :

d = {}
d["age"] += 1   # BOOM → KeyError

La solution avec defaultdict

Vous définissez le type de valeur par défaut (ici int qui vaut 0 par défaut) :

from collections import defaultdict

d = defaultdict(int)  # int() = 0 par défaut
d["age"] += 1
print(d["age"])
# Résultat : 1

Pourquoi ? Parce que defaultdict(int) crée automatiquement {"age": 0} au moment de l'accès.

Les types de valeurs par défaut les plus utilisés

1) int → compteur automatique
from collections import defaultdict

compteur = defaultdict(int)

for lettre in "hello":
    compteur[lettre] += 1

print(compteur)
2) list → pour grouper des données
from collections import defaultdict

groupes = defaultdict(list)

groupes["A"].append(1)
groupes["A"].append(2)
groupes["B"].append(3)

print(groupes)
# Résultat : {'A': [1, 2], 'B': [3]}
3) set → éviter les doublons
from collections import defaultdict

groupes = defaultdict(set)

groupes["x"].add(10)
groupes["x"].add(10)
groupes["x"].add(20)

print(groupes)

Exemple concret : Compter par catégorie

from collections import defaultdict

produits = [
    ("fruit", "pomme"),
    ("fruit", "banane"),
    ("legume", "carotte"),
]

groups = defaultdict(list)

for cat, item in produits:
    groups[cat].append(item)

print(groups)

Exercice 1 : Regroupement de notes

Soit la liste notes = [('Alice', 15), ('Bob', 12), ('Alice', 18), ('Bob', 14)]. Utilisez un defaultdict(list) pour regrouper les notes par étudiant.

Exercice 2 : Inversion de dictionnaire

Soit d = {'a': 1, 'b': 2, 'c': 1}. Créez un dictionnaire inversé où les clés sont les nombres et les valeurs sont les listes des lettres correspondantes (ex: {1: ['a', 'c'], ...}).

Partie 1 : Bases & Environnement

Chapitre 1 : Manipulation des Modules

1. Création des modules

Un module est un fichier .py contenant un ensemble de variables, fonctions et classes que l'on peut importer et utiliser dans le programme principal (ou dans d'autres modules).

Pourquoi utiliser des modules ?
  • Organisation : Séparation du code en fichiers logiques.
  • Réutilisation : Écrire une fois, utiliser partout.
  • Partage : Facilite la distribution du code.
Exemple de module (mon_module.py)
"""
Exemple de module, test
"""

exemple_variable = 3

def exemple_fonction():
    """Exemple de fonction"""
    return 0

class ExempleClasse:
    """Exemple de classe"""
    def __str__(self):
        return "ExempleClasse"

2. Importation des modules

L'instruction import permet de charger un module. Python offre plusieurs syntaxes :

Import classique
import mon_module

print(mon_module.exemple_variable)
c = mon_module.ExempleClasse()
Import avec alias (recommandé)
import mon_module as mm

print(mm.exemple_variable)
c = mm.ExempleClasse()
Import spécifique
from mon_module import exemple_fonction

print(exemple_fonction())
Import total (Déconseillé ⚠️)
from mon_module import *

# Masque l'origine des fonctions
print(exemple_variable)

3. Où Python cherche-t-il les modules ?

Lors d'un import, Python cherche dans cet ordre :

  1. Le répertoire courant.
  2. Les répertoires de la variable d'environnement PYTHONPATH.
  3. Le chemin par défaut de l'installation (ex: \Python\Python39\Lib).
Modifier le chemin dynamiquement

Vous pouvez ajouter un dossier temporairement via sys.path :

import sys
sys.path.insert(0, "E:/mon_dossier_perso")
print(sys.path)

4. Packages et Arborescence

Un package est un dossier contenant des modules et un fichier spécial __init__.py (obligatoire avant Python 3.3).

Structure typique
mesmodules/
├── __init__.py
├── monfichier.py
├── part1/
│   ├── __init__.py
│   └── monfichier2.py
└── part2/
    ├── __init__.py
    └── monfichier3.py
Types d'importation
1. Importation Absolue

Chemin complet depuis la racine du projet.

import mesmodules.part1.monfichier2
from mesmodules.part1 import monfichier2
2. Importation Relative

Par rapport au fichier courant (nécessite d'être dans un package).

  • . : Dossier courant
  • .. : Dossier parent
# Depuis part1/monfichier2.py
from . import autre_fichier      # Même dossier
from ..part2 import monfichier3  # Autre dossier

Exercice : Créer et Importer

1. Créez un fichier outils.py avec une fonction dire_bonjour(nom).
2. Créez un fichier main.py dans le même dossier.
3. Importez outils dans main.py avec l'alias tl et appelez la fonction.

Chapitre 2 : Manipulation des Bibliothèques (Pip)

1. Qu'est-ce qu'une bibliothèque ?

Une bibliothèque est un ensemble de modules (classes, fonctions, constantes) qui étend les capacités de Python (calcul, graphisme, réseau, etc.). La force de Python réside dans son immense écosystème, notamment via PyPI (Python Package Index).

La Bibliothèque Standard

Python est livré "piles incluses" avec de nombreux modules natifs :

📅 Date & Heure
🖥️ Système (OS)
🔢 Mathématiques
🌐 Internet (HTTP)
📁 Fichiers
🎨 Interface Graphique

2. PIP (Python Installer Package)

pip est l'outil standard pour installer et gérer des paquets externes. Il est inclus par défaut depuis Python 3.4.

$ pip --version # Vérifier l'installation
$ pip help # Afficher l'aide
$ pip list # Lister les paquets installés
Commandes d'installation courantes
Action Commande
Installer un paquet pip install numpy
Version spécifique pip install pandas==1.0.4
Version minimale pip install "requests>=2.0"
Mettre à jour pip install --upgrade numpy
Désinstaller pip uninstall numpy

3. Créer sa propre bibliothèque

Pourquoi créer une bibliothèque au lieu de copier-coller ?

Imaginez que vous avez une fonction géniale calcul_impots(). Si vous copiez ce fichier dans 10 projets différents :

  • Maintenance cauchemardesque : Si vous trouvez un bug, vous devez le corriger dans 10 endroits différents.
  • Incohérence : Certains projets auront la version v1, d'autres la v2.
  • Perte de temps : Copier manuellement des fichiers est source d'erreurs.

✅ La solution : Créer un package installable. Vous corrigez le bug une seule fois, vous augmentez la version, et tous vos projets en profitent via une simple mise à jour pip install --upgrade.

Exemple Concret : Bibliothèque "MaSuperLib"

Créons une bibliothèque composée de 3 fichiers Python pour bien comprendre l'organisation.

mon_projet/
├── setup.py                # Script d'installation
└── ma_super_lib/           # Dossier du package
    ├── __init__.py         # Point d'entrée (expose les fonctions)
    ├── operations.py       # Module de calcul
    └── utils.py            # Module utilitaire
1. ma_super_lib/operations.py
def additionner(a, b):
    """Retourne la somme"""
    return a + b

def multiplier(a, b):
    """Retourne le produit"""
    return a * b
2. ma_super_lib/utils.py
def formater_texte(texte):
    """Met en majuscules"""
    return texte.upper() + " !!!"
3. ma_super_lib/__init__.py

Ce fichier expose les fonctions pour simplifier l'import.

from .operations import additionner
from .utils import formater_texte

# On choisit de ne pas exposer 'multiplier' par défaut
4. setup.py (Racine)
from setuptools import setup, find_packages

setup(
    name="MaSuperLib",
    version="1.0.0",
    packages=find_packages(),
    author="Votre Nom",
    description="Une lib de démo"
)
Installation et Utilisation

Une fois ces fichiers créés, installez votre bibliothèque localement :

Installation :

pip install .

Ou py setup.py install

Utilisation (n'importe où) :

import ma_super_lib as msl

print(msl.additionner(5, 10))
# Affiche : 15

4. Techniques d'importation

Importer une fonction spécifique
from random import choice

# Utilisation directe
print(choice('ABC'))
Importer tout (Wildcard)
from random import *

# Toutes les fonctions sont dispos
print(randint(1, 10))

Chapitre 3 : Panorama des Bibliothèques Data Science

Python est le langage le plus utilisé dans la science de données. Il est possible de se contenter des fonctionnalités de ce langage ou bien d'utiliser un ensemble de bibliothèques externes open source qui facilitent la manipulation des données.

🧮

Calcul Scientifique

  • NumPy : Calcul matriciel performant.
  • SciPy : Algorithmes scientifiques avancés.
🐼

Manipulation des Données

  • Pandas : Manipulation de tableaux (DataFrames).
📊

Visualisation

  • Matplotlib : Graphiques de base.
  • Seaborn : Visualisation statistique esthétique.
🐘

Big Data

  • Spark : Traitement distribué rapide.
  • Hadoop : Stockage et traitement distribué.
🗄️

Bases de Données

  • MongoDB : Base NoSQL orientée documents.
  • SQLite : Base SQL légère et intégrée.
  • PostgreSQL : SGBD relationnel robuste.
🕷️

Web Scraping

  • Scrapy : Framework complet d'extraction.
  • Beautiful Soup : Parsing HTML/XML simple.
🤖

Apprentissage Automatique (Machine Learning)

  • Scikit-learn : Algorithmes classiques (régression, classification).
  • Keras : Interface haut niveau pour réseaux de neurones.
  • TensorFlow : Plateforme complète de ML (Google).
  • PyTorch : Framework flexible de Deep Learning (Facebook).

Partie 2 : Calcul Scientifique avec NumPy

Chapitre 1 : Introduction et Fondamentaux

1. Qu'est-ce que NumPy ?

NumPy (Numerical Python) est la bibliothèque fondamentale pour le calcul scientifique en Python. Elle introduit un objet puissant : le ndarray (tableau à n-dimensions), qui permet de manipuler des vecteurs, des matrices et des tenseurs avec une efficacité redoutable.

Contrairement aux listes Python qui sont généralistes, les tableaux NumPy sont spécialisés pour le calcul numérique. Ils sont à la base de presque toutes les autres bibliothèques de Data Science (Pandas, Scikit-learn, TensorFlow).

Pourquoi utiliser NumPy plutôt que des listes ?

  • 🚀 Vitesse fulgurante : NumPy est écrit en C. Les opérations sur les tableaux sont jusqu'à 50x plus rapides que les boucles Python classiques.
  • 💾 Efficacité mémoire : Les tableaux NumPy stockent les données de manière contiguë en mémoire et utilisent des types fixes (ex: `int32`), occupant beaucoup moins de RAM.
  • Vectorisation : Vous pouvez additionner, multiplier ou transformer des millions de données en une seule ligne de code, sans écrire de boucles.

2. Installation et Importation

Si ce n'est pas déjà fait, installez NumPy via pip :

$ pip install numpy

Dans vos scripts, la convention universelle est d'importer NumPy sous l'alias np :

import numpy as np

# Vérifier la version installée
print(f"Version de NumPy : {np.__version__}")

3. Constantes et Variables Importantes

NumPy inclut plusieurs constantes mathématiques et valeurs spéciales très utiles :

import numpy as np

print(f"Pi : {np.pi}")       # 3.141592653589793
print(f"Euler (e) : {np.e}") # 2.718281828459045
print(f"Infini : {np.inf}")   # inf (Infinity)
print(f"NaN : {np.nan}")       # nan (Not a Number - Valeur manquante)

4. Comparaison : Liste vs NumPy

Regardons concrètement la différence de structure.

Liste Python (Classique)
  • • Peut contenir n'importe quoi (mix de types).
  • • Chaque élément est un objet Python complet (lourd).
  • • Mémoire dispersée (pointeurs).
L = [1, "a", True] # Valide mais lent
Tableau NumPy (ndarray)
  • • Type unique pour tout le tableau (Homogène).
  • • Données brutes compactes (léger).
  • • Mémoire contiguë (accès rapide).
A = np.array([1, 2, 3]) # Rapide et optimisé

Chapitre 2 : Création de Tableaux

Il existe de nombreuses façons de créer des tableaux NumPy. Voici les méthodes les plus courantes que vous utiliserez au quotidien.

1. À partir d'une liste Python

La méthode la plus simple pour convertir vos données existantes.

import numpy as np

# Vecteur (1 Dimension)
liste_notes = [10, 15, 20]
arr_1d = np.array(liste_notes)
print("1D :", arr_1d)

# Matrice (2 Dimensions)
liste_2d = [[1, 2, 3], [4, 5, 6]]
arr_2d = np.array(liste_2d)
print("2D :\n", arr_2d)

2. Tableaux pré-remplis (Zeros, Ones, Full)

Utile pour initialiser des matrices avant de les remplir par des calculs.

import numpy as np

# Tableau de 5 zéros (souvent utilisé pour l'initialisation)
z = np.zeros(5, dtype=int)
print("Zeros :", z)

# Matrice 2x3 remplie de 1 (float par défaut)
o = np.ones((2, 3))
print("Ones :\n", o)

# Matrice 2x2 remplie de 7
f = np.full((2, 2), 7)
print("Full :\n", f)

# Matrice Identité (carrée avec des 1 sur la diagonale)
i = np.eye(3)
print("Identité :\n", i)

3. Générer des séquences (Arange, Linspace)

Pour générer des suites de nombres automatiquement.

import numpy as np

# np.arange(début, fin, pas) - Comme range() mais renvoie un tableau
# Note : la fin est EXCLUE
seq1 = np.arange(0, 10, 2)
print("Arange :", seq1) # [0 2 4 6 8]

# np.linspace(début, fin, nombre_points)
# Note : la fin est INCLUSE. Très utile pour les graphiques.
seq2 = np.linspace(0, 1, 5)
print("Linspace :", seq2) # [0.   0.25 0.5  0.75 1.  ]

4. Génération Aléatoire (Random)

NumPy possède un module random très performant.

import numpy as np

# Fixer la graine pour avoir toujours les mêmes résultats (reproductibilité)
np.random.seed(42)

# 3 nombres aléatoires entre 0 et 1 (Distribution uniforme)
rand_uni = np.random.random(3)
print("Uniforme :", rand_uni)

# Matrice 2x2 d'entiers entre 0 et 10 (exclu)
rand_int = np.random.randint(0, 10, (2, 2))
print("Entiers :\n", rand_int)

# Distribution Normale (Gaussienne) : Moyenne=0, Écart-type=1
rand_norm = np.random.normal(0, 1, 5)
print("Normale :", rand_norm)

Chapitre 3 : Inspection des Tableaux

Une fois un tableau créé, il est crucial de savoir l'interroger pour connaître ses dimensions et son type.

Attribut Description
.ndimNombre de dimensions (1=vecteur, 2=matrice, 3=cube...)
.shapeTuple représentant la forme (ex: (3, 4) pour 3 lignes, 4 colonnes)
.sizeNombre total d'éléments dans le tableau
.dtypeType des données contenues (int64, float32, bool...)
.nbytesTaille totale occupée en mémoire (en octets)
Exemple complet d'inspection
import numpy as np

# Créons un tableau 3D (2 blocs, 3 lignes, 4 colonnes)
cube = np.random.randint(10, size=(2, 3, 4))

print("Tableau :\n", cube)
print("-" * 20)
print(f"Dimensions (ndim) : {cube.ndim}")      # 3
print(f"Forme (shape)     : {cube.shape}")     # (2, 3, 4)
print(f"Taille (size)     : {cube.size}")      # 24 (2*3*4)
print(f"Type (dtype)      : {cube.dtype}")     # int32 ou int64
print(f"Mémoire (nbytes)  : {cube.nbytes} octets")

Chapitre 4 : Indexation et Slicing (Découpage)

Accéder aux données est l'opération la plus courante. NumPy offre une syntaxe puissante pour extraire des sous-ensembles de données.

1. Accès par index (Comme les listes)

import numpy as np

arr = np.array([10, 20, 30, 40])

print(arr[0])   # 10 (Premier élément)
print(arr[-1])  # 40 (Dernier élément)

# Modification
arr[1] = 99
print(arr)      # [10 99 30 40]

2. Slicing 1D (Tranches)

La syntaxe est [début : fin : pas].

  • Si début est omis : commence à 0.
  • Si fin est omis : va jusqu'au bout.
  • Si pas est omis : pas de 1.
import numpy as np

x = np.arange(10) # [0 1 2 3 4 5 6 7 8 9]

print(x[:5])      # [0 1 2 3 4] (5 premiers)
print(x[5:])      # [5 6 7 8 9] (À partir de l'index 5)
print(x[4:7])     # [4 5 6] (De 4 inclus à 7 exclu)
print(x[::2])     # [0 2 4 6 8] (Un élément sur deux)
print(x[::-1])    # [9 8 ... 0] (Inverser le tableau)

3. Indexation et Slicing en 2D (Matrices)

En 2D, on utilise une virgule pour séparer les dimensions : [lignes, colonnes].

Règle d'or : tableau[ ligne , colonne ]

import numpy as np

M = np.array([
    [1, 2, 3, 4],
    [5, 6, 7, 8],
    [9, 10, 11, 12]
])

# Accéder à un élément précis (Ligne 1, Colonne 2)
# Rappel : les indices commencent à 0
print(M[1, 2])    # 7

# Extraire une ligne entière (Ligne 0)
print(M[0, :])    # [1 2 3 4] (ou simplement M[0])

# Extraire une colonne entière (Colonne 1)
print(M[:, 1])    # [2 6 10]

# Extraire un sous-bloc (2 premières lignes, 2 premières colonnes)
print(M[:2, :2])
# [[1 2]
#  [5 6]]

Chapitre 5 : Manipulation Avancée

1. Attention au piège : Copie vs Vue

Par défaut, le slicing crée une vue (référence). Modifier la vue modifie l'original ! Pour éviter cela, utilisez .copy().

Comportement par défaut (Vue)
a = np.array([1, 2, 3])
b = a[0:2]  # Vue !
b[0] = 99
print(a)    # [99 2 3] (MODIFIÉ !)
Avec .copy() (Sûr)
a = np.array([1, 2, 3])
b = a[0:2].copy() # Copie !
b[0] = 99
print(a)    # [1 2 3] (INTACT)

2. Redimensionnement (Reshape)

La méthode .reshape() permet de changer la forme d'un tableau sans changer ses données.
Condition : Le nombre total d'éléments doit rester le même (ex: 12 éléments peuvent devenir 3x4 ou 2x6).

import numpy as np

# Créer un vecteur de 12 éléments
x = np.arange(1, 13)
print("Original :", x)

# Transformer en matrice 3 lignes x 4 colonnes
mat = x.reshape((3, 4))
print("3x4 :\n", mat)

# Astuce : Utiliser -1 pour que NumPy calcule la dimension manquante
col = x.reshape((-1, 1)) # "Autant de lignes que nécessaire, 1 colonne"
print("Colonne :\n", col)

3. Aplatissement (Flatten & Ravel)

Pour transformer un tableau multidimensionnel en vecteur 1D.

.flatten() : Crée une COPIE
m = np.array([[1, 2], [3, 4]])
f = m.flatten()
f[0] = 99
print(m) 
# [[1 2] [3 4]] (Intact)
.ravel() : Crée une VUE (Rapide)
m = np.array([[1, 2], [3, 4]])
r = m.ravel()
r[0] = 99
print(m) 
# [[99 2] [3 4]] (Modifié !)

4. Concaténation (Assemblage)

Pour assembler plusieurs tableaux en un seul.

np.concatenate() : La méthode générique
import numpy as np

a = np.array([1, 2])
b = np.array([3, 4])

# Axe 0 (Défaut) : bout à bout
print(np.concatenate((a, b))) # [1 2 3 4]

# En 2D
m1 = np.array([[1, 2], [3, 4]])
m2 = np.array([[5, 6]])

# Axe 0 (Vertical)
print(np.concatenate((m1, m2), axis=0))
# [[1 2]
#  [3 4]
#  [5 6]]
Helpers : vstack, hstack, dstack
A = np.array([1, 2, 3])
B = np.array([4, 5, 6])

# vstack (Vertical - Empilement)
print(np.vstack((A, B))) # [[1 2 3], [4 5 6]]

# hstack (Horizontal - Côte à côte)
print(np.hstack((A, B))) # [1 2 3 4 5 6]

# dstack (Profondeur - 3ème dimension)
print(np.dstack((A, B)))
# [[[1 4], [2 5], [3 6]]] (Dimensions 1x3x2)

5. Splitting (Découpage)

Pour diviser un tableau en plusieurs sous-tableaux.

import numpy as np

x = np.arange(16).reshape(4, 4)

# vsplit : Coupe horizontalement (sépare les lignes)
haut, bas = np.vsplit(x, 2)
print("Haut :\n", haut)

# hsplit : Coupe verticalement (sépare les colonnes)
gauche, droite = np.hsplit(x, 2)
print("Gauche :\n", gauche)

# split : Générique (ici axis=1 équivaut à hsplit)
p1, p2 = np.split(x, 2, axis=1)

6. Suppression d'éléments

Utilisez np.delete(tableau, index, axis).

import numpy as np

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

# Supprimer la ligne 1 (axis=0)
M_sans_L1 = np.delete(M, 1, axis=0)

# Supprimer la colonne 2 (axis=1)
M_sans_C2 = np.delete(M, 2, axis=1)

7. Tri (Sort)

Trier les éléments d'un tableau.

import numpy as np

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

# np.sort() : Renvoie une copie triée (l'original ne change pas)
trie = np.sort(arr)
print(trie) # [1 2 3 4 5 6 7 8]

# Tri en place (modifie l'original)
arr.sort()

Chapitre 6 : Masquage (Indexation Booléenne)

Le masquage est une fonctionnalité ultra-puissante de NumPy qui permet de filtrer ou modifier des données selon une condition, sans boucle.

Le concept

  1. On crée une condition (ex: arr > 5). Cela génère un tableau de Booléens (Masque).
  2. On utilise ce masque comme index : arr[masque].
  3. NumPy ne garde que les éléments où le masque est True.
import numpy as np

a = np.array([1, 10, 3, 25, 7, 2])

# 1. Créer le masque (Qui est supérieur à 5 ?)
mask = a > 5
print(mask) # [False  True False  True  True False]

# 2. Appliquer le filtre
print(a[mask]) # [10 25  7]

# Version compacte (très courante)
print(a[a > 5])

# 3. Modification conditionnelle (Ex: Remplacer les > 5 par 0)
a[a > 5] = 0
print(a) # [1 0 3 0 0 2]

Chapitre 7 : Opérations et Broadcasting

1. Opérations Arithmétiques (UFuncs)

Les opérateurs classiques (+, -, *, /) sont des raccourcis vers des fonctions universelles NumPy (UFuncs). Elles s'appliquent élément par élément.

Opérateur Fonction NumPy Description
+np.add()Addition
-np.subtract()Soustraction
*np.multiply()Multiplication
/np.divide()Division
//np.floor_divide()Division entière
**np.power()Puissance
%np.mod()Modulo (Reste)
import numpy as np

a = np.arange(4) # [0 1 2 3]

print("a     =", a)
print("a + 4 =", a + 4)  # [4 5 6 7]
print("a * 2 =", a * 2)  # [0 2 4 6]
print("a ** 2=", a ** 2) # [0 1 4 9]

2. Le Broadcasting (Diffusion)

Le broadcasting est la règle qui permet à NumPy de faire des calculs entre des tableaux de tailles différentes.

Règle simplifiée :

NumPy "étire" automatiquement les dimensions plus petites pour qu'elles correspondent aux plus grandes, si c'est possible.

Exemple 1 : Scalaire sur Vecteur

Le scalaire 5 est étiré en [5, 5, 5]

arr = np.array([1, 2, 3])
print(arr + 5) 
# [6 7 8]
Exemple 2 : Vecteur sur Matrice

Le vecteur est ajouté à CHAQUE ligne.

M = np.ones((3, 3))
v = np.array([0, 1, 2])
print(M + v)
# [[1. 2. 3.]
#  [1. 2. 3.]
#  [1. 2. 3.]]

Chapitre 8 : Statistiques (Moyenne, Médiane, Pondérée)

En Data Science, il est crucial de bien choisir son indicateur de tendance centrale.

1. Moyenne (Mean)

La somme divisée par le nombre d'éléments.

⚠️ Sensible aux valeurs extrêmes (outliers).

Exemple vie réelle :

Consommation moyenne d'essence sur un trajet.

np.mean(arr)
2. Médiane (Median)

La valeur du milieu (50% au-dessus, 50% en dessous).

✅ Robuste aux valeurs extrêmes.

Exemple vie réelle :

Prix médian de l'immobilier (non faussé par les châteaux).

np.median(arr)
3. Moyenne Pondérée

Moyenne où chaque valeur a un "poids" différent.

Prise en compte de l'importance.

Exemple vie réelle :

Note du Bac (Maths coeff 7, Sport coeff 2).

np.average(arr, weights=w)
4. Mode

La valeur la plus fréquente dans le jeu de données.

Utile pour les catégories.

Exemple vie réelle :

La pointure de chaussures la plus vendue (gestion de stock).

via np.unique()

Exemple Concret : Salaires dans une startup

Imaginez une entreprise avec 4 stagiaires et 1 PDG.
Stagiaires : 1 500 € | PDG : 100 000 €

import numpy as np

salaires = np.array([1500, 1500, 1500, 1500, 100000])

# 1. Moyenne : "Le salaire moyen est élevé" (Trompeur !)
print(f"Moyenne : {np.mean(salaires)} €") 
# Résultat : 21 200 € (Personne ne gagne ça !)

# 2. Médiane : "Le salaire médian est réaliste"
print(f"Médiane : {np.median(salaires)} €")
# Résultat : 1 500 € (Représente mieux la majorité)

# 3. Mode : "Le salaire le plus fréquent"
# NumPy n'a pas de fonction mode directe, on utilise unique
valeurs, comptes = np.unique(salaires, return_counts=True)
index_max = np.argmax(comptes)
mode = valeurs[index_max]
print(f"Mode : {mode} €") # 1 500 €

# 4. Moyenne Pondérée (Exemple notes)
notes = np.array([10, 20])
coeffs = np.array([1, 9]) # Le 20 compte 9 fois plus
moy_pond = np.average(notes, weights=coeffs)
print(f"Moyenne pondérée : {moy_pond}") # 19.0

Chapitre 9 : Glossaire des Fonctions Utiles

Référence rapide des fonctions essentielles pour l'analyse de données.

Agrégation & Stats
  • np.sum(arr) : Somme totale.
  • np.prod(arr) : Produit des éléments.
  • np.mean(arr) : Moyenne.
  • np.median(arr) : Médiane.
  • np.average(arr, weights=w) : Moyenne pondérée.
  • np.std(arr) : Écart-type (Standard Deviation).
  • np.var(arr) : Variance.
  • np.min() / np.max() : Minimum / Maximum.
  • np.argmin() / np.argmax() : Indices du min/max.
  • np.cumsum() : Somme cumulée.
Mathématiques & Utilitaires
  • np.abs(arr) : Valeur absolue.
  • np.exp(arr) : Exponentielle.
  • np.log(arr) : Logarithme naturel.
  • np.where(cond, x, y) : Si cond alors x sinon y.
  • np.unique(arr) : Valeurs uniques triées.
  • np.transpose(arr) : Inverse les axes (Matrice transposée).
  • np.any() / np.all() : Teste si au moins un / tous sont True.
Exemple : Statistiques sur un axe
import numpy as np

notes = np.array([[10, 15], [8, 12], [18, 20]]) # 3 élèves, 2 matières

# Moyenne de chaque élève (Axe 1 : colonnes)
print(np.mean(notes, axis=1)) # [12.5 10.  19. ]

# Meilleure note par matière (Axe 0 : lignes)
print(np.max(notes, axis=0))  # [18 20]

Chapitre 10 : Exercice Récapitulatif

Objectif : Manipuler une matrice de notes.

  1. Créez une matrice 4x3 avec des notes aléatoires entre 0 et 20 (entiers).
  2. Affichez la note de la 2ème ligne, 3ème colonne.
  3. Extrayez toutes les notes de la première ligne.
  4. Créez une copie de la matrice et remplacez toutes les notes < 10 par 10 dans cette copie.

Partie 3 : Bibliothèques du Calcul Scientifique SciPy

Chapitre 1 : Introduction et Objectif

1. Qu'est-ce que SciPy ?

SciPy (Scientific Python) est une bibliothèque open-source utilisée pour le calcul scientifique et technique. Elle s'appuie sur NumPy et fournit une vaste collection d'algorithmes mathématiques et de fonctions de commodité.

Elle fournit des algorithmes pour résoudre de nombreux problèmes scientifiques standards :

  • L'optimisation
  • L'intégration
  • L'interpolation
  • Les problèmes de valeurs propres
  • Les équations algébriques
  • Les équations différentielles
  • Les statistiques

SciPy vs NumPy

Si SciPy utilise NumPy, pourquoi ne pas simplement utiliser NumPy ?

  • NumPy fournit les blocs de construction de base (tableaux, opérations élémentaires).
  • SciPy ajoute des algorithmes scientifiques de haut niveau (optimisation, traitement du signal, stats avancées) qui utilisent ces tableaux.
  • SciPy est optimisée et contient des fonctions qui ne sont pas présentes dans NumPy pour garder NumPy léger.

2. Installation

Installez SciPy via pip (NumPy sera installé automatiquement comme dépendance) :

$ pip install scipy

Vérification de l'installation

import scipy
import numpy as np

print(f"Version de SciPy : {scipy.__version__}")

Chapitre 2 : Organisation de SciPy

SciPy est organisée en sous-packages spécialisés couvrant différents domaines du calcul scientifique. Voici les principaux :

Sous-package Description
scipy.clusterAlgorithmes de partitionnement (Clustering, K-Means...)
scipy.constantsConstantes mathématiques et physiques (pi, c, h...)
scipy.integrateIntégration numérique et équations différentielles
scipy.interpolateInterpolation et lissage de données
scipy.ioEntrée et sortie (fichiers Matlab, Wav, etc.)
scipy.linalgAlgèbre linéaire avancée
scipy.optimizeOptimisation et recherche de racines
scipy.statsDistributions statistiques et tests
Exemple : Utilisation des constantes

SciPy fournit de nombreuses constantes physiques précises.

from scipy import constants

print(f"Pi : {constants.pi}")
print(f"Vitesse de la lumière (c) : {constants.c} m/s")
print(f"Constante de Planck (h) : {constants.h}")

# Conversion d'unités
print(f"1 Mile en mètres : {constants.mile}")      # 1609.344
print(f"1 Gramme en kg : {constants.gram}")        # 0.001

Chapitre 3 : Entrée et Sortie (IO)

SciPy et NumPy offrent de nombreux outils pour lire et écrire des données.

1. Fichiers Binaires NumPy (.npy, .npz)

Le format natif de NumPy est le plus efficace pour stocker des tableaux.

Sauvegarde
  • np.save() : Un seul tableau (.npy)
  • np.savez() : Plusieurs tableaux (.npz)
  • np.savez_compressed() : Compressé (.npz)
Chargement
  • np.load() : Charge .npy ou .npz
import numpy as np

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

# Sauvegarder
np.save('mon_tableau.npy', arr)

# Charger
loaded_arr = np.load('mon_tableau.npy')
print(loaded_arr)

2. Fichiers Textes (CSV, TXT)

Pour l'interopérabilité avec Excel ou d'autres logiciels.

  • np.savetxt() : Sauvegarder en texte.
  • np.loadtxt() : Charger depuis un texte (simple).
  • np.genfromtxt() : Plus robuste (gère les valeurs manquantes).

3. Spécifique SciPy : Matlab & Autres

Le module scipy.io permet de lire des formats spécifiques comme ceux de Matlab (.mat), IDL, Matrix Market, etc.

from scipy import io
import numpy as np

# Créer un dictionnaire de données
data = {'vecteur': np.array([1, 2, 3]), 'label': 'expérience 1'}

# Sauvegarder au format MATLAB (.mat)
io.savemat('data.mat', data)

# Recharger
mat_data = io.loadmat('data.mat')
print(mat_data['vecteur'])

Chapitre 4 : Distributions Statistiques

Le module scipy.stats est très complet. Il contient des centaines de distributions de probabilité continues et discrètes, ainsi que des tests statistiques.

Distributions Continues
  • norm : Normale (Gaussienne)
  • uniform : Uniforme
  • expon : Exponentielle
  • gamma : Gamma
  • beta : Beta
  • chi2 : Chi-carré
Distributions Discrètes
  • binom : Binomiale
  • poisson : Poisson
  • geom : Géométrique
  • bernoulli : Bernoulli

Méthodes communes aux distributions

Chaque objet de distribution (ex: norm) possède ces méthodes :

  • rvs() : Générer des nombres aléatoires (Random Variates).
  • pdf() : Fonction de densité de probabilité (Probability Density Function).
  • cdf() : Fonction de répartition (Cumulative Distribution Function).
  • ppf() : Fonction quantile (Percent Point Function - inverse de CDF).
from scipy.stats import norm
import numpy as np

# Distribution Normale centrée réduite (mu=0, sigma=1)
# 1. Générer 5 nombres aléatoires
r = norm.rvs(size=5)
print("Aléatoire :", r)

# 2. Probabilité d'être inférieur à 0 (CDF)
# Pour une loi normale centrée, c'est 50% (0.5)
p = norm.cdf(0)
print("P(X < 0) :", p)

# 3. Densité en 0 (Le sommet de la cloche)
d = norm.pdf(0)
print("Densité en 0 :", d)

🧠 Exercice : Pile ou Face truqué

On lance 10 fois une pièce qui a 30% de chance de tomber sur Face. Quelle est la probabilité d'obtenir exactement 3 fois Face ?
Indice : Utilisez la loi Binomiale (binom).

Voir la solution
from scipy.stats import binom

n = 10  # Nombre d'essais
p = 0.3 # Probabilité de succès (Face)
k = 3   # Nombre de succès voulus

# pmf = Probability Mass Function (pour les discrets)
proba = binom.pmf(k, n, p)

print(f"Probabilité d'avoir 3 faces : {proba:.4f}")
# Résultat : ~0.2668 (soit 26.68%)

Partie 4 : Bibliothèques de Manipulation des Données Pandas

Chapitre 1 : Introduction et Concepts Clés

1. Qu'est-ce que Pandas ?

Pandas est une bibliothèque Python de manipulation et d'analyse de données de haut niveau, construite sur la base de NumPy. C'est un outil essentiel pour l'analyse de données en Python.

Utilité de Pandas
  • Manipulation de données : Permet de manipuler des données tabulaires (similaires à une feuille de calcul Excel) et des séries chronologiques.
  • Nettoyage et organisation : Aide à nettoyer, filtrer, trier et organiser les données brutes.
Structures de Données Principales
  • Series (Série) : Un tableau unidimensionnel étiqueté, capable de contenir n'importe quel type de données.
  • DataFrame : Une structure de données bidimensionnelle (tableau), avec des colonnes de types potentiellement différents, similaire à une table SQL.

Chapitre 2 : La Structure de Données Series (Série)

Une Série est la structure fondamentale pour construire des DataFrame. Elle peut être vue de deux manières : comme un tableau NumPy amélioré ou comme un dictionnaire Python spécialisé.

A. La Série comme un Tableau NumPy

Une série enveloppe à la fois une séquence de valeurs et une séquence d'indices. Contrairement aux tableaux NumPy simples, les indices peuvent être de n'importe quel type.

import pandas as pd

# Création d'une Série simple
data = pd.Series([0.25, 0.50, 0.75, 1.0])

print(data)
# 0    0.25
# 1    0.50
# 2    0.75
# 3    1.00
# dtype: float64

print("Valeurs :", data.values) # [0.25 0.5 0.75 1. ]
print("Index   :", data.index)  # RangeIndex(start=0, stop=4, step=1)
Accès aux éléments (Comme un tableau)
# Accès par indice numérique
print(data[1])    # 0.5

# Accès par tranche (Slicing)
print(data[1:3])
# 1    0.50
# 2    0.75
Indices Explicites

On peut définir nos propres indices (lettres, dates, etc.).

data = pd.Series([0.25, 0.5, 0.75, 1.0], 
                 index=['a', 'b', 'c', 'd'])

print(data['c']) # 0.75

B. La Série comme un Dictionnaire Python

Une série fournit un mappage entre des clés (index) et des valeurs, tout comme un dictionnaire.

# Création à partir d'un dictionnaire
population_dict = {
    'Casablanca': 6200000,
    'Rabat': 1200000,
    'Marrakech': 950000,
    'Fès': 1100000
}
population = pd.Series(population_dict)

print(population)
# Casablanca    6200000
# Rabat         1200000
# ...
Accès par Clé
print(population['Rabat'])
# 1200000

# Vérifier l'existence
print('Paris' in population)
# False
Slicing avec Clés

⚠️ La borne de fin est INCLUSE !

print(population['Rabat':'Marrakech'])
# Rabat        1200000
# Marrakech     950000

Chapitre 3 : La Structure de Données DataFrame

Un DataFrame est la structure la plus puissante de Pandas. Il peut être considéré comme une collection de Séries alignées (partageant le même index). C'est l'équivalent d'une feuille Excel en Python.

1. Création d'un DataFrame

La méthode la plus simple est d'assembler plusieurs Séries ou d'utiliser un dictionnaire de listes.

# 1. Créons une deuxième série (Superficie)
area_dict = {
    'Casablanca': 1615,
    'Rabat': 118,
    'Marrakech': 230,
    'Fès': 240
}
area = pd.Series(area_dict)

# 2. Assemblons le tout dans un DataFrame
data = pd.DataFrame({
    'population': population,
    'superficie': area
})

print(data)
#             population  superficie
# Casablanca     6200000        1615
# Rabat          1200000         118
# Marrakech       950000         230
# Fès            1100000         240

2. Attributs Essentiels

Comme les Séries, les DataFrames possèdent des attributs pour inspecter leur structure.

Attribut Description
.indexLes étiquettes des lignes (ex: Casablanca, Rabat...)
.columnsLes noms des colonnes (ex: population, superficie)
.valuesLes données brutes sous forme de tableau NumPy 2D
.shapeDimensions (Lignes, Colonnes)
print(data.index)
# Index(['Casablanca', 'Rabat', 'Marrakech', 'Fès'], dtype='object')

print(data.columns)
# Index(['population', 'superficie'], dtype='object')

print(data.values)
# [[6200000    1615]
#  [1200000     118] ... ]

3. Inspection Rapide des Données

Pour avoir un aperçu rapide de vos données sans tout afficher :

MéthodeDescription
.head(n)Affiche les n premières lignes (5 par défaut).
.tail(n)Affiche les n dernières lignes (5 par défaut).
.info()Résumé concis (types, non-nulls, mémoire).
.shapeDimensions du DataFrame (lignes, colonnes).
# Les 2 premières lignes
print(data.head(2))

# Les dimensions
print(data.shape) 
# (4, 2) -> 4 lignes, 2 colonnes

# Infos techniques
data.info()
# class 'pandas.core.frame.DataFrame'
# Index: 4 entries, Casablanca to Fès
# Data columns (total 2 columns): ...

🧠 Exercice : Gestion de Stock

Créez un DataFrame représentant un stock de fruits avec les colonnes 'Prix' et 'Quantité'.
Fruits (Index) : Pomme, Banane, Orange.
Prix : 2, 1.5, 3
Quantité : 100, 50, 75

Voir la solution
import pandas as pd

stock = pd.DataFrame({
    'Prix': [2, 1.5, 3],
    'Quantité': [100, 50, 75]
}, index=['Pomme', 'Banane', 'Orange'])

print(stock)

Chapitre 4 : Indexation Avancée et Sélection

Accéder aux données avec précision est l'une des forces majeures de Pandas. Il est crucial de distinguer la sélection par étiquette (Label) de la sélection par position (Position).

📊 Dataset Fil rouge : Gestion RH

Pour ce chapitre et les suivants, nous utiliserons un DataFrame df_employes réaliste.

import pandas as pd

data = {
    'Nom': ['Alice', 'Bob', 'Charlie', 'David', 'Eva'],
    'Departement': ['RH', 'IT', 'Finance', 'IT', 'Marketing'],
    'Salaire': [45000, 60000, 52000, 58000, 48000],
    'Ville': ['Paris', 'Lyon', 'Paris', 'Marseille', 'Lyon']
}

df_employes = pd.DataFrame(data)
df_employes = df_employes.set_index('Nom') # Utilisons 'Nom' comme index

1. Sélection par Étiquette (.loc) vs Position (.iloc)

.loc[ligne, colonne]

Utilise les NOMS des index et des colonnes.

  • Dernier élément inclus (inclusive).
  • Erreur si le label n'existe pas.
.iloc[ligne, colonne]

Utilise les POSITIONS ENTIÈRES (0, 1, 2...).

  • Comme les listes Python.
  • Dernier élément exclu (exclusive).
# --- EXEMPLE AVEC .LOC ---
# Sélectionner le salaire de Alice
print(df_employes.loc['Alice', 'Salaire']) # 45000

# Sélectionner de Alice à Charlie, colonnes Departement et Ville
print(df_employes.loc['Alice':'Charlie', ['Departement', 'Ville']])

# --- EXEMPLE AVEC .ILOC ---
# Sélectionner la 2ème ligne (Bob), 3ème colonne (Salaire) -> Index 1, 2
print(df_employes.iloc[1, 2]) # 60000

# Sélectionner les 3 premières lignes et les 2 dernières colonnes
print(df_employes.iloc[0:3, -2:])

2. Filtrage conditionnel (Masquage)

C'est la méthode la plus courante pour extraire des données : poser une question logique (Vrai/Faux) au DataFrame.

# 1. Créer un masque (Filtre)
# Qui gagne plus de 50 000 ?
masque_salaire = df_employes['Salaire'] > 50000

# 2. Appliquer le masque
riches = df_employes[masque_salaire]
print(riches)
# Affiche Bob, Charlie, David

# 3. Conditions multiples (Et=&, Ou=|)
# Salaire > 50000 ET Ville est Paris
top_paris = df_employes[(df_employes['Salaire'] > 50000) & (df_employes['Ville'] == 'Paris')]

🧠 Exercice 1 : Sélection ciblée

À partir de df_employes, affichez uniquement les colonnes "Nom" (qui est l'index) et "Salaire" pour les employés dont le nom est "Bob" ou "Eva".
Indice : Utilisez .loc avec une liste de noms.

Voir la solution
# Sélection par étiquettes spécifiques
print(df_employes.loc[['Bob', 'Eva'], ['Salaire']])

🧠 Exercice 2 : Recherche complexe

Trouvez tous les employés du département "IT" qui habitent à "Lyon" ou "Marseille".

Voir la solution
# Méthode 1 : Booléens classiques
cond_dept = df_employes['Departement'] == 'IT'
cond_ville = df_employes['Ville'].isin(['Lyon', 'Marseille'])

print(df_employes[cond_dept & cond_ville])

# Méthode 2 : Query (Plus lisible)
print(df_employes.query("Departement == 'IT' and Ville in ['Lyon', 'Marseille']"))

Chapitre 5 : Nettoyage des Données

"Garbage in, garbage out". Avant toute analyse, il faut nettoyer les données. Pandas excelle dans la détection et la correction des anomalies comme les valeurs manquantes (NaN) ou les doublons.

📊 Dataset "Sale" (Dirty Data)

Introduisons des erreurs volontaires pour apprendre à les corriger.

import numpy as np

data_dirty = {
    'Nom': ['Alice', 'Bob', 'Charlie', 'Alice', 'David'], # Alice en double
    'Age': [25, 30, np.nan, 25, 40],            # Manquant pour Charlie
    'Salaire': [45000, 60000, 52000, 45000, np.nan] # Manquant pour David
}

df = pd.DataFrame(data_dirty)
print(df)

1. Gestion des Valeurs Manquantes (NaN)

Détection
  • df.isnull() : Renvoie True si NaN.
  • df.isnull().sum() : Compte les NaN par colonne.
Correction
  • dropna() : Supprime la ligne/colonne.
  • fillna(valeur) : Remplace par une constante ou une moyenne.
# 1. Identifier les manques
print(df.isnull().sum())
# Age: 1, Salaire: 1

# 2. Stratégie radicale : Supprimer les lignes avec au moins un NaN
df_clean = df.dropna()

# 3. Stratégie douce : Remplacer (Imputation)
# Remplir l'âge manquant par la moyenne des âges
age_moyen = df['Age'].mean()
df['Age'] = df['Age'].fillna(age_moyen)

# Remplir le salaire par 0 ou une valeur fixe
df['Salaire'] = df['Salaire'].fillna(0)

2. Gestion des Doublons

Les doublons faussent les statistiques. Il est facile de les supprimer.

# Vérifier les doublons complets (toutes les colonnes identiques)
print(df.duplicated())

# Supprimer les doublons (garde la première occurrence par défaut)
df = df.drop_duplicates()

# Supprimer basé sur une seule colonne (ex: Nom unique)
df.drop_duplicates(subset=['Nom'], keep='last')

🧠 Exercice 1 : Nettoyage sélectif

Soit un DataFrame examens avec des colonnes "Etudiant", "Maths", "Physique". Certaines notes sont NaN.
Remplissez les NaN de "Maths" par 10 (moyenne par défaut) et supprimez les lignes où "Physique" est NaN.

Voir la solution
# Remplissage
examens['Maths'] = examens['Maths'].fillna(10)

# Suppression ciblée
examens.dropna(subset=['Physique'], inplace=True)

🧠 Exercice 2 : Doublons subtils

Dans un journal de transactions bancaires, supprimez les doublons mais uniquement ceux qui ont le même "id_transaction", en gardant la dernière entrée (la plus récente).

Voir la solution
import pandas as pd

# Création du DataFrame avec doublons
transactions = pd.DataFrame({
    'id_transaction': [101, 102, 103, 101, 104],
    'montant': [100, 250, 50, 100, 300],
    'date': ['2023-01-01', '2023-01-02', '2023-01-03', '2023-01-05', '2023-01-06']
})

print("Avant suppression :")
print(transactions)

# Suppression des doublons sur 'id_transaction', en gardant le dernier (le plus récent 2023-01-05 pour l'id 101)
transactions.drop_duplicates(subset=['id_transaction'], keep='last', inplace=True)

print("\nAprès suppression :")
print(transactions)

Chapitre 6 : Combinaison de DataFrames

Les données sont rarement dans un seul fichier. Il faut souvent assembler des morceaux (Concaténation) ou croiser des informations (Fusion/Jointure).

1. Concaténation (Empilement)

Imaginez coller deux feuilles Excel l'une en dessous de l'autre. C'est pd.concat().

# Deux équipes de vente
equipe_A = pd.DataFrame({'Vendeur': ['A1', 'A2'], 'Vente': [100, 200]})
equipe_B = pd.DataFrame({'Vendeur': ['B1'], 'Vente': [50]})

# Rassembler tout le monde (axe 0 par défaut = vertical)
total = pd.concat([equipe_A, equipe_B], ignore_index=True)

print(total)
#   Vendeur  Vente
# 0      A1    100
# 1      A2    200
# 2      B1     50

2. Fusion (Jointures SQL)

Pour enrichir un tableau avec des informations d'un autre (ex: ajouter le nom du département à un employé), on utilise pd.merge().

📊 Données relationnelles
df_staff = pd.DataFrame({
    'Employe': ['Alice', 'Bob', 'Charlie'],
    'Dept_ID': [1, 2, 99] # Charlie est dans un département inconnu
})

df_depts = pd.DataFrame({
    'Dept_ID': [1, 2, 3],
    'Nom_Dept': ['RH', 'IT', 'Marketing']
})
Type (how)Explication Logique
'inner'Intersection. Ne garde que ceux qui sont présents dans les deux tables.
'left'Garde tout le tableau de gauche (Employés) et ajoute les infos de droite si disponibles (sinon NaN).
'outer'Union. Garde tout le monde, quitte à avoir des trous des deux côtés.
# Inner Join : Charlie va disparaître (Dept 99 inconnu)
print(pd.merge(df_staff, df_depts, on='Dept_ID', how='inner'))

# Left Join : Charlie reste, mais Nom_Dept sera NaN
print(pd.merge(df_staff, df_depts, on='Dept_ID', how='left'))

🧠 Exercice 1 : Consolidation mensuelle

Vous avez les ventes de Janvier et Février dans deux DataFrames distincts.
Créez un DataFrame unique ventes_Q1 contenant toutes les lignes.

Voir la solution
import pandas as pd

# Données de Janvier
janvier = pd.DataFrame({
    'Produit': ['Pomme', 'Orange'],
    'Ventes': [150, 200]
})

# Données de Février
fevrier = pd.DataFrame({
    'Produit': ['Pomme', 'Banane'],
    'Ventes': [120, 180]
})

# Consolidation
ventes_Q1 = pd.concat([janvier, fevrier], ignore_index=True)

print(ventes_Q1)

🧠 Exercice 2 : Catalogues produits

Table produits (id_prod, nom, prix, id_cat) et Table categories (id_cat, nom_categorie).
Affichez le nom de chaque produit avec le nom de sa catégorie. Si un produit n'a pas de catégorie, il doit quand même apparaître.

Voir la solution
import pandas as pd

# Table Produits
produits = pd.DataFrame({
    'id_prod': [1, 2, 3, 4],
    'nom': ['Laptop', 'Souris', 'Clavier', 'Écran'],
    'prix': [1200, 20, 45, 150],
    'id_cat': [100, 101, 101, 999] # 999 = Catégorie inconnue
})

# Table Catégories
categories = pd.DataFrame({
    'id_cat': [100, 101, 102],
    'nom_categorie': ['Informatique', 'Accessoire', 'Bureautique']
})

# Fusion Left Join pour garder tous les produits
resultat = pd.merge(produits, categories, on='id_cat', how='left')

print(resultat[['nom', 'nom_categorie']])

Chapitre 7 : Tri, Statistiques et GroupBy

Transformer des données brutes en informations utiles (Insights). La clé de voute de l'analyse est souvent le regroupement (GroupBy).

1. Tri et Analyses Rapides

Trier (Sort)
# Trier par Salaire décroissant
df.sort_values(by='Salaire', ascending=False)

# Multi-critères (Dept puis Salaire)
df.sort_values(by=['Dept', 'Salaire'])
Décrire (Describe)
# Stats rapides (mean, std, min, max...)
df.describe()

# Compter les occurrences (Fréquence)
df['Ville'].value_counts()
# Paris: 15, Lyon: 10...

2. Agrégation avec GroupBy

C'est l'opération "Split-Apply-Combine". On divise le DataFrame en groupes, on applique une fonction (somme, moyenne...), et on combine le résultat.

📊 Question Business

"Quel est le salaire moyen par Département ?"

# 1. Grouper par Département
groupes = df_employes.groupby('Departement')

# 2. Appliquer une agrégation (Moyenne sur le Salaire)
moyennes = groupes['Salaire'].mean()

print(moyennes)
# Departement
# Finance      52000.0
# IT           59000.0
# ...

# Plusieurs métriques d'un coup
stats = df_employes.groupby('Departement')['Salaire'].agg(['mean', 'max', 'count'])

🧠 Exercice 1 : Le Top 3

Affichez les 3 employés les mieux payés.

Voir la solution
df_employes.sort_values(by='Salaire', ascending=False).head(3)

🧠 Exercice 2 : Analyse des Ventes

Soit un DataFrame ventes (Region, Montant). Calculez le chiffre d'affaires total (somme) par Région.

Voir la solution
ca_region = ventes.groupby('Region')['Montant'].sum()
print(ca_region)

Chapitre 8 : Lecture et Écriture de Fichiers

Pandas communique avec le monde extérieur. Il peut lire et écrire dans une multitude de formats (CSV, Excel, JSON, SQL, Parquet...).

1. Lire des données (Input)

CSV (.csv)

Le format le plus universel.

df = pd.read_csv(
    'donnees.csv',
    sep=';',       # Séparateur (souvent , ou ;)
    encoding='utf-8', # Pour les accents
    index_col='ID' # Utiliser une col comme index
)
Excel (.xlsx)

Nécessite la librairie openpyxl.

df = pd.read_excel(
    'rapport.xlsx',
    sheet_name='Feuille1'
)

2. Sauvegarder des données (Output)

Une fois vos analyses terminées (nettoyage, calculs...), vous devez sauvegarder le résultat.

# 1. Vers CSV (Le classique)
# index=False évite d'écrire la colonne d'index (0, 1, 2...) dans le fichier
df.to_csv('resultat_final.csv', index=False, sep=',')

# 2. Vers Excel
df.to_excel('bilan_2024.xlsx', sheet_name='Bilan')

# 3. Vers JSON (Pour le web)
df.to_json('data.json', orient='records', indent=4)

🧠 Exercice 1 : Lecture complexe

Vous devez lire un fichier "clients.txt" où les colonnes sont séparées par des tabulations (\t) et il n'y a pas d'en-tête (header).

Voir la solution
# header=None indique qu'il n'y a pas de noms de colonnes dans le fichier
# names=[...] permet de les définir manuellement
df = pd.read_csv('clients.txt', sep='\t', header=None, names=['ID', 'Nom', 'Email'])

🧠 Exercice 2 : Export filtré

Sauvegardez dans "rh_paris.csv" uniquement les employés habitant à Paris.

Voir la solution
df_paris = df_employes[df_employes['Ville'] == 'Paris']
df_paris.to_csv('rh_paris.csv')

Chapitre 9 : Statistiques Avancées et Séries Temporelles

Au-delà des bases, Pandas est un outil redoutable pour l'analyse temporelle (Time Series) et les calculs statistiques poussés.

📊 Dataset "Ventes Chronologiques"
import pandas as pd

data_ventes = {
    'Date': ['2023-01-15', '2023-01-20', '2023-02-10', '2023-03-05', '2024-01-10'],
    'Montant': [150, 200, 120, 300, 400],
    'Produit': ['A', 'B', 'A', 'C', 'A']
}

df_ventes = pd.DataFrame(data_ventes)

# Conversion cruciale vers le format Datetime
df_ventes['Date'] = pd.to_datetime(df_ventes['Date'])

1. Manipulation de Dates (.dt accessor)

Une fois convertie, la colonne Date permet d'extraire très facilement des composants (Mois, Année, Jour de la semaine...).

Extraction
df_ventes['Annee'] = df_ventes['Date'].dt.year
df_ventes['Mois'] = df_ventes['Date'].dt.month
df_ventes['Jour'] = df_ventes['Date'].dt.day_name()
print(df_ventes[['Date', 'Annee', 'Jour']])
Filtrage Temporel
# Ventes après Février 2023
filtre = df_ventes['Date'] > '2023-02-01'

# Sélectionner une année spécifique
ventes_2024 = df_ventes[df_ventes['Date'].dt.year == 2024]

2. Fonctions Statistiques (Agrégation & Transformation)

FonctionDescription
.sum(), .mean(), .median()Somme, Moyenne, Médiane.
.cumsum()Somme cumulée (Running Total).
.quantile(0.5)Quantile (0.5 = Médiane, 0.25 = Q1...).
.var(), .std()Variance et Écart-Type.
# Somme cumulée des ventes au fil du temps
df_ventes = df_ventes.sort_values('Date')
df_ventes['Cumul'] = df_ventes['Montant'].cumsum()

print(df_ventes[['Date', 'Montant', 'Cumul']])

🧠 Exercice 1 : Analyse mensuelle

Ajoutez une colonne "Nom_Mois" (Janvier, Février...) et calculez le montant total des ventes par Mois (toutes années confondues).

Voir la solution
df_ventes['Nom_Mois'] = df_ventes['Date'].dt.month_name()
resultat = df_ventes.groupby('Nom_Mois')['Montant'].sum()
print(resultat)

🧠 Exercice 2 : Filtrage temporel

Affichez uniquement les ventes réalisées le "Week-end" (Samedi ou Dimanche).
Indice : Utilisez dt.dayofweek (5=Samedi, 6=Dimanche) ou dt.day_name().

Voir la solution
weekend = df_ventes[df_ventes['Date'].dt.dayofweek.isin([5, 6])]
print(weekend)

Chapitre 10 : Étude de Cas Complète (Projet Final)

Mettez en pratique tout ce que vous avez appris. Nous allons analyser un jeu de données de commandes E-commerce réaliste.

📦 Dataset "Commandes E-commerce"
data_ecommerce = {
    'ID_Commande': [101, 102, 103, 104, 105, 106],
    'Date': ['2023-01-15', '2023-01-20', '2023-02-10', '2023-02-12', '2023-03-05', '2023-03-05'],
    'Produit': ['Ordinateur', 'Souris', 'Clavier', 'Écran', 'Ordinateur', 'Souris'],
    'Categorie': ['High-Tech', 'Accessoire', 'Accessoire', 'High-Tech', 'High-Tech', 'Accessoire'],
    'Prix': [1200, 25, 45, 200, 1200, np.nan], # Prix manquant pour la dernière souris
    'Statut': ['Livré', 'Annulé', 'Livré', 'Livré', 'En cours', 'Livré']
}

df = pd.DataFrame(data_ecommerce)
df['Date'] = pd.to_datetime(df['Date'])

1. Nettoyage des données

Il manque un prix pour une souris. Sachant qu'une autre souris coûte 25€, nous allons remplir le NaN par la médiane des prix de la catégorie 'Accessoire'.

Voir le code
# Calculer la médiane des accessoires
mediane_accessoires = df.loc[df['Categorie'] == 'Accessoire', 'Prix'].median()

# Remplir les NaN
df['Prix'] = df['Prix'].fillna(mediane_accessoires)

2. Enrichissement (Feature Engineering)

Ajoutez une colonne 'Mois' pour analyser la saisonnalité.

Voir le code
df['Mois'] = df['Date'].dt.month_name()

3. Analyse Business

Question : Quel est le Chiffre d'Affaires (CA) total par Catégorie, en excluant les commandes "Annulées" ?

Voir le code
# 1. Filtrer les commandes valides (Non annulées)
df_valide = df[df['Statut'] != 'Annulé']

# 2. Grouper par Catégorie et sommer les Prix
ca_par_categorie = df_valide.groupby('Categorie')['Prix'].sum()

print(ca_par_categorie)
# Categorie
# Accessoire       70.0
# High-Tech      2600.0

4. Export du Rapport

Sauvegardez ce résumé dans un fichier CSV "rapport_ca.csv".

Voir le code
ca_par_categorie.to_csv('rapport_ca.csv')

Partie 5 : Bibliothèques de Visualisation

La visualisation des données est une étape cruciale en Data Science. Elle permet de comprendre les tendances, les modèles et les anomalies. Nous allons explorer les deux bibliothèques les plus populaires : Matplotlib et Seaborn.

Chapitre 1 : Matplotlib

1. Introduction

Matplotlib est la bibliothèque la plus utilisée en Python pour tracer des graphiques 2D. Elle est puissante, flexible et permet de créer des tracés de haute qualité en quelques lignes de code.

Types de graphiques courants
  • 📈 Graphiques linéaires (Line plots)
  • 📊 Diagrammes à barres (Bar charts)
  • 📉 Histogrammes (Histograms)
  • 📍 Diagrammes de dispersion (Scatter)
  • 🍰 Graphiques circulaires (Pie charts)

2. Installation et Importation

Installation via pip :

$ pip install matplotlib

Le module principal est pyplot, importé conventionnellement sous l'alias plt.

3. Exemple de Base

Traçons une simple courbe sinusoïdale.

import matplotlib.pyplot as plt
import numpy as np

# 1. Création des données
x = np.linspace(0, 10, 100)
y = np.sin(x)

# 2. Création du tracé (Line plot)
plt.plot(x, y, label='Sinus', color='blue')

# Ajout de titres et légendes
plt.title("Mon Premier Graphique")
plt.xlabel("Axe X")
plt.ylabel("Axe Y")
plt.legend()

# 3. Affichage
plt.show()

🧠 Exercice : Nuage de points

Générez 50 points aléatoires pour X et Y et affichez-les sous forme de nuage de points (Scatter plot) rouge.
Indice : Utilisez plt.scatter().

Voir la solution
import matplotlib.pyplot as plt
import numpy as np

x = np.random.rand(50)
y = np.random.rand(50)

plt.scatter(x, y, color='red', marker='x')
plt.title("Nuage de points aléatoires")
plt.show()

Chapitre 2 : Seaborn

1. Pourquoi Seaborn ?

Seaborn est construit au-dessus de Matplotlib. Elle fournit une interface de plus haut niveau pour dessiner des graphiques statistiques attrayants.

Avantages
  • 🎨 Meilleurs thèmes et palettes par défaut.
  • 🐼 Intégration native avec les DataFrames Pandas.
  • 📊 Facilite les graphiques complexes (Heatmaps, Violins).
Installation
$ pip install seaborn

Import alias : sns

2. Exemple : Histogramme et Densité

Seaborn rend très simple l'ajout d'une estimation de densité (KDE) sur un histogramme.

import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np

# Générer des données suivant une loi normale
data = np.random.normal(size=1000)

# Création du graphique (histplot)
# kde=True ajoute la courbe de densité
sns.histplot(data, kde=True, color='teal')

plt.title("Distribution Normale avec Seaborn")
plt.show()

Chapitre 3 : Autres Outils

Bien que Matplotlib et Seaborn soient les standards, d'autres bibliothèques méritent d'être mentionnées pour des besoins spécifiques :

Plotly

Pour des graphiques interactifs (zoom, survol) utilisables dans le web.

Bokeh

Similaire à Plotly, excellent pour les tableaux de bord interactifs et les grands jeux de données.

Vispy

Basé sur OpenGL, pour la visualisation haute performance (millions de points) en temps réel.

Partie 6 : Bibliothèques d'Apprentissage Automatique (Machine Learning)

Chapitre 1 : Introduction à Scikit-learn (sklearn)

1. Qu'est-ce que Scikit-learn ?

Scikit-learn est la bibliothèque la plus populaire et la plus utilisée en Python pour l'apprentissage automatique (Machine Learning). Elle fournit des outils efficaces pour l'analyse statistique et la modélisation de données.

Caractéristiques Principales
  • Simple et efficace : Interface cohérente pour tous les modèles.
  • Open source : Disponible gratuitement.
  • Réutilisable : Les modèles peuvent être sauvegardés.
  • Interopérabilité : Construite sur NumPy, SciPy et Matplotlib.
Domaines Couverts
  • Apprentissage Supervisé : Classification, Régression.
  • Apprentissage Non Supervisé : Clustering, Réduction de dimension.
  • Note : Le Deep Learning est plutôt géré par TensorFlow/PyTorch.

2. Installation

$ pip install scikit-learn

Chapitre 2 : Algorithmes et Fonctions Clés

Scikit-learn est organisé en sous-modules spécialisés. Voici les plus importants :

Sous-module Description Exemples
sklearn.linear_model Modèles linéaires LinearRegression, LogisticRegression
sklearn.svm Machines à Vecteurs de Support SVC, SVR
sklearn.tree Arbres de décision DecisionTreeClassifier
sklearn.ensemble Méthodes d'ensemble RandomForestClassifier
sklearn.cluster Clustering (Non supervisé) KMeans, DBSCAN
sklearn.preprocessing Prétraitement des données StandardScaler, MinMaxScaler
sklearn.model_selection Sélection et validation train_test_split, GridSearchCV
sklearn.metrics Mesures de performance accuracy_score, mean_squared_error

Chapitre 3 : Les Étapes d'un Projet ML (Workflow)

L'utilisation de Scikit-learn suit presque toujours le même schéma en 5 étapes.

1
Choisir la classe de Modèle

Importer la classe appropriée.

from sklearn.linear_model import LinearRegression
2
Instancier le Modèle (Hyperparamètres)

Créer une instance en fixant les paramètres qui ne sont pas appris.

model = LinearRegression()
3
Organiser les Données (X et y)

X (Features) : Matrice 2D (Lignes=Échantillons, Colonnes=Caractéristiques).
y (Target) : Vecteur 1D (La valeur à prédire).

4
Entraîner le Modèle (.fit)

C'est ici que le modèle "apprend".

model.fit(X_train, y_train)
5
Prédire (.predict)

Utiliser le modèle sur de nouvelles données.

y_pred = model.predict(X_test)

Exemple Complet : Régression Linéaire

import numpy as np
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split

# 1. Générer des données factices
X = np.random.rand(100, 1) * 10 # 100 valeurs entre 0 et 10
y = 2 * X + 1 + np.random.randn(100, 1) # y = 2x + 1 + bruit

# 2. Séparer les données (Train / Test)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

# 3. Choisir et Instancier le modèle
model = LinearRegression()

# 4. Entraîner
model.fit(X_train, y_train)

# 5. Prédire
predictions = model.predict(X_test)

print(f"Coefficient appris : {model.coef_[0][0]:.2f}") # Devrait être proche de 2
print(f"Ordonnée à l'origine : {model.intercept_[0]:.2f}") # Devrait être proche de 1

🧠 Exercice : Classification Iris

Utilisez le jeu de données Iris (inclus dans sklearn) pour entraîner un arbre de décision (DecisionTreeClassifier) et prédire la classe d'une fleur.

Voir la solution
from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

# 1. Charger les données
iris = load_iris()
X, y = iris.data, iris.target

# 2. Split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 3. Modèle
clf = DecisionTreeClassifier()

# 4. Entraînement
clf.fit(X_train, y_train)

# 5. Prédiction et Score
y_pred = clf.predict(X_test)
print(f"Précision : {accuracy_score(y_test, y_pred):.2f}")

Chapitre 4 : Autres Bibliothèques

Pour l'apprentissage profond (Deep Learning) et les réseaux de neurones complexes, d'autres bibliothèques sont privilégiées :

TensorFlow / Keras

Développé par Google. Très populaire pour la production et le déploiement de modèles.

PyTorch

Développé par Facebook (Meta). Très apprécié en recherche pour sa flexibilité et son approche dynamique.

Partie 7 : Bibliothèques du Traitement de Langage Naturel (TALN)

Le Traitement de Langage Naturel (TALN), ou NLP en anglais, est un sous-domaine de l'intelligence artificielle qui permet aux machines de lire, de comprendre et de générer le langage humain.

Chapitre 1 : NLTK (Natural Language Toolkit)

1. Présentation

NLTK est l'une des bibliothèques open source les plus populaires et puissantes en Python pour la recherche et le développement dans le domaine du TALN. C'est un outil d'introduction idéal pour les étudiants.

Utilité
  • Fournit des ensembles de données (corpus) prêts à l'emploi.
  • Offre des algorithmes pour effectuer des tâches courantes du TALN.
  • Facilite l'apprentissage et le prototypage rapide.
Installation
$ pip install nltk

Chapitre 2 : Sous-modules et Fonctionnalités Clés

NLTK est riche en sous-modules couvrant l'ensemble du processus de traitement du langage.

Sous-module Description Exemples
nltk.tokenize Segmentation du texte en unités (tokens). word_tokenize, sent_tokenize
nltk.stem Réduction des mots à leur racine. PorterStemmer, WordNetLemmatizer
nltk.corpus Collection de corpus (jeux de données). WordNet, Brown Corpus
nltk.tag Étiquetage des parties du discours (POS Tagging). n-gram, HMM
nltk.classify Classification de texte. Naive Bayes, Decision Tree
nltk.chunk Segmentation en morceaux (Chunking). Named Entity Recognition (NER)

Chapitre 3 : Exemple Détaillé - La Tokenisation

1. Concept

La tokenisation est un processus fondamental en TALN. Elle vise à transformer un texte brut en une série d'unités individuelles appelées tokens (mots, signes de ponctuation, symboles).

Objectif : Décomposer une phrase ou un paragraphe en une liste d'éléments significatifs pour l'analyse.

2. Exemple de Code

Tokenisons une phrase concernant l'OFPPT.

import nltk
from nltk.tokenize import word_tokenize

# Note : Il faut parfois télécharger les ressources NLTK au préalable
# nltk.download('punkt')

sentence = "L'OFPPT a développé au cours de la dernière décennie une coopération fructueuse avec de nombreux pays. Une politique volontariste qui s'est concrétisée par des projets allant de missions d'assistance technique, la formation, jusqu'à l'amélioration des compétences des formateurs."

tokens = word_tokenize(sentence)

print(tokens)
Résultat (Sortie Console)
['L', "'", 'OFPPT', 'a', 'développé', 'au', 'cours', 'de', 'la', 'dernière', 'décennie', 'une', 'coopération', 'fructueuse', 'avec', 'de', 'nombreux', 'pays', '.', 'Une', 'politique', 'volontariste', 'qui', 's', "'", 'est', 'concrétisée', 'par', 'des', 'projets', 'allant', 'de', 'missions', "d'assistance", 'technique', ',', 'la', 'formation', ',', 'jusqu', "'", 'à', "l'amélioration", 'des', 'compétences', 'des', 'formateurs', '.']

🧠 Exercice : Tokenisation de Phrases

Essayez d'utiliser sent_tokenize pour diviser le texte ci-dessus en phrases plutôt qu'en mots.

Voir la solution
from nltk.tokenize import sent_tokenize

sentences = sent_tokenize(sentence)

for s in sentences:
    print("-", s)

# Résultat :
# - L'OFPPT a développé au cours de la dernière décennie une coopération fructueuse avec de nombreux pays.
# - Une politique volontariste qui s'est concrétisée par des projets...