═══════════════════════════════════════════════════════════════════════════════
    CHEAT SHEET PYTHON - PRÉREQUIS POUR L'ANALYSE DE DONNÉES
═══════════════════════════════════════════════════════════════════════════════

[IMPORTANT] Ce document contient TOUT ce qu'un débutant doit connaître en Python avant
   de commencer l'analyse de données. Gardez-le sous la main en permanence !

═══════════════════════════════════════════════════════════════════════════════
1. BASES ABSOLUES
═══════════════════════════════════════════════════════════════════════════════

1.1 AFFICHAGE ET COMMENTAIRES
─────────────────────────────

# Ceci est un commentaire (ignoré par Python)

"""
Ceci est un commentaire
sur plusieurs lignes
"""

print("Bonjour")              # Affiche : Bonjour
print("Nombre:", 42)          # Affiche : Nombre: 42
print(10 + 5)                 # Affiche : 15


1.2 VARIABLES
─────────────

nom = "Alice"                 # Chaîne de caractères (str)
age = 25                      # Nombre entier (int)
taille = 1.75                 # Nombre décimal (float)
est_etudiant = True           # Booléen (bool)

# Afficher le type
print(type(nom))              # <class 'str'>
print(type(age))              # <class 'int'>


1.3 OPÉRATIONS MATHÉMATIQUES
────────────────────────────

a = 10
b = 3

print(a + b)                  # Addition : 13
print(a - b)                  # Soustraction : 7
print(a * b)                  # Multiplication : 30
print(a / b)                  # Division : 3.333...
print(a // b)                 # Division entière : 3
print(a % b)                  # Modulo (reste) : 1
print(a ** b)                 # Puissance : 1000


1.4 CHAÎNES DE CARACTÈRES (strings)
───────────────────────────────────

texte = "Python"

print(texte[0])               # Premier caractère : P
print(texte[-1])              # Dernier caractère : n
print(texte[0:3])             # Extraction : Pyt
print(len(texte))             # Longueur : 6

# Méthodes utiles
print(texte.upper())          # PYTHON
print(texte.lower())          # python
print(texte.replace("P", "J"))# Jython

# Formatage de chaînes
nom = "Alice"
age = 25
print(f"Je m'appelle {nom} et j'ai {age} ans")
# Affiche : Je m'appelle Alice et j'ai 25 ans


═══════════════════════════════════════════════════════════════════════════════
2. STRUCTURES DE DONNÉES
═══════════════════════════════════════════════════════════════════════════════

2.1 LISTES (list) - Modifiable, Ordonnée
────────────────────────────────────────

fruits = ["pomme", "banane", "orange"]

# Accès
print(fruits[0])              # pomme
print(fruits[-1])             # orange (dernier)

# Modification
fruits[1] = "kiwi"            # Remplace banane par kiwi
fruits.append("fraise")       # Ajoute à la fin
fruits.insert(1, "mangue")    # Insère à l'index 1
fruits.remove("pomme")        # Supprime pomme
del fruits[0]                 # Supprime par index
element = fruits.pop()        # Retire et retourne le dernier

# Opérations
print(len(fruits))            # Nombre d'éléments
print("pomme" in fruits)      # Vérifier présence : True/False
fruits.sort()                 # Trier
fruits.reverse()              # Inverser

# Slicing (extraction)
nombres = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(nombres[2:5])           # [2, 3, 4]
print(nombres[:3])            # [0, 1, 2]
print(nombres[5:])            # [5, 6, 7, 8, 9]
print(nombres[::2])           # [0, 2, 4, 6, 8] (un élément sur 2)


2.2 TUPLES (tuple) - Non modifiable, Ordonnée
─────────────────────────────────────────────

coordonnees = (10, 20)
print(coordonnees[0])         # 10

# On NE PEUT PAS modifier un tuple
# coordonnees[0] = 15         # ERREUR !

# Utile pour des données qui ne changent pas
point = (3.5, 7.2)
nom_complet = ("Alice", "Dupont")


2.3 DICTIONNAIRES (dict) - Clé:Valeur
─────────────────────────────────────

personne = {
    "nom": "Alice",
    "age": 25,
    "ville": "Paris"
}

# Accès
print(personne["nom"])        # Alice
print(personne.get("age"))    # 25

# Modification
personne["age"] = 26          # Modifier
personne["pays"] = "France"   # Ajouter
del personne["ville"]         # Supprimer

# Méthodes utiles
print(personne.keys())        # Toutes les clés
print(personne.values())      # Toutes les valeurs
print(personne.items())       # Paires clé-valeur

# Vérifier présence
if "nom" in personne:
    print("Le nom existe")


2.4 ENSEMBLES (set) - Pas de doublons, Non ordonnés
───────────────────────────────────────────────────

nombres = {1, 2, 3, 3, 4}
print(nombres)                # {1, 2, 3, 4} (pas de doublon)

nombres.add(5)                # Ajouter
nombres.remove(2)             # Supprimer

# Opérations ensemblistes
a = {1, 2, 3}
b = {3, 4, 5}
print(a | b)                  # Union : {1, 2, 3, 4, 5}
print(a & b)                  # Intersection : {3}
print(a - b)                  # Différence : {1, 2}


═══════════════════════════════════════════════════════════════════════════════
3. STRUCTURES DE CONTRÔLE
═══════════════════════════════════════════════════════════════════════════════

3.1 CONDITIONS (if/elif/else)
────────────────────────────

age = 18

if age < 18:
    print("Mineur")
elif age == 18:
    print("Tout juste majeur")
else:
    print("Majeur")

# Opérateurs de comparaison
# ==  égal
# !=  différent
# <   inférieur
# >   supérieur
# <=  inférieur ou égal
# >=  supérieur ou égal

# Opérateurs logiques
# and  ET
# or   OU
# not  NON

age = 25
if age >= 18 and age < 65:
    print("Adulte actif")


3.2 BOUCLE FOR (parcourir une séquence)
───────────────────────────────────────

# Parcourir une liste
fruits = ["pomme", "banane", "orange"]
for fruit in fruits:
    print(fruit)

# Parcourir avec range()
for i in range(5):            # 0, 1, 2, 3, 4
    print(i)

for i in range(2, 7):         # 2, 3, 4, 5, 6
    print(i)

for i in range(0, 10, 2):     # 0, 2, 4, 6, 8
    print(i)

# Parcourir avec index
fruits = ["pomme", "banane", "orange"]
for index, fruit in enumerate(fruits):
    print(f"{index}: {fruit}")
# Affiche :
# 0: pomme
# 1: banane
# 2: orange


3.3 BOUCLE WHILE (tant que condition vraie)
───────────────────────────────────────────

compteur = 0
while compteur < 5:
    print(compteur)
    compteur += 1             # Équivalent à : compteur = compteur + 1


3.4 BREAK et CONTINUE
────────────────────

# break : sortir de la boucle
for i in range(10):
    if i == 5:
        break                 # Sort quand i = 5
    print(i)                  # Affiche : 0, 1, 2, 3, 4

# continue : passer à l'itération suivante
for i in range(5):
    if i == 2:
        continue              # Saute quand i = 2
    print(i)                  # Affiche : 0, 1, 3, 4


═══════════════════════════════════════════════════════════════════════════════
4. FONCTIONS
═══════════════════════════════════════════════════════════════════════════════

4.1 DÉFINIR UNE FONCTION
───────────────────────

def dire_bonjour():
    print("Bonjour !")

dire_bonjour()                # Appel de la fonction


4.2 FONCTION AVEC PARAMÈTRES
────────────────────────────

def saluer(nom):
    print(f"Bonjour {nom} !")

saluer("Alice")               # Bonjour Alice !


4.3 FONCTION AVEC RETURN
────────────────────────

def additionner(a, b):
    return a + b

resultat = additionner(5, 3)
print(resultat)               # 8


4.4 PARAMÈTRES PAR DÉFAUT
─────────────────────────

def saluer(nom, message="Bonjour"):
    print(f"{message} {nom} !")

saluer("Alice")               # Bonjour Alice !
saluer("Bob", "Salut")        # Salut Bob !


4.5 FONCTIONS LAMBDA (fonctions anonymes)
─────────────────────────────────────────

# Forme classique
def carre(x):
    return x ** 2

# Forme lambda (équivalent)
carre = lambda x: x ** 2

print(carre(5))               # 25

# Utile pour opérations simples
nombres = [1, 2, 3, 4, 5]
carres = list(map(lambda x: x**2, nombres))
print(carres)                 # [1, 4, 9, 16, 25]


═══════════════════════════════════════════════════════════════════════════════
5. COMPRÉHENSIONS DE LISTES (List Comprehensions)
═══════════════════════════════════════════════════════════════════════════════

# Méthode classique
carres = []
for i in range(10):
    carres.append(i ** 2)

# Compréhension de liste (équivalent, plus concis)
carres = [i**2 for i in range(10)]
print(carres)                 # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

# Avec condition
pairs = [i for i in range(10) if i % 2 == 0]
print(pairs)                  # [0, 2, 4, 6, 8]

# Compréhension de dictionnaire
carres_dict = {i: i**2 for i in range(5)}
print(carres_dict)            # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}


═══════════════════════════════════════════════════════════════════════════════
6. GESTION DES FICHIERS
═══════════════════════════════════════════════════════════════════════════════

6.1 LIRE UN FICHIER
──────────────────

# Méthode 1 : Fermeture manuelle
fichier = open("donnees.txt", "r")
contenu = fichier.read()
fichier.close()

# Méthode 2 : Avec 'with' (recommandé, ferme automatiquement)
with open("donnees.txt", "r") as fichier:
    contenu = fichier.read()
    print(contenu)

# Lire ligne par ligne
with open("donnees.txt", "r") as fichier:
    for ligne in fichier:
        print(ligne.strip())  # strip() enlève les espaces/retours


6.2 ÉCRIRE DANS UN FICHIER
──────────────────────────

# Mode 'w' : écrase le fichier existant
with open("sortie.txt", "w") as fichier:
    fichier.write("Première ligne\n")
    fichier.write("Deuxième ligne\n")

# Mode 'a' : ajoute à la fin
with open("sortie.txt", "a") as fichier:
    fichier.write("Ligne ajoutée\n")


═══════════════════════════════════════════════════════════════════════════════
7. GESTION DES ERREURS
═══════════════════════════════════════════════════════════════════════════════

try:
    nombre = int(input("Entrez un nombre : "))
    resultat = 10 / nombre
    print(f"Résultat : {resultat}")
except ValueError:
    print("Erreur : Ce n'est pas un nombre valide")
except ZeroDivisionError:
    print("Erreur : Division par zéro impossible")
except Exception as e:
    print(f"Erreur inattendue : {e}")
finally:
    print("Cette partie s'exécute toujours")


═══════════════════════════════════════════════════════════════════════════════
8. MODULES ET IMPORTS
═══════════════════════════════════════════════════════════════════════════════

# Importer un module entier
import math
print(math.sqrt(16))          # 4.0

# Importer une fonction spécifique
from math import sqrt, pi
print(sqrt(25))               # 5.0
print(pi)                     # 3.14159...

# Importer avec alias
import pandas as pd
import numpy as np

# Modules utiles pour Data Analysis
# - math : fonctions mathématiques
# - random : nombres aléatoires
# - datetime : dates et heures
# - os : système de fichiers
# - csv : fichiers CSV
# - json : fichiers JSON


═══════════════════════════════════════════════════════════════════════════════
9. FONCTIONS BUILT-IN ESSENTIELLES
═══════════════════════════════════════════════════════════════════════════════

# len() : longueur
print(len([1, 2, 3]))         # 3
print(len("Python"))          # 6

# sum() : somme
print(sum([1, 2, 3, 4]))      # 10

# min() et max()
print(min([5, 2, 9, 1]))      # 1
print(max([5, 2, 9, 1]))      # 9

# sorted() : trier
print(sorted([5, 2, 9, 1]))   # [1, 2, 5, 9]

# reversed() : inverser
print(list(reversed([1, 2, 3])))  # [3, 2, 1]

# zip() : combiner
noms = ["Alice", "Bob"]
ages = [25, 30]
for nom, age in zip(noms, ages):
    print(f"{nom}: {age}")
# Alice: 25
# Bob: 30

# any() et all()
print(any([False, True, False]))  # True (au moins un True)
print(all([True, True, False]))   # False (pas tous True)

# round() : arrondir
print(round(3.14159, 2))      # 3.14


═══════════════════════════════════════════════════════════════════════════════
10. BONNES PRATIQUES
═══════════════════════════════════════════════════════════════════════════════

[OK] Nommer les variables de manière claire
   age_utilisateur    [OK]
   x                  [X]

[OK] Utiliser des commentaires
   # Calcule la moyenne
   moyenne = sum(notes) / len(notes)

[OK] Respecter PEP 8 (conventions Python)
   - Variables et fonctions : minuscules_avec_underscores
   - Classes : MajusculePourChaqueMot
   - Constantes : TOUT_EN_MAJUSCULES

[OK] Éviter les lignes trop longues (max 79 caractères)

[OK] Laisser 2 lignes vides avant une fonction


═══════════════════════════════════════════════════════════════════════════════
11. ERREURS COURANTES À ÉVITER
═══════════════════════════════════════════════════════════════════════════════

[X] Oublier les deux-points après if, for, def
   if age > 18      # ERREUR : manque ':'
   if age > 18:     # CORRECT

[X] Mauvaise indentation
   def saluer():
   print("Bonjour") # ERREUR : pas indenté
   
   def saluer():
       print("Bonjour")  # CORRECT

[X] Comparer avec = au lieu de ==
   if age = 18:     # ERREUR : = est pour assigner
   if age == 18:    # CORRECT : == pour comparer

[X] Modifier une liste pendant qu'on la parcourt
   liste = [1, 2, 3]
   for item in liste:
       liste.remove(item)  # DANGEREUX !

[X] Division entière non voulue (Python 2)
   # En Python 3, / donne toujours un float
   print(5 / 2)     # 2.5


═══════════════════════════════════════════════════════════════════════════════
12. ASTUCES RAPIDES
═══════════════════════════════════════════════════════════════════════════════

# Échanger deux variables
a, b = 10, 20
a, b = b, a
print(a, b)                   # 20 10

# Assigner plusieurs variables
x, y, z = 1, 2, 3

# Répéter une liste
zeros = [0] * 10              # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

# Joindre des chaînes
mots = ["Python", "est", "génial"]
phrase = " ".join(mots)       # "Python est génial"

# Séparer une chaîne
phrase = "Python est génial"
mots = phrase.split()         # ["Python", "est", "génial"]

# Vérifier si vide
liste = []
if not liste:
    print("Liste vide")

# Opérateur ternaire
age = 18
statut = "Majeur" if age >= 18 else "Mineur"


═══════════════════════════════════════════════════════════════════════════════
[DOCS] EXERCICE DE RÉVISION RAPIDE
═══════════════════════════════════════════════════════════════════════════════

Essayez de faire cet exercice pour vérifier votre compréhension :

ÉNONCÉ :
Créez un programme qui :
1. Demande à l'utilisateur d'entrer 5 nombres
2. Stocke ces nombres dans une liste
3. Affiche la moyenne de ces nombres
4. Affiche le nombre le plus grand et le plus petit
5. Affiche les nombres pairs uniquement

SOLUTION :
──────────

nombres = []

# 1. Demander 5 nombres
for i in range(5):
    nombre = float(input(f"Entrez le nombre {i+1}: "))
    nombres.append(nombre)

# 2. Déjà stockés dans la liste 'nombres'

# 3. Moyenne
moyenne = sum(nombres) / len(nombres)
print(f"Moyenne: {moyenne}")

# 4. Min et Max
print(f"Plus grand: {max(nombres)}")
print(f"Plus petit: {min(nombres)}")

# 5. Nombres pairs
pairs = [n for n in nombres if int(n) % 2 == 0]
print(f"Nombres pairs: {pairs}")


═══════════════════════════════════════════════════════════════════════════════
[OK] CHECKLIST AVANT DE COMMENCER L'ANALYSE DE DONNÉES
═══════════════════════════════════════════════════════════════════════════════

Assurez-vous de maîtriser :

[ ] Variables (int, float, str, bool)
[ ] Listes et opérations sur les listes
[ ] Dictionnaires
[ ] Boucles for et while
[ ] Conditions if/elif/else
[ ] Fonctions (def, return)
[ ] Lire et écrire des fichiers
[ ] Gestion des erreurs (try/except)
[ ] Imports de modules

Si vous cochez toutes ces cases, vous êtes PRÊT pour l'analyse de données ! [BRAVO]


═══════════════════════════════════════════════════════════════════════════════
FIN DU CHEAT SHEET - GARDEZ-LE TOUJOURS À PORTÉE DE MAIN !
═══════════════════════════════════════════════════════════════════════════════

═══════════════════════════════════════════════════════════════════════════════
    CHAPITRE 1 : INTRODUCTION À L'ANALYSE DE DONNÉES
═══════════════════════════════════════════════════════════════════════════════

[LIVRE] OBJECTIFS DU CHAPITRE :
   • Comprendre ce qu'est l'analyse de données
   • Connaître le rôle d'un Data Analyst
   • Découvrir les outils et bibliothèques Python
   • Installer et configurer l'environnement de travail
   • Comprendre le processus d'analyse de données

═══════════════════════════════════════════════════════════════════════════════
PARTIE 1 : QU'EST-CE QUE L'ANALYSE DE DONNÉES ?
═══════════════════════════════════════════════════════════════════════════════

1.1 DÉFINITION
─────────────

L'analyse de données est le processus de transformation de données brutes en 
informations exploitables pour prendre des décisions éclairées.

DONNÉES BRUTES -> NETTOYAGE -> ANALYSE -> VISUALISATION -> INSIGHTS -> DÉCISIONS

Exemple concret :
├─ DONNÉES : Ventes quotidiennes d'un magasin sur 1 an
├─ ANALYSE : Identifier les tendances, pic de ventes, produits populaires
└─ DÉCISION : Augmenter le stock des produits populaires en période de pic


1.2 POURQUOI PYTHON POUR L'ANALYSE DE DONNÉES ?
───────────────────────────────────────────────

[OK] Facile à apprendre et à lire
[OK] Bibliothèques puissantes (Pandas, NumPy, Matplotlib)
[OK] Communauté active et ressources abondantes
[OK] Gratuit et open-source
[OK] Utilisé par les plus grandes entreprises (Google, Netflix, NASA)

Alternatives :
- R : Très bon pour statistiques, mais syntaxe plus complexe
- Excel : Limité pour grands volumes de données
- SQL : Excellent pour bases de données, mais moins flexible pour analyse
- Power BI / Tableau : Outils de visualisation, moins programmables


1.3 LES 3 TYPES D'ANALYSE DE DONNÉES
────────────────────────────────────

┌─────────────────────────────────────────────────────────────────────┐
│ 1. ANALYSE DESCRIPTIVE                                              │
│    -> Que s'est-il passé ?                                           │
│    -> Exemple : "Les ventes ont augmenté de 15% ce mois-ci"          │
├─────────────────────────────────────────────────────────────────────┤
│ 2. ANALYSE DIAGNOSTIQUE                                             │
│    -> Pourquoi cela s'est-il passé ?                                 │
│    -> Exemple : "Les ventes ont augmenté grâce à la promotion"       │
├─────────────────────────────────────────────────────────────────────┤
│ 3. ANALYSE PRÉDICTIVE                                               │
│    -> Que va-t-il se passer ?                                        │
│    -> Exemple : "Les ventes devraient augmenter de 20% le mois       │
│       prochain basé sur les tendances"                              │
└─────────────────────────────────────────────────────────────────────┘


═══════════════════════════════════════════════════════════════════════════════
PARTIE 2 : LE RÔLE DU DATA ANALYST
═══════════════════════════════════════════════════════════════════════════════

2.1 MISSIONS PRINCIPALES
────────────────────────

1. Collecter les données
   └─ Depuis bases de données, fichiers CSV, APIs, web scraping

2. Nettoyer les données
   └─ Supprimer doublons, gérer valeurs manquantes, corriger erreurs

3. Explorer les données
   └─ Statistiques descriptives, distributions, corrélations

4. Analyser les données
   └─ Trouver patterns, tendances, anomalies

5. Visualiser les résultats
   └─ Graphiques, tableaux de bord, rapports

6. Communiquer les insights
   └─ Présenter conclusions de manière claire aux décideurs


2.2 COMPÉTENCES REQUISES
────────────────────────

TECHNIQUES :
[OK] Python (Pandas, NumPy)
[OK] SQL (requêtes de bases de données)
[OK] Statistiques de base
[OK] Visualisation de données
[OK] Excel (souvent utilisé en entreprise)

NON-TECHNIQUES :
[OK] Pensée critique
[OK] Résolution de problèmes
[OK] Communication claire
[OK] Curiosité et esprit analytique


2.3 DIFFÉRENCE AVEC D'AUTRES MÉTIERS DATA
─────────────────────────────────────────

DATA ANALYST
├─ Focus : Analyser le passé et le présent
├─ Outils : SQL, Python, Excel, Tableau
└─ Output : Rapports, dashboards

DATA SCIENTIST
├─ Focus : Prédire le futur, Machine Learning
├─ Outils : Python, R, TensorFlow
└─ Output : Modèles prédictifs

DATA ENGINEER
├─ Focus : Infrastructure et pipelines de données
├─ Outils : Spark, Hadoop, AWS
└─ Output : Systèmes de données


═══════════════════════════════════════════════════════════════════════════════
PARTIE 3 : ÉCOSYSTÈME PYTHON POUR DATA ANALYSIS
═══════════════════════════════════════════════════════════════════════════════

3.1 BIBLIOTHÈQUES ESSENTIELLES
──────────────────────────────

┌──────────────┬──────────────────────────────────────────────────────┐
│ NumPy        │ Calculs numériques et tableaux multidimensionnels    │
│              │ -> Base de toutes les autres bibliothèques            │
├──────────────┼──────────────────────────────────────────────────────┤
│ Pandas       │ Manipulation et analyse de données structurées      │
│              │ -> LA bibliothèque principale pour Data Analysis      │
├──────────────┼──────────────────────────────────────────────────────┤
│ Matplotlib   │ Visualisation de base (graphiques)                  │
│              │ -> Fondation pour autres bibliothèques de viz        │
├──────────────┼──────────────────────────────────────────────────────┤
│ Seaborn      │ Visualisation statistique avancée                   │
│              │ -> Plus belle et simple que Matplotlib               │
├──────────────┼──────────────────────────────────────────────────────┤
│ Scikit-learn │ Machine Learning (bonus, niveau avancé)             │
│              │ -> Pour modèles prédictifs                           │
└──────────────┴──────────────────────────────────────────────────────┘


3.2 OUTILS DE TRAVAIL
─────────────────────

1. ÉDITEUR DE CODE
   ├─ VS Code (recommandé) : Léger, extensions Python
   ├─ PyCharm : Plus lourd mais très complet
   └─ Sublime Text : Rapide et simple

2. JUPYTER NOTEBOOK
   ├─ Environnement interactif
   ├─ Mélange code + texte + graphiques
   ├─ Idéal pour exploration et présentation
   └─ Utilisé par 90% des Data Analysts

3. TERMINAL / LIGNE DE COMMANDE
   ├─ Installation de packages
   ├─ Exécution de scripts
   └─ Gestion de l'environnement


═══════════════════════════════════════════════════════════════════════════════
PARTIE 4 : INSTALLATION ET CONFIGURATION DE L'ENVIRONNEMENT
═══════════════════════════════════════════════════════════════════════════════

4.1 INSTALLER PYTHON
────────────────────

WINDOWS :
1. Aller sur https://www.python.org/downloads/
2. Télécharger Python 3.11 ou supérieur
3. IMPORTANT : Cocher "Add Python to PATH" lors de l'installation
4. Vérifier l'installation :
   Ouvrir CMD et taper : python --version

MAC :
1. Ouvrir Terminal
2. Installer Homebrew (si pas déjà fait) :
   /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
3. Installer Python : brew install python
4. Vérifier : python3 --version

LINUX :
1. Ouvrir Terminal
2. Installer : sudo apt update && sudo apt install python3 python3-pip
3. Vérifier : python3 --version


4.2 INSTALLER LES BIBLIOTHÈQUES
───────────────────────────────

Ouvrir le terminal et exécuter :

# Installation de base
pip install numpy pandas matplotlib seaborn

# Vérifier l'installation
python
>>> import numpy as np
>>> import pandas as pd
>>> import matplotlib.pyplot as plt
>>> import seaborn as sns
>>> print("Tout est installé correctement !")
>>> exit()


4.3 INSTALLER JUPYTER NOTEBOOK
──────────────────────────────

# Installation
pip install notebook

# Lancer Jupyter
jupyter notebook

# Une page web s'ouvrira automatiquement
# Créer un nouveau notebook : New -> Python 3


4.4 CRÉER UN ENVIRONNEMENT VIRTUEL (Recommandé)
───────────────────────────────────────────────

Un environnement virtuel isole les dépendances de chaque projet.

# Créer l'environnement
python -m venv mon_env_data

# Activer l'environnement
# Windows :
mon_env_data\Scripts\activate

# Mac/Linux :
source mon_env_data/bin/activate

# Installer les packages dans cet environnement
pip install numpy pandas matplotlib seaborn jupyter

# Désactiver l'environnement
deactivate


═══════════════════════════════════════════════════════════════════════════════
PARTIE 5 : LE PROCESSUS D'ANALYSE DE DONNÉES
═══════════════════════════════════════════════════════════════════════════════

5.1 LES 6 ÉTAPES DU PROCESSUS
─────────────────────────────

┌────────────────────────────────────────────────────────────────────┐
│ ÉTAPE 1 : DÉFINIR LA QUESTION                                      │
│ ────────────────────────────                                       │
│ Que cherche-t-on à savoir ?                                        │
│ Exemple : "Quels produits génèrent le plus de revenus ?"           │
└────────────────────────────────────────────────────────────────────┘
                              v
┌────────────────────────────────────────────────────────────────────┐
│ ÉTAPE 2 : COLLECTER LES DONNÉES                                    │
│ ─────────────────────────────                                      │
│ Où sont les données ? CSV, base de données, API ?                  │
│ Exemple : Exporter les ventes depuis le logiciel de caisse         │
└────────────────────────────────────────────────────────────────────┘
                              v
┌────────────────────────────────────────────────────────────────────┐
│ ÉTAPE 3 : NETTOYER LES DONNÉES                                     │
│ ───────────────────────────                                        │
│ Supprimer doublons, gérer valeurs manquantes, corriger erreurs     │
│ Exemple : Remplir les dates manquantes, supprimer ventes à 0€      │
└────────────────────────────────────────────────────────────────────┘
                              v
┌────────────────────────────────────────────────────────────────────┐
│ ÉTAPE 4 : EXPLORER LES DONNÉES                                     │
│ ────────────────────────────                                       │
│ Statistiques descriptives, distributions, corrélations             │
│ Exemple : Calculer la moyenne des ventes par jour                  │
└────────────────────────────────────────────────────────────────────┘
                              v
┌────────────────────────────────────────────────────────────────────┐
│ ÉTAPE 5 : ANALYSER ET VISUALISER                                   │
│ ──────────────────────────────                                     │
│ Créer graphiques, identifier tendances et patterns                 │
│ Exemple : Graphique montrant les ventes par catégorie de produit   │
└────────────────────────────────────────────────────────────────────┘
                              v
┌────────────────────────────────────────────────────────────────────┐
│ ÉTAPE 6 : COMMUNIQUER LES RÉSULTATS                                │
│ ─────────────────────────────────                                  │
│ Rapport, présentation, dashboard                                   │
│ Exemple : "Les produits X et Y représentent 60% des revenus"       │
└────────────────────────────────────────────────────────────────────┘


5.2 EXEMPLE COMPLET DE BOUT EN BOUT
───────────────────────────────────

QUESTION :
"Quel est le jour de la semaine où nous vendons le plus ?"

ÉTAPES :

1. COLLECTER
   └─ Fichier CSV des ventes : date, produit, quantité, prix

2. NETTOYER
   ├─ Supprimer lignes avec date invalide
   ├─ Convertir colonne 'date' en format date
   └─ Supprimer doublons

3. EXPLORER
   ├─ Combien de ventes au total ?
   ├─ Sur quelle période ?
   └─ Y a-t-il des valeurs aberrantes ?

4. ANALYSER
   ├─ Extraire le jour de la semaine de chaque vente
   ├─ Grouper par jour de la semaine
   └─ Calculer le total des ventes par jour

5. VISUALISER
   └─ Créer un graphique en barres : jour vs ventes

6. COMMUNIQUER
   └─ "Le samedi génère 35% des ventes hebdomadaires.
       Recommandation : Augmenter le personnel le samedi."


═══════════════════════════════════════════════════════════════════════════════
PARTIE 6 : TYPES DE DONNÉES
═══════════════════════════════════════════════════════════════════════════════

6.1 DONNÉES STRUCTURÉES vs NON STRUCTURÉES
──────────────────────────────────────────

DONNÉES STRUCTURÉES (faciles à analyser)
├─ Format : Tableaux avec lignes et colonnes
├─ Exemples : CSV, Excel, Bases de données SQL
└─ Outils : Pandas

DONNÉES NON STRUCTURÉES (plus complexes)
├─ Format : Texte libre, images, audio, vidéo
├─ Exemples : Emails, posts réseaux sociaux, photos
└─ Outils : Techniques de NLP (Natural Language Processing)

-> Dans ce cours, nous nous concentrons sur données STRUCTURÉES


6.2 TYPES DE VARIABLES
──────────────────────

┌─────────────────────────────────────────────────────────────────┐
│ VARIABLES QUANTITATIVES (numériques)                            │
├─────────────────────────────────────────────────────────────────┤
│ -> CONTINUES : Peuvent prendre n'importe quelle valeur           │
│   Exemples : Taille (1.75m), Prix (19.99€), Température (23.5°) │
│                                                                  │
│ -> DISCRÈTES : Valeurs entières seulement                        │
│   Exemples : Nombre d'enfants (2), Âge (25 ans), Quantité (10)  │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ VARIABLES QUALITATIVES (catégorielles)                          │
├─────────────────────────────────────────────────────────────────┤
│ -> NOMINALES : Pas d'ordre logique                               │
│   Exemples : Couleur (rouge, bleu), Pays, Type de produit       │
│                                                                  │
│ -> ORDINALES : Ordre logique                                     │
│   Exemples : Niveau (bas, moyen, haut), Note (1-5 étoiles)      │
└─────────────────────────────────────────────────────────────────┘


═══════════════════════════════════════════════════════════════════════════════
EXERCICE PRATIQUE : PREMIER PROGRAMME D'ANALYSE
═══════════════════════════════════════════════════════════════════════════════

[NOTE] ÉNONCÉ :

Vous êtes Data Analyst dans une école. On vous donne les notes de 10 élèves
en mathématiques. Vous devez :

1. Créer une liste avec les notes suivantes : 15, 18, 12, 20, 9, 17, 13, 16, 14, 11
2. Calculer et afficher :
   - Le nombre total d'élèves
   - La note moyenne
   - La note la plus haute
   - La note la plus basse
   - Le nombre d'élèves ayant au moins 15/20
3. Afficher un message indiquant si la classe a une bonne moyenne (≥ 14)


─────────────────────────────────────────────────────────────────────────────
[IDEE] SOLUTION DÉTAILLÉE :
─────────────────────────────────────────────────────────────────────────────

# 1. Créer la liste des notes
notes = [15, 18, 12, 20, 9, 17, 13, 16, 14, 11]

print("=" * 60)
print("ANALYSE DES NOTES DE MATHÉMATIQUES")
print("=" * 60)

# 2. Analyser les notes

# Nombre total d'élèves
nombre_eleves = len(notes)
print(f"\n[GRAPHIQUE] Nombre d'élèves : {nombre_eleves}")

# Note moyenne
moyenne = sum(notes) / len(notes)
print(f"[HAUSSE] Note moyenne : {moyenne:.2f}/20")
# Explication du :.2f -> affiche 2 décimales

# Note la plus haute
note_max = max(notes)
print(f"[TROPHEE] Meilleure note : {note_max}/20")

# Note la plus basse
note_min = min(notes)
print(f"[ATTENTION]  Note la plus basse : {note_min}/20")

# Nombre d'élèves avec au moins 15/20
eleves_15_et_plus = len([note for note in notes if note >= 15])
# Alternative sans compréhension de liste :
# eleves_15_et_plus = 0
# for note in notes:
#     if note >= 15:
#         eleves_15_et_plus += 1

print(f"[OK] Élèves ayant ≥ 15/20 : {eleves_15_et_plus}")

# 3. Message sur la performance de la classe
print("\n" + "-" * 60)
if moyenne >= 14:
    print("[BRAVO] EXCELLENTE PERFORMANCE ! La classe a une bonne moyenne.")
else:
    print("[DOCS] La classe pourrait améliorer sa moyenne.")
print("-" * 60)


─────────────────────────────────────────────────────────────────────────────
[SORTIE] RÉSULTAT ATTENDU :
─────────────────────────────────────────────────────────────────────────────

============================================================
ANALYSE DES NOTES DE MATHÉMATIQUES
============================================================

[GRAPHIQUE] Nombre d'élèves : 10
[HAUSSE] Note moyenne : 14.50/20
[TROPHEE] Meilleure note : 20/20
[ATTENTION]  Note la plus basse : 9/20
[OK] Élèves ayant ≥ 15/20 : 5

------------------------------------------------------------
[BRAVO] EXCELLENTE PERFORMANCE ! La classe a une bonne moyenne.
------------------------------------------------------------


─────────────────────────────────────────────────────────────────────────────
[RECHERCHE] EXPLICATION LIGNE PAR LIGNE :
─────────────────────────────────────────────────────────────────────────────

notes = [15, 18, 12, 20, 9, 17, 13, 16, 14, 11]
-> Crée une liste contenant les 10 notes

nombre_eleves = len(notes)
-> len() compte le nombre d'éléments dans la liste
-> Résultat : 10

moyenne = sum(notes) / len(notes)
-> sum() additionne tous les éléments : 15+18+12+...+11 = 145
-> Division par le nombre d'élèves : 145 / 10 = 14.5

print(f"[HAUSSE] Note moyenne : {moyenne:.2f}/20")
-> f"..." permet d'insérer des variables dans une chaîne
-> {moyenne:.2f} affiche moyenne avec 2 décimales
-> Résultat : "[HAUSSE] Note moyenne : 14.50/20"

note_max = max(notes)
-> max() trouve la valeur maximale dans la liste
-> Résultat : 20

note_min = min(notes)
-> min() trouve la valeur minimale dans la liste
-> Résultat : 9

eleves_15_et_plus = len([note for note in notes if note >= 15])
-> [note for note in notes if note >= 15] crée une nouvelle liste
   contenant uniquement les notes ≥ 15
-> Résultat : [15, 18, 20, 17, 16] (5 éléments)
-> len() compte ces éléments : 5

if moyenne >= 14:
-> Compare la moyenne à 14
-> Si vrai, exécute le bloc indenté suivant
-> Ici : 14.5 >= 14 est vrai, donc affiche le message positif


═══════════════════════════════════════════════════════════════════════════════
[DOCS] EXERCICE SUPPLÉMENTAIRE (À FAIRE SEUL)
═══════════════════════════════════════════════════════════════════════════════

ÉNONCÉ :

Vous avez les températures quotidiennes (en °C) de la semaine dernière :
[22, 25, 23, 28, 30, 27, 24]

Créez un programme qui :
1. Affiche la température moyenne
2. Identifie le jour le plus chaud (indice dans la liste)
3. Compte combien de jours ont dépassé 25°C
4. Indique si la semaine a été chaude (moyenne > 25°C)

CONSEIL : Utilisez la méthode .index() pour trouver la position
          Exemple : liste.index(max(liste)) -> position du maximum


─────────────────────────────────────────────────────────────────────────────
[IDEE] SOLUTION :
─────────────────────────────────────────────────────────────────────────────

temperatures = [22, 25, 23, 28, 30, 27, 24]
jours = ["Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche"]

print("=" * 60)
print("ANALYSE DES TEMPÉRATURES DE LA SEMAINE")
print("=" * 60)

# 1. Température moyenne
moyenne_temp = sum(temperatures) / len(temperatures)
print(f"\n[THERMOMETER]  Température moyenne : {moyenne_temp:.1f}°C")

# 2. Jour le plus chaud
temp_max = max(temperatures)
indice_max = temperatures.index(temp_max)
jour_max = jours[indice_max]
print(f"[HOT] Jour le plus chaud : {jour_max} avec {temp_max}°C")

# 3. Jours au-dessus de 25°C
jours_chauds = len([t for t in temperatures if t > 25])
print(f"[BLACK_SUN_WITH_RAYS]  Nombre de jours > 25°C : {jours_chauds}")

# 4. Semaine chaude ?
print("\n" + "-" * 60)
if moyenne_temp > 25:
    print("[BEACH_WITH_UMBRELLA]  Semaine très chaude !")
else:
    print("[WHITE_SUN_WITH_SMALL_CLOUD]  Semaine à température modérée")
print("-" * 60)


═══════════════════════════════════════════════════════════════════════════════
[GUIDE] RÉSUMÉ DU CHAPITRE
═══════════════════════════════════════════════════════════════════════════════

[OK] Vous savez maintenant :

1. Ce qu'est l'analyse de données et son importance
2. Le rôle d'un Data Analyst
3. Les bibliothèques Python essentielles (NumPy, Pandas, Matplotlib)
4. Comment installer Python et les bibliothèques
5. Les 6 étapes du processus d'analyse
6. Les types de données et variables
7. Faire une première analyse simple avec Python

[OBJECTIF] PROCHAINES ÉTAPES :

Chapitre 2 : Nous allons apprendre à manipuler des fichiers CSV et travailler
             avec de vraies données (pas juste des listes Python).


═══════════════════════════════════════════════════════════════════════════════
[?] QUESTIONS DE RÉVISION
═══════════════════════════════════════════════════════════════════════════════

1. Quelles sont les 6 étapes du processus d'analyse de données ?
2. Quelle est la différence entre données structurées et non structurées ?
3. Pourquoi utilise-t-on Python plutôt qu'Excel pour l'analyse de données ?
4. Quelle est la différence entre un Data Analyst et un Data Scientist ?
5. Citez 3 bibliothèques Python essentielles pour l'analyse de données

RÉPONSES en fin de document


═══════════════════════════════════════════════════════════════════════════════
FIN DU CHAPITRE 1
═══════════════════════════════════════════════════════════════════════════════

═══════════════════════════════════════════════════════════════════════════════
    CHAPITRE 2 : MANIPULATION DE FICHIERS CSV AVEC PYTHON
═══════════════════════════════════════════════════════════════════════════════

[LIVRE] OBJECTIFS DU CHAPITRE :
   • Comprendre le format CSV
   • Lire et écrire des fichiers CSV
   • Manipuler les données CSV avec le module csv
   • Nettoyer des données simples
   • Préparer la transition vers Pandas

═══════════════════════════════════════════════════════════════════════════════
PARTIE 1 : COMPRENDRE LE FORMAT CSV
═══════════════════════════════════════════════════════════════════════════════

1.1 QU'EST-CE QU'UN FICHIER CSV ?
─────────────────────────────────

CSV = Comma-Separated Values (Valeurs Séparées par des Virgules)

C'est un format de fichier texte où :
• Chaque ligne représente un enregistrement (une observation)
• Les colonnes sont séparées par une virgule (ou point-virgule)
• La première ligne contient souvent les en-têtes (noms des colonnes)

EXEMPLE DE FICHIER CSV (ventes.csv) :
──────────────────────────────────────
produit,quantite,prix
Pomme,50,1.20
Banane,30,0.80
Orange,40,1.50
──────────────────────────────────────

Ce fichier représente un tableau :

┌─────────┬──────────┬───────┐
│ produit │ quantite │ prix  │
├─────────┼──────────┼───────┤
│ Pomme   │    50    │ 1.20  │
│ Banane  │    30    │ 0.80  │
│ Orange  │    40    │ 1.50  │
└─────────┴──────────┴───────┘


1.2 POURQUOI UTILISER CSV ?
───────────────────────────

[OK] Format universel (lisible par Excel, Python, R, etc.)
[OK] Fichier texte simple (léger, pas de compression)
[OK] Facile à créer et éditer
[OK] Compatible avec toutes les plateformes
[OK] Idéal pour partager des données

ALTERNATIVES :
• Excel (.xlsx) : Plus riche (formules, couleurs) mais plus lourd
• JSON : Bon pour données hiérarchiques
• TXT : Moins structuré
• Bases de données SQL : Pour très grands volumes


1.3 VARIANTES DE CSV
────────────────────

Séparateur peut varier selon les régions :

VIRGULE (États-Unis, UK) :
nom,age,ville
Alice,25,Paris

POINT-VIRGULE (France, Europe) :
nom;age;ville
Alice;25;Paris

TABULATION (TSV - Tab-Separated Values) :
nom    age    ville
Alice  25     Paris

-> Python peut gérer toutes ces variantes !


═══════════════════════════════════════════════════════════════════════════════
PARTIE 2 : LIRE UN FICHIER CSV
═══════════════════════════════════════════════════════════════════════════════

2.1 MÉTHODE 1 : LECTURE MANUELLE (sans module csv)
──────────────────────────────────────────────────

# Contenu du fichier ventes.csv :
# produit,quantite,prix
# Pomme,50,1.20
# Banane,30,0.80

# Lecture basique
with open("ventes.csv", "r", encoding="utf-8") as fichier:
    lignes = fichier.readlines()
    
    for ligne in lignes:
        print(ligne.strip())

# Résultat :
# produit,quantite,prix
# Pomme,50,1.20
# Banane,30,0.80

# Séparer les colonnes
with open("ventes.csv", "r", encoding="utf-8") as fichier:
    lignes = fichier.readlines()
    
    for ligne in lignes:
        colonnes = ligne.strip().split(",")
        print(colonnes)

# Résultat :
# ['produit', 'quantite', 'prix']
# ['Pomme', '50', '1.20']
# ['Banane', '30', '0.80']

[ATTENTION] PROBLÈME : Tout est en chaîne de caractères (str) !
              '50' n'est pas le nombre 50


2.2 MÉTHODE 2 : MODULE CSV (Recommandé)
───────────────────────────────────────

Python fournit le module csv qui gère automatiquement les CSV.

# Import du module
import csv

# Lire le fichier
with open("ventes.csv", "r", encoding="utf-8") as fichier:
    lecteur = csv.reader(fichier)
    
    for ligne in lecteur:
        print(ligne)

# Résultat :
# ['produit', 'quantite', 'prix']
# ['Pomme', '50', '1.20']
# ['Banane', '30', '0.80']


2.3 LIRE AVEC EN-TÊTES (DictReader)
───────────────────────────────────

DictReader lit chaque ligne comme un dictionnaire :

import csv

with open("ventes.csv", "r", encoding="utf-8") as fichier:
    lecteur = csv.DictReader(fichier)
    
    for ligne in lecteur:
        print(ligne)

# Résultat :
# {'produit': 'Pomme', 'quantite': '50', 'prix': '1.20'}
# {'produit': 'Banane', 'quantite': '30', 'prix': '0.80'}

# Accès aux colonnes par nom
with open("ventes.csv", "r", encoding="utf-8") as fichier:
    lecteur = csv.DictReader(fichier)
    
    for ligne in lecteur:
        print(f"Produit : {ligne['produit']}, Prix : {ligne['prix']}€")

# Résultat :
# Produit : Pomme, Prix : 1.20€
# Produit : Banane, Prix : 0.80€


2.4 EXEMPLE COMPLET : STOCKER DANS UNE LISTE
────────────────────────────────────────────

import csv

# Lire et stocker toutes les données
donnees = []

with open("ventes.csv", "r", encoding="utf-8") as fichier:
    lecteur = csv.DictReader(fichier)
    
    for ligne in lecteur:
        donnees.append(ligne)

# Afficher toutes les données
for ligne in donnees:
    print(ligne)

# Accéder à une ligne spécifique
print("\nPremière ligne :")
print(donnees[0])

# Accéder à une colonne spécifique
print("\nTous les produits :")
for ligne in donnees:
    print(ligne['produit'])


═══════════════════════════════════════════════════════════════════════════════
PARTIE 3 : ÉCRIRE DANS UN FICHIER CSV
═══════════════════════════════════════════════════════════════════════════════

3.1 CRÉER UN FICHIER CSV SIMPLE
───────────────────────────────

import csv

# Données à écrire
donnees = [
    ['nom', 'age', 'ville'],
    ['Alice', 25, 'Paris'],
    ['Bob', 30, 'Lyon'],
    ['Charlie', 35, 'Marseille']
]

# Écrire dans le fichier
with open("personnes.csv", "w", newline="", encoding="utf-8") as fichier:
    ecrivain = csv.writer(fichier)
    
    for ligne in donnees:
        ecrivain.writerow(ligne)

# Le fichier personnes.csv contient maintenant :
# nom,age,ville
# Alice,25,Paris
# Bob,30,Lyon
# Charlie,35,Marseille

[ATTENTION] IMPORTANT : newline="" empêche les lignes vides supplémentaires


3.2 ÉCRIRE AVEC writerows() (Plus Rapide)
─────────────────────────────────────────

import csv

donnees = [
    ['nom', 'age', 'ville'],
    ['Alice', 25, 'Paris'],
    ['Bob', 30, 'Lyon']
]

with open("personnes.csv", "w", newline="", encoding="utf-8") as fichier:
    ecrivain = csv.writer(fichier)
    ecrivain.writerows(donnees)  # Écrit toutes les lignes d'un coup


3.3 ÉCRIRE AVEC DictWriter (En-têtes automatiques)
──────────────────────────────────────────────────

import csv

# Données sous forme de dictionnaires
donnees = [
    {'nom': 'Alice', 'age': 25, 'ville': 'Paris'},
    {'nom': 'Bob', 'age': 30, 'ville': 'Lyon'},
    {'nom': 'Charlie', 'age': 35, 'ville': 'Marseille'}
]

# En-têtes (colonnes)
en_tetes = ['nom', 'age', 'ville']

with open("personnes.csv", "w", newline="", encoding="utf-8") as fichier:
    ecrivain = csv.DictWriter(fichier, fieldnames=en_tetes)
    
    ecrivain.writeheader()        # Écrire les en-têtes
    ecrivain.writerows(donnees)   # Écrire les données


═══════════════════════════════════════════════════════════════════════════════
PARTIE 4 : MANIPULATION AVANCÉE DE CSV
═══════════════════════════════════════════════════════════════════════════════

4.1 LIRE ET CONVERTIR LES TYPES
───────────────────────────────

[ATTENTION] PROBLÈME : Par défaut, CSV lit tout comme du texte (str)

import csv

with open("ventes.csv", "r", encoding="utf-8") as fichier:
    lecteur = csv.DictReader(fichier)
    
    for ligne in lecteur:
        quantite = ligne['quantite']
        print(type(quantite))  # <class 'str'> [X]
        # On ne peut pas faire de calculs avec '50'

SOLUTION : Convertir manuellement

import csv

with open("ventes.csv", "r", encoding="utf-8") as fichier:
    lecteur = csv.DictReader(fichier)
    
    for ligne in lecteur:
        produit = ligne['produit']           # str
        quantite = int(ligne['quantite'])    # int
        prix = float(ligne['prix'])          # float
        
        total = quantite * prix
        print(f"{produit}: {quantite} × {prix}€ = {total}€")

# Résultat :
# Pomme: 50 × 1.2€ = 60.0€
# Banane: 30 × 0.8€ = 24.0€


4.2 FILTRER LES DONNÉES
──────────────────────

import csv

# Lire uniquement les produits avec quantité > 35
with open("ventes.csv", "r", encoding="utf-8") as fichier:
    lecteur = csv.DictReader(fichier)
    
    print("Produits avec quantité > 35 :")
    for ligne in lecteur:
        if int(ligne['quantite']) > 35:
            print(f"- {ligne['produit']}: {ligne['quantite']}")

# Résultat :
# Produits avec quantité > 35 :
# - Pomme: 50
# - Orange: 40


4.3 CALCULER DES STATISTIQUES
─────────────────────────────

import csv

# Calculer la somme et la moyenne des prix
with open("ventes.csv", "r", encoding="utf-8") as fichier:
    lecteur = csv.DictReader(fichier)
    
    prix_total = 0
    nombre_produits = 0
    
    for ligne in lecteur:
        prix_total += float(ligne['prix'])
        nombre_produits += 1
    
    prix_moyen = prix_total / nombre_produits
    
    print(f"Prix total : {prix_total}€")
    print(f"Prix moyen : {prix_moyen:.2f}€")

# Résultat :
# Prix total : 3.5€
# Prix moyen : 1.17€


4.4 AJOUTER UNE COLONNE CALCULÉE
────────────────────────────────

import csv

# Lire le fichier et ajouter une colonne 'total'
with open("ventes.csv", "r", encoding="utf-8") as fichier_entree:
    lecteur = csv.DictReader(fichier_entree)
    
    # Nouvelle structure avec colonne 'total'
    en_tetes = ['produit', 'quantite', 'prix', 'total']
    
    donnees_modifiees = []
    
    for ligne in lecteur:
        quantite = int(ligne['quantite'])
        prix = float(ligne['prix'])
        total = quantite * prix
        
        donnees_modifiees.append({
            'produit': ligne['produit'],
            'quantite': quantite,
            'prix': prix,
            'total': total
        })

# Écrire dans un nouveau fichier
with open("ventes_avec_total.csv", "w", newline="", encoding="utf-8") as fichier_sortie:
    ecrivain = csv.DictWriter(fichier_sortie, fieldnames=en_tetes)
    ecrivain.writeheader()
    ecrivain.writerows(donnees_modifiees)

# Le fichier ventes_avec_total.csv contient :
# produit,quantite,prix,total
# Pomme,50,1.2,60.0
# Banane,30,0.8,24.0
# Orange,40,1.5,60.0


═══════════════════════════════════════════════════════════════════════════════
PARTIE 5 : NETTOYAGE DE DONNÉES SIMPLES
═══════════════════════════════════════════════════════════════════════════════

5.1 GÉRER LES VALEURS MANQUANTES
────────────────────────────────

Fichier avec données manquantes (ventes_sale.csv) :
produit,quantite,prix
Pomme,50,1.20
Banane,,0.80
Orange,40,

Code pour gérer les valeurs manquantes :

import csv

with open("ventes_sale.csv", "r", encoding="utf-8") as fichier:
    lecteur = csv.DictReader(fichier)
    
    for ligne in lecteur:
        produit = ligne['produit']
        
        # Vérifier si quantite est vide
        if ligne['quantite'] == '':
            quantite = 0  # Valeur par défaut
        else:
            quantite = int(ligne['quantite'])
        
        # Vérifier si prix est vide
        if ligne['prix'] == '':
            prix = 0.0  # Valeur par défaut
        else:
            prix = float(ligne['prix'])
        
        print(f"{produit}: {quantite} × {prix}€")

# Résultat :
# Pomme: 50 × 1.2€
# Banane: 0 × 0.8€
# Orange: 40 × 0.0€


5.2 SUPPRIMER LES LIGNES INVALIDES
──────────────────────────────────

import csv

donnees_propres = []

with open("ventes_sale.csv", "r", encoding="utf-8") as fichier:
    lecteur = csv.DictReader(fichier)
    
    for ligne in lecteur:
        # Ignorer les lignes avec valeurs manquantes
        if ligne['quantite'] != '' and ligne['prix'] != '':
            donnees_propres.append(ligne)

print(f"Données propres : {len(donnees_propres)} lignes")
for ligne in donnees_propres:
    print(ligne)

# Résultat :
# Données propres : 1 lignes
# {'produit': 'Pomme', 'quantite': '50', 'prix': '1.20'}


5.3 CORRIGER LES ERREURS DE SAISIE
──────────────────────────────────

import csv

with open("ventes.csv", "r", encoding="utf-8") as fichier:
    lecteur = csv.DictReader(fichier)
    
    for ligne in lecteur:
        # Corriger les noms de produits
        produit = ligne['produit'].strip()  # Enlever espaces
        produit = produit.capitalize()      # Première lettre majuscule
        
        # Vérifier quantité positive
        quantite = int(ligne['quantite'])
        if quantite < 0:
            quantite = 0
        
        print(f"{produit}: {quantite}")


═══════════════════════════════════════════════════════════════════════════════
EXERCICE PRATIQUE 1 : ANALYSER DES VENTES
═══════════════════════════════════════════════════════════════════════════════

[NOTE] ÉNONCÉ :

Vous travaillez dans une boutique et avez un fichier CSV des ventes du mois.

ÉTAPE 1 : Créez le fichier "ventes_janvier.csv" avec ce contenu :

date,produit,quantite,prix_unitaire
2024-01-05,Laptop,2,899.99
2024-01-05,Souris,5,25.50
2024-01-07,Clavier,3,75.00
2024-01-10,Écran,1,299.99
2024-01-12,Laptop,1,899.99
2024-01-15,Souris,10,25.50

ÉTAPE 2 : Écrivez un programme qui :
1. Lit le fichier
2. Calcule le revenu total (quantite × prix_unitaire) pour chaque vente
3. Affiche le revenu total du mois
4. Trouve le produit qui a généré le plus de revenu
5. Écrit un nouveau fichier "ventes_janvier_analyse.csv" avec une colonne 'revenu'


─────────────────────────────────────────────────────────────────────────────
[IDEE] SOLUTION DÉTAILLÉE :
─────────────────────────────────────────────────────────────────────────────

import csv

# Étape 1 : Créer le fichier ventes_janvier.csv
donnees_ventes = [
    ['date', 'produit', 'quantite', 'prix_unitaire'],
    ['2024-01-05', 'Laptop', '2', '899.99'],
    ['2024-01-05', 'Souris', '5', '25.50'],
    ['2024-01-07', 'Clavier', '3', '75.00'],
    ['2024-01-10', 'Écran', '1', '299.99'],
    ['2024-01-12', 'Laptop', '1', '899.99'],
    ['2024-01-15', 'Souris', '10', '25.50']
]

with open("ventes_janvier.csv", "w", newline="", encoding="utf-8") as fichier:
    ecrivain = csv.writer(fichier)
    ecrivain.writerows(donnees_ventes)

print("[OK] Fichier ventes_janvier.csv créé")
print()

# Étape 2 : Analyser les données
print("=" * 70)
print("ANALYSE DES VENTES - JANVIER 2024")
print("=" * 70)
print()

ventes = []
revenu_total_mois = 0
revenus_par_produit = {}

# Lire le fichier
with open("ventes_janvier.csv", "r", encoding="utf-8") as fichier:
    lecteur = csv.DictReader(fichier)
    
    print("Détail des ventes :")
    print("-" * 70)
    
    for ligne in lecteur:
        date = ligne['date']
        produit = ligne['produit']
        quantite = int(ligne['quantite'])
        prix_unitaire = float(ligne['prix_unitaire'])
        
        # Calculer le revenu
        revenu = quantite * prix_unitaire
        revenu_total_mois += revenu
        
        # Stocker pour analyse
        ventes.append({
            'date': date,
            'produit': produit,
            'quantite': quantite,
            'prix_unitaire': prix_unitaire,
            'revenu': revenu
        })
        
        # Accumuler par produit
        if produit in revenus_par_produit:
            revenus_par_produit[produit] += revenu
        else:
            revenus_par_produit[produit] = revenu
        
        # Afficher
        print(f"{date} | {produit:15} | {quantite:2} × {prix_unitaire:7.2f}€ = {revenu:8.2f}€")

print("-" * 70)
print(f"REVENU TOTAL DU MOIS : {revenu_total_mois:.2f}€")
print()

# Trouver le produit avec le plus grand revenu
produit_max = max(revenus_par_produit, key=revenus_par_produit.get)
revenu_max = revenus_par_produit[produit_max]

print("Revenus par produit :")
for produit, revenu in revenus_par_produit.items():
    print(f"  {produit:15} : {revenu:8.2f}€")

print()
print(f"[TROPHEE] Produit le plus rentable : {produit_max} ({revenu_max:.2f}€)")
print()

# Écrire le fichier avec analyse
en_tetes = ['date', 'produit', 'quantite', 'prix_unitaire', 'revenu']

with open("ventes_janvier_analyse.csv", "w", newline="", encoding="utf-8") as fichier:
    ecrivain = csv.DictWriter(fichier, fieldnames=en_tetes)
    ecrivain.writeheader()
    ecrivain.writerows(ventes)

print("[OK] Fichier ventes_janvier_analyse.csv créé")
print("=" * 70)


─────────────────────────────────────────────────────────────────────────────
[SORTIE] RÉSULTAT ATTENDU :
─────────────────────────────────────────────────────────────────────────────

[OK] Fichier ventes_janvier.csv créé

======================================================================
ANALYSE DES VENTES - JANVIER 2024
======================================================================

Détail des ventes :
----------------------------------------------------------------------
2024-01-05 | Laptop          |  2 ×  899.99€ =  1799.98€
2024-01-05 | Souris          |  5 ×   25.50€ =   127.50€
2024-01-07 | Clavier         |  3 ×   75.00€ =   225.00€
2024-01-10 | Écran           |  1 ×  299.99€ =   299.99€
2024-01-12 | Laptop          |  1 ×  899.99€ =   899.99€
2024-01-15 | Souris          | 10 ×   25.50€ =   255.00€
----------------------------------------------------------------------
REVENU TOTAL DU MOIS : 3607.46€

Revenus par produit :
  Laptop          :  2699.97€
  Souris          :   382.50€
  Clavier         :   225.00€
  Écran           :   299.99€

[TROPHEE] Produit le plus rentable : Laptop (2699.97€)

[OK] Fichier ventes_janvier_analyse.csv créé
======================================================================


═══════════════════════════════════════════════════════════════════════════════
EXERCICE PRATIQUE 2 : NETTOYAGE DE DONNÉES
═══════════════════════════════════════════════════════════════════════════════

[NOTE] ÉNONCÉ :

Vous recevez un fichier "etudiants_sale.csv" avec des données mal saisies :

nom,age,note
Alice,25,18
Bob,,15
Charlie,30,
David,-5,12
Eve,22,25

Créez un programme qui :
1. Lit le fichier
2. Nettoie les données :
   - Si age manquant -> 20 par défaut
   - Si note manquante -> 10 par défaut
   - Si age négatif -> valeur absolue
   - Si note > 20 -> limiter à 20
3. Écrit un nouveau fichier "etudiants_propre.csv"
4. Affiche un rapport du nettoyage


─────────────────────────────────────────────────────────────────────────────
[IDEE] SOLUTION :
─────────────────────────────────────────────────────────────────────────────

import csv

# Créer le fichier sale
donnees_sales = [
    ['nom', 'age', 'note'],
    ['Alice', '25', '18'],
    ['Bob', '', '15'],
    ['Charlie', '30', ''],
    ['David', '-5', '12'],
    ['Eve', '22', '25']
]

with open("etudiants_sale.csv", "w", newline="", encoding="utf-8") as fichier:
    ecrivain = csv.writer(fichier)
    ecrivain.writerows(donnees_sales)

print("[OK] Fichier etudiants_sale.csv créé")
print()

# Nettoyage
print("=" * 70)
print("RAPPORT DE NETTOYAGE DES DONNÉES")
print("=" * 70)
print()

donnees_propres = []
compteur_corrections = {
    'age_manquant': 0,
    'note_manquante': 0,
    'age_negatif': 0,
    'note_trop_haute': 0
}

with open("etudiants_sale.csv", "r", encoding="utf-8") as fichier:
    lecteur = csv.DictReader(fichier)
    
    for ligne in lecteur:
        nom = ligne['nom']
        age = ligne['age']
        note = ligne['note']
        
        # Nettoyage age
        if age == '':
            age = 20
            compteur_corrections['age_manquant'] += 1
            print(f"[ATTENTION]  {nom}: Age manquant -> défini à 20")
        else:
            age = int(age)
            if age < 0:
                age = abs(age)
                compteur_corrections['age_negatif'] += 1
                print(f"[ATTENTION]  {nom}: Age négatif -> converti en {age}")
        
        # Nettoyage note
        if note == '':
            note = 10
            compteur_corrections['note_manquante'] += 1
            print(f"[ATTENTION]  {nom}: Note manquante -> définie à 10")
        else:
            note = float(note)
            if note > 20:
                note = 20
                compteur_corrections['note_trop_haute'] += 1
                print(f"[ATTENTION]  {nom}: Note > 20 -> limitée à 20")
        
        donnees_propres.append({
            'nom': nom,
            'age': age,
            'note': note
        })

# Écrire fichier propre
with open("etudiants_propre.csv", "w", newline="", encoding="utf-8") as fichier:
    ecrivain = csv.DictWriter(fichier, fieldnames=['nom', 'age', 'note'])
    ecrivain.writeheader()
    ecrivain.writerows(donnees_propres)

print()
print("-" * 70)
print("RÉSUMÉ DES CORRECTIONS :")
print(f"  Ages manquants corrigés : {compteur_corrections['age_manquant']}")
print(f"  Notes manquantes corrigées : {compteur_corrections['note_manquante']}")
print(f"  Ages négatifs corrigés : {compteur_corrections['age_negatif']}")
print(f"  Notes trop hautes corrigées : {compteur_corrections['note_trop_haute']}")
print("-" * 70)
print()
print("[OK] Fichier etudiants_propre.csv créé")
print("=" * 70)


═══════════════════════════════════════════════════════════════════════════════
[GUIDE] RÉSUMÉ DU CHAPITRE
═══════════════════════════════════════════════════════════════════════════════

[OK] Vous savez maintenant :

1. Comprendre le format CSV et ses variantes
2. Lire un fichier CSV avec le module csv
3. Utiliser csv.reader() et csv.DictReader()
4. Écrire dans un fichier CSV avec csv.writer() et csv.DictWriter()
5. Convertir les types de données (str -> int, float)
6. Filtrer et calculer des statistiques sur des données CSV
7. Nettoyer des données (gérer valeurs manquantes, corriger erreurs)
8. Créer des colonnes calculées

[OBJECTIF] PROCHAINES ÉTAPES :

Chapitre 3 : Introduction à NumPy pour calculs numériques efficaces


═══════════════════════════════════════════════════════════════════════════════
[EFFORT] EXERCICE SUPPLÉMENTAIRE (À FAIRE SEUL)
═══════════════════════════════════════════════════════════════════════════════

Créez un fichier "temperatures.csv" avec les températures de 7 villes :

ville,temperature
Paris,15.5
Lyon,18.2
Marseille,22.0
Bordeaux,19.8
Lille,12.3
Toulouse,20.5
Nantes,16.7

Écrivez un programme qui :
1. Lit le fichier
2. Calcule la température moyenne
3. Trouve la ville la plus chaude et la plus froide
4. Ajoute une colonne 'categorie' :
   - "Froid" si < 15°C
   - "Modéré" si 15-20°C
   - "Chaud" si > 20°C
5. Écrit le résultat dans "temperatures_analyse.csv"


═══════════════════════════════════════════════════════════════════════════════
FIN DU CHAPITRE 2
═══════════════════════════════════════════════════════════════════════════════

═══════════════════════════════════════════════════════════════════════════════
    CHAPITRE 3 : NUMPY - CALCULS NUMÉRIQUES PUISSANTS
═══════════════════════════════════════════════════════════════════════════════

[LIVRE] OBJECTIFS DU CHAPITRE :
   • Comprendre ce qu'est NumPy et pourquoi l'utiliser
   • Créer et manipuler des tableaux NumPy (arrays)
   • Effectuer des calculs vectorisés ultra-rapides
   • Indexer et découper (slicing) les tableaux
   • Utiliser les fonctions mathématiques et statistiques
   • Préparer la transition vers Pandas

═══════════════════════════════════════════════════════════════════════════════
PARTIE 1 : INTRODUCTION À NUMPY
═══════════════════════════════════════════════════════════════════════════════

1.1 QU'EST-CE QUE NUMPY ?
─────────────────────────

NumPy = Numerical Python

• Bibliothèque fondamentale pour calcul scientifique en Python
• Fournit des tableaux multidimensionnels (arrays) ultra-performants
• Base de tout l'écosystème Data Science (Pandas, Scikit-learn...)
• 50x plus rapide que les listes Python pour calculs numériques

INSTALLATION :
pip install numpy


1.2 POURQUOI NUMPY PLUTÔT QUE LES LISTES PYTHON ?
─────────────────────────────────────────────────

LISTES PYTHON :
[X] Lentes pour calculs mathématiques
[X] Consomment beaucoup de mémoire
[X] Pas optimisées pour opérations vectorisées

NUMPY ARRAYS :
[OK] Ultra-rapides (code en C)
[OK] Économes en mémoire
[OK] Opérations vectorisées (pas de boucles)
[OK] Fonctions mathématiques intégrées

EXEMPLE DE RAPIDITÉ :

# Avec liste Python (LENT)
nombres = [1, 2, 3, 4, 5]
carres = []
for n in nombres:
    carres.append(n ** 2)
# Résultat : [1, 4, 9, 16, 25]

# Avec NumPy (RAPIDE)
import numpy as np
nombres = np.array([1, 2, 3, 4, 5])
carres = nombres ** 2
# Résultat : array([1, 4, 9, 16, 25])

-> NumPy fait l'opération en UNE SEULE LIGNE, 50x plus vite !


═══════════════════════════════════════════════════════════════════════════════
PARTIE 2 : CRÉER DES TABLEAUX NUMPY
═══════════════════════════════════════════════════════════════════════════════

2.1 IMPORT ET CONVENTION
───────────────────────

import numpy as np  # Convention universelle

# Vérifier version
print(np.__version__)


2.2 CRÉER UN ARRAY DEPUIS UNE LISTE
───────────────────────────────────

# Array 1D (une dimension)
arr = np.array([1, 2, 3, 4, 5])
print(arr)
# [1 2 3 4 5]

print(type(arr))
# <class 'numpy.ndarray'>

# Array 2D (deux dimensions = matrice)
matrice = np.array([[1, 2, 3],
                    [4, 5, 6]])
print(matrice)
# [[1 2 3]
#  [4 5 6]]

# Array 3D (trois dimensions)
cube = np.array([[[1, 2], [3, 4]],
                 [[5, 6], [7, 8]]])
print(cube)


2.3 PROPRIÉTÉS D'UN ARRAY
─────────────────────────

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

print(arr.shape)      # (2, 3) -> 2 lignes, 3 colonnes
print(arr.ndim)       # 2 -> nombre de dimensions
print(arr.size)       # 6 -> nombre total d'éléments
print(arr.dtype)      # dtype('int64') -> type des éléments


2.4 CRÉER DES ARRAYS SPÉCIAUX
─────────────────────────────

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

zeros_2d = np.zeros((3, 4))  # 3 lignes, 4 colonnes
print(zeros_2d)
# [[0. 0. 0. 0.]
#  [0. 0. 0. 0.]
#  [0. 0. 0. 0.]]

# Array de uns
ones = np.ones(4)
print(ones)
# [1. 1. 1. 1.]

# Array rempli d'une valeur spécifique
full = np.full(5, 7)
print(full)
# [7 7 7 7 7]

# Séquence de nombres (comme range())
sequence = np.arange(10)
print(sequence)
# [0 1 2 3 4 5 6 7 8 9]

sequence2 = np.arange(5, 15, 2)  # start, stop, step
print(sequence2)
# [ 5  7  9 11 13]

# Valeurs espacées linéairement
linspace = np.linspace(0, 10, 5)  # 5 valeurs entre 0 et 10
print(linspace)
# [ 0.   2.5  5.   7.5 10. ]

# Matrice identité
identite = np.eye(3)
print(identite)
# [[1. 0. 0.]
#  [0. 1. 0.]
#  [0. 0. 1.]]

# Nombres aléatoires
random = np.random.rand(3, 3)  # Valeurs entre 0 et 1
print(random)

random_int = np.random.randint(0, 100, size=10)  # 10 entiers entre 0 et 100
print(random_int)


═══════════════════════════════════════════════════════════════════════════════
PARTIE 3 : INDEXATION ET SLICING
═══════════════════════════════════════════════════════════════════════════════

3.1 INDEXATION 1D (comme les listes)
────────────────────────────────────

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

print(arr[0])      # 10 (premier élément)
print(arr[-1])     # 50 (dernier élément)
print(arr[2])      # 30 (index 2)


3.2 SLICING 1D
─────────────

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

print(arr[1:4])    # [20 30 40] (index 1 à 3)
print(arr[:3])     # [10 20 30] (début à index 2)
print(arr[3:])     # [40 50 60] (index 3 à la fin)
print(arr[::2])    # [10 30 50] (un élément sur 2)
print(arr[::-1])   # [60 50 40 30 20 10] (inverser)


3.3 INDEXATION 2D
────────────────

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

print(matrice[0, 0])     # 10 (ligne 0, colonne 0)
print(matrice[1, 2])     # 60 (ligne 1, colonne 2)
print(matrice[-1, -1])   # 90 (dernière ligne, dernière colonne)


3.4 SLICING 2D
─────────────

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

# Première ligne complète
print(matrice[0, :])
# [10 20 30]

# Première colonne complète
print(matrice[:, 0])
# [10 40 70]

# Sous-matrice
print(matrice[0:2, 1:3])
# [[20 30]
#  [50 60]]


3.5 INDEXATION BOOLÉENNE (Très puissant !)
──────────────────────────────────────────

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

# Créer un masque booléen
masque = arr > 25
print(masque)
# [False False  True  True  True]

# Filtrer avec le masque
resultat = arr[masque]
print(resultat)
# [30 40 50]

# Directement en une ligne
resultat = arr[arr > 25]
print(resultat)
# [30 40 50]

# Conditions multiples
resultat = arr[(arr > 20) & (arr < 45)]
print(resultat)
# [30 40]


═══════════════════════════════════════════════════════════════════════════════
PARTIE 4 : OPÉRATIONS MATHÉMATIQUES VECTORISÉES
═══════════════════════════════════════════════════════════════════════════════

4.1 OPÉRATIONS DE BASE
──────────────────────

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

# Addition
print(arr + 10)
# [11 12 13 14 15]

# Multiplication
print(arr * 2)
# [ 2  4  6  8 10]

# Puissance
print(arr ** 2)
# [ 1  4  9 16 25]

# Division
print(arr / 2)
# [0.5 1.  1.5 2.  2.5]


4.2 OPÉRATIONS ENTRE ARRAYS
───────────────────────────

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

print(a + b)      # [ 5  7  9]
print(a * b)      # [ 4 10 18]
print(a / b)      # [0.25 0.4  0.5 ]


4.3 FONCTIONS MATHÉMATIQUES
───────────────────────────

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

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

# Logarithme
print(np.log(arr))

# Exponentielle
print(np.exp(arr))

# Sinus, Cosinus
angles = np.array([0, np.pi/2, np.pi])
print(np.sin(angles))
# [0.0, 1.0, 0.0]


4.4 FONCTIONS D'AGRÉGATION
──────────────────────────

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

print(np.sum(arr))          # 150 (somme)
print(np.mean(arr))         # 30.0 (moyenne)
print(np.min(arr))          # 10 (minimum)
print(np.max(arr))          # 50 (maximum)
print(np.std(arr))          # 14.14... (écart-type)
print(np.median(arr))       # 30.0 (médiane)

# Avec matrices 2D
matrice = np.array([[1, 2, 3],
                    [4, 5, 6]])

print(np.sum(matrice))          # 21 (somme totale)
print(np.sum(matrice, axis=0))  # [5 7 9] (somme par colonne)
print(np.sum(matrice, axis=1))  # [ 6 15] (somme par ligne)


═══════════════════════════════════════════════════════════════════════════════
PARTIE 5 : MANIPULATION DE FORME (RESHAPING)
═══════════════════════════════════════════════════════════════════════════════

5.1 RESHAPE
──────────

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

# Transformer en matrice 3×4
matrice = arr.reshape(3, 4)
print(matrice)
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]]

# Transformer en matrice 4×3
matrice2 = arr.reshape(4, 3)
print(matrice2)
# [[ 0  1  2]
#  [ 3  4  5]
#  [ 6  7  8]
#  [ 9 10 11]]


5.2 FLATTEN ET RAVEL (Aplatir)
──────────────────────────────

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

# Aplatir en 1D
plat = matrice.flatten()
print(plat)
# [1 2 3 4 5 6]

# Ravel (plus rapide, ne copie pas)
plat2 = matrice.ravel()
print(plat2)
# [1 2 3 4 5 6]


5.3 TRANSPOSER
─────────────

matrice = np.array([[1, 2, 3],
                    [4, 5, 6]])
print(matrice)
# [[1 2 3]
#  [4 5 6]]

transpose = matrice.T
print(transpose)
# [[1 4]
#  [2 5]
#  [3 6]]


═══════════════════════════════════════════════════════════════════════════════
PARTIE 6 : COMBINER DES ARRAYS
═══════════════════════════════════════════════════════════════════════════════

6.1 CONCATENATION
────────────────

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

# Horizontal
concat = np.concatenate([a, b])
print(concat)
# [1 2 3 4 5 6]

# Vertical (2D)
a2d = np.array([[1, 2], [3, 4]])
b2d = np.array([[5, 6], [7, 8]])

concat_vert = np.vstack([a2d, b2d])
print(concat_vert)
# [[1 2]
#  [3 4]
#  [5 6]
#  [7 8]]

concat_horiz = np.hstack([a2d, b2d])
print(concat_horiz)
# [[1 2 5 6]
#  [3 4 7 8]]


═══════════════════════════════════════════════════════════════════════════════
EXERCICE PRATIQUE 1 : ANALYSER DES TEMPÉRATURES
═══════════════════════════════════════════════════════════════════════════════

[NOTE] ÉNONCÉ :

Vous avez les températures (°C) enregistrées sur 7 jours dans 4 villes :

         Paris  Lyon  Marseille  Bordeaux
Lundi     15     18      22         19
Mardi     16     19      23         20
Mercredi  14     17      21         18
Jeudi     15     18      24         21
Vendredi  17     20      25         22
Samedi    18     21      26         23
Dimanche  16     19      23         20

Avec NumPy, calculez :
1. La température moyenne de chaque ville sur la semaine
2. La température moyenne de chaque jour (toutes villes confondues)
3. La température maximale enregistrée
4. Le nombre de jours où Paris a dépassé 16°C
5. Les villes où la température moyenne > 20°C


─────────────────────────────────────────────────────────────────────────────
[IDEE] SOLUTION DÉTAILLÉE :
─────────────────────────────────────────────────────────────────────────────

import numpy as np

# Données : chaque ligne = jour, chaque colonne = ville
temperatures = np.array([
    [15, 18, 22, 19],  # Lundi
    [16, 19, 23, 20],  # Mardi
    [14, 17, 21, 18],  # Mercredi
    [15, 18, 24, 21],  # Jeudi
    [17, 20, 25, 22],  # Vendredi
    [18, 21, 26, 23],  # Samedi
    [16, 19, 23, 20]   # Dimanche
])

villes = ["Paris", "Lyon", "Marseille", "Bordeaux"]
jours = ["Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche"]

print("=" * 70)
print("ANALYSE DES TEMPÉRATURES - SEMAINE")
print("=" * 70)
print()

# Afficher le tableau
print("Tableau des températures :")
print(f"{'Jour':<12} | {'Paris':>6} | {'Lyon':>6} | {'Marseille':>10} | {'Bordeaux':>8}")
print("-" * 70)
for i, jour in enumerate(jours):
    print(f"{jour:<12} | {temperatures[i,0]:>6} | {temperatures[i,1]:>6} | {temperatures[i,2]:>10} | {temperatures[i,3]:>8}")
print()

# 1. Température moyenne par ville (moyenne par colonne)
moyennes_villes = np.mean(temperatures, axis=0)
print("1. Température moyenne par ville :")
for i, ville in enumerate(villes):
    print(f"   {ville:12} : {moyennes_villes[i]:.1f}°C")
print()

# 2. Température moyenne par jour (moyenne par ligne)
moyennes_jours = np.mean(temperatures, axis=1)
print("2. Température moyenne par jour :")
for i, jour in enumerate(jours):
    print(f"   {jour:12} : {moyennes_jours[i]:.1f}°C")
print()

# 3. Température maximale
temp_max = np.max(temperatures)
print(f"3. Température maximale de la semaine : {temp_max}°C")
print()

# 4. Jours où Paris > 16°C
paris_col = temperatures[:, 0]  # Colonne Paris
jours_paris_chaud = np.sum(paris_col > 16)
print(f"4. Nombre de jours où Paris > 16°C : {jours_paris_chaud}")
print()

# 5. Villes avec moyenne > 20°C
villes_chaudes = [villes[i] for i, moy in enumerate(moyennes_villes) if moy > 20]
print("5. Villes avec température moyenne > 20°C :")
for ville in villes_chaudes:
    print(f"   - {ville}")

print()
print("=" * 70)


─────────────────────────────────────────────────────────────────────────────
[SORTIE] RÉSULTAT ATTENDU :
─────────────────────────────────────────────────────────────────────────────

======================================================================
ANALYSE DES TEMPÉRATURES - SEMAINE
======================================================================

Tableau des températures :
Jour         |  Paris |   Lyon | Marseille | Bordeaux
----------------------------------------------------------------------
Lundi        |     15 |     18 |        22 |       19
Mardi        |     16 |     19 |        23 |       20
Mercredi     |     14 |     17 |        21 |       18
Jeudi        |     15 |     18 |        24 |       21
Vendredi     |     17 |     20 |        25 |       22
Samedi       |     18 |     21 |        26 |       23
Dimanche     |     16 |     19 |        23 |       20

1. Température moyenne par ville :
   Paris        : 15.9°C
   Lyon         : 18.9°C
   Marseille    : 23.4°C
   Bordeaux     : 20.4°C

2. Température moyenne par jour :
   Lundi        : 18.5°C
   Mardi        : 19.5°C
   Mercredi     : 17.5°C
   Jeudi        : 19.5°C
   Vendredi     : 21.0°C
   Samedi       : 22.0°C
   Dimanche     : 19.5°C

3. Température maximale de la semaine : 26°C

4. Nombre de jours où Paris > 16°C : 3

5. Villes avec température moyenne > 20°C :
   - Marseille
   - Bordeaux

======================================================================


═══════════════════════════════════════════════════════════════════════════════
EXERCICE PRATIQUE 2 : NOTES D'ÉTUDIANTS
═══════════════════════════════════════════════════════════════════════════════

[NOTE] ÉNONCÉ :

5 étudiants ont passé 3 examens. Voici leurs notes :

           Math  Physique  Informatique
Alice       18      16          19
Bob         12      14          15
Charlie     15      17          16
David       19      18          20
Eve         14      13          12

Calculez avec NumPy :
1. La note moyenne de chaque étudiant
2. La note moyenne de chaque matière
3. L'étudiant avec la meilleure moyenne générale
4. La matière la plus difficile (moyenne la plus basse)
5. Combien d'étudiants ont une moyenne ≥ 15/20


─────────────────────────────────────────────────────────────────────────────
[IDEE] SOLUTION :
─────────────────────────────────────────────────────────────────────────────

import numpy as np

# Données
notes = np.array([
    [18, 16, 19],  # Alice
    [12, 14, 15],  # Bob
    [15, 17, 16],  # Charlie
    [19, 18, 20],  # David
    [14, 13, 12]   # Eve
])

etudiants = ["Alice", "Bob", "Charlie", "David", "Eve"]
matieres = ["Math", "Physique", "Informatique"]

print("=" * 70)
print("ANALYSE DES NOTES - EXAMENS")
print("=" * 70)
print()

# 1. Moyenne par étudiant
moyennes_etudiants = np.mean(notes, axis=1)
print("1. Note moyenne par étudiant :")
for i, etudiant in enumerate(etudiants):
    print(f"   {etudiant:<10} : {moyennes_etudiants[i]:.2f}/20")
print()

# 2. Moyenne par matière
moyennes_matieres = np.mean(notes, axis=0)
print("2. Note moyenne par matière :")
for i, matiere in enumerate(matieres):
    print(f"   {matiere:<15} : {moyennes_matieres[i]:.2f}/20")
print()

# 3. Meilleur étudiant
indice_meilleur = np.argmax(moyennes_etudiants)
meilleur_etudiant = etudiants[indice_meilleur]
meilleure_moyenne = moyennes_etudiants[indice_meilleur]
print(f"3. Meilleur étudiant : {meilleur_etudiant} ({meilleure_moyenne:.2f}/20)")
print()

# 4. Matière la plus difficile
indice_difficile = np.argmin(moyennes_matieres)
matiere_difficile = matieres[indice_difficile]
moyenne_difficile = moyennes_matieres[indice_difficile]
print(f"4. Matière la plus difficile : {matiere_difficile} ({moyenne_difficile:.2f}/20)")
print()

# 5. Étudiants avec moyenne ≥ 15
nb_bon_eleves = np.sum(moyennes_etudiants >= 15)
print(f"5. Nombre d'étudiants avec moyenne ≥ 15/20 : {nb_bon_eleves}")
bon_eleves = [etudiants[i] for i, moy in enumerate(moyennes_etudiants) if moy >= 15]
print(f"   Ce sont : {', '.join(bon_eleves)}")

print()
print("=" * 70)


═══════════════════════════════════════════════════════════════════════════════
[GUIDE] RÉSUMÉ DU CHAPITRE
═══════════════════════════════════════════════════════════════════════════════

[OK] Vous savez maintenant :

1. Créer des arrays NumPy (1D, 2D, 3D)
2. Utiliser les fonctions de création (zeros, ones, arange, linspace)
3. Indexer et découper (slicing) les arrays
4. Filtrer avec l'indexation booléenne
5. Effectuer des opérations vectorisées ultra-rapides
6. Utiliser les fonctions mathématiques (sqrt, log, sin, etc.)
7. Calculer des statistiques (mean, sum, min, max, std)
8. Manipuler la forme (reshape, flatten, transpose)
9. Combiner des arrays (concatenate, vstack, hstack)

[OBJECTIF] PROCHAINES ÉTAPES :

Chapitre 4 : Pandas - La bibliothèque LA PLUS IMPORTANTE pour Data Analysis !


═══════════════════════════════════════════════════════════════════════════════
FIN DU CHAPITRE 3
═══════════════════════════════════════════════════════════════════════════════

═══════════════════════════════════════════════════════════════════════════════
    CHAPITRE 4A : PANDAS - PARTIE 1 : BASES ESSENTIELLES
═══════════════════════════════════════════════════════════════════════════════

[LIVRE] OBJECTIFS DU CHAPITRE :
   • Comprendre Series et DataFrame
   • Créer des DataFrames de différentes manières
   • Importer et exporter des données (CSV, Excel, JSON)
   • Explorer les données (head, tail, info, describe)
   • Sélectionner et filtrer des données
   • Comprendre l'indexation (loc et iloc)

[ATTENTION] PANDAS EST LA BIBLIOTHÈQUE LA PLUS IMPORTANTE POUR L'ANALYSE DE DONNÉES !
   Prenez le temps de bien maîtriser ce chapitre.

═══════════════════════════════════════════════════════════════════════════════
PARTIE 1 : INTRODUCTION À PANDAS
═══════════════════════════════════════════════════════════════════════════════

1.1 QU'EST-CE QUE PANDAS ?
──────────────────────────

Pandas = Panel Data

• LA bibliothèque n°1 pour manipulation et analyse de données en Python
• Permet de travailler avec des données tabulaires (comme Excel)
• Construite sur NumPy (donc très rapide)
• 90% des Data Analysts utilisent Pandas quotidiennement

INSTALLATION :
pip install pandas


1.2 LES DEUX STRUCTURES PRINCIPALES
───────────────────────────────────

┌─────────────────────────────────────────────────────────────────┐
│ SERIES                                                           │
│ ──────                                                           │
│ • Tableau 1D (une seule colonne)                                │
│ • Comme une liste NumPy, mais avec des index nommés             │
│ • Exemple : Colonne "Prix" d'un tableau                         │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ DATAFRAME                                                        │
│ ─────────                                                        │
│ • Tableau 2D (lignes et colonnes)                               │
│ • Comme une feuille Excel ou une table SQL                      │
│ • Collection de Series                                          │
│ • C'EST CE QUE VOUS UTILISEREZ 99% DU TEMPS                     │
└─────────────────────────────────────────────────────────────────┘


1.3 CONVENTION D'IMPORT
──────────────────────

import pandas as pd  # Convention universelle
import numpy as np   # Souvent utilisé avec Pandas

# Vérifier version
print(pd.__version__)


═══════════════════════════════════════════════════════════════════════════════
PARTIE 2 : LES SERIES
═══════════════════════════════════════════════════════════════════════════════

2.1 CRÉER UNE SERIES
───────────────────

import pandas as pd

# Depuis une liste
s = pd.Series([10, 20, 30, 40, 50])
print(s)
# 0    10
# 1    20
# 2    30
# 3    40
# 4    50
# dtype: int64

# Avec des index personnalisés
s = pd.Series([10, 20, 30], index=['a', 'b', 'c'])
print(s)
# a    10
# b    20
# c    30
# dtype: int64


2.2 ACCÉDER AUX ÉLÉMENTS
────────────────────────

s = pd.Series([10, 20, 30, 40], index=['a', 'b', 'c', 'd'])

# Par index
print(s['a'])          # 10
print(s['c'])          # 30

# Par position
print(s[0])            # 10
print(s[2])            # 30

# Slicing
print(s['a':'c'])
# a    10
# b    20
# c    30


═══════════════════════════════════════════════════════════════════════════════
PARTIE 3 : LES DATAFRAMES
═══════════════════════════════════════════════════════════════════════════════

3.1 CRÉER UN DATAFRAME DEPUIS UN DICTIONNAIRE
─────────────────────────────────────────────

import pandas as pd

# Méthode la plus courante
data = {
    'nom': ['Alice', 'Bob', 'Charlie', 'David'],
    'age': [25, 30, 35, 28],
    'ville': ['Paris', 'Lyon', 'Marseille', 'Bordeaux']
}

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

#       nom  age      ville
# 0   Alice   25      Paris
# 1     Bob   30       Lyon
# 2 Charlie   35  Marseille
# 3   David   28   Bordeaux

# Chaque clé du dictionnaire devient une colonne


3.2 CRÉER DEPUIS UNE LISTE DE LISTES
────────────────────────────────────

data = [
    ['Alice', 25, 'Paris'],
    ['Bob', 30, 'Lyon'],
    ['Charlie', 35, 'Marseille']
]

df = pd.DataFrame(data, columns=['nom', 'age', 'ville'])
print(df)


3.3 CRÉER DEPUIS UN ARRAY NUMPY
───────────────────────────────

import numpy as np

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

df = pd.DataFrame(arr, columns=['A', 'B', 'C'])
print(df)
#    A  B  C
# 0  1  2  3
# 1  4  5  6
# 2  7  8  9


═══════════════════════════════════════════════════════════════════════════════
PARTIE 4 : PROPRIÉTÉS DE BASE D'UN DATAFRAME
═══════════════════════════════════════════════════════════════════════════════

data = {
    'nom': ['Alice', 'Bob', 'Charlie'],
    'age': [25, 30, 35],
    'ville': ['Paris', 'Lyon', 'Marseille']
}
df = pd.DataFrame(data)

# Voir les premières lignes
print(df.head())      # Par défaut : 5 premières lignes
print(df.head(2))     # 2 premières lignes

# Voir les dernières lignes
print(df.tail())      # Par défaut : 5 dernières lignes
print(df.tail(2))     # 2 dernières lignes

# Forme (lignes, colonnes)
print(df.shape)       # (3, 3)

# Nombre de lignes et colonnes
nb_lignes, nb_colonnes = df.shape
print(f"Lignes : {nb_lignes}, Colonnes : {nb_colonnes}")

# Noms des colonnes
print(df.columns)     # Index(['nom', 'age', 'ville'], dtype='object')

# Index (numéros/noms des lignes)
print(df.index)       # RangeIndex(start=0, stop=3, step=1)

# Types de données
print(df.dtypes)
# nom      object
# age       int64
# ville    object
# dtype: object

# Informations générales
print(df.info())
# Affiche : nombre de lignes, colonnes, types, mémoire utilisée


═══════════════════════════════════════════════════════════════════════════════
PARTIE 5 : IMPORTER ET EXPORTER DES DONNÉES
═══════════════════════════════════════════════════════════════════════════════

5.1 LIRE UN FICHIER CSV
──────────────────────

# Lire un CSV basique
df = pd.read_csv('ventes.csv')

# Avec séparateur point-virgule (;)
df = pd.read_csv('ventes.csv', sep=';')

# Spécifier l'encodage
df = pd.read_csv('ventes.csv', encoding='utf-8')

# Utiliser une colonne comme index
df = pd.read_csv('ventes.csv', index_col='date')

# Lire seulement certaines colonnes
df = pd.read_csv('ventes.csv', usecols=['produit', 'prix'])

# Ignorer les premières lignes
df = pd.read_csv('ventes.csv', skiprows=2)


5.2 ÉCRIRE DANS UN FICHIER CSV
──────────────────────────────

# Sauvegarder sans index
df.to_csv('sortie.csv', index=False)

# Avec séparateur point-virgule
df.to_csv('sortie.csv', sep=';', index=False)

# Spécifier encodage
df.to_csv('sortie.csv', encoding='utf-8', index=False)


5.3 LIRE UN FICHIER EXCEL
─────────────────────────

# Installer d'abord : pip install openpyxl

# Lire un fichier Excel
df = pd.read_excel('donnees.xlsx')

# Lire une feuille spécifique
df = pd.read_excel('donnees.xlsx', sheet_name='Ventes')

# Lire la 2ème feuille (index 1)
df = pd.read_excel('donnees.xlsx', sheet_name=1)


5.4 ÉCRIRE DANS UN FICHIER EXCEL
────────────────────────────────

# Écrire dans Excel
df.to_excel('sortie.xlsx', index=False)

# Avec un nom de feuille
df.to_excel('sortie.xlsx', sheet_name='Résultats', index=False)


5.5 LIRE UN FICHIER JSON
────────────────────────

df = pd.read_json('donnees.json')

# Écrire en JSON
df.to_json('sortie.json', orient='records', indent=2)


═══════════════════════════════════════════════════════════════════════════════
PARTIE 6 : EXPLORER LES DONNÉES
═══════════════════════════════════════════════════════════════════════════════

6.1 DESCRIBE() - Statistiques Descriptives
──────────────────────────────────────────

data = {
    'nom': ['Alice', 'Bob', 'Charlie', 'David', 'Eve'],
    'age': [25, 30, 35, 28, 32],
    'salaire': [35000, 42000, 55000, 38000, 48000]
}
df = pd.DataFrame(data)

print(df.describe())

#              age       salaire
# count   5.000000      5.000000
# mean   30.000000  43600.000000
# std     3.807887   7665.353193
# min    25.000000  35000.000000
# 25%    28.000000  38000.000000
# 50%    30.000000  42000.000000
# 75%    32.000000  48000.000000
# max    35.000000  55000.000000

EXPLICATION :
• count : nombre de valeurs
• mean : moyenne
• std : écart-type
• min : minimum
• 25%, 50%, 75% : quartiles
• max : maximum


6.2 INFO() - Informations Générales
───────────────────────────────────

print(df.info())

# <class 'pandas.core.frame.DataFrame'>
# RangeIndex: 5 entries, 0 to 4
# Data columns (total 3 columns):
#  #   Column    Non-Null Count  Dtype 
# ---  ------    --------------  ----- 
#  0   nom       5 non-null      object
#  1   age       5 non-null      int64 
#  2   salaire   5 non-null      int64 
# dtypes: int64(2), object(1)
# memory usage: 248.0+ bytes


6.3 VALUE_COUNTS() - Compter les Valeurs Uniques
────────────────────────────────────────────────

data = {
    'ville': ['Paris', 'Lyon', 'Paris', 'Marseille', 'Lyon', 'Paris']
}
df = pd.DataFrame(data)

print(df['ville'].value_counts())

# Paris        3
# Lyon         2
# Marseille    1
# Name: ville, dtype: int64


═══════════════════════════════════════════════════════════════════════════════
PARTIE 7 : SÉLECTIONNER DES COLONNES
═══════════════════════════════════════════════════════════════════════════════

data = {
    'nom': ['Alice', 'Bob', 'Charlie'],
    'age': [25, 30, 35],
    'ville': ['Paris', 'Lyon', 'Marseille'],
    'salaire': [35000, 42000, 55000]
}
df = pd.DataFrame(data)

# Sélectionner UNE SEULE colonne (retourne une Series)
print(df['nom'])
# 0      Alice
# 1        Bob
# 2    Charlie
# Name: nom, dtype: object

# Sélectionner PLUSIEURS colonnes (retourne un DataFrame)
print(df[['nom', 'salaire']])
#       nom  salaire
# 0   Alice    35000
# 1     Bob    42000
# 2 Charlie    55000


═══════════════════════════════════════════════════════════════════════════════
PARTIE 8 : INDEXATION LOC ET ILOC (TRÈS IMPORTANT !)
═══════════════════════════════════════════════════════════════════════════════

8.1 LOC : Indexation par LABEL (nom)
────────────────────────────────────

data = {
    'nom': ['Alice', 'Bob', 'Charlie', 'David'],
    'age': [25, 30, 35, 28],
    'ville': ['Paris', 'Lyon', 'Marseille', 'Bordeaux']
}
df = pd.DataFrame(data, index=['A', 'B', 'C', 'D'])

print(df)
#       nom  age      ville
# A   Alice   25      Paris
# B     Bob   30       Lyon
# C Charlie   35  Marseille
# D   David   28   Bordeaux

# Sélectionner UNE LIGNE par label
print(df.loc['B'])
# nom        Bob
# age         30
# ville     Lyon
# Name: B, dtype: object

# Sélectionner PLUSIEURS LIGNES
print(df.loc[['A', 'C']])
#       nom  age      ville
# A   Alice   25      Paris
# C Charlie   35  Marseille

# Sélectionner une ligne et une colonne
print(df.loc['B', 'nom'])
# Bob

# Sélectionner plusieurs lignes et colonnes
print(df.loc[['A', 'C'], ['nom', 'ville']])
#       nom      ville
# A   Alice      Paris
# C Charlie  Marseille

# Sélectionner toutes les lignes, certaines colonnes
print(df.loc[:, ['nom', 'age']])


8.2 ILOC : Indexation par POSITION (numéro)
───────────────────────────────────────────

# Sélectionner ligne par position
print(df.iloc[1])        # 2ème ligne (index 1)

# Sélectionner plusieurs lignes
print(df.iloc[[0, 2]])   # Lignes 1 et 3

# Sélectionner ligne et colonne par position
print(df.iloc[1, 0])     # Ligne 2, Colonne 1 -> "Bob"

# Slicing
print(df.iloc[0:2])      # 2 premières lignes
print(df.iloc[:, 0:2])   # Toutes lignes, 2 premières colonnes


8.3 DIFFÉRENCE LOC vs ILOC
──────────────────────────

LOC :
[OK] Utilise les LABELS (noms)
[OK] Inclusif : df.loc['A':'C'] inclut 'C'

ILOC :
[OK] Utilise les POSITIONS (numéros)
[OK] Exclusif : df.iloc[0:3] exclut 3 (comme en Python normal)


═══════════════════════════════════════════════════════════════════════════════
PARTIE 9 : FILTRER LES DONNÉES
═══════════════════════════════════════════════════════════════════════════════

9.1 FILTRAGE SIMPLE
──────────────────

data = {
    'nom': ['Alice', 'Bob', 'Charlie', 'David', 'Eve'],
    'age': [25, 30, 35, 28, 32],
    'salaire': [35000, 42000, 55000, 38000, 48000]
}
df = pd.DataFrame(data)

# Personnes de plus de 30 ans
filtre_age = df['age'] > 30
print(df[filtre_age])
#       nom  age  salaire
# 2 Charlie   35    55000
# 4     Eve   32    48000

# Directement en une ligne
print(df[df['age'] > 30])

# Salaire >= 40000
print(df[df['salaire'] >= 40000])


9.2 FILTRES MULTIPLES (AND, OR)
───────────────────────────────

# ET : age > 28 ET salaire > 40000
print(df[(df['age'] > 28) & (df['salaire'] > 40000)])
#       nom  age  salaire
# 1     Bob   30    42000
# 2 Charlie   35    55000
# 4     Eve   32    48000

# OU : age < 28 OU salaire > 50000
print(df[(df['age'] < 28) | (df['salaire'] > 50000)])
#       nom  age  salaire
# 0   Alice   25    35000
# 2 Charlie   35    55000

[ATTENTION] IMPORTANT : Utilisez & pour ET, | pour OU, pas 'and' et 'or'


9.3 FILTRAGE PAR VALEURS TEXTUELLES
───────────────────────────────────

# Personnes à Paris
print(df[df['ville'] == 'Paris'])

# Noms commençant par 'A'
print(df[df['nom'].str.startswith('A')])

# Noms contenant 'ar'
print(df[df['nom'].str.contains('ar')])


═══════════════════════════════════════════════════════════════════════════════
EXERCICE PRATIQUE 1 : ANALYSER DES VENTES
═══════════════════════════════════════════════════════════════════════════════

[NOTE] ÉNONCÉ :

Créez un DataFrame des ventes d'un magasin :

date,produit,quantite,prix_unitaire,categorie
2024-01-05,Laptop,2,899.99,Informatique
2024-01-05,Souris,5,25.50,Informatique
2024-01-07,Bureau,1,350.00,Mobilier
2024-01-10,Chaise,3,120.00,Mobilier
2024-01-12,Écran,2,299.99,Informatique
2024-01-15,Lampe,10,45.00,Mobilier

Avec Pandas :
1. Importez les données
2. Affichez les 3 premières lignes
3. Affichez les informations générales (types, mémoire)
4. Calculez le revenu total (quantite × prix_unitaire) pour chaque ligne
5. Affichez uniquement les ventes d'Informatique
6. Trouvez les ventes avec un revenu > 500€


─────────────────────────────────────────────────────────────────────────────
[IDEE] SOLUTION DÉTAILLÉE :
─────────────────────────────────────────────────────────────────────────────

import pandas as pd

# 1. Créer le fichier CSV
donnees_csv = """date,produit,quantite,prix_unitaire,categorie
2024-01-05,Laptop,2,899.99,Informatique
2024-01-05,Souris,5,25.50,Informatique
2024-01-07,Bureau,1,350.00,Mobilier
2024-01-10,Chaise,3,120.00,Mobilier
2024-01-12,Écran,2,299.99,Informatique
2024-01-15,Lampe,10,45.00,Mobilier"""

with open('ventes_magasin.csv', 'w', encoding='utf-8') as f:
    f.write(donnees_csv)

print("[OK] Fichier ventes_magasin.csv créé\n")

# 1. Importer les données
df = pd.read_csv('ventes_magasin.csv')

print("=" * 80)
print("ANALYSE DES VENTES - MAGASIN")
print("=" * 80)
print()

# 2. Afficher les 3 premières lignes
print("1. Les 3 premières lignes :")
print(df.head(3))
print()

# 3. Informations générales
print("2. Informations générales :")
print(df.info())
print()

# 4. Calculer le revenu
df['revenu'] = df['quantite'] * df['prix_unitaire']

print("3. DataFrame avec colonne 'revenu' :")
print(df)
print()

# 5. Ventes d'Informatique
print("4. Ventes de la catégorie Informatique :")
ventes_info = df[df['categorie'] == 'Informatique']
print(ventes_info)
print()

# 6. Ventes avec revenu > 500€
print("5. Ventes avec revenu > 500€ :")
ventes_importantes = df[df['revenu'] > 500]
print(ventes_importantes)

print()
print("=" * 80)


─────────────────────────────────────────────────────────────────────────────
[SORTIE] RÉSULTAT ATTENDU :
─────────────────────────────────────────────────────────────────────────────

[OK] Fichier ventes_magasin.csv créé

================================================================================
ANALYSE DES VENTES - MAGASIN
================================================================================

1. Les 3 premières lignes :
         date produit  quantite  prix_unitaire    categorie
0  2024-01-05  Laptop         2         899.99  Informatique
1  2024-01-05  Souris         5          25.50  Informatique
2  2024-01-07  Bureau         1         350.00     Mobilier

2. Informations générales :
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 6 entries, 0 to 5
Data columns (total 5 columns):
 #   Column         Non-Null Count  Dtype  
---  ------         --------------  -----  
 0   date           6 non-null      object 
 1   produit        6 non-null      object 
 2   quantite       6 non-null      int64  
 3   prix_unitaire  6 non-null      float64
 4   categorie      6 non-null      object 
dtypes: float64(1), int64(1), object(3)

3. DataFrame avec colonne 'revenu' :
         date produit  quantite  prix_unitaire    categorie   revenu
0  2024-01-05  Laptop         2         899.99  Informatique  1799.98
1  2024-01-05  Souris         5          25.50  Informatique   127.50
2  2024-01-07  Bureau         1         350.00     Mobilier   350.00
3  2024-01-10  Chaise         3         120.00     Mobilier   360.00
4  2024-01-12   Écran         2         299.99  Informatique   599.98
5  2024-01-15   Lampe        10          45.00     Mobilier   450.00

4. Ventes de la catégorie Informatique :
         date produit  quantite  prix_unitaire    categorie   revenu
0  2024-01-05  Laptop         2         899.99  Informatique  1799.98
1  2024-01-05  Souris         5          25.50  Informatique   127.50
4  2024-01-12   Écran         2         299.99  Informatique   599.98

5. Ventes avec revenu > 500€ :
         date produit  quantite  prix_unitaire    categorie   revenu
0  2024-01-05  Laptop         2         899.99  Informatique  1799.98
4  2024-01-12   Écran         2         299.99  Informatique   599.98

================================================================================


═══════════════════════════════════════════════════════════════════════════════
[GUIDE] RÉSUMÉ DE LA PARTIE 1
═══════════════════════════════════════════════════════════════════════════════

[OK] Vous savez maintenant :

1. Créer des DataFrames (dictionnaires, listes, NumPy)
2. Importer/exporter CSV, Excel, JSON
3. Explorer les données (head, tail, info, describe)
4. Sélectionner des colonnes
5. Utiliser loc et iloc pour indexer
6. Filtrer les données (conditions simples et multiples)

[OBJECTIF] PROCHAINE PARTIE :

Chapitre 4B : Pandas Partie 2 - Manipulation avancée, nettoyage, groupby


═══════════════════════════════════════════════════════════════════════════════
FIN DU CHAPITRE 4A
═══════════════════════════════════════════════════════════════════════════════

═══════════════════════════════════════════════════════════════════════════════
    CHAPITRE 4B : PANDAS - PARTIE 2 : MANIPULATION AVANCÉE
═══════════════════════════════════════════════════════════════════════════════

[LIVRE] OBJECTIFS :
   • Ajouter, modifier, supprimer colonnes et lignes
   • Nettoyer les données (valeurs manquantes, doublons)
   • Grouper et agréger (groupby)
   • Fusionner des DataFrames (merge, join, concat)
   • Trier les données
   • Manipuler les dates

═══════════════════════════════════════════════════════════════════════════════
PARTIE 1 : AJOUTER ET MODIFIER DES COLONNES
═══════════════════════════════════════════════════════════════════════════════

import pandas as pd

data = {
    'nom': ['Alice', 'Bob', 'Charlie'],
    'age': [25, 30, 35],
    'salaire': [35000, 42000, 55000]
}
df = pd.DataFrame(data)

# 1.1 AJOUTER UNE NOUVELLE COLONNE (calcul simple)
df['bonus'] = df['salaire'] * 0.10
print(df)
#       nom  age  salaire   bonus
# 0   Alice   25    35000  3500.0
# 1     Bob   30    42000  4200.0
# 2 Charlie   35    55000  5500.0

# 1.2 AJOUTER UNE COLONNE CONSTANTE
df['pays'] = 'France'
print(df)

# 1.3 AJOUTER UNE COLONNE AVEC apply() (fonction personnalisée)
def categorie_age(age):
    if age < 30:
        return 'Jeune'
    elif age < 40:
        return 'Adulte'
    else:
        return 'Senior'

df['categorie'] = df['age'].apply(categorie_age)
print(df)
#       nom  age  salaire   bonus    pays categorie
# 0   Alice   25    35000  3500.0  France     Jeune
# 1     Bob   30    42000  4200.0  France    Adulte
# 2 Charlie   35    55000  5500.0  France    Adulte

# 1.4 MODIFIER UNE COLONNE EXISTANTE
df['salaire'] = df['salaire'] * 1.05  # Augmentation 5%
print(df)

# 1.5 SUPPRIMER UNE COLONNE
df = df.drop('bonus', axis=1)
# OU
df = df.drop(columns=['bonus'])
print(df)


═══════════════════════════════════════════════════════════════════════════════
PARTIE 2 : GÉRER LES VALEURS MANQUANTES
═══════════════════════════════════════════════════════════════════════════════

2.1 DÉTECTER LES VALEURS MANQUANTES

data = {
    'nom': ['Alice', 'Bob', 'Charlie', 'David'],
    'age': [25, None, 35, 28],
    'salaire': [35000, 42000, None, 38000]
}
df = pd.DataFrame(data)

print(df)
#       nom   age   salaire
# 0   Alice  25.0   35000.0
# 1     Bob   NaN   42000.0
# 2 Charlie  35.0       NaN
# 3   David  28.0   38000.0

# Vérifier s'il y a des NaN
print(df.isnull())
#      nom    age  salaire
# 0  False  False    False
# 1  False   True    False
# 2  False  False     True
# 3  False  False    False

# Compter les NaN par colonne
print(df.isnull().sum())
# nom        0
# age        1
# salaire    1
# dtype: int64


2.2 SUPPRIMER LES LIGNES AVEC NaN

# Supprimer toutes les lignes contenant au moins un NaN
df_clean = df.dropna()
print(df_clean)
#      nom   age   salaire
# 0  Alice  25.0   35000.0
# 3  David  28.0   38000.0


2.3 REMPLIR LES VALEURS MANQUANTES

# Remplir avec 0
df_filled = df.fillna(0)

# Remplir avec la moyenne (pour colonnes numériques)
df['age'] = df['age'].fillna(df['age'].mean())

# Remplir avec la valeur précédente (forward fill)
df_filled = df.fillna(method='ffill')

# Remplir avec la valeur suivante (backward fill)
df_filled = df.fillna(method='bfill')

# Remplir différemment par colonne
df['age'] = df['age'].fillna(30)  # Age par défaut
df['salaire'] = df['salaire'].fillna(df['salaire'].median())


═══════════════════════════════════════════════════════════════════════════════
PARTIE 3 : GÉRER LES DOUBLONS
═══════════════════════════════════════════════════════════════════════════════

data = {
    'nom': ['Alice', 'Bob', 'Alice', 'Charlie'],
    'age': [25, 30, 25, 35]
}
df = pd.DataFrame(data)

# Détecter les doublons
print(df.duplicated())
# 0    False
# 1    False
# 2     True  <- Doublon de la ligne 0
# 3    False

# Supprimer les doublons
df_unique = df.drop_duplicates()
print(df_unique)
#       nom  age
# 0   Alice   25
# 1     Bob   30
# 3 Charlie   35

# Garder la dernière occurrence
df_unique = df.drop_duplicates(keep='last')


═══════════════════════════════════════════════════════════════════════════════
PARTIE 4 : TRIER LES DONNÉES
═══════════════════════════════════════════════════════════════════════════════

data = {
    'nom': ['Charlie', 'Alice', 'Bob'],
    'age': [35, 25, 30],
    'salaire': [55000, 35000, 42000]
}
df = pd.DataFrame(data)

# Trier par une colonne (croissant)
df_sorted = df.sort_values('age')
print(df_sorted)
#       nom  age  salaire
# 1   Alice   25    35000
# 2     Bob   30    42000
# 0 Charlie   35    55000

# Trier décroissant
df_sorted = df.sort_values('salaire', ascending=False)

# Trier par plusieurs colonnes
df_sorted = df.sort_values(['age', 'salaire'], ascending=[True, False])


═══════════════════════════════════════════════════════════════════════════════
PARTIE 5 : GROUPBY (TRÈS IMPORTANT !)
═══════════════════════════════════════════════════════════════════════════════

data = {
    'ville': ['Paris', 'Lyon', 'Paris', 'Lyon', 'Marseille', 'Paris'],
    'produit': ['A', 'A', 'B', 'B', 'A', 'A'],
    'ventes': [100, 150, 200, 120, 180, 90]
}
df = pd.DataFrame(data)

print(df)
#        ville produit  ventes
# 0      Paris       A     100
# 1       Lyon       A     150
# 2      Paris       B     200
# 3       Lyon       B     120
# 4  Marseille       A     180
# 5      Paris       A      90

# 5.1 Grouper et calculer la somme
ventes_par_ville = df.groupby('ville')['ventes'].sum()
print(ventes_par_ville)
# ville
# Lyon         270
# Marseille    180
# Paris        390
# Name: ventes, dtype: int64

# 5.2 Grouper et calculer la moyenne
moyenne_par_ville = df.groupby('ville')['ventes'].mean()

# 5.3 Grouper par plusieurs colonnes
ventes_ville_produit = df.groupby(['ville', 'produit'])['ventes'].sum()
print(ventes_ville_produit)

# 5.4 Appliquer plusieurs agrégations
stats = df.groupby('ville')['ventes'].agg(['sum', 'mean', 'count'])
print(stats)
#             sum        mean  count
# ville                             
# Lyon        270  135.000000      2
# Marseille   180  180.000000      1
# Paris       390  130.000000      3


═══════════════════════════════════════════════════════════════════════════════
PARTIE 6 : FUSIONNER DES DATAFRAMES
═══════════════════════════════════════════════════════════════════════════════

6.1 CONCAT (empiler des DataFrames)

df1 = pd.DataFrame({'A': [1, 2], 'B': [3, 4]})
df2 = pd.DataFrame({'A': [5, 6], 'B': [7, 8]})

# Empiler verticalement
result = pd.concat([df1, df2])
print(result)
#    A  B
# 0  1  3
# 1  2  4
# 0  5  7
# 1  6  8

# Réinitialiser l'index
result = pd.concat([df1, df2], ignore_index=True)


6.2 MERGE (comme JOIN en SQL)

clients = pd.DataFrame({
    'client_id': [1, 2, 3],
    'nom': ['Alice', 'Bob', 'Charlie']
})

commandes = pd.DataFrame({
    'commande_id': [101, 102, 103],
    'client_id': [1, 1, 3],
    'montant': [150, 200, 300]
})

# Fusionner sur client_id
merged = pd.merge(clients, commandes, on='client_id')
print(merged)
#    client_id      nom  commande_id  montant
# 0          1    Alice          101      150
# 1          1    Alice          102      200
# 2          3  Charlie          103      300

# Left join (garder tous les clients)
merged = pd.merge(clients, commandes, on='client_id', how='left')


═══════════════════════════════════════════════════════════════════════════════
EXERCICE PRATIQUE : ANALYSE COMPLÈTE
═══════════════════════════════════════════════════════════════════════════════

[NOTE] ÉNONCÉ :

Dataset de ventes d'une chaîne de magasins :

magasin,date,produit,quantite,prix_unitaire,categorie
Paris,2024-01-05,Laptop,2,899.99,Informatique
Lyon,2024-01-05,Souris,5,25.50,Informatique
Paris,2024-01-07,Bureau,1,350.00,Mobilier
Lyon,2024-01-07,Chaise,3,120.00,Mobilier
Paris,2024-01-10,Écran,2,299.99,Informatique
Lyon,2024-01-10,,4,45.00,Mobilier
Paris,2024-01-12,Laptop,1,899.99,Informatique

TÂCHES :
1. Importer et nettoyer les données (gérer valeurs manquantes)
2. Calculer le revenu (quantite × prix)
3. Calculer le revenu total par magasin
4. Trouver le produit le plus vendu
5. Comparer les ventes Informatique vs Mobilier


[IDEE] SOLUTION DANS LE FICHIER SUIVANT...

═══════════════════════════════════════════════════════════════════════════════
FIN DU CHAPITRE 4B
═══════════════════════════════════════════════════════════════════════════════

═══════════════════════════════════════════════════════════════════════════════
    CHAPITRE 5 : VISUALISATION DES DONNÉES (MATPLOTLIB & SEABORN)
═══════════════════════════════════════════════════════════════════════════════

[LIVRE] OBJECTIFS DU CHAPITRE :
   • Maîtriser Matplotlib pour créer des graphiques de base
   • Utiliser Seaborn pour des visualisations statistiques élégantes
   • Personnaliser les graphiques (couleurs, styles, légendes)
   • Choisir le bon type de graphique selon l'objectif
   • Créer des visualisations professionnelles
   • Sauvegarder les graphiques en haute qualité

[ATTENTION] "Un bon graphique vaut mille tableaux de chiffres !" - Proverbe Data Analyst

═══════════════════════════════════════════════════════════════════════════════
PARTIE 1 : INTRODUCTION À LA VISUALISATION
═══════════════════════════════════════════════════════════════════════════════

1.1 POURQUOI VISUALISER LES DONNÉES ?
─────────────────────────────────────

[OK] Identifier rapidement des tendances et patterns
[OK] Communiquer les insights de manière claire et impactante
[OK] Détecter des anomalies et valeurs aberrantes
[OK] Comparer plusieurs variables facilement
[OK] Raconter une histoire avec les données (Data Storytelling)

EXEMPLE CONCRET :
Tableau de 1000 lignes de ventes -> Difficile à comprendre
Graphique d'évolution mensuelle -> Tendance visible en 3 secondes !


1.2 LES DEUX BIBLIOTHÈQUES PRINCIPALES
──────────────────────────────────────

┌─────────────────────────────────────────────────────────────────┐
│ MATPLOTLIB                                                       │
│ ──────────                                                       │
│ • Bibliothèque de base pour visualisation en Python             │
│ • Contrôle total sur chaque élément du graphique                │
│ • Plus verbeux mais très flexible                               │
│ • Utilisé par toutes les autres bibliothèques de viz            │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ SEABORN                                                          │
│ ────────                                                         │
│ • Construit sur Matplotlib                                      │
│ • Graphiques statistiques élégants avec moins de code           │
│ • Styles modernes et couleurs harmonieuses                      │
│ • Parfait pour exploration rapide et présentations              │
└─────────────────────────────────────────────────────────────────┘


1.3 INSTALLATION
───────────────

# Dans le terminal
pip install matplotlib seaborn

# Vérification
import matplotlib
import seaborn as sns
print(matplotlib.__version__)
print(sns.__version__)


═══════════════════════════════════════════════════════════════════════════════
PARTIE 2 : MATPLOTLIB - GRAPHIQUES DE BASE
═══════════════════════════════════════════════════════════════════════════════

2.1 IMPORT ET CONFIGURATION
───────────────────────────

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

# Convention universelle : plt pour matplotlib.pyplot


2.2 GRAPHIQUE EN LIGNE (LINE PLOT)
──────────────────────────────────

# Données : évolution des ventes sur 12 mois
mois = ['Jan', 'Fév', 'Mar', 'Avr', 'Mai', 'Juin', 
        'Juil', 'Août', 'Sep', 'Oct', 'Nov', 'Déc']
ventes = [120, 135, 150, 148, 170, 165, 180, 195, 185, 200, 215, 230]

# Créer le graphique
plt.figure(figsize=(10, 6))  # Taille : 10 pouces × 6 pouces
plt.plot(mois, ventes)

# Personnalisation
plt.title('Évolution des Ventes 2024', fontsize=16, fontweight='bold')
plt.xlabel('Mois', fontsize=12)
plt.ylabel('Ventes (K€)', fontsize=12)
plt.grid(True, alpha=0.3)  # Grille légère

# Afficher
plt.show()


# AVEC PLUSIEURS LIGNES
ventes_2023 = [100, 110, 125, 130, 145, 155, 160, 175, 170, 180, 190, 200]
ventes_2024 = [120, 135, 150, 148, 170, 165, 180, 195, 185, 200, 215, 230]

plt.figure(figsize=(12, 6))
plt.plot(mois, ventes_2023, marker='o', label='2023', linewidth=2, color='blue')
plt.plot(mois, ventes_2024, marker='s', label='2024', linewidth=2, color='green')

plt.title('Comparaison des Ventes 2023 vs 2024', fontsize=16, fontweight='bold')
plt.xlabel('Mois', fontsize=12)
plt.ylabel('Ventes (K€)', fontsize=12)
plt.legend(fontsize=12)
plt.grid(True, alpha=0.3)
plt.tight_layout()  # Ajuste automatiquement les marges
plt.show()


2.3 GRAPHIQUE EN BARRES (BAR PLOT)
──────────────────────────────────

# Données : ventes par catégorie
categories = ['Informatique', 'Mobilier', 'Électronique', 'Livres']
ventes = [45000, 32000, 38000, 25000]

plt.figure(figsize=(10, 6))
plt.bar(categories, ventes, color='skyblue', edgecolor='black')

plt.title('Ventes par Catégorie', fontsize=16, fontweight='bold')
plt.xlabel('Catégorie', fontsize=12)
plt.ylabel('Ventes (€)', fontsize=12)
plt.grid(True, alpha=0.3, axis='y')

# Ajouter les valeurs sur les barres
for i, v in enumerate(ventes):
    plt.text(i, v + 1000, f'{v:,}€', ha='center', fontsize=10)

plt.tight_layout()
plt.show()


# BARRES HORIZONTALES (pour noms longs)
produits = ['Laptop Dell XPS 13', 'iPhone 15 Pro', 'Chaise Bureau Ergonomique', 
            'Écran Samsung 27"', 'Souris Logitech MX']
quantites = [45, 78, 32, 56, 120]

plt.figure(figsize=(10, 6))
plt.barh(produits, quantites, color='coral', edgecolor='black')

plt.title('Top 5 Produits Vendus', fontsize=16, fontweight='bold')
plt.xlabel('Quantité Vendue', fontsize=12)
plt.ylabel('Produit', fontsize=12)
plt.grid(True, alpha=0.3, axis='x')
plt.tight_layout()
plt.show()


# BARRES GROUPÉES (Comparaison)
import numpy as np

categories = ['Q1', 'Q2', 'Q3', 'Q4']
ventes_2023 = [50, 60, 55, 70]
ventes_2024 = [55, 70, 65, 85]

x = np.arange(len(categories))  # Positions des barres
largeur = 0.35  # Largeur des barres

plt.figure(figsize=(10, 6))
plt.bar(x - largeur/2, ventes_2023, largeur, label='2023', color='#3498db')
plt.bar(x + largeur/2, ventes_2024, largeur, label='2024', color='#2ecc71')

plt.title('Ventes Trimestrielles : 2023 vs 2024', fontsize=16, fontweight='bold')
plt.xlabel('Trimestre', fontsize=12)
plt.ylabel('Ventes (K€)', fontsize=12)
plt.xticks(x, categories)
plt.legend()
plt.grid(True, alpha=0.3, axis='y')
plt.tight_layout()
plt.show()


2.4 HISTOGRAMME (Distribution)
──────────────────────────────

# Données : âges de 1000 clients
np.random.seed(42)
ages = np.random.normal(35, 10, 1000)  # Moyenne 35, écart-type 10

plt.figure(figsize=(10, 6))
plt.hist(ages, bins=30, color='steelblue', edgecolor='black', alpha=0.7)

plt.title('Distribution des Âges des Clients', fontsize=16, fontweight='bold')
plt.xlabel('Âge', fontsize=12)
plt.ylabel('Fréquence', fontsize=12)
plt.axvline(ages.mean(), color='red', linestyle='--', linewidth=2, label=f'Moyenne: {ages.mean():.1f}')
plt.legend()
plt.grid(True, alpha=0.3, axis='y')
plt.tight_layout()
plt.show()


2.5 NUAGE DE POINTS (SCATTER PLOT)
──────────────────────────────────

# Données : Budget publicitaire vs Ventes
budget_pub = [10, 15, 20, 25, 30, 35, 40, 45, 50, 55]
ventes = [120, 135, 155, 170, 190, 195, 210, 220, 240, 250]

plt.figure(figsize=(10, 6))
plt.scatter(budget_pub, ventes, s=100, c='purple', alpha=0.6, edgecolors='black')

plt.title('Budget Publicitaire vs Ventes', fontsize=16, fontweight='bold')
plt.xlabel('Budget Publicitaire (K€)', fontsize=12)
plt.ylabel('Ventes (K€)', fontsize=12)
plt.grid(True, alpha=0.3)

# Ajouter une ligne de tendance
z = np.polyfit(budget_pub, ventes, 1)
p = np.poly1d(z)
plt.plot(budget_pub, p(budget_pub), "r--", linewidth=2, label='Tendance')
plt.legend()

plt.tight_layout()
plt.show()


2.6 CAMEMBERT (PIE CHART)
─────────────────────────

# Données : Part de marché
parts = [35, 25, 20, 15, 5]
labels = ['Produit A', 'Produit B', 'Produit C', 'Produit D', 'Autres']
colors = ['#ff9999', '#66b3ff', '#99ff99', '#ffcc99', '#ff99cc']

plt.figure(figsize=(10, 8))
plt.pie(parts, labels=labels, colors=colors, autopct='%1.1f%%', 
        startangle=90, explode=(0.1, 0, 0, 0, 0))  # Explode la première part

plt.title('Part de Marché par Produit', fontsize=16, fontweight='bold')
plt.axis('equal')  # Cercle parfait
plt.tight_layout()
plt.show()


2.7 SOUS-GRAPHIQUES (SUBPLOTS)
──────────────────────────────

# Créer une grille 2×2 de graphiques
fig, axes = plt.subplots(2, 2, figsize=(15, 10))

# Graphique 1 : Ligne
axes[0, 0].plot(mois[:6], ventes[:6], marker='o', color='blue')
axes[0, 0].set_title('Ventes Semestre 1')
axes[0, 0].grid(True, alpha=0.3)

# Graphique 2 : Barres
axes[0, 1].bar(categories, ventes, color='green')
axes[0, 1].set_title('Ventes par Catégorie')
axes[0, 1].grid(True, alpha=0.3, axis='y')

# Graphique 3 : Histogramme
axes[1, 0].hist(ages, bins=20, color='orange', edgecolor='black')
axes[1, 0].set_title('Distribution des Âges')
axes[1, 0].grid(True, alpha=0.3)

# Graphique 4 : Scatter
axes[1, 1].scatter(budget_pub, ventes, c='purple', s=100)
axes[1, 1].set_title('Budget vs Ventes')
axes[1, 1].grid(True, alpha=0.3)

plt.suptitle('DASHBOARD ANALYTIQUE', fontsize=18, fontweight='bold')
plt.tight_layout()
plt.show()


═══════════════════════════════════════════════════════════════════════════════
PARTIE 3 : SEABORN - VISUALISATIONS STATISTIQUES ÉLÉGANTES
═══════════════════════════════════════════════════════════════════════════════

3.1 INTRODUCTION À SEABORN
──────────────────────────

import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd

# Configurer le style Seaborn
sns.set_style("whitegrid")  # Autres: darkgrid, white, dark, ticks
sns.set_palette("husl")      # Palette de couleurs


3.2 BOX PLOT (Boîte à Moustaches)
─────────────────────────────────

# Créer des données
data = {
    'Catégorie': ['A']*30 + ['B']*30 + ['C']*30,
    'Valeur': list(np.random.normal(50, 10, 30)) + 
              list(np.random.normal(60, 15, 30)) + 
              list(np.random.normal(55, 8, 30))
}
df = pd.DataFrame(data)

plt.figure(figsize=(10, 6))
sns.boxplot(x='Catégorie', y='Valeur', data=df, palette='Set2')

plt.title('Distribution des Valeurs par Catégorie', fontsize=16, fontweight='bold')
plt.xlabel('Catégorie', fontsize=12)
plt.ylabel('Valeur', fontsize=12)
plt.tight_layout()
plt.show()

# EXPLICATION DU BOX PLOT :
# • Ligne centrale : médiane
# • Boîte : 50% des données (Q1 à Q3)
# • Moustaches : min et max (hors outliers)
# • Points : valeurs aberrantes (outliers)


3.3 VIOLIN PLOT (Distribution détaillée)
────────────────────────────────────────

plt.figure(figsize=(10, 6))
sns.violinplot(x='Catégorie', y='Valeur', data=df, palette='muted')

plt.title('Distribution Détaillée par Catégorie', fontsize=16, fontweight='bold')
plt.xlabel('Catégorie', fontsize=12)
plt.ylabel('Valeur', fontsize=12)
plt.tight_layout()
plt.show()


3.4 HEATMAP (Carte de Chaleur) - CORRÉLATIONS
─────────────────────────────────────────────

# Créer des données corrélées
data = {
    'Heures_Étude': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
    'Note_Examen': [10, 12, 14, 15, 16, 17, 19, 20, 19, 20],
    'Heures_Sommeil': [5, 6, 7, 7, 8, 8, 8, 9, 9, 9],
    'Stress': [8, 7, 6, 6, 5, 4, 3, 3, 2, 2]
}
df = pd.DataFrame(data)

# Calculer la matrice de corrélation
correlation = df.corr()

plt.figure(figsize=(8, 6))
sns.heatmap(correlation, annot=True, cmap='coolwarm', center=0, 
            square=True, linewidths=1, cbar_kws={"shrink": 0.8})

plt.title('Matrice de Corrélation', fontsize=16, fontweight='bold')
plt.tight_layout()
plt.show()

# INTERPRÉTATION :
# • Rouge foncé : forte corrélation positive (+1)
# • Bleu foncé : forte corrélation négative (-1)
# • Blanc : pas de corrélation (0)


3.5 PAIRPLOT (Matrice de Scatter Plots)
───────────────────────────────────────

# Charger un dataset exemple
tips = sns.load_dataset('tips')

# Créer pairplot
sns.pairplot(tips, hue='sex', palette='husl')
plt.suptitle('Relations entre Variables', y=1.02, fontsize=16, fontweight='bold')
plt.tight_layout()
plt.show()


3.6 COUNT PLOT (Comptage)
─────────────────────────

plt.figure(figsize=(10, 6))
sns.countplot(x='day', data=tips, palette='Set3', order=['Thur', 'Fri', 'Sat', 'Sun'])

plt.title('Nombre de Clients par Jour', fontsize=16, fontweight='bold')
plt.xlabel('Jour', fontsize=12)
plt.ylabel('Nombre de Clients', fontsize=12)
plt.tight_layout()
plt.show()


3.7 LINE PLOT AVEC INTERVALLE DE CONFIANCE
──────────────────────────────────────────

# Données avec plusieurs mesures
data = {
    'Temps': list(range(1, 11)) * 3,
    'Valeur': list(np.random.normal(10, 2, 10)) + 
              list(np.random.normal(15, 2, 10)) + 
              list(np.random.normal(20, 2, 10)),
    'Groupe': ['A']*10 + ['B']*10 + ['C']*10
}
df = pd.DataFrame(data)

plt.figure(figsize=(10, 6))
sns.lineplot(x='Temps', y='Valeur', hue='Groupe', data=df, marker='o')

plt.title('Évolution avec Intervalle de Confiance', fontsize=16, fontweight='bold')
plt.xlabel('Temps', fontsize=12)
plt.ylabel('Valeur', fontsize=12)
plt.legend(title='Groupe')
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()


═══════════════════════════════════════════════════════════════════════════════
PARTIE 4 : PERSONNALISATION AVANCÉE
═══════════════════════════════════════════════════════════════════════════════

4.1 COULEURS PERSONNALISÉES
───────────────────────────

# Couleurs par nom
colors_name = ['red', 'blue', 'green', 'yellow', 'purple']

# Couleurs par code hexadécimal
colors_hex = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#FFA07A', '#98D8C8']

# Couleurs par code RGB
colors_rgb = [(0.8, 0.2, 0.2), (0.2, 0.8, 0.2), (0.2, 0.2, 0.8)]

# Utilisation
plt.bar(range(5), [1, 2, 3, 4, 5], color=colors_hex)


4.2 PALETTES DE COULEURS SEABORN
────────────────────────────────

# Palettes disponibles
palettes = ['deep', 'muted', 'pastel', 'bright', 'dark', 'colorblind']
palettes_divergentes = ['RdBu', 'coolwarm', 'seismic']
palettes_sequentielles = ['Blues', 'Greens', 'Reds', 'viridis', 'plasma']

# Définir palette
sns.set_palette("Set2")


4.3 STYLES DE LIGNES
────────────────────

styles = {
    '-': 'ligne continue',
    '--': 'tirets',
    '-.': 'tiret-point',
    ':': 'pointillés'
}

# Exemple
plt.plot(x, y, linestyle='--', linewidth=2)


4.4 MARQUEURS
────────────

marqueurs = {
    'o': 'cercle',
    's': 'carré',
    '^': 'triangle haut',
    'v': 'triangle bas',
    'D': 'diamant',
    '*': 'étoile',
    '+': 'plus',
    'x': 'croix'
}

# Exemple
plt.plot(x, y, marker='o', markersize=8, markerfacecolor='red', markeredgecolor='black')


4.5 SAUVEGARDER LES GRAPHIQUES
──────────────────────────────

# Sauvegarder en haute qualité
plt.savefig('graphique.png', dpi=300, bbox_inches='tight')

# Différents formats
plt.savefig('graphique.pdf')   # PDF vectoriel
plt.savefig('graphique.svg')   # SVG vectoriel
plt.savefig('graphique.jpg', quality=95)  # JPEG


═══════════════════════════════════════════════════════════════════════════════
PARTIE 5 : CHOISIR LE BON TYPE DE GRAPHIQUE
═══════════════════════════════════════════════════════════════════════════════

┌─────────────────────────────────────────────────────────────────────────┐
│ OBJECTIF                    │ TYPE DE GRAPHIQUE RECOMMANDÉ              │
├─────────────────────────────────────────────────────────────────────────┤
│ Évolution dans le temps     │ Line Plot (courbe)                        │
│ Comparer des catégories     │ Bar Plot (barres)                         │
│ Voir une distribution       │ Histogram, Box Plot, Violin Plot          │
│ Relation entre 2 variables  │ Scatter Plot (nuage de points)            │
│ Proportions / Parts         │ Pie Chart (camembert)                     │
│ Corrélations multiples      │ Heatmap (carte de chaleur)                │
│ Comparer distributions      │ Box Plot, Violin Plot                     │
│ Comptage de catégories      │ Count Plot, Bar Plot                      │
│ Relations multiples         │ Pair Plot                                 │
└─────────────────────────────────────────────────────────────────────────┘


═══════════════════════════════════════════════════════════════════════════════
EXERCICE PRATIQUE : VISUALISER DES DONNÉES DE VENTES
═══════════════════════════════════════════════════════════════════════════════

[NOTE] ÉNONCÉ :

Vous avez des données de ventes mensuelles pour 3 produits :

Mois       | Laptop | Écran | Souris
-----------|--------|-------|-------
Janvier    | 45     | 32    | 120
Février    | 52     | 38    | 135
Mars       | 48     | 35    | 110
Avril      | 60     | 42    | 145
Mai        | 55     | 40    | 130
Juin       | 70     | 48    | 160

Créez :
1. Un graphique en ligne montrant l'évolution des 3 produits
2. Un graphique en barres groupées comparant les ventes de Mars et Juin
3. Un camembert montrant la répartition totale des ventes (tous mois confondus)
4. Un dashboard avec les 3 graphiques


─────────────────────────────────────────────────────────────────────────────
[IDEE] SOLUTION DÉTAILLÉE :
─────────────────────────────────────────────────────────────────────────────

import matplotlib.pyplot as plt
import pandas as pd
import numpy as np

# Données
data = {
    'Mois': ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin'],
    'Laptop': [45, 52, 48, 60, 55, 70],
    'Écran': [32, 38, 35, 42, 40, 48],
    'Souris': [120, 135, 110, 145, 130, 160]
}
df = pd.DataFrame(data)

print("=" * 80)
print("VISUALISATION DES VENTES - EXERCICE PRATIQUE")
print("=" * 80)
print()

# ═══════════════════════════════════════════════════════════════════════════
# GRAPHIQUE 1 : Évolution des 3 produits
# ═══════════════════════════════════════════════════════════════════════════

plt.figure(figsize=(12, 6))
plt.plot(df['Mois'], df['Laptop'], marker='o', linewidth=2, label='Laptop', color='#3498db')
plt.plot(df['Mois'], df['Écran'], marker='s', linewidth=2, label='Écran', color='#2ecc71')
plt.plot(df['Mois'], df['Souris'], marker='^', linewidth=2, label='Souris', color='#e74c3c')

plt.title('Évolution des Ventes par Produit', fontsize=16, fontweight='bold')
plt.xlabel('Mois', fontsize=12)
plt.ylabel('Quantité Vendue', fontsize=12)
plt.legend(fontsize=12)
plt.grid(True, alpha=0.3)
plt.xticks(rotation=45)
plt.tight_layout()
plt.savefig('graphique_1_evolution.png', dpi=300, bbox_inches='tight')
print("[OK] Graphique 1 sauvegardé : graphique_1_evolution.png")
plt.show()


# ═══════════════════════════════════════════════════════════════════════════
# GRAPHIQUE 2 : Comparaison Mars vs Juin (barres groupées)
# ═══════════════════════════════════════════════════════════════════════════

produits = ['Laptop', 'Écran', 'Souris']
ventes_mars = [48, 35, 110]
ventes_juin = [70, 48, 160]

x = np.arange(len(produits))
largeur = 0.35

plt.figure(figsize=(10, 6))
bars1 = plt.bar(x - largeur/2, ventes_mars, largeur, label='Mars', color='#9b59b6')
bars2 = plt.bar(x + largeur/2, ventes_juin, largeur, label='Juin', color='#f39c12')

# Ajouter valeurs sur les barres
for bars in [bars1, bars2]:
    for bar in bars:
        height = bar.get_height()
        plt.text(bar.get_x() + bar.get_width()/2., height,
                f'{int(height)}', ha='center', va='bottom', fontsize=10)

plt.title('Comparaison des Ventes : Mars vs Juin', fontsize=16, fontweight='bold')
plt.xlabel('Produit', fontsize=12)
plt.ylabel('Quantité Vendue', fontsize=12)
plt.xticks(x, produits)
plt.legend()
plt.grid(True, alpha=0.3, axis='y')
plt.tight_layout()
plt.savefig('graphique_2_comparaison.png', dpi=300, bbox_inches='tight')
print("[OK] Graphique 2 sauvegardé : graphique_2_comparaison.png")
plt.show()


# ═══════════════════════════════════════════════════════════════════════════
# GRAPHIQUE 3 : Camembert répartition totale
# ═══════════════════════════════════════════════════════════════════════════

total_laptop = df['Laptop'].sum()
total_ecran = df['Écran'].sum()
total_souris = df['Souris'].sum()

totals = [total_laptop, total_ecran, total_souris]
labels = ['Laptop', 'Écran', 'Souris']
colors = ['#3498db', '#2ecc71', '#e74c3c']

plt.figure(figsize=(10, 8))
plt.pie(totals, labels=labels, colors=colors, autopct='%1.1f%%', 
        startangle=90, explode=(0.05, 0.05, 0.05), shadow=True)

plt.title('Répartition des Ventes Totales (6 mois)', fontsize=16, fontweight='bold')
plt.axis('equal')

# Ajouter légende avec totaux
legend_labels = [f'{label}: {total} unités' for label, total in zip(labels, totals)]
plt.legend(legend_labels, loc='upper left', bbox_to_anchor=(1, 1))

plt.tight_layout()
plt.savefig('graphique_3_repartition.png', dpi=300, bbox_inches='tight')
print("[OK] Graphique 3 sauvegardé : graphique_3_repartition.png")
plt.show()


# ═══════════════════════════════════════════════════════════════════════════
# GRAPHIQUE 4 : Dashboard complet
# ═══════════════════════════════════════════════════════════════════════════

fig, axes = plt.subplots(2, 2, figsize=(16, 12))

# Sous-graphique 1 : Évolution
axes[0, 0].plot(df['Mois'], df['Laptop'], marker='o', label='Laptop', color='#3498db')
axes[0, 0].plot(df['Mois'], df['Écran'], marker='s', label='Écran', color='#2ecc71')
axes[0, 0].plot(df['Mois'], df['Souris'], marker='^', label='Souris', color='#e74c3c')
axes[0, 0].set_title('Évolution Mensuelle', fontsize=14, fontweight='bold')
axes[0, 0].set_xlabel('Mois')
axes[0, 0].set_ylabel('Quantité')
axes[0, 0].legend()
axes[0, 0].grid(True, alpha=0.3)
axes[0, 0].tick_params(axis='x', rotation=45)

# Sous-graphique 2 : Barres groupées
x = np.arange(len(produits))
axes[0, 1].bar(x - largeur/2, ventes_mars, largeur, label='Mars', color='#9b59b6')
axes[0, 1].bar(x + largeur/2, ventes_juin, largeur, label='Juin', color='#f39c12')
axes[0, 1].set_title('Mars vs Juin', fontsize=14, fontweight='bold')
axes[0, 1].set_xlabel('Produit')
axes[0, 1].set_ylabel('Quantité')
axes[0, 1].set_xticks(x)
axes[0, 1].set_xticklabels(produits)
axes[0, 1].legend()
axes[0, 1].grid(True, alpha=0.3, axis='y')

# Sous-graphique 3 : Camembert
axes[1, 0].pie(totals, labels=labels, colors=colors, autopct='%1.1f%%', startangle=90)
axes[1, 0].set_title('Répartition Totale', fontsize=14, fontweight='bold')
axes[1, 0].axis('equal')

# Sous-graphique 4 : Barres empilées par mois
axes[1, 1].bar(df['Mois'], df['Laptop'], label='Laptop', color='#3498db')
axes[1, 1].bar(df['Mois'], df['Écran'], bottom=df['Laptop'], label='Écran', color='#2ecc71')
axes[1, 1].bar(df['Mois'], df['Souris'], 
               bottom=df['Laptop'] + df['Écran'], label='Souris', color='#e74c3c')
axes[1, 1].set_title('Ventes Cumulées par Mois', fontsize=14, fontweight='bold')
axes[1, 1].set_xlabel('Mois')
axes[1, 1].set_ylabel('Quantité Totale')
axes[1, 1].legend()
axes[1, 1].grid(True, alpha=0.3, axis='y')
axes[1, 1].tick_params(axis='x', rotation=45)

plt.suptitle('DASHBOARD ANALYTIQUE - VENTES PRODUITS', fontsize=18, fontweight='bold', y=0.995)
plt.tight_layout()
plt.savefig('graphique_4_dashboard.png', dpi=300, bbox_inches='tight')
print("[OK] Graphique 4 sauvegardé : graphique_4_dashboard.png")
plt.show()

print()
print("=" * 80)
print("[OK] TOUS LES GRAPHIQUES ONT ÉTÉ CRÉÉS ET SAUVEGARDÉS !")
print("=" * 80)


═══════════════════════════════════════════════════════════════════════════════
[GUIDE] RÉSUMÉ DU CHAPITRE
═══════════════════════════════════════════════════════════════════════════════

[OK] Vous savez maintenant :

1. Créer des graphiques de base avec Matplotlib :
   • Line plot (courbes)
   • Bar plot (barres)
   • Histogram (distributions)
   • Scatter plot (nuages de points)
   • Pie chart (camemberts)

2. Créer des visualisations statistiques avec Seaborn :
   • Box plot (boîtes à moustaches)
   • Violin plot
   • Heatmap (corrélations)
   • Pair plot

3. Personnaliser les graphiques :
   • Couleurs, styles, marqueurs
   • Titres, légendes, grilles
   • Sous-graphiques (subplots)

4. Choisir le bon type de graphique selon l'objectif

5. Sauvegarder en haute qualité

[OBJECTIF] PROCHAINES ÉTAPES :

Chapitre 6 : Statistiques pour Data Analysis


═══════════════════════════════════════════════════════════════════════════════
[IDEE] CONSEILS PROFESSIONNELS
═══════════════════════════════════════════════════════════════════════════════

[OK] Toujours ajouter des titres et légendes clairs
[OK] Utiliser des couleurs harmonieuses et accessibles
[OK] Éviter les graphiques 3D (illisibles)
[OK] Adapter le type de graphique à l'audience
[OK] Garder les graphiques simples et épurés
[OK] Sauvegarder en haute résolution (300 DPI minimum)


═══════════════════════════════════════════════════════════════════════════════
FIN DU CHAPITRE 5
═══════════════════════════════════════════════════════════════════════════════

═══════════════════════════════════════════════════════════════════════════════
    CHAPITRE 6 : STATISTIQUES POUR DATA ANALYSIS
═══════════════════════════════════════════════════════════════════════════════

[LIVRE] OBJECTIFS DU CHAPITRE :
   • Comprendre les mesures de tendance centrale (moyenne, médiane, mode)
   • Maîtriser les mesures de dispersion (variance, écart-type)
   • Calculer et interpréter les corrélations
   • Détecter les valeurs aberrantes (outliers)
   • Comprendre les percentiles et quartiles
   • Appliquer ces concepts à l'analyse de données réelles

[ATTENTION] "Sans statistiques, l'analyse de données n'est que de la divination !"

═══════════════════════════════════════════════════════════════════════════════
PARTIE 1 : MESURES DE TENDANCE CENTRALE
═══════════════════════════════════════════════════════════════════════════════

Les mesures de tendance centrale nous disent "où se trouve le centre" des données.

1.1 MOYENNE (MEAN)
─────────────────

La somme de toutes les valeurs divisée par le nombre de valeurs.

import numpy as np
import pandas as pd

# Exemple : Notes d'étudiants
notes = [12, 15, 18, 14, 16, 13, 17, 15, 14, 16]

# Calcul de la moyenne
moyenne = np.mean(notes)
print(f"Moyenne : {moyenne}")  # 15.0

# Avec Pandas
df = pd.DataFrame({'notes': notes})
moyenne_pd = df['notes'].mean()
print(f"Moyenne (Pandas) : {moyenne_pd}")  # 15.0

# FORMULE :
# Moyenne = (x₁ + x₂ + ... + xₙ) / n

# QUAND L'UTILISER :
# [OK] Données sans valeurs aberrantes
# [OK] Distribution symétrique
# [X] Données avec outliers (utiliser médiane)


1.2 MÉDIANE (MEDIAN)
────────────────────

La valeur du milieu quand les données sont triées.

notes = [12, 15, 18, 14, 16, 13, 17, 15, 14, 16]

mediane = np.median(notes)
print(f"Médiane : {mediane}")  # 15.0

# Avec valeur aberrante
salaires = [30000, 32000, 35000, 38000, 42000, 45000, 48000, 150000]
print(f"Moyenne : {np.mean(salaires):.0f}€")   # 52500€ (biaisée par 150000)
print(f"Médiane : {np.median(salaires):.0f}€") # 40000€ (plus représentative)

# POURQUOI C'EST IMPORTANT :
# La médiane n'est PAS affectée par les valeurs extrêmes !

# QUAND L'UTILISER :
# [OK] Données avec outliers
# [OK] Distributions asymétriques
# [OK] Salaires, prix immobiliers


1.3 MODE
────────

La valeur la plus fréquente.

from scipy import stats

notes = [12, 15, 18, 15, 16, 13, 15, 15, 14, 16]

mode = stats.mode(notes, keepdims=True)
print(f"Mode : {mode.mode[0]}")  # 15 (apparaît 4 fois)

# Avec Pandas
df = pd.DataFrame({'notes': notes})
mode_pd = df['notes'].mode()[0]
print(f"Mode (Pandas) : {mode_pd}")  # 15

# QUAND L'UTILISER :
# [OK] Données catégorielles (couleur préférée, ville)
# [OK] Identifier la valeur la plus commune


1.4 COMPARAISON MOYENNE vs MÉDIANE
──────────────────────────────────

# Distribution symétrique
donnees_symetriques = [10, 12, 14, 15, 16, 18, 20]
print(f"Moyenne : {np.mean(donnees_symetriques):.1f}")  # 15.0
print(f"Médiane : {np.median(donnees_symetriques):.1f}") # 15.0
# -> Moyenne ≈ Médiane

# Distribution asymétrique (skewed)
donnees_asymetriques = [10, 12, 14, 15, 16, 18, 100]
print(f"Moyenne : {np.mean(donnees_asymetriques):.1f}")  # 26.4 (biaisée)
print(f"Médiane : {np.median(donnees_asymetriques):.1f}") # 15.0 (robuste)
# -> Moyenne >> Médiane -> Présence d'outliers !


═══════════════════════════════════════════════════════════════════════════════
PARTIE 2 : MESURES DE DISPERSION
═══════════════════════════════════════════════════════════════════════════════

Les mesures de dispersion nous disent "à quel point les données sont éparpillées".

2.1 ÉTENDUE (RANGE)
──────────────────

Différence entre le maximum et le minimum.

notes = [12, 15, 18, 14, 16, 13, 17, 15, 14, 16]

etendue = max(notes) - min(notes)
print(f"Étendue : {etendue}")  # 6

# Avec NumPy
etendue_np = np.ptp(notes)  # ptp = peak to peak
print(f"Étendue (NumPy) : {etendue_np}")  # 6

# PROBLÈME : Très sensible aux outliers !


2.2 VARIANCE
───────────

Moyenne des carrés des écarts à la moyenne.

notes = [12, 15, 18, 14, 16, 13, 17, 15, 14, 16]

# Variance
variance = np.var(notes, ddof=1)  # ddof=1 pour échantillon (n-1)
print(f"Variance : {variance:.2f}")  # 3.38

# FORMULE :
# Variance = Σ(xᵢ - moyenne)² / (n-1)

# Exemple de calcul manuel :
moyenne = np.mean(notes)
ecarts_carres = [(x - moyenne)**2 for x in notes]
variance_manuelle = sum(ecarts_carres) / (len(notes) - 1)
print(f"Variance manuelle : {variance_manuelle:.2f}")  # 3.38

# INTERPRÉTATION :
# Plus la variance est grande, plus les données sont dispersées


2.3 ÉCART-TYPE (STANDARD DEVIATION)
───────────────────────────────────

Racine carrée de la variance. Plus facile à interpréter car dans la même unité que les données.

notes = [12, 15, 18, 14, 16, 13, 17, 15, 14, 16]

ecart_type = np.std(notes, ddof=1)
print(f"Écart-type : {ecart_type:.2f}")  # 1.84

# FORMULE :
# Écart-type = √Variance

# Avec Pandas
df = pd.DataFrame({'notes': notes})
ecart_type_pd = df['notes'].std()
print(f"Écart-type (Pandas) : {ecart_type_pd:.2f}")  # 1.84

# INTERPRÉTATION :
# • Écart-type faible -> Données concentrées autour de la moyenne
# • Écart-type élevé -> Données très dispersées

# EXEMPLE CONCRET :
# Classe A : notes = [14, 15, 15, 16] -> écart-type = 0.82 (homogène)
# Classe B : notes = [5, 10, 18, 20]  -> écart-type = 6.45 (hétérogène)


2.4 COEFFICIENT DE VARIATION (CV)
─────────────────────────────────

Écart-type relatif à la moyenne (en %).

# Permet de comparer la dispersion de variables d'échelles différentes

# Exemple 1 : Salaires (€)
salaires = [30000, 35000, 40000, 45000]
cv_salaires = (np.std(salaires) / np.mean(salaires)) * 100
print(f"CV Salaires : {cv_salaires:.1f}%")  # 15.8%

# Exemple 2 : Âges (années)
ages = [25, 28, 30, 35]
cv_ages = (np.std(ages) / np.mean(ages)) * 100
print(f"CV Âges : {cv_ages:.1f}%")  # 11.7%

# INTERPRÉTATION :
# • CV < 15% : Faible dispersion (homogène)
# • CV 15-30% : Dispersion modérée
# • CV > 30% : Forte dispersion (hétérogène)


═══════════════════════════════════════════════════════════════════════════════
PARTIE 3 : PERCENTILES ET QUARTILES
═══════════════════════════════════════════════════════════════════════════════

3.1 PERCENTILES
──────────────

Le Xème percentile est la valeur en-dessous de laquelle se trouvent X% des données.

notes = [12, 15, 18, 14, 16, 13, 17, 15, 14, 16]

# Percentiles courants
p25 = np.percentile(notes, 25)  # 25ème percentile (Q1)
p50 = np.percentile(notes, 50)  # 50ème percentile (médiane)
p75 = np.percentile(notes, 75)  # 75ème percentile (Q3)
p90 = np.percentile(notes, 90)  # 90ème percentile

print(f"25ème percentile (Q1) : {p25}")  # 14.0
print(f"50ème percentile (Médiane) : {p50}")  # 15.0
print(f"75ème percentile (Q3) : {p75}")  # 16.25
print(f"90ème percentile : {p90}")  # 17.1

# INTERPRÉTATION :
# p25 = 14 -> 25% des notes sont ≤ 14


3.2 QUARTILES
────────────

Les quartiles divisent les données en 4 parties égales.

# Q1 (1er quartile) : 25% des données
# Q2 (2ème quartile) : Médiane (50%)
# Q3 (3ème quartile) : 75% des données

Q1 = np.percentile(notes, 25)
Q2 = np.percentile(notes, 50)
Q3 = np.percentile(notes, 75)

print(f"Q1 : {Q1}")  # 14.0
print(f"Q2 : {Q2}")  # 15.0 (médiane)
print(f"Q3 : {Q3}")  # 16.25


3.3 INTERQUARTILE RANGE (IQR)
─────────────────────────────

Différence entre Q3 et Q1. Mesure robuste de dispersion.

IQR = Q3 - Q1
print(f"IQR : {IQR}")  # 2.25

# UTILITÉ : Détecter les outliers
# Outlier si : valeur < Q1 - 1.5×IQR  OU  valeur > Q3 + 1.5×IQR

limite_basse = Q1 - 1.5 * IQR
limite_haute = Q3 + 1.5 * IQR

print(f"Limite basse : {limite_basse}")  # 10.625
print(f"Limite haute : {limite_haute}")  # 19.625

# Identifier les outliers
outliers = [x for x in notes if x < limite_basse or x > limite_haute]
print(f"Outliers : {outliers}")  # []


═══════════════════════════════════════════════════════════════════════════════
PARTIE 4 : CORRÉLATION
═══════════════════════════════════════════════════════════════════════════════

La corrélation mesure la relation linéaire entre deux variables.

4.1 CORRÉLATION DE PEARSON
──────────────────────────

Mesure la force et la direction de la relation linéaire.

# Exemple : Heures d'étude vs Note obtenue
heures_etude = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
notes_examen = [10, 12, 14, 15, 16, 17, 19, 20, 19, 20]

# Calcul de la corrélation
correlation = np.corrcoef(heures_etude, notes_examen)[0, 1]
print(f"Corrélation : {correlation:.3f}")  # 0.986

# Avec Pandas
df = pd.DataFrame({
    'heures_etude': heures_etude,
    'notes_examen': notes_examen
})
correlation_pd = df['heures_etude'].corr(df['notes_examen'])
print(f"Corrélation (Pandas) : {correlation_pd:.3f}")  # 0.986


4.2 INTERPRÉTATION DE LA CORRÉLATION
────────────────────────────────────

┌─────────────────────────────────────────────────────────────────┐
│ VALEUR           │ INTERPRÉTATION                               │
├─────────────────────────────────────────────────────────────────┤
│ r = +1           │ Corrélation positive parfaite                │
│ +0.7 à +1        │ Forte corrélation positive                   │
│ +0.3 à +0.7      │ Corrélation positive modérée                 │
│ -0.3 à +0.3      │ Faible ou pas de corrélation                 │
│ -0.7 à -0.3      │ Corrélation négative modérée                 │
│ -1 à -0.7        │ Forte corrélation négative                   │
│ r = -1           │ Corrélation négative parfaite                │
└─────────────────────────────────────────────────────────────────┘

# EXEMPLES :

# Corrélation positive forte
heures_travail = [10, 20, 30, 40, 50]
salaire = [1500, 3000, 4500, 6000, 7500]
r = np.corrcoef(heures_travail, salaire)[0, 1]
print(f"Heures vs Salaire : r = {r:.3f}")  # r ≈ 1.0 (parfait)

# Corrélation négative
temperature = [30, 25, 20, 15, 10]
ventes_chocolat_chaud = [50, 100, 150, 200, 250]
r = np.corrcoef(temperature, ventes_chocolat_chaud)[0, 1]
print(f"Température vs Chocolat chaud : r = {r:.3f}")  # r ≈ -1.0

# Pas de corrélation
pointure = [38, 40, 42, 44, 46]
QI = [105, 110, 95, 120, 100]
r = np.corrcoef(pointure, QI)[0, 1]
print(f"Pointure vs QI : r = {r:.3f}")  # r ≈ 0


4.3 MATRICE DE CORRÉLATION
──────────────────────────

# Avec plusieurs variables
data = {
    'Heures_Étude': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
    'Note_Examen': [10, 12, 14, 15, 16, 17, 19, 20, 19, 20],
    'Heures_Sommeil': [5, 6, 7, 7, 8, 8, 8, 9, 9, 9],
    'Stress': [8, 7, 6, 6, 5, 4, 3, 3, 2, 2]
}
df = pd.DataFrame(data)

# Matrice de corrélation
matrice_corr = df.corr()
print("\nMatrice de Corrélation :")
print(matrice_corr)

# Résultat :
#                Heures_Étude  Note_Examen  Heures_Sommeil  Stress
# Heures_Étude          1.000        0.986           0.912  -0.983
# Note_Examen           0.986        1.000           0.878  -0.963
# Heures_Sommeil        0.912        0.878           1.000  -0.958
# Stress               -0.983       -0.963          -0.958   1.000

# ANALYSE :
# • Heures_Étude <-> Note_Examen : r = 0.986 (très forte corrélation +)
# • Heures_Étude <-> Stress : r = -0.983 (très forte corrélation -)
# -> Plus on étudie, moins on est stressé !


4.4 [ATTENTION] CORRÉLATION ≠ CAUSALITÉ
──────────────────────────────

# IMPORTANT : Corrélation ne signifie PAS causalité !

# Exemple célèbre :
# Ventes de glaces et noyades sont corrélées (+0.9)
# -> La glace ne CAUSE PAS les noyades !
# -> Variable cachée : Température (été)

# TOUJOURS se demander :
# 1. Y a-t-il une relation de cause à effet ?
# 2. Y a-t-il une 3ème variable cachée ?
# 3. Est-ce une simple coïncidence ?


═══════════════════════════════════════════════════════════════════════════════
PARTIE 5 : DÉTECTER LES VALEURS ABERRANTES (OUTLIERS)
═══════════════════════════════════════════════════════════════════════════════

5.1 MÉTHODE IQR (Interquartile Range)
─────────────────────────────────────

# Données : Salaires avec un outlier
salaires = [30000, 32000, 35000, 38000, 40000, 42000, 45000, 150000]

Q1 = np.percentile(salaires, 25)
Q3 = np.percentile(salaires, 75)
IQR = Q3 - Q1

limite_basse = Q1 - 1.5 * IQR
limite_haute = Q3 + 1.5 * IQR

print(f"Q1 : {Q1}")
print(f"Q3 : {Q3}")
print(f"IQR : {IQR}")
print(f"Limite basse : {limite_basse}")
print(f"Limite haute : {limite_haute}")

# Identifier outliers
outliers = [x for x in salaires if x < limite_basse or x > limite_haute]
print(f"\nOutliers détectés : {outliers}")  # [150000]


5.2 MÉTHODE Z-SCORE
──────────────────

# Un z-score mesure combien d'écarts-types une valeur est de la moyenne

from scipy import stats

salaires = [30000, 32000, 35000, 38000, 40000, 42000, 45000, 150000]

# Calculer z-scores
z_scores = np.abs(stats.zscore(salaires))

# Outliers : |z-score| > 3
seuil = 3
outliers_indices = np.where(z_scores > seuil)[0]
outliers = [salaires[i] for i in outliers_indices]

print(f"Z-scores : {z_scores}")
print(f"Outliers (z-score > 3) : {outliers}")  # [150000]


5.3 QUE FAIRE AVEC LES OUTLIERS ?
─────────────────────────────────

OPTIONS :

1. LES GARDER
   -> Si c'est une vraie valeur (PDG avec salaire élevé)

2. LES SUPPRIMER
   -> Si c'est une erreur de saisie

3. LES REMPLACER
   -> Par la médiane ou moyenne (imputation)

4. LES ANALYSER SÉPARÉMENT
   -> Créer deux groupes : "Normaux" et "Exceptionnels"

# Exemple : Supprimer les outliers
salaires_propres = [x for x in salaires if x < limite_haute]
print(f"Salaires sans outliers : {salaires_propres}")

print(f"\nAvec outlier :")
print(f"  Moyenne : {np.mean(salaires):.0f}€")      # 51500€
print(f"  Médiane : {np.median(salaires):.0f}€")    # 39000€

print(f"\nSans outlier :")
print(f"  Moyenne : {np.mean(salaires_propres):.0f}€")  # 37429€
print(f"  Médiane : {np.median(salaires_propres):.0f}€") # 38000€


═══════════════════════════════════════════════════════════════════════════════
EXERCICE PRATIQUE COMPLET : ANALYSER DES SALAIRES
═══════════════════════════════════════════════════════════════════════════════

[NOTE] ÉNONCÉ :

Vous êtes Data Analyst RH. Voici les salaires annuels (en €) de 20 employés :

35000, 38000, 42000, 40000, 45000, 48000, 52000, 50000, 55000, 58000,
60000, 62000, 65000, 70000, 68000, 72000, 150000, 75000, 80000, 85000

Analysez ces salaires :
1. Calculez moyenne, médiane, mode
2. Calculez variance et écart-type
3. Trouvez les quartiles (Q1, Q2, Q3) et l'IQR
4. Détectez les outliers (méthode IQR)
5. Recalculez les statistiques sans outliers
6. Concluez sur la distribution


─────────────────────────────────────────────────────────────────────────────
[IDEE] SOLUTION COMPLÈTE :
─────────────────────────────────────────────────────────────────────────────

import numpy as np
import pandas as pd
from scipy import stats

print("=" * 80)
print("ANALYSE STATISTIQUE DES SALAIRES - EXERCICE PRATIQUE")
print("=" * 80)
print()

# Données
salaires = [35000, 38000, 42000, 40000, 45000, 48000, 52000, 50000, 55000, 58000,
            60000, 62000, 65000, 70000, 68000, 72000, 150000, 75000, 80000, 85000]

print(f"Nombre d'employés : {len(salaires)}")
print(f"Salaires : {sorted(salaires)}")
print()

# ═══════════════════════════════════════════════════════════════════════════
# 1. TENDANCE CENTRALE
# ═══════════════════════════════════════════════════════════════════════════

print("1⃣  MESURES DE TENDANCE CENTRALE")
print("─" * 80)

moyenne = np.mean(salaires)
mediane = np.median(salaires)
mode_result = stats.mode(salaires, keepdims=True)

print(f"Moyenne    : {moyenne:>10,.2f}€")
print(f"Médiane    : {mediane:>10,.2f}€")

if mode_result.count[0] > 1:
    print(f"Mode       : {mode_result.mode[0]:>10,.2f}€ (apparaît {mode_result.count[0]} fois)")
else:
    print(f"Mode       : Aucun (toutes les valeurs sont uniques)")

print()
print("[IDEE] Observation : Moyenne (63750€) >> Médiane (59000€)")
print("   -> Cela suggère la présence d'outliers !")
print()


# ═══════════════════════════════════════════════════════════════════════════
# 2. DISPERSION
# ═══════════════════════════════════════════════════════════════════════════

print("2⃣  MESURES DE DISPERSION")
print("─" * 80)

etendue = max(salaires) - min(salaires)
variance = np.var(salaires, ddof=1)
ecart_type = np.std(salaires, ddof=1)
cv = (ecart_type / moyenne) * 100

print(f"Étendue    : {etendue:>10,.2f}€")
print(f"Variance   : {variance:>10,.2f}")
print(f"Écart-type : {ecart_type:>10,.2f}€")
print(f"CV         : {cv:>10,.2f}%")
print()
print("[IDEE] Observation : CV = 34.8% > 30%")
print("   -> Forte dispersion ! Salaires très hétérogènes.")
print()


# ═══════════════════════════════════════════════════════════════════════════
# 3. QUARTILES
# ═══════════════════════════════════════════════════════════════════════════

print("3⃣  QUARTILES ET PERCENTILES")
print("─" * 80)

Q1 = np.percentile(salaires, 25)
Q2 = np.percentile(salaires, 50)  # Médiane
Q3 = np.percentile(salaires, 75)
IQR = Q3 - Q1

print(f"Q1 (25%)   : {Q1:>10,.2f}€")
print(f"Q2 (50%)   : {Q2:>10,.2f}€ (Médiane)")
print(f"Q3 (75%)   : {Q3:>10,.2f}€")
print(f"IQR        : {IQR:>10,.2f}€")
print()
print("[IDEE] Interprétation :")
print(f"   • 25% des employés gagnent ≤ {Q1:,.0f}€")
print(f"   • 50% des employés gagnent ≤ {Q2:,.0f}€")
print(f"   • 75% des employés gagnent ≤ {Q3:,.0f}€")
print()


# ═══════════════════════════════════════════════════════════════════════════
# 4. DÉTECTION DES OUTLIERS
# ═══════════════════════════════════════════════════════════════════════════

print("4⃣  DÉTECTION DES OUTLIERS (Méthode IQR)")
print("─" * 80)

limite_basse = Q1 - 1.5 * IQR
limite_haute = Q3 + 1.5 * IQR

print(f"Limite basse : {limite_basse:>10,.2f}€")
print(f"Limite haute : {limite_haute:>10,.2f}€")
print()

outliers = [x for x in salaires if x < limite_basse or x > limite_haute]
salaires_propres = [x for x in salaires if limite_basse <= x <= limite_haute]

print(f"Outliers détectés : {outliers}")
print(f"Nombre d'outliers : {len(outliers)}")
print()

if outliers:
    print("[ATTENTION]  ANALYSE DES OUTLIERS :")
    for outlier in outliers:
        if outlier > limite_haute:
            print(f"   • {outlier:,}€ est anormalement ÉLEVÉ")
            print(f"     -> Peut-être un cadre dirigeant ou une erreur de saisie")
        else:
            print(f"   • {outlier:,}€ est anormalement BAS")
print()


# ═══════════════════════════════════════════════════════════════════════════
# 5. STATISTIQUES SANS OUTLIERS
# ═══════════════════════════════════════════════════════════════════════════

print("5⃣  COMPARAISON AVEC/SANS OUTLIERS")
print("─" * 80)

moyenne_propre = np.mean(salaires_propres)
mediane_propre = np.median(salaires_propres)
ecart_type_propre = np.std(salaires_propres, ddof=1)

print("AVEC OUTLIERS :")
print(f"  Moyenne    : {moyenne:>10,.2f}€")
print(f"  Médiane    : {mediane:>10,.2f}€")
print(f"  Écart-type : {ecart_type:>10,.2f}€")
print()

print("SANS OUTLIERS :")
print(f"  Moyenne    : {moyenne_propre:>10,.2f}€")
print(f"  Médiane    : {mediane_propre:>10,.2f}€")
print(f"  Écart-type : {ecart_type_propre:>10,.2f}€")
print()

diff_moyenne = moyenne - moyenne_propre
diff_pct = (diff_moyenne / moyenne) * 100

print(f"[IDEE] Impact de l'outlier :")
print(f"   • La moyenne a été surestimée de {diff_moyenne:,.2f}€ ({diff_pct:.1f}%)")
print(f"   • La médiane est restée stable (robuste aux outliers)")
print(f"   • L'écart-type a diminué de {ecart_type - ecart_type_propre:,.2f}€")
print()


# ═══════════════════════════════════════════════════════════════════════════
# 6. CONCLUSION
# ═══════════════════════════════════════════════════════════════════════════

print("6⃣  CONCLUSION DE L'ANALYSE")
print("─" * 80)

print("[GRAPHIQUE] RÉSUMÉ :")
print()
print("1. Distribution asymétrique (moyenne > médiane)")
print("2. Présence d'un outlier significatif (150000€)")
print("3. Forte dispersion des salaires (CV = 34.8%)")
print("4. Sans l'outlier, le salaire moyen est de 58158€")
print()
print("[OBJECTIF] RECOMMANDATIONS :")
print()
print("1. Utiliser la MÉDIANE (59000€) pour communiquer le salaire typique")
print("   -> Plus représentative que la moyenne biaisée par l'outlier")
print()
print("2. Analyser l'employé à 150000€ séparément")
print("   -> Est-ce un dirigeant ? Une erreur de saisie ?")
print()
print("3. Créer des tranches salariales pour mieux comprendre la répartition")
print("   -> 30-40K, 40-50K, 50-60K, 60-70K, 70-80K, >80K")
print()

print("=" * 80)
print("[OK] ANALYSE STATISTIQUE TERMINÉE")
print("=" * 80)


═══════════════════════════════════════════════════════════════════════════════
[GUIDE] RÉSUMÉ DU CHAPITRE
═══════════════════════════════════════════════════════════════════════════════

[OK] Vous savez maintenant :

1. Calculer les mesures de tendance centrale (moyenne, médiane, mode)
2. Calculer les mesures de dispersion (variance, écart-type, CV)
3. Comprendre les percentiles et quartiles
4. Calculer et interpréter les corrélations
5. Détecter les valeurs aberrantes (IQR et Z-score)
6. Analyser l'impact des outliers
7. Choisir la bonne mesure selon le contexte

[OBJECTIF] PROCHAINES ÉTAPES :

Chapitre 7 : Projet complet - Mettre en pratique TOUT le cours !


═══════════════════════════════════════════════════════════════════════════════
[IDEE] RÈGLES D'OR DU DATA ANALYST
═══════════════════════════════════════════════════════════════════════════════

[OK] Toujours calculer moyenne ET médiane
[OK] Vérifier la présence d'outliers avant de conclure
[OK] Corrélation ≠ Causalité
[OK] Visualiser les distributions (histogrammes, box plots)
[OK] Contextualiser les chiffres (CV pour comparer des échelles différentes)


═══════════════════════════════════════════════════════════════════════════════
FIN DU CHAPITRE 6
═══════════════════════════════════════════════════════════════════════════════

═══════════════════════════════════════════════════════════════════════════════
    CHAPITRE 7 : PROJET FINAL - ANALYSE COMPLÈTE E-COMMERCE
═══════════════════════════════════════════════════════════════════════════════

[OBJECTIF] OBJECTIF : Réaliser votre première analyse professionnelle de A à Z !

Ce projet combine TOUT ce que vous avez appris dans ce cours.
Vous allez faire le travail d'un vrai Data Analyst professionnel.

═══════════════════════════════════════════════════════════════════════════════
[LISTE] CONTEXTE BUSINESS
═══════════════════════════════════════════════════════════════════════════════

[CONVENIENCE_STORE] ENTREPRISE : "TechStore France"
[PACKAGE] SECTEUR : E-commerce (produits tech et mobilier bureau)
[CALENDRIER] PÉRIODE : 6 mois (Janvier - Juin 2024)
[UTILISATEURS] CLIENTS : Particuliers et entreprises
[PRO] VOTRE RÔLE : Data Analyst Junior

MISSION CONFIÉE PAR LE DIRECTEUR :
"Nous avons 6 mois de données de ventes. J'ai besoin de comprendre :
• Quelle est notre performance globale ?
• Quels produits marchent le mieux ?
• Y a-t-il des tendances à exploiter ?
• Comment améliorer nos ventes ?

Faites une analyse complète avec visualisations et recommandations."


═══════════════════════════════════════════════════════════════════════════════
[GRAPHIQUE] LES DONNÉES
═══════════════════════════════════════════════════════════════════════════════

Fichier : ventes_ecommerce_2024.csv

Colonnes :
• date : Date de la vente (AAAA-MM-JJ)
• produit : Nom du produit
• categorie : Catégorie (Informatique ou Mobilier)
• quantite : Quantité vendue
• prix_unitaire : Prix unitaire en €
• ville : Ville du client
• moyen_paiement : CB, Virement, PayPal


═══════════════════════════════════════════════════════════════════════════════
[OBJECTIF] QUESTIONS BUSINESS À RÉPONDRE
═══════════════════════════════════════════════════════════════════════════════

1. Quel est le revenu total sur 6 mois ?
2. Quel mois a généré le plus de revenu ?
3. Quels sont les 5 produits les plus vendus (quantité) ?
4. Quels sont les 5 produits qui génèrent le plus de revenu ?
5. Quelle catégorie (Informatique vs Mobilier) est la plus rentable ?
6. Quelle ville génère le plus de revenus ?
7. Y a-t-il une tendance d'évolution (croissance/décroissance) ?
8. Quel est le panier moyen ?
9. Quel moyen de paiement est le plus utilisé ?
10. Y a-t-il des valeurs aberrantes à investiguer ?


═══════════════════════════════════════════════════════════════════════════════
[CODE] SOLUTION COMPLÈTE - CODE PROFESSIONNEL
═══════════════════════════════════════════════════════════════════════════════

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from datetime import datetime

# Configuration
sns.set_style("whitegrid")
plt.rcParams['figure.figsize'] = (12, 6)

print("═" * 80)
print(" " * 15 + "[GRAPHIQUE] ANALYSE COMPLÈTE - TECHSTORE FRANCE")
print(" " * 20 + "Période : Janvier - Juin 2024")
print("═" * 80)
print()


# ═══════════════════════════════════════════════════════════════════════════
# ÉTAPE 1 : CRÉER LES DONNÉES
# ═══════════════════════════════════════════════════════════════════════════

donnees_csv = """date,produit,categorie,quantite,prix_unitaire,ville,moyen_paiement
2024-01-05,Laptop Dell XPS,Informatique,2,899.99,Paris,CB
2024-01-08,Souris Logitech,Informatique,5,25.50,Lyon,PayPal
2024-01-12,Bureau Standing,Mobilier,1,450.00,Marseille,Virement
2024-01-15,Chaise Ergonomique,Mobilier,3,220.00,Paris,CB
2024-01-20,Écran Samsung 27,Informatique,2,299.99,Bordeaux,CB
2024-01-25,Clavier Mécanique,Informatique,4,89.99,Lyon,PayPal
2024-02-02,Laptop HP,Informatique,1,799.99,Paris,CB
2024-02-05,Lampe LED,Mobilier,8,45.00,Marseille,CB
2024-02-10,Souris Logitech,Informatique,10,25.50,Bordeaux,PayPal
2024-02-15,Bureau Standard,Mobilier,2,350.00,Lyon,Virement
2024-02-20,Webcam HD,Informatique,6,75.00,Paris,CB
2024-02-25,Chaise Gaming,Mobilier,2,180.00,Marseille,PayPal
2024-03-01,Laptop Dell XPS,Informatique,3,899.99,Lyon,CB
2024-03-05,Écran LG 24,Informatique,4,199.99,Paris,CB
2024-03-10,Bureau Standing,Mobilier,1,450.00,Bordeaux,Virement
2024-03-15,Clavier Sans Fil,Informatique,8,45.00,Marseille,PayPal
2024-03-20,Chaise Ergonomique,Mobilier,5,220.00,Lyon,CB
2024-03-25,Souris Gaming,Informatique,12,55.00,Paris,CB
2024-04-02,Laptop MacBook,Informatique,2,1299.99,Paris,CB
2024-04-08,Lampe Bureau,Mobilier,15,35.00,Lyon,PayPal
2024-04-12,Écran Samsung 27,Informatique,3,299.99,Marseille,CB
2024-04-18,Bureau Compact,Mobilier,4,280.00,Bordeaux,Virement
2024-04-22,Webcam 4K,Informatique,5,120.00,Paris,CB
2024-04-28,Chaise Bureau,Mobilier,7,150.00,Lyon,PayPal
2024-05-03,Laptop HP,Informatique,4,799.99,Marseille,CB
2024-05-08,Souris Logitech,Informatique,20,25.50,Paris,PayPal
2024-05-15,Bureau Standing,Mobilier,3,450.00,Lyon,Virement
2024-05-20,Écran Dell 27,Informatique,2,259.99,Bordeaux,CB
2024-05-25,Clavier RGB,Informatique,10,65.00,Marseille,PayPal
2024-05-30,Lampe LED,Mobilier,12,45.00,Paris,CB
2024-06-02,Laptop Dell XPS,Informatique,5,899.99,Lyon,CB
2024-06-08,Chaise Gaming,Mobilier,6,180.00,Marseille,CB
2024-06-12,Bureau Executive,Mobilier,2,550.00,Paris,Virement
2024-06-18,Écran Samsung 32,Informatique,3,399.99,Bordeaux,CB
2024-06-22,Souris Gaming,Informatique,15,55.00,Lyon,PayPal
2024-06-28,Webcam HD,Informatique,8,75.00,Paris,CB"""

with open('ventes_ecommerce_2024.csv', 'w', encoding='utf-8') as f:
    f.write(donnees_csv)

print("[OK] Données créées : ventes_ecommerce_2024.csv")
print()


# ═══════════════════════════════════════════════════════════════════════════
# ÉTAPE 2 : IMPORT ET EXPLORATION INITIALE
# ═══════════════════════════════════════════════════════════════════════════

print("─" * 80)
print("ÉTAPE 1 : IMPORT ET EXPLORATION INITIALE")
print("─" * 80)
print()

df = pd.read_csv('ventes_ecommerce_2024.csv')

print("[LISTE] Aperçu des données :")
print(df.head(10))
print()

print("ℹ  Informations générales :")
print(df.info())
print()

print(f"[GRAPHIQUE] Dimensions : {df.shape[0]} lignes × {df.shape[1]} colonnes")
print()


# ═══════════════════════════════════════════════════════════════════════════
# ÉTAPE 3 : NETTOYAGE DES DONNÉES
# ═══════════════════════════════════════════════════════════════════════════

print("─" * 80)
print("ÉTAPE 2 : NETTOYAGE DES DONNÉES")
print("─" * 80)
print()

# Vérifier valeurs manquantes
print("[RECHERCHE] Valeurs manquantes :")
valeurs_manquantes = df.isnull().sum()
print(valeurs_manquantes[valeurs_manquantes > 0] if valeurs_manquantes.sum() > 0 
      else "  [OK] Aucune valeur manquante")
print()

# Vérifier doublons
nb_doublons = df.duplicated().sum()
print(f"[RECHERCHE] Doublons : {nb_doublons}")
if nb_doublons > 0:
    df = df.drop_duplicates()
    print(f"  [OK] {nb_doublons} doublons supprimés")
print()

# Convertir les dates
df['date'] = pd.to_datetime(df['date'])
print("[OK] Dates converties au format datetime")
print()

# Créer colonne mois
df['mois'] = df['date'].dt.to_period('M')
df['mois_nom'] = df['date'].dt.strftime('%B')
print("[OK] Colonnes 'mois' et 'mois_nom' ajoutées")
print()


# ═══════════════════════════════════════════════════════════════════════════
# ÉTAPE 4 : CALCULS ESSENTIELS
# ═══════════════════════════════════════════════════════════════════════════

print("─" * 80)
print("ÉTAPE 3 : CALCULS ESSENTIELS")
print("─" * 80)
print()

# Calculer le revenu
df['revenu'] = df['quantite'] * df['prix_unitaire']
print("[OK] Colonne 'revenu' calculée")
print()

print("[GRAPHIQUE] Aperçu avec revenu :")
print(df[['date', 'produit', 'quantite', 'prix_unitaire', 'revenu']].head())
print()


# ═══════════════════════════════════════════════════════════════════════════
# ÉTAPE 5 : ANALYSES STATISTIQUES
# ═══════════════════════════════════════════════════════════════════════════

print("═" * 80)
print("[HAUSSE] ANALYSES ET RÉSULTATS")
print("═" * 80)
print()

# Question 1 : Revenu total
revenu_total = df['revenu'].sum()
print(f"1⃣  REVENU TOTAL (6 mois) : {revenu_total:,.2f}€")
print()

# Question 2 : Revenu par mois
revenu_par_mois = df.groupby('mois')['revenu'].sum().sort_values(ascending=False)
print("2⃣  REVENU PAR MOIS :")
for mois, revenu in revenu_par_mois.items():
    print(f"   {mois} : {revenu:>12,.2f}€")
meilleur_mois = revenu_par_mois.index[0]
print(f"\n   [TROPHEE] Meilleur mois : {meilleur_mois} ({revenu_par_mois.iloc[0]:,.2f}€)")
print()

# Question 3 : Top 5 produits par quantité
ventes_quantite = df.groupby('produit')['quantite'].sum().sort_values(ascending=False)
print("3⃣  TOP 5 PRODUITS PAR QUANTITÉ VENDUE :")
for i, (produit, qte) in enumerate(ventes_quantite.head(5).items(), 1):
    print(f"   {i}. {produit:<25} : {qte:>3} unités")
print()

# Question 4 : Top 5 produits par revenu
ventes_revenu = df.groupby('produit')['revenu'].sum().sort_values(ascending=False)
print("4⃣  TOP 5 PRODUITS PAR REVENU :")
for i, (produit, rev) in enumerate(ventes_revenu.head(5).items(), 1):
    print(f"   {i}. {produit:<25} : {rev:>12,.2f}€")
print()

# Question 5 : Revenu par catégorie
revenu_categorie = df.groupby('categorie')['revenu'].sum().sort_values(ascending=False)
print("5⃣  REVENU PAR CATÉGORIE :")
for cat, rev in revenu_categorie.items():
    pct = (rev / revenu_total) * 100
    print(f"   {cat:<15} : {rev:>12,.2f}€ ({pct:>5.1f}%)")
print()

# Question 6 : Revenu par ville
revenu_ville = df.groupby('ville')['revenu'].sum().sort_values(ascending=False)
print("6⃣  REVENU PAR VILLE :")
for ville, rev in revenu_ville.items():
    pct = (rev / revenu_total) * 100
    print(f"   {ville:<12} : {rev:>12,.2f}€ ({pct:>5.1f}%)")
print()

# Question 7 : Tendance
print("7⃣  TENDANCE D'ÉVOLUTION :")
evolution = df.groupby('mois')['revenu'].sum()
premier_mois = evolution.iloc[0]
dernier_mois = evolution.iloc[-1]
croissance = ((dernier_mois - premier_mois) / premier_mois) * 100
print(f"   Premier mois : {premier_mois:,.2f}€")
print(f"   Dernier mois : {dernier_mois:,.2f}€")
if croissance > 0:
    print(f"   [HAUSSE] Croissance : +{croissance:.1f}%")
else:
    print(f"   [BAISSE] Décroissance : {croissance:.1f}%")
print()

# Question 8 : Panier moyen
panier_moyen = df['revenu'].mean()
print(f"8⃣  PANIER MOYEN : {panier_moyen:,.2f}€")
print()

# Question 9 : Moyen de paiement
paiement_stats = df.groupby('moyen_paiement').agg({
    'revenu': 'sum',
    'produit': 'count'
}).rename(columns={'produit': 'nb_transactions'})
paiement_stats = paiement_stats.sort_values('revenu', ascending=False)
print("9⃣  MOYENS DE PAIEMENT :")
for paiement, row in paiement_stats.iterrows():
    pct = (row['revenu'] / revenu_total) * 100
    print(f"   {paiement:<10} : {row['nb_transactions']:>2} transactions | "
          f"{row['revenu']:>10,.2f}€ ({pct:>5.1f}%)")
print()

# Question 10 : Outliers
print("[10] DÉTECTION DES VALEURS ABERRANTES :")
Q1 = df['revenu'].quantile(0.25)
Q3 = df['revenu'].quantile(0.75)
IQR = Q3 - Q1
limite_basse = Q1 - 1.5 * IQR
limite_haute = Q3 + 1.5 * IQR

outliers = df[df['revenu'] > limite_haute]
if len(outliers) > 0:
    print(f"   [ATTENTION]  {len(outliers)} vente(s) exceptionnelle(s) détectée(s) :")
    for idx, row in outliers.iterrows():
        print(f"      • {row['produit']} : {row['revenu']:,.2f}€ "
              f"({row['quantite']} × {row['prix_unitaire']:,.2f}€)")
else:
    print("   [OK] Aucune valeur aberrante détectée")
print()


# ═══════════════════════════════════════════════════════════════════════════
# ÉTAPE 6 : VISUALISATIONS
# ═══════════════════════════════════════════════════════════════════════════

print("─" * 80)
print("ÉTAPE 4 : CRÉATION DES VISUALISATIONS")
print("─" * 80)
print()

# Dashboard complet
fig = plt.figure(figsize=(18, 12))
gs = fig.add_gridspec(3, 3, hspace=0.3, wspace=0.3)

# Graphique 1 : Évolution mensuelle
ax1 = fig.add_subplot(gs[0, :])
evolution_mois = df.groupby('mois')['revenu'].sum()
ax1.plot(evolution_mois.index.astype(str), evolution_mois.values, 
         marker='o', linewidth=3, markersize=10, color='#2E86AB')
ax1.set_title('Évolution du Revenu Mensuel', fontsize=14, fontweight='bold')
ax1.set_xlabel('Mois', fontsize=11)
ax1.set_ylabel('Revenu (€)', fontsize=11)
ax1.grid(True, alpha=0.3)
ax1.tick_params(axis='x', rotation=45)
for i, v in enumerate(evolution_mois.values):
    ax1.text(i, v + 500, f'{v:,.0f}€', ha='center', va='bottom', fontsize=9)

# Graphique 2 : Top 5 produits
ax2 = fig.add_subplot(gs[1, 0])
top5 = ventes_revenu.head(5)
ax2.barh(range(len(top5)), top5.values, color='#06A77D')
ax2.set_yticks(range(len(top5)))
ax2.set_yticklabels([p[:20] for p in top5.index], fontsize=9)
ax2.set_title('Top 5 Produits (Revenu)', fontsize=12, fontweight='bold')
ax2.set_xlabel('Revenu (€)')
ax2.grid(True, alpha=0.3, axis='x')
ax2.invert_yaxis()

# Graphique 3 : Catégories
ax3 = fig.add_subplot(gs[1, 1])
colors = ['#3498db', '#e74c3c']
ax3.pie(revenu_categorie.values, labels=revenu_categorie.index, 
        autopct='%1.1f%%', colors=colors, startangle=90)
ax3.set_title('Répartition par Catégorie', fontsize=12, fontweight='bold')

# Graphique 4 : Villes
ax4 = fig.add_subplot(gs[1, 2])
ax4.bar(revenu_ville.index, revenu_ville.values, color='#F18F01')
ax4.set_title('Revenu par Ville', fontsize=12, fontweight='bold')
ax4.set_xlabel('Ville')
ax4.set_ylabel('Revenu (€)')
ax4.tick_params(axis='x', rotation=45)
ax4.grid(True, alpha=0.3, axis='y')

# Graphique 5 : Distribution des revenus
ax5 = fig.add_subplot(gs[2, 0])
ax5.hist(df['revenu'], bins=20, color='#9b59b6', edgecolor='black', alpha=0.7)
ax5.axvline(df['revenu'].mean(), color='red', linestyle='--', linewidth=2, 
            label=f'Moyenne: {df["revenu"].mean():.0f}€')
ax5.set_title('Distribution des Revenus', fontsize=12, fontweight='bold')
ax5.set_xlabel('Revenu (€)')
ax5.set_ylabel('Fréquence')
ax5.legend()
ax5.grid(True, alpha=0.3)

# Graphique 6 : Moyens de paiement
ax6 = fig.add_subplot(gs[2, 1])
ax6.bar(paiement_stats.index, paiement_stats['nb_transactions'], color='#1abc9c')
ax6.set_title('Transactions par Moyen de Paiement', fontsize=12, fontweight='bold')
ax6.set_xlabel('Moyen de Paiement')
ax6.set_ylabel('Nombre de Transactions')
ax6.grid(True, alpha=0.3, axis='y')

# Graphique 7 : Box plot par catégorie
ax7 = fig.add_subplot(gs[2, 2])
df.boxplot(column='revenu', by='categorie', ax=ax7, patch_artist=True)
ax7.set_title('Distribution Revenus par Catégorie', fontsize=12, fontweight='bold')
ax7.set_xlabel('Catégorie')
ax7.set_ylabel('Revenu (€)')
plt.suptitle('')  # Supprimer titre auto

plt.suptitle('[GRAPHIQUE] DASHBOARD ANALYTIQUE - TECHSTORE FRANCE | JAN-JUIN 2024', 
             fontsize=16, fontweight='bold', y=0.995)
plt.savefig('dashboard_techstore.png', dpi=300, bbox_inches='tight')
print("[OK] Dashboard sauvegardé : dashboard_techstore.png")
plt.show()


# ═══════════════════════════════════════════════════════════════════════════
# ÉTAPE 7 : INSIGHTS ET RECOMMANDATIONS
# ═══════════════════════════════════════════════════════════════════════════

print()
print("═" * 80)
print("[IDEE] INSIGHTS ET RECOMMANDATIONS STRATÉGIQUES")
print("═" * 80)
print()

print("[RECHERCHE] INSIGHTS CLÉS :")
print()
print(f"1. Revenu total de {revenu_total:,.2f}€ sur 6 mois")
print(f"   -> Objectif atteint ? Comparer avec budget prévisionnel")
print()

print(f"2. Tendance {'POSITIVE' if croissance > 0 else 'NÉGATIVE'} : "
      f"{'+' if croissance > 0 else ''}{croissance:.1f}%")
print(f"   -> {'[HAUSSE] Croissance saine' if croissance > 0 else '[BAISSE] Attention à la baisse'}")
print()

categorie_dominante = revenu_categorie.index[0]
pct_dominante = (revenu_categorie.iloc[0] / revenu_total) * 100
print(f"3. {categorie_dominante} domine avec {pct_dominante:.1f}% du CA")
print(f"   -> Portfolio déséquilibré ?")
print()

produit_star = ventes_revenu.index[0]
print(f"4. Produit star : {produit_star}")
print(f"   -> Assurer le stock, promouvoir davantage")
print()

ville_principale = revenu_ville.index[0]
pct_ville = (revenu_ville.iloc[0] / revenu_total) * 100
print(f"5. {ville_principale} génère {pct_ville:.1f}% du CA")
print(f"   -> Potentiel de développement dans autres villes ?")
print()

print()
print("[OBJECTIF] RECOMMANDATIONS STRATÉGIQUES :")
print()
print("1. PRODUITS")
print("   • Augmenter stock des top 5 produits (risque de rupture)")
print(f"   • Analyser pourquoi {ventes_quantite.index[-1]} se vend peu")
print("   • Créer des bundles : Laptop + Écran + Souris")
print()

print("2. CATÉGORIES")
if pct_dominante > 60:
    print("   [ATTENTION]  Forte dépendance à une catégorie")
    print(f"   • Promouvoir la catégorie {revenu_categorie.index[1]}")
    print("   • Diversifier l'offre pour réduire le risque")
print()

print("3. GÉOGRAPHIQUE")
print(f"   • Renforcer présence à {ville_principale} (marché porteur)")
villes_faibles = revenu_ville.tail(2).index.tolist()
print(f"   • Campagne marketing ciblée sur {', '.join(villes_faibles)}")
print()

print("4. TEMPORALITÉ")
if croissance > 0:
    print("   • Capitaliser sur la dynamique positive")
    print("   • Maintenir les actions qui fonctionnent")
else:
    print("   [ATTENTION]  Inverser la tendance baissière")
    print("   • Analyser les causes (concurrence, prix, offre ?)")
print()

print("5. PAIEMENT")
paiement_dominant = paiement_stats.index[0]
print(f"   • {paiement_dominant} privilégié : optimiser ce canal")
print("   • Inciter vers moyens à moindres frais (virement ?)")
print()


# ═══════════════════════════════════════════════════════════════════════════
# ÉTAPE 8 : RAPPORT EXÉCUTIF (RÉSUMÉ)
# ═══════════════════════════════════════════════════════════════════════════

print()
print("═" * 80)
print("[LISTE] RAPPORT EXÉCUTIF - RÉSUMÉ POUR LA DIRECTION")
print("═" * 80)
print()

print("PERFORMANCE GLOBALE :")
print(f"• Chiffre d'affaires semestriel : {revenu_total:,.2f}€")
print(f"• Évolution : {'+' if croissance > 0 else ''}{croissance:.1f}%")
print(f"• Panier moyen : {panier_moyen:,.2f}€")
print(f"• Nombre de transactions : {len(df)}")
print()

print("PRODUITS PHARES :")
for i, (produit, rev) in enumerate(ventes_revenu.head(3).items(), 1):
    pct = (rev / revenu_total) * 100
    print(f"{i}. {produit} ({pct:.1f}% du CA)")
print()

print("AXES D'AMÉLIORATION :")
print("1. Diversifier le portefeuille produits")
print("2. Développer les marchés sous-exploités")
print("3. Optimiser la gestion des stocks")
print()

print("═" * 80)
print("[OK] ANALYSE TERMINÉE AVEC SUCCÈS !")
print("═" * 80)
print()
print("[PAPERCLIP] Fichiers générés :")
print("   • ventes_ecommerce_2024.csv (données)")
print("   • dashboard_techstore.png (visualisations)")
print()
print("-> Prochaines étapes : Présenter ce rapport à la direction")
print()


═══════════════════════════════════════════════════════════════════════════════
[COURS] COMPÉTENCES UTILISÉES DANS CE PROJET
═══════════════════════════════════════════════════════════════════════════════

[OK] Import de données (pd.read_csv)
[OK] Exploration (head, info, describe)
[OK] Nettoyage (doublons, valeurs manquantes)
[OK] Transformation (conversion dates, création colonnes)
[OK] Calculs (revenu, agrégations)
[OK] Groupby et agrégations multiples
[OK] Statistiques (Q1, Q3, IQR, outliers)
[OK] Visualisations multiples (line, bar, pie, hist, box)
[OK] Dashboard professionnel
[OK] Communication d'insights
[OK] Recommandations business


═══════════════════════════════════════════════════════════════════════════════
[TROPHEE] FÉLICITATIONS !
═══════════════════════════════════════════════════════════════════════════════

Vous venez de réaliser votre PREMIER PROJET COMPLET d'analyse de données !

Vous avez fait EXACTEMENT le travail d'un Data Analyst professionnel :
• Comprendre le contexte business
• Nettoyer et préparer les données
• Effectuer des analyses statistiques
• Créer des visualisations impactantes
• Tirer des insights actionnables
• Formuler des recommandations stratégiques

-> VOUS ÊTES MAINTENANT UN DATA ANALYST OPÉRATIONNEL ! [BRAVO]


═══════════════════════════════════════════════════════════════════════════════
[RAPIDE] SUITE DE VOTRE PARCOURS
═══════════════════════════════════════════════════════════════════════════════

PROCHAINS DÉFIS :

1. Refaire ce projet avec vos propres données
   (vos dépenses, données sportives, etc.)

2. Ajouter des analyses plus poussées :
   • Prévisions (tendances futures)
   • Segmentation clients (clustering)
   • Tests A/B

3. Apprendre des outils complémentaires :
   • SQL pour bases de données
   • Tableau/Power BI pour dashboards interactifs
   • APIs pour collecter des données
   • Machine Learning (Scikit-learn)

4. Construire un portfolio GitHub
   • Publier ce projet sur GitHub
   • Ajouter 3-5 projets similaires
   • Créer un README professionnel

5. Postuler comme Data Analyst Junior !


═══════════════════════════════════════════════════════════════════════════════
FIN DU CHAPITRE 7 - FIN DU COURS
═══════════════════════════════════════════════════════════════════════════════

Merci d'avoir suivi ce cours jusqu'au bout ! [MERCI]

Vous avez maintenant toutes les compétences pour réussir en Data Analysis.

La seule chose qui manque : PRATIQUER, PRATIQUER, PRATIQUER ! [FORCE]

Bon courage pour la suite de votre carrière en Data ! [GRAPHIQUE]*

═══════════════════════════════════════════════════════════════════════════════
    CHAPITRE 8 : SQL POUR DATA ANALYSIS (GRAND DÉBUTANT)
═══════════════════════════════════════════════════════════════════════════════

[LIVRE] OBJECTIFS DU CHAPITRE :
   • Comprendre ce qu'est SQL et pourquoi c'est ESSENTIEL
   • Créer et manipuler des bases de données
   • Maîtriser les requêtes SELECT (filtrer, trier, limiter)
   • Utiliser les JOIN pour combiner des tables
   • Fonctions d'agrégation (SUM, COUNT, AVG, etc.)
   • Connecter Python et SQL (SQLite)
   • Importer des données SQL dans Pandas
   • Projet complet : Analyser une base de données de ventes

[ATTENTION] SQL est utilisé par 90% des Data Analysts ! C'est INCONTOURNABLE !

═══════════════════════════════════════════════════════════════════════════════
PARTIE 1 : INTRODUCTION À SQL
═══════════════════════════════════════════════════════════════════════════════

1.1 QU'EST-CE QUE SQL ?
───────────────────────

SQL = Structured Query Language (Langage de Requête Structuré)

C'est un langage pour :
• Communiquer avec des bases de données
• Extraire des données (SELECT)
• Insérer des données (INSERT)
• Modifier des données (UPDATE)
• Supprimer des données (DELETE)

POURQUOI SQL EST ESSENTIEL ?
[OK] 90% des données d'entreprise sont dans des bases de données SQL
[OK] Plus rapide que Pandas pour gros volumes (millions de lignes)
[OK] Standard universel (MySQL, PostgreSQL, SQLite, SQL Server...)
[OK] Requis dans 95% des offres Data Analyst


1.2 BASE DE DONNÉES vs FICHIER CSV
──────────────────────────────────

FICHIER CSV :
[X] Lent pour gros volumes (>1 million lignes)
[X] Pas de relations entre tables
[X] Pas de contrôle d'accès
[OK] Simple pour petits projets

BASE DE DONNÉES SQL :
[OK] Ultra-rapide même avec milliards de lignes
[OK] Relations entre tables (clients <-> commandes)
[OK] Sécurité et contrôle d'accès
[OK] Plusieurs utilisateurs simultanés
[X] Plus complexe à configurer


1.3 LES DIFFÉRENTS SGBD (Systèmes de Gestion de BD)
────────────────────────────────────────────────────

┌─────────────────────────────────────────────────────────────────┐
│ SQLite       │ Léger, fichier unique, parfait pour apprendre   │
│              │ -> On va l'utiliser dans ce cours                │
├─────────────────────────────────────────────────────────────────┤
│ MySQL        │ Open-source, très populaire (web)               │
├─────────────────────────────────────────────────────────────────┤
│ PostgreSQL   │ Puissant, features avancées, open-source        │
├─────────────────────────────────────────────────────────────────┤
│ SQL Server   │ Microsoft, entreprises Windows                  │
├─────────────────────────────────────────────────────────────────┤
│ Oracle       │ Très puissant, grandes entreprises             │
└─────────────────────────────────────────────────────────────────┘

-> La syntaxe SQL de base est 95% identique partout !


═══════════════════════════════════════════════════════════════════════════════
PARTIE 2 : CONCEPTS FONDAMENTAUX
═══════════════════════════════════════════════════════════════════════════════

2.1 STRUCTURE D'UNE BASE DE DONNÉES
───────────────────────────────────

Une BASE DE DONNÉES contient des TABLES.
Une TABLE ressemble à un fichier Excel/CSV.

Exemple : Base de données "magasin"

TABLE : clients
┌────┬──────────┬──────┬─────────────┐
│ id │   nom    │ age  │    ville    │
├────┼──────────┼──────┼─────────────┤
│ 1  │ Alice    │  25  │ Paris       │
│ 2  │ Bob      │  30  │ Lyon        │
│ 3  │ Charlie  │  35  │ Marseille   │
└────┴──────────┴──────┴─────────────┘

TABLE : commandes
┌────┬────────────┬──────────┬─────────┐
│ id │ client_id  │ produit  │ montant │
├────┼────────────┼──────────┼─────────┤
│ 1  │     1      │ Laptop   │  899.99 │
│ 2  │     1      │ Souris   │   25.50 │
│ 3  │     2      │ Clavier  │   75.00 │
└────┴────────────┴──────────┴─────────┘

-> client_id dans "commandes" fait référence à id dans "clients"
  C'est une CLEF ÉTRANGÈRE (Foreign Key)


2.2 TYPES DE DONNÉES SQL
────────────────────────

┌──────────────┬─────────────────────────────────────────────┐
│ INTEGER      │ Nombres entiers (1, 2, 100, -5)             │
│ REAL / FLOAT │ Nombres décimaux (3.14, 99.99)              │
│ TEXT         │ Chaînes de caractères ("Alice", "Paris")    │
│ DATE         │ Dates (2024-01-15)                          │
│ BOOLEAN      │ Vrai/Faux (TRUE, FALSE)                     │
└──────────────┴─────────────────────────────────────────────┘


═══════════════════════════════════════════════════════════════════════════════
PARTIE 3 : SQLite AVEC PYTHON
═══════════════════════════════════════════════════════════════════════════════

3.1 INSTALLATION (RIEN À INSTALLER !)
─────────────────────────────────────

SQLite est INCLUS avec Python ! Pas besoin d'installation.

import sqlite3

# Créer/connecter à une base de données
conn = sqlite3.connect('ma_base.db')
cursor = conn.cursor()

# Exécuter des requêtes SQL
cursor.execute("SELECT * FROM clients")

# Fermer la connexion
conn.close()


3.2 CRÉER UNE BASE DE DONNÉES ET UNE TABLE
──────────────────────────────────────────

import sqlite3

# 1. Connexion (crée le fichier si inexistant)
conn = sqlite3.connect('magasin.db')
cursor = conn.cursor()

# 2. Créer une table
cursor.execute('''
CREATE TABLE IF NOT EXISTS clients (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    nom TEXT NOT NULL,
    age INTEGER,
    ville TEXT
)
''')

# 3. Sauvegarder (commit)
conn.commit()

print("[OK] Table 'clients' créée !")

# 4. Fermer
conn.close()

EXPLICATION :
• CREATE TABLE IF NOT EXISTS : Créer seulement si elle n'existe pas
• id INTEGER PRIMARY KEY : Clé primaire (identifiant unique)
• AUTOINCREMENT : S'incrémente automatiquement (1, 2, 3...)
• NOT NULL : Le champ ne peut pas être vide
• conn.commit() : Sauvegarder les changements


3.3 INSÉRER DES DONNÉES (INSERT)
────────────────────────────────

import sqlite3

conn = sqlite3.connect('magasin.db')
cursor = conn.cursor()

# Insérer UNE ligne
cursor.execute('''
INSERT INTO clients (nom, age, ville)
VALUES ('Alice', 25, 'Paris')
''')

# Insérer PLUSIEURS lignes
donnees = [
    ('Bob', 30, 'Lyon'),
    ('Charlie', 35, 'Marseille'),
    ('David', 28, 'Bordeaux'),
    ('Eve', 32, 'Toulouse')
]

cursor.executemany('''
INSERT INTO clients (nom, age, ville)
VALUES (?, ?, ?)
''', donnees)

conn.commit()
print("[OK] Données insérées !")

conn.close()

EXPLICATION :
• VALUES (?, ?, ?) : Les ? sont des placeholders (sécurité)
• executemany() : Insérer plusieurs lignes d'un coup


═══════════════════════════════════════════════════════════════════════════════
PARTIE 4 : REQUÊTES SELECT (LIRE DES DONNÉES)
═══════════════════════════════════════════════════════════════════════════════

4.1 SELECT BASIQUE
──────────────────

import sqlite3

conn = sqlite3.connect('magasin.db')
cursor = conn.cursor()

# Sélectionner TOUTES les colonnes
cursor.execute("SELECT * FROM clients")
resultats = cursor.fetchall()

print("Tous les clients :")
for row in resultats:
    print(row)

conn.close()

RÉSULTAT :
(1, 'Alice', 25, 'Paris')
(2, 'Bob', 30, 'Lyon')
(3, 'Charlie', 35, 'Marseille')
(4, 'David', 28, 'Bordeaux')
(5, 'Eve', 32, 'Toulouse')


4.2 SELECT COLONNES SPÉCIFIQUES
───────────────────────────────

# Sélectionner seulement nom et ville
cursor.execute("SELECT nom, ville FROM clients")
resultats = cursor.fetchall()

for row in resultats:
    print(f"{row[0]} habite à {row[1]}")

RÉSULTAT :
Alice habite à Paris
Bob habite à Lyon
Charlie habite à Marseille
...


4.3 WHERE - FILTRER LES DONNÉES
───────────────────────────────

# Clients de plus de 30 ans
cursor.execute("SELECT * FROM clients WHERE age > 30")

# Client spécifique
cursor.execute("SELECT * FROM clients WHERE nom = 'Alice'")

# Plusieurs conditions (AND)
cursor.execute("SELECT * FROM clients WHERE age > 25 AND ville = 'Paris'")

# Ou (OR)
cursor.execute("SELECT * FROM clients WHERE ville = 'Paris' OR ville = 'Lyon'")

# IN (liste de valeurs)
cursor.execute("SELECT * FROM clients WHERE ville IN ('Paris', 'Lyon', 'Marseille')")

# LIKE (recherche partielle)
cursor.execute("SELECT * FROM clients WHERE nom LIKE 'A%'")  # Commence par A

OPÉRATEURS :
• = : égal
• != ou <> : différent
• > : supérieur
• < : inférieur
• >= : supérieur ou égal
• <= : inférieur ou égal
• LIKE : correspondance partielle
• IN : dans une liste


4.4 ORDER BY - TRIER
────────────────────

# Trier par age (croissant)
cursor.execute("SELECT * FROM clients ORDER BY age")

# Trier par age (décroissant)
cursor.execute("SELECT * FROM clients ORDER BY age DESC")

# Trier par plusieurs colonnes
cursor.execute("SELECT * FROM clients ORDER BY ville, age DESC")


4.5 LIMIT - LIMITER LE NOMBRE DE RÉSULTATS
──────────────────────────────────────────

# Les 3 premiers clients
cursor.execute("SELECT * FROM clients LIMIT 3")

# Les 3 clients les plus âgés
cursor.execute("SELECT * FROM clients ORDER BY age DESC LIMIT 3")


4.6 DISTINCT - VALEURS UNIQUES
──────────────────────────────

# Liste des villes (sans doublons)
cursor.execute("SELECT DISTINCT ville FROM clients")


═══════════════════════════════════════════════════════════════════════════════
PARTIE 5 : FONCTIONS D'AGRÉGATION
═══════════════════════════════════════════════════════════════════════════════

5.1 COUNT - COMPTER
───────────────────

# Nombre total de clients
cursor.execute("SELECT COUNT(*) FROM clients")
resultat = cursor.fetchone()
print(f"Nombre de clients : {resultat[0]}")

# Nombre de clients à Paris
cursor.execute("SELECT COUNT(*) FROM clients WHERE ville = 'Paris'")


5.2 SUM - SOMME
───────────────

# Créons d'abord une table commandes
cursor.execute('''
CREATE TABLE IF NOT EXISTS commandes (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    client_id INTEGER,
    produit TEXT,
    montant REAL
)
''')

donnees_commandes = [
    (1, 'Laptop', 899.99),
    (1, 'Souris', 25.50),
    (2, 'Clavier', 75.00),
    (3, 'Écran', 299.99)
]

cursor.executemany("INSERT INTO commandes (client_id, produit, montant) VALUES (?, ?, ?)", 
                   donnees_commandes)
conn.commit()

# Somme de tous les montants
cursor.execute("SELECT SUM(montant) FROM commandes")
resultat = cursor.fetchone()
print(f"Montant total : {resultat[0]:.2f}€")


5.3 AVG - MOYENNE
─────────────────

# Montant moyen des commandes
cursor.execute("SELECT AVG(montant) FROM commandes")
resultat = cursor.fetchone()
print(f"Montant moyen : {resultat[0]:.2f}€")


5.4 MIN et MAX
──────────────

# Commande la plus petite
cursor.execute("SELECT MIN(montant) FROM commandes")

# Commande la plus grande
cursor.execute("SELECT MAX(montant) FROM commandes")


5.5 GROUP BY - GROUPER ET AGRÉGER
─────────────────────────────────

# Montant total par client
cursor.execute('''
SELECT client_id, SUM(montant) as total
FROM commandes
GROUP BY client_id
''')

resultats = cursor.fetchall()
for row in resultats:
    print(f"Client {row[0]} : {row[1]:.2f}€")

RÉSULTAT :
Client 1 : 925.49€
Client 2 : 75.00€
Client 3 : 299.99€


5.6 HAVING - FILTRER APRÈS GROUP BY
───────────────────────────────────

# Clients ayant dépensé plus de 100€
cursor.execute('''
SELECT client_id, SUM(montant) as total
FROM commandes
GROUP BY client_id
HAVING total > 100
''')

NOTE : WHERE filtre AVANT agrégation, HAVING filtre APRÈS


═══════════════════════════════════════════════════════════════════════════════
PARTIE 6 : JOIN - COMBINER DES TABLES (TRÈS IMPORTANT !)
═══════════════════════════════════════════════════════════════════════════════

6.1 POURQUOI LES JOIN ?
───────────────────────

Souvent, les données sont réparties dans plusieurs tables :
• Table "clients" : id, nom, ville
• Table "commandes" : id, client_id, produit, montant

Pour avoir nom ET montant, il faut JOINDRE les tables !


6.2 INNER JOIN (Le plus courant)
────────────────────────────────

Retourne seulement les lignes qui ont une correspondance dans les DEUX tables.

# Commandes avec nom du client
cursor.execute('''
SELECT clients.nom, commandes.produit, commandes.montant
FROM commandes
INNER JOIN clients ON commandes.client_id = clients.id
''')

resultats = cursor.fetchall()
for row in resultats:
    print(f"{row[0]} a commandé {row[1]} pour {row[2]:.2f}€")

RÉSULTAT :
Alice a commandé Laptop pour 899.99€
Alice a commandé Souris pour 25.50€
Bob a commandé Clavier pour 75.00€
Charlie a commandé Écran pour 299.99€

EXPLICATION :
• FROM commandes : Table principale
• INNER JOIN clients : Table à joindre
• ON commandes.client_id = clients.id : Condition de jointure


6.3 LEFT JOIN
─────────────

Retourne TOUTES les lignes de la table de gauche + correspondances de droite.

# Tous les clients, même ceux sans commande
cursor.execute('''
SELECT clients.nom, commandes.produit
FROM clients
LEFT JOIN commandes ON clients.id = commandes.client_id
''')

-> David et Eve apparaîtront avec NULL pour produit (pas de commande)


6.4 EXEMPLE COMPLET AVEC JOIN + GROUP BY
─────────────────────────────────────────

# Montant total dépensé par client (avec nom)
cursor.execute('''
SELECT clients.nom, clients.ville, SUM(commandes.montant) as total
FROM clients
LEFT JOIN commandes ON clients.id = commandes.client_id
GROUP BY clients.id
ORDER BY total DESC
''')

resultats = cursor.fetchall()
print("Dépenses par client :")
for row in resultats:
    nom, ville, total = row
    total_str = f"{total:.2f}€" if total else "0.00€"
    print(f"{nom} ({ville}) : {total_str}")


═══════════════════════════════════════════════════════════════════════════════
PARTIE 7 : PYTHON + SQL + PANDAS (COMBINAISON PUISSANTE)
═══════════════════════════════════════════════════════════════════════════════

7.1 IMPORTER RÉSULTATS SQL DANS PANDAS
──────────────────────────────────────

import sqlite3
import pandas as pd

conn = sqlite3.connect('magasin.db')

# Méthode 1 : Directement avec Pandas
df = pd.read_sql_query("SELECT * FROM clients", conn)
print(df)

# Méthode 2 : Requête complexe
query = '''
SELECT clients.nom, clients.ville, SUM(commandes.montant) as total
FROM clients
LEFT JOIN commandes ON clients.id = commandes.client_id
GROUP BY clients.id
'''

df_ventes = pd.read_sql_query(query, conn)
print(df_ventes)

conn.close()

-> Maintenant vous pouvez utiliser TOUTE LA PUISSANCE de Pandas !


7.2 EXPORTER DATAFRAME PANDAS VERS SQL
──────────────────────────────────────

import pandas as pd
import sqlite3

# Créer un DataFrame
data = {
    'nom': ['Frank', 'Grace'],
    'age': [40, 28],
    'ville': ['Nice', 'Strasbourg']
}
df = pd.DataFrame(data)

# Exporter vers SQL
conn = sqlite3.connect('magasin.db')
df.to_sql('clients', conn, if_exists='append', index=False)
conn.close()

print("[OK] DataFrame exporté vers SQL")

OPTIONS if_exists :
• 'fail' : Erreur si table existe
• 'replace' : Remplacer la table
• 'append' : Ajouter à la table existante


═══════════════════════════════════════════════════════════════════════════════
EXERCICE PRATIQUE COMPLET : BASE DE DONNÉES E-COMMERCE
═══════════════════════════════════════════════════════════════════════════════

[NOTE] ÉNONCÉ :

Créez une base de données complète pour un e-commerce avec :

1. Table "produits" : id, nom, categorie, prix
2. Table "clients" : id, nom, email, ville
3. Table "commandes" : id, client_id, produit_id, quantite, date

Puis répondez à ces questions avec SQL :
1. Liste de tous les produits avec leur catégorie
2. Nombre total de clients
3. Montant total des ventes
4. Top 3 produits les plus vendus (quantité)
5. Client ayant dépensé le plus
6. Ventes par catégorie


─────────────────────────────────────────────────────────────────────────────
[IDEE] SOLUTION COMPLÈTE :
─────────────────────────────────────────────────────────────────────────────

import sqlite3
import pandas as pd

print("=" * 80)
print("EXERCICE SQL - BASE DE DONNÉES E-COMMERCE")
print("=" * 80)
print()

# Connexion
conn = sqlite3.connect('ecommerce.db')
cursor = conn.cursor()

# ═══════════════════════════════════════════════════════════════════════════
# ÉTAPE 1 : CRÉER LES TABLES
# ═══════════════════════════════════════════════════════════════════════════

print("[LISTE] Création des tables...")

# Table produits
cursor.execute('''
CREATE TABLE IF NOT EXISTS produits (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    nom TEXT NOT NULL,
    categorie TEXT,
    prix REAL
)
''')

# Table clients
cursor.execute('''
CREATE TABLE IF NOT EXISTS clients (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    nom TEXT NOT NULL,
    email TEXT UNIQUE,
    ville TEXT
)
''')

# Table commandes
cursor.execute('''
CREATE TABLE IF NOT EXISTS commandes (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    client_id INTEGER,
    produit_id INTEGER,
    quantite INTEGER,
    date TEXT,
    FOREIGN KEY (client_id) REFERENCES clients(id),
    FOREIGN KEY (produit_id) REFERENCES produits(id)
)
''')

conn.commit()
print("[OK] Tables créées")
print()


# ═══════════════════════════════════════════════════════════════════════════
# ÉTAPE 2 : INSÉRER LES DONNÉES
# ═══════════════════════════════════════════════════════════════════════════

print("[ENTREE] Insertion des données...")

# Produits
produits_data = [
    ('Laptop Dell', 'Informatique', 899.99),
    ('Souris Logitech', 'Informatique', 25.50),
    ('Clavier Mécanique', 'Informatique', 89.99),
    ('Écran Samsung', 'Informatique', 299.99),
    ('Bureau Standing', 'Mobilier', 450.00),
    ('Chaise Ergonomique', 'Mobilier', 220.00)
]
cursor.executemany("INSERT INTO produits (nom, categorie, prix) VALUES (?, ?, ?)", produits_data)

# Clients
clients_data = [
    ('Alice Martin', 'alice@email.com', 'Paris'),
    ('Bob Dupont', 'bob@email.com', 'Lyon'),
    ('Charlie Bernard', 'charlie@email.com', 'Marseille'),
    ('Diana Petit', 'diana@email.com', 'Paris')
]
cursor.executemany("INSERT INTO clients (nom, email, ville) VALUES (?, ?, ?)", clients_data)

# Commandes
commandes_data = [
    (1, 1, 2, '2024-01-15'),  # Alice achète 2 Laptops
    (1, 2, 5, '2024-01-15'),  # Alice achète 5 Souris
    (2, 3, 3, '2024-02-10'),  # Bob achète 3 Claviers
    (3, 4, 1, '2024-02-20'),  # Charlie achète 1 Écran
    (1, 5, 1, '2024-03-05'),  # Alice achète 1 Bureau
    (4, 6, 2, '2024-03-10'),  # Diana achète 2 Chaises
    (2, 1, 1, '2024-03-15'),  # Bob achète 1 Laptop
    (3, 2, 10, '2024-04-01')  # Charlie achète 10 Souris
]
cursor.executemany("INSERT INTO commandes (client_id, produit_id, quantite, date) VALUES (?, ?, ?, ?)", 
                   commandes_data)

conn.commit()
print("[OK] Données insérées")
print()


# ═══════════════════════════════════════════════════════════════════════════
# ÉTAPE 3 : REQUÊTES SQL
# ═══════════════════════════════════════════════════════════════════════════

print("─" * 80)
print("ANALYSES SQL")
print("─" * 80)
print()

# Question 1 : Liste des produits
print("1⃣  LISTE DES PRODUITS :")
cursor.execute("SELECT nom, categorie, prix FROM produits ORDER BY categorie, prix DESC")
for row in cursor.fetchall():
    print(f"   {row[0]:<25} | {row[1]:<15} | {row[2]:>8.2f}€")
print()

# Question 2 : Nombre de clients
cursor.execute("SELECT COUNT(*) FROM clients")
nb_clients = cursor.fetchone()[0]
print(f"2⃣  NOMBRE TOTAL DE CLIENTS : {nb_clients}")
print()

# Question 3 : Montant total des ventes
cursor.execute('''
SELECT SUM(commandes.quantite * produits.prix) as total
FROM commandes
INNER JOIN produits ON commandes.produit_id = produits.id
''')
total_ventes = cursor.fetchone()[0]
print(f"3⃣  MONTANT TOTAL DES VENTES : {total_ventes:,.2f}€")
print()

# Question 4 : Top 3 produits
print("4⃣  TOP 3 PRODUITS LES PLUS VENDUS :")
cursor.execute('''
SELECT produits.nom, SUM(commandes.quantite) as total_quantite
FROM commandes
INNER JOIN produits ON commandes.produit_id = produits.id
GROUP BY produits.id
ORDER BY total_quantite DESC
LIMIT 3
''')
for i, row in enumerate(cursor.fetchall(), 1):
    print(f"   {i}. {row[0]:<25} : {row[1]} unités")
print()

# Question 5 : Client ayant le plus dépensé
print("5⃣  CLIENT AYANT LE PLUS DÉPENSÉ :")
cursor.execute('''
SELECT clients.nom, clients.ville, SUM(commandes.quantite * produits.prix) as total
FROM clients
INNER JOIN commandes ON clients.id = commandes.client_id
INNER JOIN produits ON commandes.produit_id = produits.id
GROUP BY clients.id
ORDER BY total DESC
LIMIT 1
''')
row = cursor.fetchone()
print(f"   {row[0]} ({row[1]}) : {row[2]:,.2f}€")
print()

# Question 6 : Ventes par catégorie
print("6⃣  VENTES PAR CATÉGORIE :")
cursor.execute('''
SELECT produits.categorie, 
       SUM(commandes.quantite) as quantite_totale,
       SUM(commandes.quantite * produits.prix) as montant_total
FROM commandes
INNER JOIN produits ON commandes.produit_id = produits.id
GROUP BY produits.categorie
ORDER BY montant_total DESC
''')
for row in cursor.fetchall():
    print(f"   {row[0]:<15} : {row[1]:>3} unités | {row[2]:>10,.2f}€")
print()


# ═══════════════════════════════════════════════════════════════════════════
# BONUS : EXPORT VERS PANDAS
# ═══════════════════════════════════════════════════════════════════════════

print("─" * 80)
print("BONUS : EXPORT VERS PANDAS")
print("─" * 80)
print()

query = '''
SELECT 
    clients.nom as client,
    clients.ville,
    produits.nom as produit,
    produits.categorie,
    commandes.quantite,
    produits.prix,
    (commandes.quantite * produits.prix) as montant_total,
    commandes.date
FROM commandes
INNER JOIN clients ON commandes.client_id = clients.id
INNER JOIN produits ON commandes.produit_id = produits.id
ORDER BY commandes.date
'''

df = pd.read_sql_query(query, conn)
print("[GRAPHIQUE] DataFrame Pandas :")
print(df)
print()

print("[HAUSSE] Statistiques avec Pandas :")
print(df.groupby('categorie')['montant_total'].sum())
print()

conn.close()

print("=" * 80)
print("[OK] EXERCICE TERMINÉ !")
print("=" * 80)


═══════════════════════════════════════════════════════════════════════════════
[GUIDE] RÉSUMÉ DU CHAPITRE
═══════════════════════════════════════════════════════════════════════════════

[OK] Vous savez maintenant :

1. Créer des bases de données et tables (CREATE TABLE)
2. Insérer des données (INSERT)
3. Interroger avec SELECT (WHERE, ORDER BY, LIMIT)
4. Fonctions d'agrégation (COUNT, SUM, AVG, MIN, MAX)
5. Grouper (GROUP BY, HAVING)
6. Joindre des tables (INNER JOIN, LEFT JOIN)
7. Connecter Python et SQL (sqlite3)
8. Importer/exporter avec Pandas

[OBJECTIF] PROCHAINES ÉTAPES :

Chapitre 9 : Séries Temporelles & Tests Statistiques Avancés


═══════════════════════════════════════════════════════════════════════════════
[IDEE] RÈGLES D'OR DU SQL
═══════════════════════════════════════════════════════════════════════════════

[OK] Toujours utiliser des placeholders (?) pour éviter SQL injection
[OK] Commit après INSERT/UPDATE/DELETE
[OK] Fermer les connexions (conn.close())
[OK] Utiliser des index sur colonnes fréquemment filtrées (WHERE)
[OK] JOIN plutôt que sous-requêtes quand possible (plus rapide)
[OK] LIMIT pour tester les requêtes sur gros volumes


═══════════════════════════════════════════════════════════════════════════════
FIN DU CHAPITRE 8
═══════════════════════════════════════════════════════════════════════════════

═══════════════════════════════════════════════════════════════════════════════
    CHAPITRE 9 : SÉRIES TEMPORELLES & TESTS STATISTIQUES AVANCÉS
═══════════════════════════════════════════════════════════════════════════════

[LIVRE] OBJECTIFS DU CHAPITRE :
   • Maîtriser les dates et séries temporelles avec Pandas
   • Identifier tendances, saisonnalité, cycles
   • Effectuer des tests statistiques (t-test, chi-2)
   • Réaliser des A/B tests professionnels
   • Analyser des cohortes (rétention clients)
   • Décomposer des séries temporelles
   • Appliquer tout cela sur des cas réels

[ATTENTION] NIVEAU EXPERT - Ces compétences vous différencient des autres !

═══════════════════════════════════════════════════════════════════════════════
PARTIE 1 : SÉRIES TEMPORELLES AVEC PANDAS
═══════════════════════════════════════════════════════════════════════════════

1.1 QU'EST-CE QU'UNE SÉRIE TEMPORELLE ?
───────────────────────────────────────

Une série temporelle est une suite de données ordonnées dans le temps.

EXEMPLES :
• Ventes quotidiennes d'un magasin
• Cours d'une action en bourse
• Température journalière
• Nombre de visiteurs sur un site web

COMPOSANTES D'UNE SÉRIE TEMPORELLE :
┌──────────────────────────────────────────────────────────────────┐
│ 1. TENDANCE (Trend)                                               │
│    -> Direction générale (hausse, baisse, stable)                  │
│                                                                    │
│ 2. SAISONNALITÉ (Seasonality)                                     │
│    -> Variations régulières (mensuelles, annuelles)                │
│                                                                    │
│ 3. CYCLES                                                         │
│    -> Variations à long terme (économiques)                        │
│                                                                    │
│ 4. BRUIT (Noise)                                                  │
│    -> Variations aléatoires                                        │
└──────────────────────────────────────────────────────────────────┘


1.2 MANIPULER DES DATES AVEC PANDAS
───────────────────────────────────

import pandas as pd
import numpy as np
from datetime import datetime, timedelta

# Créer une date
date1 = pd.to_datetime('2024-01-15')
print(date1)  # 2024-01-15 00:00:00

# Créer une plage de dates
dates = pd.date_range(start='2024-01-01', end='2024-12-31', freq='D')
print(f"Nombre de dates : {len(dates)}")  # 366 jours (2024 est bissextile)

# Fréquences disponibles :
# 'D' : jour, 'W' : semaine, 'M' : mois, 'Y' : année
# 'H' : heure, 'T' : minute, 'S' : seconde

# Dates par mois
dates_mensuelles = pd.date_range(start='2024-01-01', end='2024-12-31', freq='M')
print(f"Mois : {len(dates_mensuelles)}")  # 12 mois


1.3 CRÉER UNE SÉRIE TEMPORELLE
──────────────────────────────

# Créer des données de ventes simulées
dates = pd.date_range(start='2024-01-01', periods=365, freq='D')
ventes = np.random.randint(100, 500, size=365)

# Créer un DataFrame
df = pd.DataFrame({
    'date': dates,
    'ventes': ventes
})

# Définir la date comme index
df.set_index('date', inplace=True)

print(df.head())
#             ventes
# date              
# 2024-01-01     234
# 2024-01-02     387
# 2024-01-03     156
# ...


1.4 EXTRAIRE DES INFORMATIONS TEMPORELLES
─────────────────────────────────────────

# Ajouter des colonnes temporelles
df['annee'] = df.index.year
df['mois'] = df.index.month
df['jour'] = df.index.day
df['jour_semaine'] = df.index.dayofweek  # 0=Lundi, 6=Dimanche
df['nom_jour'] = df.index.day_name()
df['nom_mois'] = df.index.month_name()
df['semaine'] = df.index.isocalendar().week

print(df.head())


1.5 RESAMPLE - RÉÉCHANTILLONNER LES DONNÉES
───────────────────────────────────────────

# Agréger par semaine
ventes_hebdo = df['ventes'].resample('W').sum()
print(ventes_hebdo.head())

# Agréger par mois
ventes_mensuelles = df['ventes'].resample('M').agg({
    'sum': 'sum',
    'mean': 'mean',
    'max': 'max'
})
print(ventes_mensuelles)


1.6 ROLLING WINDOW - MOYENNES MOBILES
─────────────────────────────────────

# Moyenne mobile sur 7 jours
df['moyenne_7j'] = df['ventes'].rolling(window=7).mean()

# Moyenne mobile sur 30 jours
df['moyenne_30j'] = df['ventes'].rolling(window=30).mean()

# Visualiser
import matplotlib.pyplot as plt

plt.figure(figsize=(14, 6))
plt.plot(df.index, df['ventes'], label='Ventes quotidiennes', alpha=0.5)
plt.plot(df.index, df['moyenne_7j'], label='Moyenne 7 jours', linewidth=2)
plt.plot(df.index, df['moyenne_30j'], label='Moyenne 30 jours', linewidth=2)
plt.title('Ventes avec Moyennes Mobiles', fontsize=16, fontweight='bold')
plt.xlabel('Date')
plt.ylabel('Ventes')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()


1.7 SHIFT - DÉCALAGE TEMPOREL
─────────────────────────────

# Ventes du jour précédent
df['ventes_hier'] = df['ventes'].shift(1)

# Ventes du même jour semaine dernière
df['ventes_semaine_derniere'] = df['ventes'].shift(7)

# Variation par rapport à hier
df['variation'] = df['ventes'] - df['ventes_hier']
df['variation_pct'] = ((df['ventes'] - df['ventes_hier']) / df['ventes_hier']) * 100


═══════════════════════════════════════════════════════════════════════════════
PARTIE 2 : DÉCOMPOSITION DE SÉRIES TEMPORELLES
═══════════════════════════════════════════════════════════════════════════════

2.1 INSTALLER STATSMODELS
─────────────────────────

# Dans le terminal :
# pip install statsmodels

from statsmodels.tsa.seasonal import seasonal_decompose


2.2 DÉCOMPOSER UNE SÉRIE
────────────────────────

# Créer une série avec tendance + saisonnalité
dates = pd.date_range(start='2022-01-01', periods=730, freq='D')
tendance = np.linspace(100, 200, 730)
saisonnalite = 50 * np.sin(np.linspace(0, 4*np.pi, 730))
bruit = np.random.randn(730) * 10
serie = tendance + saisonnalite + bruit

df_serie = pd.DataFrame({
    'date': dates,
    'valeur': serie
}).set_index('date')

# Décomposition
decomposition = seasonal_decompose(df_serie['valeur'], model='additive', period=365)

# Visualiser
fig, axes = plt.subplots(4, 1, figsize=(14, 10))

decomposition.observed.plot(ax=axes[0], title='Données Originales')
decomposition.trend.plot(ax=axes[1], title='Tendance')
decomposition.seasonal.plot(ax=axes[2], title='Saisonnalité')
decomposition.resid.plot(ax=axes[3], title='Résidus (Bruit)')

plt.tight_layout()
plt.show()


═══════════════════════════════════════════════════════════════════════════════
PARTIE 3 : TESTS STATISTIQUES
═══════════════════════════════════════════════════════════════════════════════

3.1 TEST T DE STUDENT - COMPARER DEUX MOYENNES
──────────────────────────────────────────────

from scipy import stats

# Exemple : Comparer les ventes entre deux magasins
magasin_A = [120, 135, 128, 142, 138, 145, 132, 140, 136, 144]
magasin_B = [110, 115, 108, 122, 118, 125, 112, 120, 116, 124]

# Test t pour échantillons indépendants
t_stat, p_value = stats.ttest_ind(magasin_A, magasin_B)

print(f"Statistique t : {t_stat:.4f}")
print(f"P-value : {p_value:.4f}")

# Interprétation
alpha = 0.05  # Seuil de signification
if p_value < alpha:
    print("[OK] Différence significative entre les deux magasins")
else:
    print("[X] Pas de différence significative")

# EXPLICATION :
# H0 (hypothèse nulle) : Les moyennes sont égales
# H1 (hypothèse alternative) : Les moyennes sont différentes
# Si p-value < 0.05 -> On rejette H0 -> Différence significative


3.2 TEST DU CHI-2 - VARIABLES CATÉGORIELLES
───────────────────────────────────────────

# Exemple : Moyen de paiement vs Ville
data = {
    'ville': ['Paris']*50 + ['Lyon']*50 + ['Marseille']*50,
    'paiement': ['CB']*30 + ['PayPal']*20 + 
                ['CB']*25 + ['PayPal']*25 + 
                ['CB']*20 + ['PayPal']*30
}
df_paiement = pd.DataFrame(data)

# Table de contingence
table_contingence = pd.crosstab(df_paiement['ville'], df_paiement['paiement'])
print(table_contingence)

# Test Chi-2
chi2, p_value, dof, expected = stats.chi2_contingency(table_contingence)

print(f"\nChi-2 : {chi2:.4f}")
print(f"P-value : {p_value:.4f}")

if p_value < 0.05:
    print("[OK] Relation significative entre ville et moyen de paiement")
else:
    print("[X] Pas de relation significative")


3.3 A/B TESTING - TESTER DEUX VERSIONS
──────────────────────────────────────

# Exemple : Comparer deux designs de site web
# Version A (ancien design) : 100 visiteurs, 12 conversions
# Version B (nouveau design) : 100 visiteurs, 18 conversions

conversions_A = 12
visiteurs_A = 100

conversions_B = 18
visiteurs_B = 100

# Taux de conversion
taux_A = conversions_A / visiteurs_A
taux_B = conversions_B / visiteurs_B

print(f"Taux de conversion A : {taux_A:.1%}")
print(f"Taux de conversion B : {taux_B:.1%}")

# Test de proportion
from statsmodels.stats.proportion import proportions_ztest

counts = np.array([conversions_A, conversions_B])
nobs = np.array([visiteurs_A, visiteurs_B])

z_stat, p_value = proportions_ztest(counts, nobs)

print(f"\nZ-statistic : {z_stat:.4f}")
print(f"P-value : {p_value:.4f}")

if p_value < 0.05:
    print("[OK] Version B est significativement meilleure")
else:
    print("[X] Pas de différence significative - continuer le test")


═══════════════════════════════════════════════════════════════════════════════
PARTIE 4 : ANALYSE DE COHORTES
═══════════════════════════════════════════════════════════════════════════════

4.1 QU'EST-CE QU'UNE COHORTE ?
─────────────────────────────

Une cohorte est un groupe d'utilisateurs qui partagent une caractéristique commune.

EXEMPLE :
• Cohorte Janvier 2024 : Tous les clients ayant fait leur 1er achat en janvier
• Analyse : Combien reviennent acheter les mois suivants ? (rétention)


4.2 CRÉER UNE ANALYSE DE COHORTE
────────────────────────────────

# Données : achats clients
data = {
    'client_id': [1, 1, 1, 2, 2, 3, 3, 3, 4, 5, 5],
    'date': pd.to_datetime([
        '2024-01-15', '2024-02-10', '2024-03-05',
        '2024-01-20', '2024-03-15',
        '2024-02-05', '2024-02-20', '2024-04-10',
        '2024-01-25',
        '2024-03-01', '2024-03-30'
    ]),
    'montant': [100, 150, 120, 200, 180, 90, 110, 130, 160, 140, 170]
}

df_achats = pd.DataFrame(data)

# Identifier la cohorte (mois du 1er achat)
df_achats['mois_achat'] = df_achats['date'].dt.to_period('M')
premiere_date = df_achats.groupby('client_id')['date'].min().reset_index()
premiere_date.columns = ['client_id', 'premiere_date']
premiere_date['cohorte'] = premiere_date['premiere_date'].dt.to_period('M')

# Fusionner
df_achats = df_achats.merge(premiere_date[['client_id', 'cohorte']], on='client_id')

# Période depuis le 1er achat
df_achats['periode'] = (df_achats['mois_achat'] - df_achats['cohorte']).apply(lambda x: x.n)

# Tableau de rétention
retention = df_achats.groupby(['cohorte', 'periode'])['client_id'].nunique().unstack(fill_value=0)

# Taux de rétention (% par rapport au mois 0)
retention_pct = retention.div(retention[0], axis=0) * 100

print("Taux de rétention (%) :")
print(retention_pct)

# Visualiser avec heatmap
import seaborn as sns

plt.figure(figsize=(10, 6))
sns.heatmap(retention_pct, annot=True, fmt='.0f', cmap='YlGnBu', cbar_kws={'label': 'Rétention (%)'})
plt.title('Analyse de Cohorte - Taux de Rétention', fontsize=16, fontweight='bold')
plt.xlabel('Période (mois depuis 1er achat)')
plt.ylabel('Cohorte (mois 1er achat)')
plt.tight_layout()
plt.show()


═══════════════════════════════════════════════════════════════════════════════
EXERCICE PRATIQUE COMPLET : ANALYSE TEMPORELLE DE VENTES
═══════════════════════════════════════════════════════════════════════════════

[NOTE] ÉNONCÉ :

Vous avez 2 ans de données de ventes quotidiennes d'un e-commerce.

MISSION :
1. Charger et explorer les données
2. Identifier la tendance générale
3. Calculer les moyennes mobiles (7 et 30 jours)
4. Décomposer la série (tendance + saisonnalité)
5. Comparer les ventes entre semaine et week-end (test t)
6. Analyser la saisonnalité mensuelle
7. Créer un rapport visuel complet


─────────────────────────────────────────────────────────────────────────────
[IDEE] SOLUTION COMPLÈTE :
─────────────────────────────────────────────────────────────────────────────

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from scipy import stats
from statsmodels.tsa.seasonal import seasonal_decompose

print("=" * 80)
print("ANALYSE DE SÉRIES TEMPORELLES - VENTES E-COMMERCE")
print("=" * 80)
print()

# ═══════════════════════════════════════════════════════════════════════════
# ÉTAPE 1 : CRÉER DES DONNÉES RÉALISTES
# ═══════════════════════════════════════════════════════════════════════════

dates = pd.date_range(start='2022-01-01', end='2023-12-31', freq='D')
n_jours = len(dates)

# Tendance croissante
tendance = np.linspace(1000, 1500, n_jours)

# Saisonnalité annuelle
saisonnalite_annuelle = 300 * np.sin(2 * np.pi * np.arange(n_jours) / 365.25)

# Saisonnalité hebdomadaire (plus de ventes le week-end)
saisonnalite_hebdo = 200 * np.sin(2 * np.pi * np.arange(n_jours) / 7 + np.pi/2)

# Bruit
bruit = np.random.randn(n_jours) * 100

# Série finale
ventes = tendance + saisonnalite_annuelle + saisonnalite_hebdo + bruit
ventes = np.maximum(ventes, 0)  # Pas de ventes négatives

df = pd.DataFrame({
    'date': dates,
    'ventes': ventes
}).set_index('date')

print("[OK] Données créées : 2 ans de ventes quotidiennes")
print(f"Période : {df.index.min()} à {df.index.max()}")
print(f"Nombre de jours : {len(df)}")
print()

# ═══════════════════════════════════════════════════════════════════════════
# ÉTAPE 2 : EXPLORATION INITIALE
# ═══════════════════════════════════════════════════════════════════════════

print("─" * 80)
print("STATISTIQUES DESCRIPTIVES")
print("─" * 80)
print(df['ventes'].describe())
print()

# ═══════════════════════════════════════════════════════════════════════════
# ÉTAPE 3 : MOYENNES MOBILES
# ═══════════════════════════════════════════════════════════════════════════

df['ma_7'] = df['ventes'].rolling(window=7).mean()
df['ma_30'] = df['ventes'].rolling(window=30).mean()

# ═══════════════════════════════════════════════════════════════════════════
# ÉTAPE 4 : DÉCOMPOSITION
# ═══════════════════════════════════════════════════════════════════════════

decomposition = seasonal_decompose(df['ventes'], model='additive', period=365)

# ═══════════════════════════════════════════════════════════════════════════
# ÉTAPE 5 : TEST SEMAINE vs WEEK-END
# ═══════════════════════════════════════════════════════════════════════════

df['jour_semaine'] = df.index.dayofweek
df['est_weekend'] = df['jour_semaine'] >= 5

ventes_semaine = df[~df['est_weekend']]['ventes']
ventes_weekend = df[df['est_weekend']]['ventes']

t_stat, p_value = stats.ttest_ind(ventes_semaine, ventes_weekend)

print("─" * 80)
print("TEST T : SEMAINE vs WEEK-END")
print("─" * 80)
print(f"Moyenne semaine : {ventes_semaine.mean():.2f}€")
print(f"Moyenne week-end : {ventes_weekend.mean():.2f}€")
print(f"T-statistic : {t_stat:.4f}")
print(f"P-value : {p_value:.4f}")

if p_value < 0.05:
    print("[OK] Différence SIGNIFICATIVE entre semaine et week-end")
else:
    print("[X] Pas de différence significative")
print()

# ═══════════════════════════════════════════════════════════════════════════
# ÉTAPE 6 : SAISONNALITÉ MENSUELLE
# ═══════════════════════════════════════════════════════════════════════════

df['mois'] = df.index.month
ventes_par_mois = df.groupby('mois')['ventes'].agg(['mean', 'sum'])
ventes_par_mois.index = ['Jan', 'Fév', 'Mar', 'Avr', 'Mai', 'Jun', 
                          'Jul', 'Aoû', 'Sep', 'Oct', 'Nov', 'Déc']

print("─" * 80)
print("VENTES PAR MOIS")
print("─" * 80)
print(ventes_par_mois)
print()

# ═══════════════════════════════════════════════════════════════════════════
# ÉTAPE 7 : VISUALISATIONS
# ═══════════════════════════════════════════════════════════════════════════

fig = plt.figure(figsize=(18, 14))

# Graphique 1 : Série originale + moyennes mobiles
ax1 = plt.subplot(4, 2, (1, 2))
ax1.plot(df.index, df['ventes'], label='Ventes quotidiennes', alpha=0.4, linewidth=0.8)
ax1.plot(df.index, df['ma_7'], label='Moyenne 7 jours', linewidth=2)
ax1.plot(df.index, df['ma_30'], label='Moyenne 30 jours', linewidth=2)
ax1.set_title('Ventes Quotidiennes avec Moyennes Mobiles', fontsize=14, fontweight='bold')
ax1.set_xlabel('Date')
ax1.set_ylabel('Ventes (€)')
ax1.legend()
ax1.grid(True, alpha=0.3)

# Graphique 2 : Tendance
ax2 = plt.subplot(4, 2, 3)
decomposition.trend.plot(ax=ax2, color='orange', linewidth=2)
ax2.set_title('Tendance', fontsize=12, fontweight='bold')
ax2.grid(True, alpha=0.3)

# Graphique 3 : Saisonnalité
ax3 = plt.subplot(4, 2, 4)
decomposition.seasonal.plot(ax=ax3, color='green', linewidth=1)
ax3.set_title('Saisonnalité', fontsize=12, fontweight='bold')
ax3.grid(True, alpha=0.3)

# Graphique 4 : Ventes par mois
ax4 = plt.subplot(4, 2, 5)
ax4.bar(ventes_par_mois.index, ventes_par_mois['mean'], color='skyblue', edgecolor='black')
ax4.set_title('Ventes Moyennes par Mois', fontsize=12, fontweight='bold')
ax4.set_xlabel('Mois')
ax4.set_ylabel('Ventes Moyennes (€)')
ax4.grid(True, alpha=0.3, axis='y')

# Graphique 5 : Semaine vs Week-end
ax5 = plt.subplot(4, 2, 6)
data_box = [ventes_semaine, ventes_weekend]
ax5.boxplot(data_box, labels=['Semaine', 'Week-end'])
ax5.set_title('Distribution Semaine vs Week-end', fontsize=12, fontweight='bold')
ax5.set_ylabel('Ventes (€)')
ax5.grid(True, alpha=0.3, axis='y')

# Graphique 6 : Distribution générale
ax6 = plt.subplot(4, 2, 7)
ax6.hist(df['ventes'], bins=50, color='purple', edgecolor='black', alpha=0.7)
ax6.axvline(df['ventes'].mean(), color='red', linestyle='--', linewidth=2, 
            label=f"Moyenne: {df['ventes'].mean():.0f}€")
ax6.set_title('Distribution des Ventes', fontsize=12, fontweight='bold')
ax6.set_xlabel('Ventes (€)')
ax6.set_ylabel('Fréquence')
ax6.legend()
ax6.grid(True, alpha=0.3)

# Graphique 7 : Résidus (bruit)
ax7 = plt.subplot(4, 2, 8)
decomposition.resid.plot(ax=ax7, color='gray', linewidth=0.5)
ax7.set_title('Résidus (Bruit)', fontsize=12, fontweight='bold')
ax7.grid(True, alpha=0.3)

plt.suptitle('[GRAPHIQUE] ANALYSE COMPLÈTE DES SÉRIES TEMPORELLES - VENTES E-COMMERCE', 
             fontsize=16, fontweight='bold', y=0.995)
plt.tight_layout()
plt.show()

print("=" * 80)
print("[OK] ANALYSE TERMINÉE")
print("=" * 80)


═══════════════════════════════════════════════════════════════════════════════
[GUIDE] RÉSUMÉ DU CHAPITRE
═══════════════════════════════════════════════════════════════════════════════

[OK] Vous savez maintenant :

1. Manipuler des dates avec Pandas
2. Créer et analyser des séries temporelles
3. Calculer des moyennes mobiles (rolling)
4. Rééchantillonner (resample)
5. Décomposer une série (tendance + saisonnalité + bruit)
6. Effectuer des tests statistiques (t-test, chi-2)
7. Réaliser des A/B tests
8. Analyser des cohortes de clients
9. Identifier des patterns temporels

[OBJECTIF] PROCHAINES ÉTAPES :

Chapitre 10 : Introduction au Machine Learning


═══════════════════════════════════════════════════════════════════════════════
FIN DU CHAPITRE 9
═══════════════════════════════════════════════════════════════════════════════

═══════════════════════════════════════════════════════════════════════════════
    CHAPITRE 10 : INTRODUCTION AU MACHINE LEARNING (GRAND DÉBUTANT)
═══════════════════════════════════════════════════════════════════════════════

[LIVRE] OBJECTIFS DU CHAPITRE :
   • Comprendre ce qu'est le Machine Learning
   • Différencier régression et classification
   • Créer votre premier modèle de prédiction
   • Évaluer la performance d'un modèle
   • Réaliser une segmentation de clients (clustering)
   • Appliquer le ML sur des cas réels

[ATTENTION] Ce chapitre vous ouvre les portes de la DATA SCIENCE !

INSTALLATION REQUISE :
pip install scikit-learn

═══════════════════════════════════════════════════════════════════════════════
PARTIE 1 : COMPRENDRE LE MACHINE LEARNING
═══════════════════════════════════════════════════════════════════════════════

1.1 QU'EST-CE QUE LE MACHINE LEARNING ?
───────────────────────────────────────

Le Machine Learning (ML) = Apprentissage Automatique

C'est l'art de faire APPRENDRE des patterns à un ordinateur à partir de données,
sans le programmer explicitement.

EXEMPLE SIMPLE :
• Vous montrez 1000 emails "spam" et 1000 emails "normaux"
• L'ordinateur apprend les différences (mots, structure, etc.)
• Il peut ensuite classer automatiquement de nouveaux emails

DIFFÉRENCE AVEC LA PROGRAMMATION CLASSIQUE :

Programmation classique :
IF email contient "gratuit" ET "urgent" -> SPAM

Machine Learning :
Données (10000 emails étiquetés) -> Algorithme -> Modèle qui prédit


1.2 LES 3 TYPES DE MACHINE LEARNING
───────────────────────────────────

┌─────────────────────────────────────────────────────────────────┐
│ 1. APPRENTISSAGE SUPERVISÉ (Supervised Learning)                │
│    -> On a les réponses (labels)                                 │
│    -> Exemples : Prédire prix maison, détecter spam              │
│    -> Algorithmes : Régression, Classification                   │
├─────────────────────────────────────────────────────────────────┤
│ 2. APPRENTISSAGE NON SUPERVISÉ (Unsupervised Learning)          │
│    -> On n'a PAS les réponses                                    │
│    -> Exemples : Segmenter clients, détecter anomalies           │
│    -> Algorithmes : Clustering, Réduction de dimensions          │
├─────────────────────────────────────────────────────────────────┤
│ 3. APPRENTISSAGE PAR RENFORCEMENT (Reinforcement Learning)      │
│    -> Apprendre par essai-erreur avec récompenses                │
│    -> Exemples : Jeux vidéo, robots                              │
│    -> Hors du scope de ce chapitre                               │
└─────────────────────────────────────────────────────────────────┘


1.3 RÉGRESSION vs CLASSIFICATION
────────────────────────────────

RÉGRESSION :
• Prédire une valeur NUMÉRIQUE
• Exemples : Prix d'une maison, température demain, ventes du mois
• Output : 125000€, 23.5°C, 4500 unités

CLASSIFICATION :
• Prédire une CATÉGORIE
• Exemples : Spam ou pas, chat ou chien, client fidèle ou non
• Output : "Spam", "Chat", "Fidèle"


1.4 VOCABULAIRE ESSENTIEL
─────────────────────────

• FEATURES (variables) : Les colonnes utilisées pour prédire (X)
• TARGET (cible) : Ce qu'on veut prédire (y)
• TRAINING SET : Données pour entraîner le modèle (70-80%)
• TEST SET : Données pour tester le modèle (20-30%)
• OVERFITTING : Le modèle mémorise au lieu d'apprendre (mauvais)
• UNDERFITTING : Le modèle est trop simple (mauvais)


═══════════════════════════════════════════════════════════════════════════════
PARTIE 2 : RÉGRESSION LINÉAIRE - PRÉDIRE DES VALEURS NUMÉRIQUES
═══════════════════════════════════════════════════════════════════════════════

2.1 CONCEPT DE LA RÉGRESSION LINÉAIRE
─────────────────────────────────────

Trouver la meilleure ligne droite qui passe près de tous les points.

Équation : y = ax + b
• a = pente (slope)
• b = ordonnée à l'origine (intercept)

EXEMPLE :
Prédire le prix d'une maison en fonction de sa surface.


2.2 PREMIER MODÈLE DE RÉGRESSION
────────────────────────────────

from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import r2_score, mean_absolute_error
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# Données : Surface (m²) vs Prix (k€)
data = {
    'surface': [50, 60, 70, 80, 90, 100, 110, 120, 130, 140],
    'prix': [150, 180, 210, 240, 270, 300, 330, 360, 390, 420]
}
df = pd.DataFrame(data)

# Préparer les données
X = df[['surface']]  # Features (doit être 2D)
y = df['prix']       # Target (1D)

# Diviser en train/test (80/20)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Créer et entraîner le modèle
model = LinearRegression()
model.fit(X_train, y_train)

print("[OK] Modèle entraîné !")
print(f"Coefficient (pente) : {model.coef_[0]:.2f}")
print(f"Intercept : {model.intercept_:.2f}")
print()

# Faire des prédictions
y_pred = model.predict(X_test)

# Évaluer
r2 = r2_score(y_test, y_pred)
mae = mean_absolute_error(y_test, y_pred)

print(f"R² Score : {r2:.4f}")  # Plus proche de 1 = meilleur
print(f"Erreur Absolue Moyenne : {mae:.2f}k€")
print()

# Prédire pour une nouvelle maison
nouvelle_surface = [[95]]
prix_predit = model.predict(nouvelle_surface)
print(f"Prédiction pour 95m² : {prix_predit[0]:.2f}k€")


# Visualisation
plt.figure(figsize=(10, 6))
plt.scatter(X_train, y_train, color='blue', label='Train', s=100)
plt.scatter(X_test, y_test, color='green', label='Test', s=100)
plt.plot(X, model.predict(X), color='red', linewidth=2, label='Régression')
plt.xlabel('Surface (m²)', fontsize=12)
plt.ylabel('Prix (k€)', fontsize=12)
plt.title('Régression Linéaire - Prix vs Surface', fontsize=14, fontweight='bold')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()


2.3 RÉGRESSION MULTIPLE (PLUSIEURS FEATURES)
────────────────────────────────────────────

# Prédire le prix avec surface + chambres + âge
data = {
    'surface': [50, 60, 70, 80, 90, 100, 110, 120, 130, 140],
    'chambres': [1, 2, 2, 3, 3, 4, 4, 5, 5, 6],
    'age': [30, 25, 20, 15, 10, 5, 8, 12, 3, 1],
    'prix': [150, 180, 210, 240, 270, 300, 330, 360, 390, 420]
}
df = pd.DataFrame(data)

X = df[['surface', 'chambres', 'age']]
y = df['prix']

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

model = LinearRegression()
model.fit(X_train, y_train)

y_pred = model.predict(X_test)
r2 = r2_score(y_test, y_pred)

print(f"R² avec plusieurs features : {r2:.4f}")


═══════════════════════════════════════════════════════════════════════════════
PARTIE 3 : CLASSIFICATION - PRÉDIRE DES CATÉGORIES
═══════════════════════════════════════════════════════════════════════════════

3.1 RÉGRESSION LOGISTIQUE (Classification Binaire)
──────────────────────────────────────────────────

from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, confusion_matrix, classification_report

# Exemple : Prédire si un client va acheter (1) ou non (0)
data = {
    'age': [25, 30, 35, 40, 45, 50, 55, 60, 65, 70],
    'revenu': [30, 35, 40, 45, 50, 55, 60, 65, 70, 75],
    'achete': [0, 0, 0, 1, 0, 1, 1, 1, 1, 1]
}
df = pd.DataFrame(data)

X = df[['age', 'revenu']]
y = df['achete']

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)

# Modèle de classification
model = LogisticRegression()
model.fit(X_train, y_train)

y_pred = model.predict(X_test)

# Évaluation
accuracy = accuracy_score(y_test, y_pred)
print(f"Précision : {accuracy:.2%}")
print()

print("Matrice de Confusion :")
print(confusion_matrix(y_test, y_pred))
print()

print("Rapport de Classification :")
print(classification_report(y_test, y_pred))


3.2 MATRICE DE CONFUSION EXPLIQUÉE
──────────────────────────────────

                PRÉDIT
             0 (Non)  1 (Oui)
RÉEL 0 (Non)   TN      FP      <- Faux Positif (erreur)
     1 (Oui)   FN      TP      <- Faux Négatif (erreur)
               ^
        Vrai Négatif  Vrai Positif

• TP (True Positive) : Prédit Oui, c'est Oui [OK]
• TN (True Negative) : Prédit Non, c'est Non [OK]
• FP (False Positive) : Prédit Oui, c'est Non [X] (Fausse alarme)
• FN (False Negative) : Prédit Non, c'est Oui [X] (Raté)

Précision (Accuracy) = (TP + TN) / Total


3.3 ARBRE DE DÉCISION
─────────────────────

from sklearn.tree import DecisionTreeClassifier
from sklearn import tree

# Exemple : Prédire si un fruit est une pomme ou orange
data = {
    'poids': [150, 170, 140, 130, 180, 160, 145, 175],
    'diametre': [7, 8, 7, 6, 9, 8, 7, 9],
    'fruit': ['pomme', 'orange', 'pomme', 'pomme', 'orange', 'orange', 'pomme', 'orange']
}
df = pd.DataFrame(data)

# Encoder les labels (pomme=0, orange=1)
df['fruit_encoded'] = df['fruit'].map({'pomme': 0, 'orange': 1})

X = df[['poids', 'diametre']]
y = df['fruit_encoded']

# Modèle d'arbre de décision
model = DecisionTreeClassifier(max_depth=3)
model.fit(X, y)

# Prédire
nouveau_fruit = [[155, 7.5]]
prediction = model.predict(nouveau_fruit)
fruit_nom = 'pomme' if prediction[0] == 0 else 'orange'
print(f"Prédiction : {fruit_nom}")

# Visualiser l'arbre (optionnel)
plt.figure(figsize=(12, 8))
tree.plot_tree(model, filled=True, feature_names=['poids', 'diametre'], 
               class_names=['pomme', 'orange'], rounded=True)
plt.title('Arbre de Décision', fontsize=14, fontweight='bold')
plt.tight_layout()
plt.show()


═══════════════════════════════════════════════════════════════════════════════
PARTIE 4 : CLUSTERING - SEGMENTER DES CLIENTS
═══════════════════════════════════════════════════════════════════════════════

4.1 K-MEANS CLUSTERING
──────────────────────

from sklearn.cluster import KMeans

# Exemple : Segmenter des clients selon Revenu et Dépenses
data = {
    'revenu': [30, 35, 40, 45, 50, 80, 85, 90, 95, 100, 60, 65, 70, 75],
    'depenses': [20, 25, 30, 28, 35, 70, 75, 80, 85, 90, 40, 45, 50, 55]
}
df = pd.DataFrame(data)

X = df[['revenu', 'depenses']]

# Appliquer K-means avec 3 clusters
kmeans = KMeans(n_clusters=3, random_state=42)
df['cluster'] = kmeans.fit_predict(X)

print("Centres des clusters :")
print(kmeans.cluster_centers_)
print()

# Visualiser
plt.figure(figsize=(10, 6))
colors = ['red', 'blue', 'green']
for i in range(3):
    cluster_data = df[df['cluster'] == i]
    plt.scatter(cluster_data['revenu'], cluster_data['depenses'], 
                c=colors[i], label=f'Cluster {i}', s=100, alpha=0.6)

# Afficher les centres
plt.scatter(kmeans.cluster_centers_[:, 0], kmeans.cluster_centers_[:, 1],
            c='black', marker='X', s=300, label='Centres')

plt.xlabel('Revenu (k€)', fontsize=12)
plt.ylabel('Dépenses (k€)', fontsize=12)
plt.title('Segmentation Clients (K-means)', fontsize=14, fontweight='bold')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()


4.2 DÉTERMINER LE NOMBRE OPTIMAL DE CLUSTERS (Méthode du Coude)
────────────────────────────────────────────────────────────────

inertias = []
K_range = range(1, 10)

for k in K_range:
    kmeans = KMeans(n_clusters=k, random_state=42)
    kmeans.fit(X)
    inertias.append(kmeans.inertia_)

plt.figure(figsize=(10, 6))
plt.plot(K_range, inertias, marker='o', linewidth=2, markersize=8)
plt.xlabel('Nombre de Clusters', fontsize=12)
plt.ylabel('Inertie', fontsize=12)
plt.title('Méthode du Coude - Nombre Optimal de Clusters', fontsize=14, fontweight='bold')
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

# Le "coude" indique le nombre optimal (souvent 3-4)


═══════════════════════════════════════════════════════════════════════════════
PARTIE 5 : VALIDATION ET BONNES PRATIQUES
═══════════════════════════════════════════════════════════════════════════════

5.1 NORMALISATION DES DONNÉES
─────────────────────────────

from sklearn.preprocessing import StandardScaler

# IMPORTANT : Normaliser quand les features ont des échelles différentes
# Exemple : Age (0-100) vs Salaire (20000-100000)

data = {
    'age': [25, 30, 35, 40, 45],
    'salaire': [30000, 50000, 70000, 90000, 110000]
}
df = pd.DataFrame(data)

scaler = StandardScaler()
df_scaled = scaler.fit_transform(df)

print("Données originales :")
print(df.head())
print("\nDonnées normalisées :")
print(df_scaled)


5.2 VALIDATION CROISÉE (Cross-Validation)
─────────────────────────────────────────

from sklearn.model_selection import cross_val_score

# Au lieu de diviser une seule fois en train/test,
# on divise plusieurs fois différemment

X = df[['surface', 'chambres', 'age']]
y = df['prix']

model = LinearRegression()

# 5-fold cross-validation
scores = cross_val_score(model, X, y, cv=5, scoring='r2')

print(f"Scores R² pour chaque fold : {scores}")
print(f"Moyenne : {scores.mean():.4f}")
print(f"Écart-type : {scores.std():.4f}")


═══════════════════════════════════════════════════════════════════════════════
EXERCICE PRATIQUE COMPLET : PRÉDIRE LES VENTES
═══════════════════════════════════════════════════════════════════════════════

[NOTE] ÉNONCÉ :

Vous avez des données historiques de ventes avec :
• budget_pub : Budget publicitaire (k€)
• temperature : Température moyenne (°C)
• jour_semaine : 0=Lundi, 6=Dimanche
• ventes : Ventes réalisées (k€)

MISSION :
1. Créer un modèle de régression pour prédire les ventes
2. Identifier les features les plus importantes
3. Évaluer la performance du modèle
4. Prédire les ventes pour de nouvelles conditions


─────────────────────────────────────────────────────────────────────────────
[IDEE] SOLUTION COMPLÈTE :
─────────────────────────────────────────────────────────────────────────────

from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import r2_score, mean_absolute_error, mean_squared_error
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

print("=" * 80)
print("PROJET ML : PRÉDIRE LES VENTES")
print("=" * 80)
print()

# Créer des données réalistes
np.random.seed(42)
n = 100

budget_pub = np.random.uniform(10, 100, n)
temperature = np.random.uniform(10, 35, n)
jour_semaine = np.random.randint(0, 7, n)
est_weekend = (jour_semaine >= 5).astype(int)

# Ventes = f(budget, température, weekend) + bruit
ventes = (
    2.5 * budget_pub +           # Plus de budget -> plus de ventes
    0.8 * temperature +          # Plus chaud -> plus de ventes
    15 * est_weekend +           # Week-end -> plus de ventes
    np.random.randn(n) * 10      # Bruit aléatoire
)

df = pd.DataFrame({
    'budget_pub': budget_pub,
    'temperature': temperature,
    'jour_semaine': jour_semaine,
    'est_weekend': est_weekend,
    'ventes': ventes
})

print("[OK] Données créées")
print(df.head())
print()

# ═══════════════════════════════════════════════════════════════════════════
# ÉTAPE 1 : PRÉPARER LES DONNÉES
# ═══════════════════════════════════════════════════════════════════════════

X = df[['budget_pub', 'temperature', 'est_weekend']]
y = df['ventes']

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

print(f"Données d'entraînement : {len(X_train)}")
print(f"Données de test : {len(X_test)}")
print()

# ═══════════════════════════════════════════════════════════════════════════
# ÉTAPE 2 : ENTRAÎNER LE MODÈLE
# ═══════════════════════════════════════════════════════════════════════════

model = LinearRegression()
model.fit(X_train, y_train)

print("[OK] Modèle entraîné")
print()

# ═══════════════════════════════════════════════════════════════════════════
# ÉTAPE 3 : ÉVALUER LE MODÈLE
# ═══════════════════════════════════════════════════════════════════════════

y_pred = model.predict(X_test)

r2 = r2_score(y_test, y_pred)
mae = mean_absolute_error(y_test, y_pred)
rmse = np.sqrt(mean_squared_error(y_test, y_pred))

print("[GRAPHIQUE] PERFORMANCE DU MODÈLE")
print("─" * 80)
print(f"R² Score           : {r2:.4f}  (Plus proche de 1 = meilleur)")
print(f"MAE (Erreur Moy.)  : {mae:.2f}k€")
print(f"RMSE               : {rmse:.2f}k€")
print()

# ═══════════════════════════════════════════════════════════════════════════
# ÉTAPE 4 : IMPORTANCE DES FEATURES
# ═══════════════════════════════════════════════════════════════════════════

print("[HAUSSE] IMPORTANCE DES FEATURES")
print("─" * 80)
features = ['Budget Pub', 'Température', 'Est Weekend']
coefficients = model.coef_

for feature, coef in zip(features, coefficients):
    print(f"{feature:<15} : {coef:>7.2f}  (impact sur les ventes)")
print(f"{'Intercept':<15} : {model.intercept_:>7.2f}")
print()

# ═══════════════════════════════════════════════════════════════════════════
# ÉTAPE 5 : PRÉDICTIONS
# ═══════════════════════════════════════════════════════════════════════════

print("[CRYSTAL_BALL] PRÉDICTIONS POUR DE NOUVELLES CONDITIONS")
print("─" * 80)

nouveaux_cas = pd.DataFrame({
    'budget_pub': [50, 80, 30],
    'temperature': [25, 15, 30],
    'est_weekend': [0, 1, 0]
})

predictions = model.predict(nouveaux_cas)

for i, row in nouveaux_cas.iterrows():
    print(f"Budget: {row['budget_pub']}k€ | Temp: {row['temperature']}°C | "
          f"Weekend: {'Oui' if row['est_weekend'] else 'Non'} "
          f"-> Ventes prédites: {predictions[i]:.2f}k€")
print()

# ═══════════════════════════════════════════════════════════════════════════
# ÉTAPE 6 : VISUALISATIONS
# ═══════════════════════════════════════════════════════════════════════════

fig, axes = plt.subplots(1, 2, figsize=(15, 5))

# Graphique 1 : Prédit vs Réel
axes[0].scatter(y_test, y_pred, alpha=0.6, s=100)
axes[0].plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()], 
             'r--', linewidth=2, label='Parfait')
axes[0].set_xlabel('Ventes Réelles (k€)', fontsize=12)
axes[0].set_ylabel('Ventes Prédites (k€)', fontsize=12)
axes[0].set_title('Prédictions vs Réalité', fontsize=14, fontweight='bold')
axes[0].legend()
axes[0].grid(True, alpha=0.3)

# Graphique 2 : Importance des features
axes[1].barh(features, coefficients, color='skyblue', edgecolor='black')
axes[1].set_xlabel('Coefficient', fontsize=12)
axes[1].set_title('Importance des Features', fontsize=14, fontweight='bold')
axes[1].grid(True, alpha=0.3, axis='x')

plt.tight_layout()
plt.show()

print("=" * 80)
print("[OK] PROJET ML TERMINÉ")
print("=" * 80)


═══════════════════════════════════════════════════════════════════════════════
[GUIDE] RÉSUMÉ DU CHAPITRE
═══════════════════════════════════════════════════════════════════════════════

[OK] Vous savez maintenant :

1. Comprendre les concepts du Machine Learning
2. Différencier régression et classification
3. Créer un modèle de régression linéaire
4. Créer un modèle de classification (Logistic Regression, Decision Tree)
5. Segmenter des clients avec K-means
6. Évaluer un modèle (R², Accuracy, Matrice de confusion)
7. Normaliser les données
8. Utiliser la validation croisée
9. Faire des prédictions sur de nouvelles données

[OBJECTIF] PROCHAINES ÉTAPES :

Chapitre 11 : Web Scraping & APIs - Collecter vos propres données !


═══════════════════════════════════════════════════════════════════════════════
[IDEE] POUR ALLER PLUS LOIN EN ML
═══════════════════════════════════════════════════════════════════════════════

Algorithmes avancés à explorer :
• Random Forest (très puissant)
• Gradient Boosting (XGBoost, LightGBM)
• Support Vector Machines (SVM)
• Réseaux de neurones (Deep Learning)

Ressources :
• Kaggle : Compétitions et datasets
• Scikit-learn documentation
• Coursera : Machine Learning (Andrew Ng)


═══════════════════════════════════════════════════════════════════════════════
FIN DU CHAPITRE 10
═══════════════════════════════════════════════════════════════════════════════

═══════════════════════════════════════════════════════════════════════════════
    CHAPITRE 11 : WEB SCRAPING & APIs - COLLECTER VOS PROPRES DONNÉES
═══════════════════════════════════════════════════════════════════════════════

[LIVRE] OBJECTIFS DU CHAPITRE :
   • Comprendre le web scraping et son utilité
   • Extraire des données de sites web avec BeautifulSoup
   • Utiliser des APIs pour collecter des données
   • Respecter l'éthique et la légalité
   • Automatiser la collecte de données
   • Créer votre propre dataset

[ATTENTION] Compétence TRÈS RECHERCHÉE - Collectez vos propres données !

INSTALLATION REQUISE :
pip install requests beautifulsoup4 lxml

═══════════════════════════════════════════════════════════════════════════════
PARTIE 1 : INTRODUCTION AU WEB SCRAPING
═══════════════════════════════════════════════════════════════════════════════

1.1 QU'EST-CE QUE LE WEB SCRAPING ?
───────────────────────────────────

Web Scraping = Extraction automatique de données depuis des sites web.

POURQUOI FAIRE DU SCRAPING ?
[OK] Collecter des prix de produits (comparaison)
[OK] Analyser des avis clients
[OK] Surveiller la concurrence
[OK] Créer des datasets pour ML
[OK] Automatiser la recherche d'infos

EXEMPLE :
Au lieu de copier manuellement 1000 prix depuis Amazon,
vous écrivez un script qui le fait en 2 minutes.


1.2 EST-CE LÉGAL ?
──────────────────

[ATTENTION] RÈGLES À RESPECTER ABSOLUMENT :

[OK] LÉGAL :
• Données publiques accessibles sans login
• Respect du fichier robots.txt
• Pas de surcharge du serveur (limiter vitesse)
• Utilisation personnelle ou académique

[X] ILLÉGAL :
• Données derrière un login (violation CGU)
• Revente de données scrapées
• Ignorer robots.txt
• Attaque DDoS (trop de requêtes)
• Données personnelles protégées (RGPD)

TOUJOURS vérifier :
1. Le fichier robots.txt (www.site.com/robots.txt)
2. Les Conditions Générales d'Utilisation (CGU)
3. La légalité dans votre pays


1.3 COMMENT FONCTIONNE LE WEB ?
───────────────────────────────

1. Votre navigateur envoie une REQUÊTE HTTP -> Serveur
2. Le serveur renvoie du HTML (code de la page)
3. Votre navigateur affiche la page joliment

Le scraping fait EXACTEMENT pareil, mais :
• Pas d'affichage joli
• On extrait juste les données qui nous intéressent


═══════════════════════════════════════════════════════════════════════════════
PARTIE 2 : REQUESTS - FAIRE DES REQUÊTES HTTP
═══════════════════════════════════════════════════════════════════════════════

2.1 INSTALLER ET IMPORTER
─────────────────────────

import requests

# Faire une requête GET
response = requests.get('https://www.example.com')

print(f"Status Code : {response.status_code}")  # 200 = OK
print(f"Contenu (HTML) : {response.text[:500]}")  # 500 premiers caractères


2.2 STATUS CODES HTTP
─────────────────────

┌──────────┬────────────────────────────────────────────────┐
│ 200      │ OK - Succès                                     │
│ 404      │ Not Found - Page inexistante                   │
│ 403      │ Forbidden - Accès interdit                     │
│ 500      │ Internal Server Error - Erreur serveur         │
│ 503      │ Service Unavailable - Service indisponible     │
└──────────┴────────────────────────────────────────────────┘


2.3 HEADERS (En-têtes)
──────────────────────

# Certains sites bloquent les requêtes "robots"
# Solution : Se faire passer pour un navigateur

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}

response = requests.get('https://www.example.com', headers=headers)


═══════════════════════════════════════════════════════════════════════════════
PARTIE 3 : BEAUTIFULSOUP - EXTRAIRE LES DONNÉES
═══════════════════════════════════════════════════════════════════════════════

3.1 PARSER DU HTML
──────────────────

from bs4 import BeautifulSoup

html = """
<html>
    <head><title>Mon site</title></head>
    <body>
        <h1>Bienvenue</h1>
        <p class="description">Ceci est un paragraphe</p>
        <ul>
            <li>Élément 1</li>
            <li>Élément 2</li>
            <li>Élément 3</li>
        </ul>
    </body>
</html>
"""

soup = BeautifulSoup(html, 'html.parser')

# Extraire le titre
title = soup.title.text
print(f"Titre : {title}")  # Mon site

# Extraire le h1
h1 = soup.h1.text
print(f"H1 : {h1}")  # Bienvenue

# Extraire le paragraphe avec classe "description"
p = soup.find('p', class_='description').text
print(f"Paragraphe : {p}")  # Ceci est un paragraphe


3.2 TROUVER DES ÉLÉMENTS
────────────────────────

# find() : Trouve le PREMIER élément
premier_li = soup.find('li')
print(premier_li.text)  # Élément 1

# find_all() : Trouve TOUS les éléments
tous_les_li = soup.find_all('li')
for li in tous_les_li:
    print(li.text)
# Élément 1
# Élément 2
# Élément 3


3.3 EXTRAIRE DES ATTRIBUTS
──────────────────────────

html = '<a href="https://www.example.com" class="lien">Cliquez ici</a>'
soup = BeautifulSoup(html, 'html.parser')

lien = soup.find('a')
url = lien['href']
texte = lien.text
classe = lien['class']

print(f"URL : {url}")
print(f"Texte : {texte}")
print(f"Classe : {classe}")


═══════════════════════════════════════════════════════════════════════════════
PARTIE 4 : EXEMPLE COMPLET - SCRAPER DES DONNÉES
═══════════════════════════════════════════════════════════════════════════════

4.1 SCRAPER DES CITATIONS (EXEMPLE SIMPLE)
──────────────────────────────────────────

import requests
from bs4 import BeautifulSoup
import pandas as pd
import time

# Site d'exemple pour apprendre le scraping (légal et éthique)
url = 'http://quotes.toscrape.com/'

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}

response = requests.get(url, headers=headers)

if response.status_code == 200:
    soup = BeautifulSoup(response.content, 'html.parser')
    
    # Trouver toutes les citations
    citations_html = soup.find_all('div', class_='quote')
    
    citations_data = []
    
    for citation in citations_html:
        texte = citation.find('span', class_='text').text
        auteur = citation.find('small', class_='author').text
        tags = [tag.text for tag in citation.find_all('a', class_='tag')]
        
        citations_data.append({
            'citation': texte,
            'auteur': auteur,
            'tags': ', '.join(tags)
        })
    
    # Créer un DataFrame
    df = pd.DataFrame(citations_data)
    print(df.head())
    
    # Sauvegarder
    df.to_csv('citations.csv', index=False, encoding='utf-8')
    print(f"\n[OK] {len(df)} citations sauvegardées dans citations.csv")
else:
    print(f"[X] Erreur : Status {response.status_code}")


4.2 SCRAPER PLUSIEURS PAGES (PAGINATION)
────────────────────────────────────────

all_citations = []

for page in range(1, 6):  # Pages 1 à 5
    url = f'http://quotes.toscrape.com/page/{page}/'
    
    print(f"Scraping page {page}...")
    response = requests.get(url, headers=headers)
    
    if response.status_code == 200:
        soup = BeautifulSoup(response.content, 'html.parser')
        citations = soup.find_all('div', class_='quote')
        
        for citation in citations:
            texte = citation.find('span', class_='text').text
            auteur = citation.find('small', class_='author').text
            
            all_citations.append({
                'citation': texte,
                'auteur': auteur
            })
    
    # IMPORTANT : Pause pour ne pas surcharger le serveur
    time.sleep(1)  # 1 seconde entre chaque requête

df_all = pd.DataFrame(all_citations)
print(f"\n[OK] Total : {len(df_all)} citations scrapées")


═══════════════════════════════════════════════════════════════════════════════
PARTIE 5 : APIs - LA MEILLEURE FAÇON DE COLLECTER DES DONNÉES
═══════════════════════════════════════════════════════════════════════════════

5.1 QU'EST-CE QU'UNE API ?
──────────────────────────

API = Application Programming Interface

C'est une façon OFFICIELLE de demander des données à un service.

SCRAPING vs API :

SCRAPING :
[X] Peut casser si le site change
[X] Parfois contre les CGU
[X] Plus lent et complexe

API :
[OK] Officiel et stable
[OK] Plus rapide
[OK] Format structuré (JSON)
[OK] Autorisé par le service


5.2 UTILISER UNE API PUBLIQUE (Exemple : JSONPlaceholder)
─────────────────────────────────────────────────────────

import requests
import pandas as pd

# API de test publique et gratuite
url = 'https://jsonplaceholder.typicode.com/posts'

response = requests.get(url)

if response.status_code == 200:
    # L'API renvoie du JSON
    data = response.json()
    
    # Convertir en DataFrame
    df = pd.DataFrame(data)
    
    print(df.head())
    print(f"\n[OK] {len(df)} posts récupérés")
    
    # Sauvegarder
    df.to_csv('posts_api.csv', index=False)
else:
    print(f"[X] Erreur : {response.status_code}")


5.3 API AVEC PARAMÈTRES
───────────────────────

# Filtrer les résultats
url = 'https://jsonplaceholder.typicode.com/posts'
params = {
    'userId': 1  # Seulement les posts de l'utilisateur 1
}

response = requests.get(url, params=params)
data = response.json()

df = pd.DataFrame(data)
print(f"Posts de l'utilisateur 1 : {len(df)}")


5.4 API MÉTÉO (Exemple Réel)
────────────────────────────

# Exemple avec Open-Meteo (gratuit, sans clé API)
latitude = 48.8566  # Paris
longitude = 2.3522

url = f'https://api.open-meteo.com/v1/forecast'
params = {
    'latitude': latitude,
    'longitude': longitude,
    'current_weather': 'true'
}

response = requests.get(url, params=params)

if response.status_code == 200:
    data = response.json()
    meteo = data['current_weather']
    
    print(f"Température à Paris : {meteo['temperature']}°C")
    print(f"Vitesse du vent : {meteo['windspeed']} km/h")
else:
    print(f"[X] Erreur : {response.status_code}")


═══════════════════════════════════════════════════════════════════════════════
PARTIE 6 : BONNES PRATIQUES ET ÉTHIQUE
═══════════════════════════════════════════════════════════════════════════════

6.1 VÉRIFIER ROBOTS.TXT
───────────────────────

import requests

# Toujours vérifier avant de scraper
url_robots = 'https://www.example.com/robots.txt'
response = requests.get(url_robots)

print(response.text)

# Chercher :
# User-agent: *
# Disallow: /admin/  <- Ne PAS scraper /admin/
# Allow: /public/    <- OK pour scraper /public/


6.2 AJOUTER DES PAUSES (Rate Limiting)
──────────────────────────────────────

import time

urls = ['url1', 'url2', 'url3']

for url in urls:
    response = requests.get(url)
    # Traiter la réponse...
    
    # PAUSE OBLIGATOIRE
    time.sleep(2)  # 2 secondes entre chaque requête


6.3 GESTION DES ERREURS
──────────────────────

import requests
from requests.exceptions import RequestException

try:
    response = requests.get(url, timeout=10)  # Timeout de 10 secondes
    response.raise_for_status()  # Lève une exception si erreur HTTP
    
    # Traiter les données
    data = response.json()
    
except RequestException as e:
    print(f"[X] Erreur lors de la requête : {e}")
except ValueError:
    print(f"[X] Erreur lors du parsing JSON")


═══════════════════════════════════════════════════════════════════════════════
EXERCICE PRATIQUE COMPLET : CRÉER UN DATASET DE LIVRES
═══════════════════════════════════════════════════════════════════════════════

[NOTE] ÉNONCÉ :

Utilisez le site http://books.toscrape.com (site d'entraînement légal)

MISSION :
1. Scraper les 50 premiers livres
2. Extraire : Titre, Prix, Note, Disponibilité
3. Créer un DataFrame Pandas
4. Sauvegarder en CSV
5. Analyser : Prix moyen, livre le plus cher, distribution des notes


─────────────────────────────────────────────────────────────────────────────
[IDEE] SOLUTION COMPLÈTE :
─────────────────────────────────────────────────────────────────────────────

import requests
from bs4 import BeautifulSoup
import pandas as pd
import time

print("=" * 80)
print("PROJET : CRÉER UN DATASET DE LIVRES PAR WEB SCRAPING")
print("=" * 80)
print()

url = 'http://books.toscrape.com/'
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}

response = requests.get(url, headers=headers)

if response.status_code == 200:
    print("[OK] Connexion réussie")
    
    soup = BeautifulSoup(response.content, 'html.parser')
    
    # Trouver tous les livres
    livres_html = soup.find_all('article', class_='product_pod')
    
    livres_data = []
    
    print(f"[DOCS] Scraping de {len(livres_html)} livres...\n")
    
    for livre in livres_html:
        # Titre
        titre = livre.find('h3').find('a')['title']
        
        # Prix
        prix_text = livre.find('p', class_='price_color').text
        prix = float(prix_text.replace('£', ''))
        
        # Note (convertir "star-rating Three" -> 3)
        rating_class = livre.find('p', class_='star-rating')['class'][1]
        rating_map = {'One': 1, 'Two': 2, 'Three': 3, 'Four': 4, 'Five': 5}
        note = rating_map.get(rating_class, 0)
        
        # Disponibilité
        disponibilite = livre.find('p', class_='instock availability').text.strip()
        
        livres_data.append({
            'titre': titre,
            'prix': prix,
            'note': note,
            'disponibilite': disponibilite
        })
    
    # Créer DataFrame
    df = pd.DataFrame(livres_data)
    
    print("─" * 80)
    print("APERÇU DES DONNÉES")
    print("─" * 80)
    print(df.head(10))
    print()
    
    # Sauvegarder
    df.to_csv('livres_scrapes.csv', index=False, encoding='utf-8')
    print(f"[OK] {len(df)} livres sauvegardés dans livres_scrapes.csv")
    print()
    
    # ═══════════════════════════════════════════════════════════════════════
    # ANALYSES
    # ═══════════════════════════════════════════════════════════════════════
    
    print("─" * 80)
    print("ANALYSES")
    print("─" * 80)
    
    print(f"\n[GRAPHIQUE] STATISTIQUES DES PRIX :")
    print(f"   Prix moyen : £{df['prix'].mean():.2f}")
    print(f"   Prix min   : £{df['prix'].min():.2f}")
    print(f"   Prix max   : £{df['prix'].max():.2f}")
    
    # Livre le plus cher
    livre_cher = df.loc[df['prix'].idxmax()]
    print(f"\n[ARGENT] Livre le plus cher :")
    print(f"   {livre_cher['titre'][:50]}... - £{livre_cher['prix']:.2f}")
    
    # Distribution des notes
    print(f"\n* DISTRIBUTION DES NOTES :")
    notes_distribution = df['note'].value_counts().sort_index()
    for note, count in notes_distribution.items():
        print(f"   {note} étoiles : {count} livres")
    
    # Livres 5 étoiles
    livres_5_etoiles = df[df['note'] == 5]
    print(f"\n[TROPHEE] Livres avec 5 étoiles : {len(livres_5_etoiles)}")
    
    print()
    print("=" * 80)
    print("[OK] PROJET TERMINÉ")
    print("=" * 80)
    
else:
    print(f"[X] Erreur de connexion : Status {response.status_code}")


═══════════════════════════════════════════════════════════════════════════════
[GUIDE] RÉSUMÉ DU CHAPITRE
═══════════════════════════════════════════════════════════════════════════════

[OK] Vous savez maintenant :

1. Comprendre le web scraping et son utilité
2. Faire des requêtes HTTP avec requests
3. Parser du HTML avec BeautifulSoup
4. Extraire des données de sites web
5. Gérer la pagination (scraper plusieurs pages)
6. Utiliser des APIs pour collecter des données
7. Respecter l'éthique et la légalité (robots.txt, rate limiting)
8. Créer vos propres datasets
9. Gérer les erreurs et timeouts

[OBJECTIF] VOUS AVEZ MAINTENANT TOUTES LES COMPÉTENCES D'UN DATA ANALYST EXPERT !


═══════════════════════════════════════════════════════════════════════════════
[WEB] APIs PUBLIQUES POUR PRATIQUER
═══════════════════════════════════════════════════════════════════════════════

Gratuites et sans clé API :
• JSONPlaceholder : https://jsonplaceholder.typicode.com
• Open-Meteo (météo) : https://open-meteo.com
• REST Countries : https://restcountries.com
• Cat Facts : https://catfact.ninja

Avec clé API gratuite :
• OpenWeatherMap (météo)
• News API (actualités)
• GitHub API (repos, commits)


═══════════════════════════════════════════════════════════════════════════════
[SCALES] RAPPEL LÉGAL ET ÉTHIQUE
═══════════════════════════════════════════════════════════════════════════════

TOUJOURS :
[OK] Vérifier robots.txt
[OK] Lire les CGU du site
[OK] Respecter le rate limiting (pauses)
[OK] Ne pas revendre les données
[OK] Utiliser les APIs officielles quand disponibles

JAMAIS :
[X] Scraper des données personnelles (RGPD)
[X] Ignorer les interdictions
[X] Surcharger les serveurs
[X] Contourner des protections


═══════════════════════════════════════════════════════════════════════════════
[BRAVO] FÉLICITATIONS ! VOUS AVEZ TERMINÉ LA FORMATION COMPLÈTE !
═══════════════════════════════════════════════════════════════════════════════

Vous êtes maintenant un DATA ANALYST EXPERT capable de :

[OK] Manipuler n'importe quelle donnée (Pandas, NumPy)
[OK] Interroger des bases de données (SQL)
[OK] Créer des visualisations professionnelles (Matplotlib, Seaborn)
[OK] Effectuer des analyses statistiques avancées
[OK] Analyser des séries temporelles
[OK] Créer des modèles de Machine Learning
[OK] Collecter vos propres données (Scraping, APIs)
[OK] Réaliser des projets complets de A à Z

-> VOUS AVEZ TOUTES LES COMPÉTENCES POUR RÉUSSIR ! [RAPIDE]

Maintenant : PRATIQUEZ, CRÉEZ UN PORTFOLIO, POSTULEZ ! [PRO]


═══════════════════════════════════════════════════════════════════════════════
FIN DU CHAPITRE 11 - FIN DE LA FORMATION COMPLÈTE
═══════════════════════════════════════════════════════════════════════════════

═══════════════════════════════════════════════════════════════════════════════
    PROJET PRATIQUE 1 : ANALYSE E-COMMERCE - OPTIMISER LES VENTES
═══════════════════════════════════════════════════════════════════════════════

[ENTREPRISE] ENTREPRISE : "TechStore Pro" - E-commerce de produits électroniques
[UTILISATEUR] VOTRE RÔLE : Data Analyst Senior
[CALENDRIER] CONTEXTE : Fin d'année 2024

═══════════════════════════════════════════════════════════════════════════════
[LISTE] CONTEXTE BUSINESS
═══════════════════════════════════════════════════════════════════════════════

Le directeur général vous convoque et dit :

"Nos ventes ne sont pas à la hauteur de nos attentes. On a investi 500K€ 
dans le marketing cette année, mais je ne sais pas si ça a été rentable.

J'ai besoin que tu analyses 12 mois de données (2024) et que tu me dises :
1. Est-ce qu'on a atteint notre objectif de 5M€ de CA ?
2. Quels produits fonctionnent bien ? Lesquels arrêter ?
3. Est-ce que notre stratégie marketing est efficace ?
4. Quels segments de clients cibler en priorité ?
5. Comment augmenter notre CA de 20% l'année prochaine ?

Tu as carte blanche sur les données. Je veux un rapport complet avec 
visualisations et recommandations concrètes pour la semaine prochaine."

═══════════════════════════════════════════════════════════════════════════════
[GRAPHIQUE] DONNÉES FOURNIES
═══════════════════════════════════════════════════════════════════════════════

3 fichiers CSV :

1. ventes.csv (15 000 lignes)
   - transaction_id, date, client_id, produit_id, quantite, prix_unitaire, 
     canal_acquisition, code_promo_utilise

2. produits.csv (50 lignes)
   - produit_id, nom_produit, categorie, prix_achat, prix_vente, stock

3. clients.csv (3 000 lignes)
   - client_id, nom, email, ville, age, date_inscription, segment

═══════════════════════════════════════════════════════════════════════════════
[OBJECTIF] QUESTIONS À RÉPONDRE (Livrables attendus)
═══════════════════════════════════════════════════════════════════════════════

PARTIE 1 : PERFORMANCE GLOBALE
1. CA total 2024 vs objectif 5M€
2. Évolution mensuelle du CA
3. Panier moyen
4. Nombre de clients actifs

PARTIE 2 : ANALYSE PRODUITS
5. Top 10 produits par CA
6. Top 10 produits par quantité vendue
7. Produits les plus rentables (marge)
8. Produits à arrêter (ventes faibles)

PARTIE 3 : ANALYSE MARKETING
9. ROI marketing (Retour sur Investissement)
10. Performance par canal d'acquisition
11. Impact des codes promo

PARTIE 4 : ANALYSE CLIENTS
12. Segmentation clients par valeur (RFM)
13. Rétention clients
14. Clients les plus rentables

PARTIE 5 : RECOMMANDATIONS
15. Actions concrètes pour +20% CA
16. Budget marketing recommandé
17. Produits à promouvoir


═══════════════════════════════════════════════════════════════════════════════
[IDEE] SOLUTION ULTRA-DÉTAILLÉE
═══════════════════════════════════════════════════════════════════════════════

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from datetime import datetime, timedelta
import warnings
warnings.filterwarnings('ignore')

# Configuration
sns.set_style("whitegrid")
plt.rcParams['figure.figsize'] = (14, 8)

print("═" * 80)
print(" " * 15 + "[GRAPHIQUE] ANALYSE COMPLÈTE - TECHSTORE PRO 2024")
print("═" * 80)
print()


# ═══════════════════════════════════════════════════════════════════════════
# ÉTAPE 1 : CRÉER LES DONNÉES (Simulation réaliste)
# ═══════════════════════════════════════════════════════════════════════════

print("[ENTREE] Génération des données...")

# Seed pour reproductibilité
np.random.seed(42)

# 1. PRODUITS
produits_data = {
    'produit_id': range(1, 51),
    'nom_produit': [
        'iPhone 15 Pro', 'Samsung Galaxy S24', 'MacBook Air M2', 'Dell XPS 15',
        'iPad Pro 12.9', 'Surface Pro 9', 'AirPods Pro', 'Sony WH-1000XM5',
        'Apple Watch Series 9', 'Samsung Galaxy Watch', 'Logitech MX Master',
        'Magic Keyboard', 'LG 27" 4K Monitor', 'Samsung 32" Curved',
        'Canon EOS R6', 'Sony A7 IV', 'GoPro Hero 12', 'DJI Mini 3',
        'PlayStation 5', 'Xbox Series X', 'Nintendo Switch OLED', 'Steam Deck',
        'Razer Blade 15', 'ASUS ROG Laptop', 'Bose SoundLink', 'JBL Flip 6',
        'Kindle Paperwhite', 'iPad Mini', 'Galaxy Tab S9', 'Chromecast',
        'Apple TV 4K', 'Fire TV Stick', 'Webcam Logitech', 'Blue Yeti Mic',
        'Elgato Stream Deck', 'Ring Doorbell', 'Nest Thermostat', 'Philips Hue Kit',
        'Anker PowerBank', 'Belkin Charger', 'USB-C Hub', 'SSD Samsung 1TB',
        'WD HDD 4TB', 'Crucial RAM 32GB', 'AMD Ryzen 9', 'Intel i9',
        'RTX 4080', 'RX 7900 XT', 'Corsair PSU', 'NZXT Case'
    ],
    'categorie': [
        'Smartphones', 'Smartphones', 'Laptops', 'Laptops',
        'Tablets', 'Tablets', 'Audio', 'Audio',
        'Wearables', 'Wearables', 'Accessories', 'Accessories',
        'Monitors', 'Monitors', 'Cameras', 'Cameras', 'Cameras', 'Drones',
        'Gaming', 'Gaming', 'Gaming', 'Gaming',
        'Laptops', 'Laptops', 'Audio', 'Audio',
        'Tablets', 'Tablets', 'Tablets', 'Streaming',
        'Streaming', 'Streaming', 'Accessories', 'Accessories',
        'Accessories', 'Smart Home', 'Smart Home', 'Smart Home',
        'Accessories', 'Accessories', 'Accessories', 'Storage',
        'Storage', 'Components', 'Components', 'Components',
        'Components', 'Components', 'Components', 'Components'
    ]
}

prix_vente = [1199, 999, 1499, 1799, 1099, 1299, 249, 349,
              449, 399, 99, 149, 499, 599, 2499, 2799, 499, 759,
              549, 549, 349, 649, 2199, 2499, 199, 129,
              139, 499, 599, 49, 179, 39, 89, 129,
              149, 99, 249, 199, 49, 39, 69, 149,
              89, 179, 549, 489, 1199, 899, 149, 99]

prix_achat = [x * 0.6 for x in prix_vente]  # Marge 40%

produits_data['prix_vente'] = prix_vente
produits_data['prix_achat'] = prix_achat
produits_data['stock'] = np.random.randint(50, 500, 50)

df_produits = pd.DataFrame(produits_data)

# 2. CLIENTS
nb_clients = 3000
df_clients = pd.DataFrame({
    'client_id': range(1, nb_clients + 1),
    'nom': [f'Client_{i}' for i in range(1, nb_clients + 1)],
    'email': [f'client{i}@email.com' for i in range(1, nb_clients + 1)],
    'ville': np.random.choice(['Paris', 'Lyon', 'Marseille', 'Toulouse', 'Nice', 
                                'Bordeaux', 'Lille', 'Strasbourg'], nb_clients),
    'age': np.random.randint(18, 70, nb_clients),
    'date_inscription': pd.date_range(start='2022-01-01', end='2024-01-01', 
                                      periods=nb_clients),
    'segment': np.random.choice(['Bronze', 'Silver', 'Gold', 'Platinum'], 
                                 nb_clients, p=[0.5, 0.3, 0.15, 0.05])
})

# 3. VENTES (Simuler 15 000 transactions avec saisonnalité)
dates = pd.date_range(start='2024-01-01', end='2024-12-31', freq='D')
ventes_data = []

transaction_id = 1

for date in dates:
    # Saisonnalité : plus de ventes en novembre/décembre (Noël)
    month = date.month
    if month in [11, 12]:
        nb_ventes_jour = np.random.randint(50, 100)
    elif month in [6, 7]:  # Soldes été
        nb_ventes_jour = np.random.randint(40, 70)
    else:
        nb_ventes_jour = np.random.randint(30, 50)
    
    for _ in range(nb_ventes_jour):
        client_id = np.random.choice(df_clients['client_id'].values)
        produit_id = np.random.choice(df_produits['produit_id'].values, 
                                       p=np.array([0.08 if i < 10 else 0.015 
                                                   for i in range(50)]))  # Produits populaires
        quantite = np.random.choice([1, 1, 1, 2], p=[0.7, 0.2, 0.05, 0.05])
        
        canal = np.random.choice(['Google Ads', 'Facebook', 'Instagram', 'Email', 
                                  'SEO', 'Direct'], p=[0.25, 0.2, 0.15, 0.1, 0.2, 0.1])
        
        code_promo = np.random.choice([True, False], p=[0.3, 0.7])
        
        prix = df_produits[df_produits['produit_id'] == produit_id]['prix_vente'].values[0]
        if code_promo:
            prix *= 0.9  # 10% réduction
        
        ventes_data.append({
            'transaction_id': transaction_id,
            'date': date,
            'client_id': client_id,
            'produit_id': produit_id,
            'quantite': quantite,
            'prix_unitaire': prix,
            'canal_acquisition': canal,
            'code_promo_utilise': code_promo
        })
        
        transaction_id += 1

df_ventes = pd.DataFrame(ventes_data)

# Sauvegarder les CSV
df_produits.to_csv('produits.csv', index=False)
df_clients.to_csv('clients.csv', index=False)
df_ventes.to_csv('ventes.csv', index=False)

print(f"[OK] Données créées :")
print(f"   - {len(df_produits)} produits")
print(f"   - {len(df_clients)} clients")
print(f"   - {len(df_ventes)} transactions")
print()


# ═══════════════════════════════════════════════════════════════════════════
# ÉTAPE 2 : CHARGEMENT ET PRÉPARATION
# ═══════════════════════════════════════════════════════════════════════════

print("[GRAPHIQUE] Chargement des données...")

# Charger
df_ventes = pd.read_csv('ventes.csv', parse_dates=['date'])
df_produits = pd.read_csv('produits.csv')
df_clients = pd.read_csv('clients.csv', parse_dates=['date_inscription'])

# Fusionner pour analyse
df = df_ventes.merge(df_produits, on='produit_id')
df = df.merge(df_clients[['client_id', 'ville', 'age', 'segment']], on='client_id')

# Calculer colonnes importantes
df['montant_total'] = df['quantite'] * df['prix_unitaire']
df['marge_unitaire'] = df['prix_vente'] - df['prix_achat']
df['marge_totale'] = df['marge_unitaire'] * df['quantite']
df['mois'] = df['date'].dt.to_period('M')
df['trimestre'] = df['date'].dt.quarter

print("[OK] Données fusionnées et enrichies")
print()


# ═══════════════════════════════════════════════════════════════════════════
# PARTIE 1 : PERFORMANCE GLOBALE
# ═══════════════════════════════════════════════════════════════════════════

print("═" * 80)
print("PARTIE 1 : PERFORMANCE GLOBALE 2024")
print("═" * 80)
print()

# 1. CA total
ca_total = df['montant_total'].sum()
objectif = 5_000_000
atteinte = (ca_total / objectif) * 100

print(f"1⃣  CHIFFRE D'AFFAIRES")
print(f"   CA Total 2024    : {ca_total:,.2f}€")
print(f"   Objectif         : {objectif:,.2f}€")
print(f"   Atteinte objectif: {atteinte:.1f}%")
if ca_total >= objectif:
    print(f"   [OK] OBJECTIF ATTEINT ! (+{ca_total - objectif:,.2f}€)")
else:
    print(f"   [X] Manque {objectif - ca_total:,.2f}€ pour atteindre l'objectif")
print()

# 2. Évolution mensuelle
ca_mensuel = df.groupby('mois')['montant_total'].sum()

print(f"2⃣  ÉVOLUTION MENSUELLE DU CA")
for mois, ca in ca_mensuel.items():
    print(f"   {mois} : {ca:>12,.2f}€")
print()

meilleur_mois = ca_mensuel.idxmax()
pire_mois = ca_mensuel.idxmin()
print(f"   [TROPHEE] Meilleur mois : {meilleur_mois} ({ca_mensuel.max():,.2f}€)")
print(f"   [ATTENTION]  Pire mois    : {pire_mois} ({ca_mensuel.min():,.2f}€)")
print()

# 3. Panier moyen
panier_moyen = df.groupby('transaction_id')['montant_total'].sum().mean()
print(f"3⃣  PANIER MOYEN : {panier_moyen:,.2f}€")
print()

# 4. Clients actifs
clients_actifs = df['client_id'].nunique()
taux_activation = (clients_actifs / len(df_clients)) * 100
print(f"4⃣  CLIENTS ACTIFS")
print(f"   Clients ayant acheté : {clients_actifs:,}")
print(f"   Total clients base   : {len(df_clients):,}")
print(f"   Taux d'activation    : {taux_activation:.1f}%")
print()


# ═══════════════════════════════════════════════════════════════════════════
# PARTIE 2 : ANALYSE PRODUITS
# ═══════════════════════════════════════════════════════════════════════════

print("═" * 80)
print("PARTIE 2 : ANALYSE PRODUITS")
print("═" * 80)
print()

# 5. Top 10 par CA
ca_par_produit = df.groupby('nom_produit')['montant_total'].sum().sort_values(ascending=False)

print(f"5⃣  TOP 10 PRODUITS PAR CHIFFRE D'AFFAIRES")
for i, (produit, ca) in enumerate(ca_par_produit.head(10).items(), 1):
    pct = (ca / ca_total) * 100
    print(f"   {i:2}. {produit:<30} : {ca:>12,.2f}€ ({pct:>5.1f}%)")
print()

# 6. Top 10 par quantité
qte_par_produit = df.groupby('nom_produit')['quantite'].sum().sort_values(ascending=False)

print(f"6⃣  TOP 10 PRODUITS PAR QUANTITÉ VENDUE")
for i, (produit, qte) in enumerate(qte_par_produit.head(10).items(), 1):
    print(f"   {i:2}. {produit:<30} : {qte:>6,} unités")
print()

# 7. Produits les plus rentables
marge_par_produit = df.groupby('nom_produit')['marge_totale'].sum().sort_values(ascending=False)

print(f"7⃣  TOP 10 PRODUITS PAR MARGE (RENTABILITÉ)")
for i, (produit, marge) in enumerate(marge_par_produit.head(10).items(), 1):
    print(f"   {i:2}. {produit:<30} : {marge:>12,.2f}€")
print()

# 8. Produits à arrêter
produits_faibles = ca_par_produit.tail(10)

print(f"8⃣  PRODUITS À FAIBLES VENTES (À CONSIDÉRER POUR ARRÊT)")
for i, (produit, ca) in enumerate(produits_faibles.items(), 1):
    qte = df[df['nom_produit'] == produit]['quantite'].sum()
    print(f"   {i:2}. {produit:<30} : {ca:>10,.2f}€ ({qte:>4} unités)")
print()


# ═══════════════════════════════════════════════════════════════════════════
# PARTIE 3 : ANALYSE MARKETING
# ═══════════════════════════════════════════════════════════════════════════

print("═" * 80)
print("PARTIE 3 : ANALYSE MARKETING")
print("═" * 80)
print()

# 9. ROI Marketing
investissement_marketing = 500_000
marge_totale = df['marge_totale'].sum()
roi = ((marge_totale - investissement_marketing) / investissement_marketing) * 100

print(f"9⃣  ROI MARKETING (Retour sur Investissement)")
print(f"   Investissement marketing : {investissement_marketing:>12,.2f}€")
print(f"   Marge totale générée     : {marge_totale:>12,.2f}€")
print(f"   Profit net               : {marge_totale - investissement_marketing:>12,.2f}€")
print(f"   ROI                      : {roi:>12.1f}%")
if roi > 100:
    print(f"   [OK] Marketing très rentable !")
elif roi > 0:
    print(f"   [ATTENTION]  Marketing rentable mais peut être optimisé")
else:
    print(f"   [X] Marketing non rentable - revoir stratégie")
print()

# 10. Performance par canal
perf_canal = df.groupby('canal_acquisition').agg({
    'montant_total': 'sum',
    'transaction_id': 'count',
    'marge_totale': 'sum'
}).round(2)

perf_canal.columns = ['CA', 'Nb_transactions', 'Marge']
perf_canal['CA_par_transaction'] = perf_canal['CA'] / perf_canal['Nb_transactions']
perf_canal = perf_canal.sort_values('CA', ascending=False)

print(f"[10] PERFORMANCE PAR CANAL D'ACQUISITION")
print(f"   {'Canal':<15} | {'CA (€)':>15} | {'Transactions':>13} | {'CA/Trans':>10} | {'Marge (€)':>15}")
print(f"   {'-'*15}-+-{'-'*15}-+-{'-'*13}-+-{'-'*10}-+-{'-'*15}")
for canal, row in perf_canal.iterrows():
    print(f"   {canal:<15} | {row['CA']:>15,.2f} | {row['Nb_transactions']:>13,.0f} | "
          f"{row['CA_par_transaction']:>10,.2f} | {row['Marge']:>15,.2f}")
print()

meilleur_canal = perf_canal['Marge'].idxmax()
print(f"   [TROPHEE] Meilleur canal (marge) : {meilleur_canal}")
print()

# 11. Impact codes promo
ventes_promo = df[df['code_promo_utilise'] == True]
ventes_sans_promo = df[df['code_promo_utilise'] == False]

ca_promo = ventes_promo['montant_total'].sum()
ca_sans_promo = ventes_sans_promo['montant_total'].sum()
panier_promo = ventes_promo.groupby('transaction_id')['montant_total'].sum().mean()
panier_sans_promo = ventes_sans_promo.groupby('transaction_id')['montant_total'].sum().mean()

print(f"1⃣1⃣ IMPACT DES CODES PROMO")
print(f"   Avec promo    : {ca_promo:>12,.2f}€ ({len(ventes_promo):>6,} transactions)")
print(f"   Sans promo    : {ca_sans_promo:>12,.2f}€ ({len(ventes_sans_promo):>6,} transactions)")
print(f"   Panier moyen avec promo : {panier_promo:>10,.2f}€")
print(f"   Panier moyen sans promo : {panier_sans_promo:>10,.2f}€")
print()


# ═══════════════════════════════════════════════════════════════════════════
# PARTIE 4 : ANALYSE CLIENTS
# ═══════════════════════════════════════════════════════════════════════════

print("═" * 80)
print("PARTIE 4 : ANALYSE CLIENTS")
print("═" * 80)
print()

# 12. Segmentation par valeur (RFM simplifiée)
client_stats = df.groupby('client_id').agg({
    'date': 'max',  # Dernière transaction
    'transaction_id': 'count',  # Fréquence
    'montant_total': 'sum'  # Valeur monétaire
}).reset_index()

client_stats.columns = ['client_id', 'derniere_transaction', 'frequence', 'valeur_totale']
client_stats['recence_jours'] = (pd.Timestamp('2024-12-31') - client_stats['derniere_transaction']).dt.days

# Quartiles pour segmentation
client_stats['score_recence'] = pd.qcut(client_stats['recence_jours'], 4, labels=[4,3,2,1])
client_stats['score_frequence'] = pd.qcut(client_stats['frequence'].rank(method='first'), 4, labels=[1,2,3,4])
client_stats['score_valeur'] = pd.qcut(client_stats['valeur_totale'].rank(method='first'), 4, labels=[1,2,3,4])

client_stats['rfm_score'] = (client_stats['score_recence'].astype(int) + 
                              client_stats['score_frequence'].astype(int) + 
                              client_stats['score_valeur'].astype(int))

# Segmentation
def categoriser_client(score):
    if score >= 10:
        return 'Champions'
    elif score >= 8:
        return 'Loyaux'
    elif score >= 6:
        return 'Potentiels'
    else:
        return 'À risque'

client_stats['segment_rfm'] = client_stats['rfm_score'].apply(categoriser_client)

segmentation = client_stats.groupby('segment_rfm').agg({
    'client_id': 'count',
    'valeur_totale': 'sum'
}).round(2)

segmentation.columns = ['Nb_clients', 'CA_total']

print(f"1⃣2⃣ SEGMENTATION CLIENTS (RFM)")
print(f"   {'Segment':<15} | {'Nb clients':>12} | {'CA total (€)':>15} | {'CA moyen':>12}")
print(f"   {'-'*15}-+-{'-'*12}-+-{'-'*15}-+-{'-'*12}")
for segment, row in segmentation.sort_values('CA_total', ascending=False).iterrows():
    ca_moyen = row['CA_total'] / row['Nb_clients']
    print(f"   {segment:<15} | {row['Nb_clients']:>12,.0f} | {row['CA_total']:>15,.2f} | {ca_moyen:>12,.2f}")
print()

# 13. Rétention
# Clients ayant acheté en Q1 et revenus en Q2+
clients_q1 = set(df[df['trimestre'] == 1]['client_id'])
clients_q2_plus = set(df[df['trimestre'] > 1]['client_id'])
clients_revenus = clients_q1 & clients_q2_plus
taux_retention = (len(clients_revenus) / len(clients_q1)) * 100 if len(clients_q1) > 0 else 0

print(f"1⃣3⃣ RÉTENTION CLIENTS")
print(f"   Clients Q1              : {len(clients_q1):>6,}")
print(f"   Revenus après Q1        : {len(clients_revenus):>6,}")
print(f"   Taux de rétention       : {taux_retention:>6.1f}%")
print()

# 14. Clients les plus rentables
top_clients = client_stats.nlargest(10, 'valeur_totale')

print(f"1⃣4⃣ TOP 10 CLIENTS LES PLUS RENTABLES")
for i, (idx, client) in enumerate(top_clients.iterrows(), 1):
    print(f"   {i:2}. Client {client['client_id']:<6} : {client['valeur_totale']:>10,.2f}€ "
          f"({client['frequence']:>3.0f} achats)")
print()


# ═══════════════════════════════════════════════════════════════════════════
# PARTIE 5 : RECOMMANDATIONS STRATÉGIQUES
# ═══════════════════════════════════════════════════════════════════════════

print("═" * 80)
print("PARTIE 5 : RECOMMANDATIONS STRATÉGIQUES POUR +20% CA")
print("═" * 80)
print()

ca_cible_2025 = ca_total * 1.20
augmentation_necessaire = ca_cible_2025 - ca_total

print(f"[OBJECTIF] OBJECTIF 2025 : {ca_cible_2025:,.2f}€ (+20%)")
print(f"   Augmentation nécessaire : +{augmentation_necessaire:,.2f}€")
print()

print("[LISTE] RECOMMANDATIONS ACTIONNABLES :")
print()

print("1⃣  PRODUITS")
print("   [OK] MAINTENIR ET PROMOUVOIR :")
for i, produit in enumerate(ca_par_produit.head(5).index, 1):
    print(f"      {i}. {produit}")
print()
print("   [X] ARRÊTER (faibles ventes) :")
for i, produit in enumerate(produits_faibles.head(5).index, 1):
    print(f"      {i}. {produit}")
print()
print("   [IDEE] Action : Libérer 200K€ de stock dormant -> Réinvestir dans top produits")
print()

print("2⃣  MARKETING")
print(f"   [TROPHEE] Canal le plus rentable : {meilleur_canal}")
print(f"   [IDEE] Recommandation budget 2025 :")
budget_recommande = investissement_marketing * 1.1  # +10%
print(f"      Budget total : {budget_recommande:,.0f}€ (+10%)")
print(f"      Répartition suggérée :")
for canal, row in perf_canal.iterrows():
    part_ca = (row['CA'] / perf_canal['CA'].sum()) * 100
    budget_canal = budget_recommande * (part_ca / 100)
    print(f"         {canal:<15} : {budget_canal:>10,.0f}€ ({part_ca:>5.1f}%)")
print()

print("3⃣  CLIENTS")
print("   [OBJECTIF] Focus sur segments :")
print(f"      Champions : Programme VIP (réductions exclusives)")
print(f"      Loyaux    : Programme fidélité renforcé")
print(f"      Potentiels: Campagne réactivation ciblée")
print(f"      À risque  : Offre de retour (15% réduction)")
print()
print(f"   [IDEE] Objectif rétention : {taux_retention + 10:.1f}% (+10 points)")
print()

print("4⃣  SAISONNALITÉ")
meilleur_mois_nom = meilleur_mois.strftime('%B')
print(f"   [HAUSSE] Capitaliser sur {meilleur_mois_nom} (Noël)")
print(f"      -> Lancer campagne Black Friday dès octobre")
print(f"      -> Stock anticipé des top produits")
print()

print("5⃣  PRIX ET PROMOTIONS")
print(f"   Panier moyen avec promo : {panier_promo:.2f}€")
print(f"   Panier moyen sans promo : {panier_sans_promo:.2f}€")
if panier_promo > panier_sans_promo:
    print(f"   [OK] Codes promo efficaces -> Continuer")
else:
    print(f"   [ATTENTION]  Codes promo réduisent panier -> Revoir stratégie")
print()


# ═══════════════════════════════════════════════════════════════════════════
# VISUALISATIONS
# ═══════════════════════════════════════════════════════════════════════════

print("[GRAPHIQUE] Génération des visualisations...")
print()

fig = plt.figure(figsize=(20, 12))
gs = fig.add_gridspec(3, 3, hspace=0.3, wspace=0.3)

# Graph 1: Évolution mensuelle CA
ax1 = fig.add_subplot(gs[0, :])
ax1.plot(ca_mensuel.index.astype(str), ca_mensuel.values, marker='o', linewidth=3, markersize=10, color='#2E86AB')
ax1.axhline(y=objectif/12, color='red', linestyle='--', linewidth=2, label='Objectif mensuel')
ax1.set_title('Évolution Mensuelle du CA 2024', fontsize=16, fontweight='bold')
ax1.set_xlabel('Mois', fontsize=12)
ax1.set_ylabel('CA (€)', fontsize=12)
ax1.legend()
ax1.grid(True, alpha=0.3)
for i, v in enumerate(ca_mensuel.values):
    ax1.text(i, v + 20000, f'{v/1000:.0f}K', ha='center', va='bottom', fontsize=9)

# Graph 2: Top 10 produits
ax2 = fig.add_subplot(gs[1, 0])
top10_ca = ca_par_produit.head(10)
ax2.barh(range(len(top10_ca)), top10_ca.values, color='#06A77D')
ax2.set_yticks(range(len(top10_ca)))
ax2.set_yticklabels([p[:20] for p in top10_ca.index], fontsize=9)
ax2.set_title('Top 10 Produits (CA)', fontsize=12, fontweight='bold')
ax2.set_xlabel('CA (€)')
ax2.invert_yaxis()
ax2.grid(True, alpha=0.3, axis='x')

# Graph 3: Performance canaux
ax3 = fig.add_subplot(gs[1, 1])
perf_canal_sorted = perf_canal.sort_values('CA', ascending=True)
ax3.barh(perf_canal_sorted.index, perf_canal_sorted['CA'], color='#F18F01')
ax3.set_title('CA par Canal Marketing', fontsize=12, fontweight='bold')
ax3.set_xlabel('CA (€)')
ax3.grid(True, alpha=0.3, axis='x')

# Graph 4: Segmentation clients
ax4 = fig.add_subplot(gs[1, 2])
segmentation_sorted = segmentation.sort_values('CA_total', ascending=False)
colors = ['#2E86AB', '#06A77D', '#F18F01', '#E63946']
ax4.pie(segmentation_sorted['CA_total'], labels=segmentation_sorted.index, autopct='%1.1f%%',
        colors=colors, startangle=90)
ax4.set_title('Répartition CA par Segment', fontsize=12, fontweight='bold')

# Graph 5: Ventes par catégorie
ax5 = fig.add_subplot(gs[2, 0])
ca_categorie = df.groupby('categorie')['montant_total'].sum().sort_values(ascending=False).head(10)
ax5.bar(range(len(ca_categorie)), ca_categorie.values, color='#9b59b6', edgecolor='black')
ax5.set_xticks(range(len(ca_categorie)))
ax5.set_xticklabels(ca_categorie.index, rotation=45, ha='right', fontsize=9)
ax5.set_title('Top 10 Catégories', fontsize=12, fontweight='bold')
ax5.set_ylabel('CA (€)')
ax5.grid(True, alpha=0.3, axis='y')

# Graph 6: Distribution paniers
ax6 = fig.add_subplot(gs[2, 1])
paniers = df.groupby('transaction_id')['montant_total'].sum()
ax6.hist(paniers, bins=50, color='steelblue', edgecolor='black', alpha=0.7)
ax6.axvline(panier_moyen, color='red', linestyle='--', linewidth=2, label=f'Moyenne: {panier_moyen:.0f}€')
ax6.set_title('Distribution des Paniers', fontsize=12, fontweight='bold')
ax6.set_xlabel('Montant (€)')
ax6.set_ylabel('Fréquence')
ax6.legend()
ax6.grid(True, alpha=0.3)

# Graph 7: Ventes avec/sans promo
ax7 = fig.add_subplot(gs[2, 2])
promo_data = [ca_promo, ca_sans_promo]
labels = ['Avec promo', 'Sans promo']
ax7.bar(labels, promo_data, color=['#2ecc71', '#e74c3c'], edgecolor='black')
ax7.set_title('Impact Codes Promo', fontsize=12, fontweight='bold')
ax7.set_ylabel('CA (€)')
ax7.grid(True, alpha=0.3, axis='y')
for i, v in enumerate(promo_data):
    ax7.text(i, v + 50000, f'{v/1e6:.1f}M€', ha='center', va='bottom', fontsize=10)

plt.suptitle('[GRAPHIQUE] DASHBOARD ANALYTIQUE - TECHSTORE PRO 2024', fontsize=18, fontweight='bold', y=0.995)
plt.savefig('rapport_techstore_2024.png', dpi=300, bbox_inches='tight')
print("[OK] Dashboard sauvegardé : rapport_techstore_2024.png")
print()

print("=" * 80)
print("[OK] ANALYSE COMPLÈTE TERMINÉE")
print("=" * 80)
print()
print("[PAPERCLIP] Fichiers générés :")
print("   - ventes.csv")
print("   - produits.csv")
print("   - clients.csv")
print("   - rapport_techstore_2024.png")
print()
print("-> Rapport prêt pour présentation à la direction !")


═══════════════════════════════════════════════════════════════════════════════
[NOTE] RÉSUMÉ EXÉCUTIF POUR LA DIRECTION
═══════════════════════════════════════════════════════════════════════════════

SITUATION ACTUELLE :
• CA 2024 : [Voir résultat du code]
• Objectif : 5M€
• Statut : [À remplir selon résultat]

FORCES :
• [Top 3 produits génèrent X% du CA]
• Canal [X] très performant (ROI élevé)
• Segment Champions très rentable

FAIBLESSES :
• [X] produits à faibles ventes
• Taux de rétention à améliorer
• Saisonnalité forte (dépendance Noël)

PLAN D'ACTION 2025 (+20% CA) :
1. Arrêter les 10 produits les moins rentables
2. Augmenter budget marketing de 10% (focus sur meilleur canal)
3. Programme fidélité pour segment Champions
4. Campagne Black Friday anticipée (octobre)
5. Objectif rétention : +10 points

BUDGET RECOMMANDÉ 2025 : 550K€ marketing

ROI ATTENDU : [Basé sur ROI 2024]

═══════════════════════════════════════════════════════════════════════════════
FIN DU PROJET 1
═══════════════════════════════════════════════════════════════════════════════