# Fichier: python_cheats/cheatsheets/machine_learning.txt
# Cheatsheet Machine Learning Python - Guide Ultra-Détaillé pour Grands Débutants


[OK] CONCEPTS FONDAMENTAUX (EXPLICATIONS TRÈS DÉTAILLÉES)

# === QU'EST-CE QUE LE MACHINE LEARNING? ===

# Imagine que tu dois prédire si un email est du spam ou pas.

# Solution classique (avec règles):
# 1. Si le mail contient "Gagnez 1 million!" -> SPAM
# 2. Si le mail contient "Cliquez ici maintenant!" -> SPAM
# 3. Si le mail contient "Offre exclusive!" -> SPAM
# = Tu écris des centaines de règles manuelles
# = Les spammeurs changent de tactique -> Les règles ne fonctionnent plus

# MACHINE LEARNING = Laisser l'ordinateur apprendre les patterns automatiquement!
# 1. Tu donnes 10,000 emails déjà classés "Spam" ou "Pas Spam"
# 2. L'algorithme ML analyse ces emails
# 3. Il trouve les patterns qui caractérisent les spams
# 4. Il peut maintenant classer automatiquement les nouveaux emails
# = Pas besoin de réécrire des règles!


# === TYPES DE MACHINE LEARNING ===

# 1. SUPERVISED LEARNING (Apprentissage supervisé)
#    = Tu donnes des exemples avec les bonnes réponses
#    = L'algorithme apprend les patterns
#    Exemples:
#    - Prédire le prix d'une maison (Régression)
#    - Classer un email comme spam ou pas (Classification)
#    - Reconnaître des chiffres manuscrits (Classification)

# 2. UNSUPERVISED LEARNING (Apprentissage non supervisé)
#    = Tu donnes des données SANS réponses
#    = L'algorithme trouve des patterns tout seul
#    Exemples:
#    - Grouper les clients par profil (Clustering)
#    - Réduire la dimension des données (PCA)
#    - Trouver des anomalies (Outlier Detection)

# 3. REINFORCEMENT LEARNING (Apprentissage par renforcement)
#    = L'algorithme apprend en interagissant avec son environnement
#    = Il reçoit des récompenses pour les bonnes actions
#    Exemples:
#    - Apprendre à jouer aux échecs
#    - Apprendre à conduire une voiture autonome
#    - Apprendre à jouer à Dota 2


# === VOCABULAIRE MACHINE LEARNING (TRÈS IMPORTANT!) ===

# FEATURES (Caractéristiques)
# = Les données d'entrée que tu donnes à l'algorithme
# = Les colonnes du DataFrame (sauf la colonne à prédire)
# Exemple: Pour prédire le prix d'une maison
# Features = superficie, nombre de chambres, localisation, année de construction
# = X (variable indépendante)

# TARGET / LABEL (Cible)
# = Ce que tu veux prédire
# = La colonne que tu veux que l'algorithme apprenne
# Exemple: Prix de la maison
# = y (variable dépendante)

# TRAINING SET (Ensemble d'apprentissage)
# = Les données utilisées pour entraîner le modèle
# = L'algorithme apprend à partir de ces données
# = Typiquement 70-80% des données

# TEST SET (Ensemble de test)
# = Les données utilisées pour évaluer le modèle
# = L'algorithme ne les a jamais vues
# = Typiquement 20-30% des données

# MODEL (Modèle)
# = L'algorithme entraîné
# = C'est une "boîte noire" qui peut faire des prédictions
# Analogue: C'est comme un cerveau après l'apprentissage

# ACCURACY (Précision)
# = % de prédictions correctes sur le test set
# = Métrique pour évaluer la qualité du modèle
# Exemple: 95% accuracy = 95% de bonnes prédictions

# OVERFITTING (Surapprentissage)
# = Le modèle apprend TOO WELL les données d'entraînement
# = Il mémorise les patterns spécifiques (incluant le bruit)
# = Il ne généralise pas bien sur les nouvelles données
# Analogie: Un étudiant qui mémorise les réponses sans comprendre

# UNDERFITTING (Sous-apprentissage)
# = Le modèle ne comprend pas bien les patterns
# = Il fait mal sur l'entraînement ET le test
# = Le modèle est trop simple
# Analogie: Un étudiant qui n'a pas étudié

# HYPERPARAMETERS (Hyperparamètres)
# = Les "paramètres" du modèle qu'on ajuste manuellement
# = Différent des "paramètres" que le modèle apprend
# Exemple: nombre d'arbres dans une forêt aléatoire


# === WORKFLOW COMPLET DE MACHINE LEARNING ===

# 1. DÉFINIR LE PROBLÈME
#    - C'est une classification ou une régression?
#    - Quelle est la variable cible?
#    - Comment va-t-on mesurer la performance?

# 2. COLLECTER LES DONNÉES
#    - Où prendre les données?
#    - Combien de données faut-il?
#    - Les données sont-elles représentatives?

# 3. PRÉPARER LES DONNÉES
#    - Nettoyer (NaN, outliers, doublons)
#    - Explorer (statistiques, distributions)
#    - Transformer (normalisation, encoding)
#    - Sélectionner (features pertinentes)

# 4. DIVISER LES DONNÉES
#    - 80% train, 20% test (ou 70/30)
#    - Stratification (pour les classes déséquilibrées)

# 5. ENTRAÎNER LE MODÈLE
#    - Choisir un algorithme
#    - Configurer les hyperparamètres
#    - Entraîner sur les données d'entraînement

# 6. ÉVALUER LE MODÈLE
#    - Calculer les métriques (accuracy, precision, recall...)
#    - Visualiser les résultats
#    - Détecter le overfitting/underfitting

# 7. OPTIMISER LE MODÈLE
#    - Ajuster les hyperparamètres
#    - Ajouter/supprimer des features
#    - Essayer d'autres algorithmes

# 8. FAIRE DES PRÉDICTIONS
#    - Prédire sur les nouvelles données
#    - Expliquer les prédictions


# === POURQUOI MACHINE LEARNING? ===

# 1. AUTOMATISATION
#    - Les règles manuelles deviennent vite trop complexes
#    - Le ML trouve les patterns automatiquement

# 2. PRÉCISION
#    - Mieux que les humains pour reconnaître des patterns
#    - Traite des millions de points de données

# 3. ADAPTABILITÉ
#    - S'adapte aux nouvelles données
#    - Les règles manuelles deviendraient obsolètes

# 4. NOUVEAUX INSIGHTS
#    - Découvre des patterns cachés
#    - Les humains ne pouvaient pas les voir

# 5. ÉCONOMIES
#    - Une fois entraîné, fonctionne automatiquement
#    - Réduit les coûts opérationnels


[OK] INSTALLATION ET SETUP

# === BIBLIOTHÈQUES ESSENTIELLES ===

# Activer ton environnement virtuel (voir analyse_de_données.txt)

# Installer les bibliothèques ML:
pip install scikit-learn

# Scikit-learn = La bibliothèque ML la plus populaire en Python
# Contient: régression, classification, clustering, preprocessing

# Installer d'autres dépendances:
pip install pandas numpy matplotlib seaborn scipy

# Pour le deep learning (optionnel):
pip install tensorflow  # Google's deep learning framework
pip install torch  # Facebook's deep learning framework

# Pour XGBoost (algorithme très puissant):
pip install xgboost

# OU installer tout d'un coup:
pip install scikit-learn pandas numpy matplotlib seaborn scipy xgboost


# === VÉRIFIER L'INSTALLATION ===

import sklearn
print(sklearn.__version__)

import pandas as pd
print(pd.__version__)


[OK] SUPERVISED LEARNING: RÉGRESSION

# === QU'EST-CE QUE LA RÉGRESSION? ===

# Pourquoi? Prédire une valeur CONTINUE (un nombre réel)

# Exemples:
# - Prédire le prix d'une maison (€)
# - Prédire la température de demain (°C)
# - Prédire les ventes du mois (unités)
# - Prédire le salaire en fonction de l'expérience (€)

# = PAS de catégories, mais des nombres continues


# === RÉGRESSION LINÉAIRE ===

# === CONCEPT ===

# Imagine une relation simple entre deux variables:
# y = a * x + b
# 
# Exemple: Salaire = 2000 + (100 * Expérience)
# 
# - Si tu as 5 ans d'expérience: 2000 + (100 * 5) = 2500€
# - Si tu as 10 ans: 2000 + (100 * 10) = 3000€
# 
# La régression linéaire TROUVE les valeurs de a et b!


# === RÉGRESSION LINÉAIRE SIMPLE (1 VARIABLE) ===

from sklearn.linear_model import LinearRegression
import pandas as pd
import numpy as np

# Créer des données d'exemple:
data = {
    'Expérience': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
    'Salaire': [2100, 2200, 2350, 2500, 2700, 2900, 3100, 3300, 3500, 3700]
}
df = pd.DataFrame(data)

# Séparer Features et Target:
X = df[['Expérience']].values  # Features (entrée)
y = df['Salaire'].values       # Target (sortie)

# IMPORTANT: X doit être 2D (N, 1), même pour 1 variable!

# Créer le modèle:
model = LinearRegression()

# Entraîner le modèle:
model.fit(X, y)

# Faire une prédiction:
pred = model.predict([[5.5]])
print(f"Salaire prédit pour 5.5 ans d'expérience: {pred[0]:.2f}€")
# Output: Salaire prédit pour 5.5 ans d'expérience: 2800.00€

# Accéder aux paramètres:
print(f"Pente (a): {model.coef_[0]:.2f}")
print(f"Intercept (b): {model.intercept_:.2f}")
# Pente (a): 200.00
# Intercept (b): 1900.00
# Équation: Salaire = 1900 + (200 * Expérience)


# === RÉGRESSION LINÉAIRE MULTIPLE (PLUSIEURS VARIABLES) ===

# Pourquoi? La plupart du temps, plusieurs variables influencent le résultat

# Créer des données:
data = {
    'Expérience': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
    'Éducation': [12, 14, 16, 12, 14, 16, 12, 16, 14, 16],  # années d'études
    'Salaire': [2100, 2400, 2900, 2200, 2700, 3200, 2300, 3500, 3000, 3800]
}
df = pd.DataFrame(data)

# Sélectionner PLUSIEURS features:
X = df[['Expérience', 'Éducation']]
y = df['Salaire']

# Créer et entraîner:
model = LinearRegression()
model.fit(X, y)

# Prédire:
pred = model.predict([[5.5, 14]])
print(f"Salaire prédit: {pred[0]:.2f}€")

# Accéder aux coefficients:
print("Coefficients:")
for feature, coef in zip(df.columns[:-1], model.coef_):
    print(f"  {feature}: {coef:.2f}")


# === ÉVALUER LA RÉGRESSION ===

# === MÉTRIQUES ===

from sklearn.metrics import mean_squared_error, r2_score

# Faire des prédictions sur les données d'entraînement:
y_pred = model.predict(X)

# R² Score (coefficient de détermination)
# = % de variance expliquée par le modèle
# = Entre 0 et 1 (1 = parfait)
r2 = r2_score(y, y_pred)
print(f"R² Score: {r2:.4f}")

# Mean Squared Error (MSE)
# = Erreur quadratique moyenne
# = Plus bas = mieux
mse = mean_squared_error(y, y_pred)
print(f"MSE: {mse:.2f}")

# Root Mean Squared Error (RMSE)
# = Racine carrée du MSE
# = Dans les mêmes unités que y
rmse = np.sqrt(mse)
print(f"RMSE: {rmse:.2f}€")

# Mean Absolute Error (MAE)
# = Erreur absolue moyenne
# = Plus intuitive que MSE
from sklearn.metrics import mean_absolute_error
mae = mean_absolute_error(y, y_pred)
print(f"MAE: {mae:.2f}€")


# === TRAIN/TEST SPLIT ===

# Pourquoi? Évaluer sur des données que le modèle n'a jamais vues

from sklearn.model_selection import train_test_split

# Diviser les données:
X_train, X_test, y_train, y_test = train_test_split(
    X, y,
    test_size=0.2,  # 20% pour le test
    random_state=42  # Pour reproductibilité
)

# Entraîner sur le training set:
model = LinearRegression()
model.fit(X_train, y_train)

# Évaluer sur le test set:
y_pred = model.predict(X_test)
r2_test = r2_score(y_test, y_pred)
print(f"R² Score (Test): {r2_test:.4f}")

# Comparer avec le training set:
y_pred_train = model.predict(X_train)
r2_train = r2_score(y_train, y_pred_train)
print(f"R² Score (Train): {r2_train:.4f}")

# Si r2_train >> r2_test = OVERFITTING


[OK] SUPERVISED LEARNING: CLASSIFICATION

# === QU'EST-CE QUE LA CLASSIFICATION? ===

# Pourquoi? Prédire une catégorie (classe)

# Exemples:
# - Email = Spam ou Pas Spam (2 classes)
# - Image = Chat, Chien, ou Oiseau (3 classes)
# - Patient = Malade ou Sain (2 classes)
# - Fruit = Pomme, Orange, ou Banane (3 classes)

# = Résultat est une CATÉGORIE, pas un nombre


# === CLASSIFICATION BINAIRE (2 CLASSES) ===

# Créer des données d'exemple:
data = {
    'Âge': [25, 35, 45, 55, 65, 28, 38, 48, 58, 68],
    'Cholestérol': [180, 200, 220, 240, 260, 190, 210, 230, 250, 270],
    'Maladie': [0, 0, 0, 1, 1, 0, 0, 1, 1, 1]  # 0 = Sain, 1 = Malade
}
df = pd.DataFrame(data)

# Séparer:
X = df[['Âge', 'Cholestérol']]
y = df['Maladie']

# Diviser:
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)


# === LOGISTIC REGRESSION ===

# Malgré le nom, c'est un algorithme de CLASSIFICATION (pas regression)

from sklearn.linear_model import LogisticRegression

# Créer le modèle:
model = LogisticRegression()

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

# Faire une prédiction (classe):
pred_class = model.predict([[50, 240]])
print(f"Classe prédite: {pred_class[0]}")  # 0 ou 1

# Probabilités (plus utile):
pred_proba = model.predict_proba([[50, 240]])
print(f"Probabilité de chaque classe: {pred_proba[0]}")
# Output: [0.3, 0.7] = 30% Sain, 70% Malade

# Prédire sur le test set:
y_pred = model.predict(X_test)


# === DECISION TREE (ARBRE DE DÉCISION) ===

# === CONCEPT ===

# Un arbre de décision pose des questions successives:
# 1. Âge > 50?
#    - OUI: Cholestérol > 230?
#           - OUI: Malade [OK]
#           - NON: Sain
#    - NON: Sain

from sklearn.tree import DecisionTreeClassifier

# Créer le modèle:
model = DecisionTreeClassifier(max_depth=3)  # Limiter la profondeur

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

# Prédire:
y_pred = model.predict(X_test)

# Visualiser l'arbre (optionnel):
from sklearn.tree import plot_tree
import matplotlib.pyplot as plt

plot_tree(model, feature_names=['Âge', 'Cholestérol'], class_names=['Sain', 'Malade'])
plt.show()


# === RANDOM FOREST (FORÊT ALÉATOIRE) ===

# === CONCEPT ===

# Combine plusieurs arbres de décision
# Chaque arbre vote pour la classe finale
# Vote majoritaire = prédiction finale

from sklearn.ensemble import RandomForestClassifier

# Créer le modèle:
model = RandomForestClassifier(n_estimators=100)  # 100 arbres

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

# Prédire:
y_pred = model.predict(X_test)

# Probabilités:
pred_proba = model.predict_proba(X_test)


# === K-NEAREST NEIGHBORS (KNN) ===

# === CONCEPT ===

# Pour prédire une classe, on regarde les K voisins les plus proches
# Vote majoritaire parmi les K voisins

from sklearn.neighbors import KNeighborsClassifier

# Créer le modèle:
model = KNeighborsClassifier(n_neighbors=3)  # Regarder les 3 voisins les plus proches

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

# Prédire:
y_pred = model.predict(X_test)


# === SUPPORT VECTOR MACHINE (SVM) ===

# === CONCEPT ===

# Trouve la meilleure "ligne" (ou hyperplan) séparant les classes
# Maximise la distance entre les classes

from sklearn.svm import SVC

# Créer le modèle:
model = SVC(kernel='rbf', probability=True)  # kernel='rbf' = non-linéaire

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

# Prédire:
y_pred = model.predict(X_test)


# === ÉVALUER LA CLASSIFICATION ===

# === ACCURACY ===

from sklearn.metrics import accuracy_score

accuracy = accuracy_score(y_test, y_pred)
print(f"Accuracy: {accuracy:.4f}")  # % de prédictions correctes


# === CONFUSION MATRIX ===

# === CONCEPT ===

# Montre ce que le modèle a bien prédit et ce qu'il a raté

from sklearn.metrics import confusion_matrix

cm = confusion_matrix(y_test, y_pred)
print(cm)

# Matrice (exemple):
# [[15  2]   (True Negatives, False Positives)
#  [ 1  8]]  (False Negatives, True Positives)

# Interprétation:
# - 15: Patients correctement prédits comme Sains (True Negative)
# - 2: Patients sains prédits comme Malades (False Positive = Erreur!)
# - 1: Patients malades prédits comme Sains (False Negative = Erreur!)
# - 8: Patients correctement prédits comme Malades (True Positive)


# === PRECISION, RECALL, F1-SCORE ===

from sklearn.metrics import precision_score, recall_score, f1_score

# PRECISION: Sur les prédictions POSITIVES, combien étaient correctes?
# = TP / (TP + FP)
# Utile quand on veut minimiser les faux positifs
precision = precision_score(y_test, y_pred)
print(f"Precision: {precision:.4f}")

# RECALL: Sur les vrais POSITIFS, combien ont été trouvés?
# = TP / (TP + FN)
# Utile quand on veut minimiser les faux négatifs
recall = recall_score(y_test, y_pred)
print(f"Recall: {recall:.4f}")

# F1-SCORE: Moyenne harmonique de Precision et Recall
# = 2 * (Precision * Recall) / (Precision + Recall)
# Meilleur compromis
f1 = f1_score(y_test, y_pred)
print(f"F1-Score: {f1:.4f}")

# === EXEMPLE RÉEL ===

# Détecteur de maladie grave:
# - False Positive (diagnostic malade alors que sain) = Patient anxieux, aucun mal
# - False Negative (diagnostic sain alors que malade) = TRÈS GRAVE! Patient ne se traite pas!
# 
# On préfère maximiser RECALL (trouver TOUS les malades)
# Même si ça augmente les faux positifs


# === ROC CURVE ===

# Représente le trade-off entre True Positive Rate et False Positive Rate

from sklearn.metrics import roc_curve, auc

fpr, tpr, _ = roc_curve(y_test, model.predict_proba(X_test)[:, 1])
roc_auc = auc(fpr, tpr)

import matplotlib.pyplot as plt
plt.plot(fpr, tpr, label=f'ROC Curve (AUC = {roc_auc:.2f})')
plt.plot([0, 1], [0, 1], 'k--', label='Random')
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.legend()
plt.show()

# AUC = Area Under Curve
# - 0.5 = Aléatoire (mauvais)
# - 1.0 = Parfait


# === CLASSIFICATION MULTICLASSE ===

# Exemple: Prédire 3 types de fruits

data = {
    'Poids': [100, 120, 150, 110, 130, 160, 95, 125, 155],
    'Couleur': [0, 0, 1, 0, 0, 1, 2, 2, 2],  # 0=Rouge, 1=Orange, 2=Jaune
    'Fruit': [0, 0, 0, 0, 0, 0, 1, 1, 1]  # 0=Pomme, 1=Orange, 2=Banane
}
df = pd.DataFrame(data)

X = df[['Poids', 'Couleur']]
y = df['Fruit']

# Les algorithmes fonctionnent exactement pareils!
model = RandomForestClassifier()
model.fit(X, y)

# Prédire:
pred = model.predict([[120, 0]])
print(f"Fruit prédit: {pred[0]}")  # 0, 1 ou 2


[OK] UNSUPERVISED LEARNING: CLUSTERING

# === QU'EST-CE QUE LE CLUSTERING? ===

# Pourquoi? Grouper des données similaires sans labels prédéfinis

# Exemples:
# - Grouper les clients par profil (segmentation marketing)
# - Grouper les articles de news similaires
# - Grouper les gènes similaires
# - Déterminer les anomalies

# = Pas de "bonne réponse", on DÉCOUVRE les patterns


# === K-MEANS ===

# === CONCEPT ===

# L'algorithme choisit K points de départ (centroids)
# Chaque point est assigné au centroid le plus proche
# Les centroids se déplacent vers la moyenne de leurs points
# Répéter jusqu'à convergence

from sklearn.cluster import KMeans

# Créer des données:
data = {
    'Revenu': [30000, 35000, 65000, 70000, 75000, 25000, 32000, 68000, 72000],
    'Âge': [25, 28, 45, 50, 48, 22, 26, 46, 49]
}
df = pd.DataFrame(data)

X = df[['Revenu', 'Âge']]

# Créer le modèle:
model = KMeans(n_clusters=3, random_state=42)  # 3 groupes

# Entraîner:
model.fit(X)

# Assigner les clusters:
clusters = model.predict(X)
print(clusters)
# Output: [0 0 1 1 1 0 0 2 2]

# Accéder aux centroids:
print(model.cluster_centers_)

# Visualiser:
import matplotlib.pyplot as plt

plt.scatter(X['Revenu'], X['Âge'], c=clusters, cmap='viridis')
plt.scatter(model.cluster_centers_[:, 0], model.cluster_centers_[:, 1], 
            c='red', marker='X', s=200)  # Centroids en rouge
plt.xlabel('Revenu')
plt.ylabel('Âge')
plt.show()


# === CHOISIR LE NOMBRE DE CLUSTERS ===

# Pourquoi? K-Means nécessite de définir K à l'avance

# Méthode 1: ELBOW METHOD

inertias = []
for k in range(1, 10):
    kmeans = KMeans(n_clusters=k, random_state=42)
    kmeans.fit(X)
    inertias.append(kmeans.inertia_)

plt.plot(range(1, 10), inertias)
plt.xlabel('Nombre de clusters (K)')
plt.ylabel('Inertie')
plt.show()

# Chercher le "coude" où la courbe ralentit


# === HIERARCHICAL CLUSTERING ===

# === CONCEPT ===

# Crée un arbre de clustering (dendrogram)
# Plus facile de voir les relations hiérarchiques

from sklearn.cluster import AgglomerativeClustering
from scipy.cluster.hierarchy import dendrogram, linkage
import matplotlib.pyplot as plt

# Créer le modèle:
model = AgglomerativeClustering(n_clusters=3)

# Entraîner:
clusters = model.fit_predict(X)

# Visualiser avec dendrogram:
Z = linkage(X, method='ward')
dendrogram(Z)
plt.show()


# === DBSCAN ===

# === CONCEPT ===

# Regroupe les points proches ensemble
# Peut trouver les anomalies (points isolés)

from sklearn.cluster import DBSCAN

# Créer le modèle:
model = DBSCAN(eps=0.5, min_samples=2)

# Entraîner:
clusters = model.fit_predict(X)

# -1 = Point anomalique (pas assigné à un cluster)


[OK] PREPROCESSING ET FEATURE ENGINEERING

# === POURQUOI PREPROCESSING? ===

# Les données brutes ne sont JAMAIS prêtes pour le ML
# Garbage in = Garbage out
# Donc: 70% du temps d'un projet ML = Preprocessing


# === NORMALISATION ET STANDARDISATION ===

# POURQUOI? Certains algorithmes fonctionnent mieux avec des données à la même échelle

# Exemple: Revenu (30,000-100,000) vs Âge (18-65)
# Sans normalisation, le Revenu dominerait

# MIN-MAX NORMALIZATION:
from sklearn.preprocessing import MinMaxScaler

scaler = MinMaxScaler()
X_scaled = scaler.fit_transform(X)

# Ramène toutes les valeurs entre 0 et 1

# STANDARDIZATION (Z-SCORE):
from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# Moyenne = 0, Écart-type = 1


# === ENCODAGE DES VARIABLES CATÉGORIELLES ===

# Pourquoi? Les algorithmes ML ne comprennent que les nombres

# Exemple: Ville = 'Paris', 'Lyon', 'Marseille'

# LABEL ENCODING (pour variables ordinales):
from sklearn.preprocessing import LabelEncoder

le = LabelEncoder()
cities_encoded = le.fit_transform(['Paris', 'Lyon', 'Marseille', 'Paris'])
# Output: [2 1 0 2]

# Paris -> 2, Lyon -> 1, Marseille -> 0

# ONE-HOT ENCODING (pour variables nominales):
import pandas as pd

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

df_encoded = pd.get_dummies(df, columns=['Ville'], prefix='Ville')

# Résultat:
#    Ville_Lyon  Ville_Marseille  Ville_Paris
# 0            0                 0            1
# 1            1                 0            0
# 2            0                 1

# === GESTION DES VALEURS MANQUANTES ===

# Pourquoi? Les NaN causent des erreurs dans les algorithmes

# Méthode 1: SUPPRIMER (simple mais perd des données)
df_clean = df.dropna()

# Méthode 2: REMPLACER PAR LA MOYENNE
from sklearn.impute import SimpleImputer

imputer = SimpleImputer(strategy='mean')
X_imputed = imputer.fit_transform(X)

# Stratégies disponibles: 'mean', 'median', 'most_frequent'

# Méthode 3: REMPLACER PAR K-NEAREST NEIGHBORS
from sklearn.impute import KNNImputer

imputer = KNNImputer(n_neighbors=3)
X_imputed = imputer.fit_transform(X)


# === SÉLECTION DES FEATURES ===

# Pourquoi? Garder seulement les features pertinentes
# - Réduit la complexité
# - Améliore la performance
# - Réduit l'overfitting

# Méthode 1: VARIANCE (éliminer les colonnes avec peu de variation)
from sklearn.feature_selection import VarianceThreshold

selector = VarianceThreshold(threshold=0.01)
X_selected = selector.fit_transform(X)


# Méthode 2: CORRELATION AVEC LA CIBLE
# Garder les features corrélées à y

correlations = pd.DataFrame(X).corrwith(y).abs()
top_features = correlations[correlations > 0.3].index
X_selected = X[top_features]


# Méthode 3: RECURSIVE FEATURE ELIMINATION
from sklearn.feature_selection import RFE
from sklearn.linear_model import LogisticRegression

model = LogisticRegression()
selector = RFE(model, n_features_to_select=3)
X_selected = selector.fit_transform(X, y)


# Méthode 4: FEATURE IMPORTANCE (après entraînement)
from sklearn.ensemble import RandomForestClassifier

model = RandomForestClassifier()
model.fit(X, y)

importances = model.feature_importances_
print(importances)  # Poids de chaque feature


# === GESTION DES OUTLIERS ===

# Pourquoi? Les outliers peuvent dominer l'apprentissage

# Détection par IQR:
Q1 = X.quantile(0.25)
Q3 = X.quantile(0.75)
IQR = Q3 - Q1

X_clean = X[~((X < (Q1 - 1.5 * IQR)) | (X > (Q3 + 1.5 * IQR))).any(axis=1)]


# === CLASS IMBALANCE ===

# Pourquoi? Si 95% des données sont la classe 0 et 5% la classe 1,
# le modèle apprendra mal la classe 1

# Vérifier le déséquilibre:
print(y.value_counts())

# Solution 1: OVERSAMPLING (dupliquer la classe minoritaire)
from imblearn.over_sampling import RandomOverSampler

oversampler = RandomOverSampler(random_state=42)
X_resampled, y_resampled = oversampler.fit_resample(X, y)


# Solution 2: UNDERSAMPLING (supprimer de la classe majoritaire)
from imblearn.under_sampling import RandomUnderSampler

undersampler = RandomUnderSampler(random_state=42)
X_resampled, y_resampled = undersampler.fit_resample(X, y)


# Solution 3: SMOTE (Synthetic Minority Over-sampling Technique)
from imblearn.over_sampling import SMOTE

smote = SMOTE(random_state=42)
X_resampled, y_resampled = smote.fit_resample(X, y)


[OK] VALIDATION ET HYPERTUNING

# === CROSS-VALIDATION ===

# Pourquoi? Évaluer plus fiablement sur l'ensemble des données

# Sans cross-validation: diviser une fois (peut avoir du hasard)
# Avec cross-validation: diviser plusieurs fois, moyenner les résultats

from sklearn.model_selection import cross_val_score

model = LogisticRegression()

# K-Fold (par défaut 5):
scores = cross_val_score(model, X, y, cv=5)

print(f"Scores: {scores}")
print(f"Moyenne: {scores.mean():.4f}")
print(f"Écart-type: {scores.std():.4f}")

# Output (exemple):
# Scores: [0.85 0.87 0.84 0.86 0.85]
# Moyenne: 0.8540
# Écart-type: 0.0102


# === STRATIFIED K-FOLD ===

# Pourquoi? Pour les données déséquilibrées

from sklearn.model_selection import StratifiedKFold

skf = StratifiedKFold(n_splits=5)

scores = cross_val_score(model, X, y, cv=skf)


# === HYPERPARAMETER TUNING ===

# Pourquoi? Les hyperparamètres affectent ÉNORMÉMENT la performance

# Exemple: RandomForestClassifier a beaucoup d'hyperparamètres:
# - n_estimators: nombre d'arbres
# - max_depth: profondeur maximale d'un arbre
# - min_samples_split: nombre minimum de samples pour splitter
# - etc...

# Méthode 1: GRID SEARCH (essayer toutes les combinaisons)

from sklearn.model_selection import GridSearchCV

param_grid = {
    'n_estimators': [50, 100, 200],
    'max_depth': [5, 10, 15, None],
    'min_samples_split': [2, 5, 10]
}

model = RandomForestClassifier()

grid_search = GridSearchCV(model, param_grid, cv=5)
grid_search.fit(X, y)

print(f"Meilleurs hyperparamètres: {grid_search.best_params_}")
print(f"Meilleur score: {grid_search.best_score_:.4f}")

# Utiliser le meilleur modèle:
best_model = grid_search.best_estimator_
predictions = best_model.predict(X_test)


# Méthode 2: RANDOM SEARCH (essayer au hasard)

from sklearn.model_selection import RandomizedSearchCV

random_search = RandomizedSearchCV(model, param_grid, n_iter=10, cv=5, random_state=42)
random_search.fit(X, y)

# Plus rapide pour beaucoup de hyperparamètres


# === LEARNING CURVE ===

# Pourquoi? Visualiser le overfitting/underfitting

from sklearn.model_selection import learning_curve

train_sizes, train_scores, val_scores = learning_curve(
    LogisticRegression(), X, y, cv=5, 
    train_sizes=np.linspace(0.1, 1.0, 10)
)

# Tracer:
plt.plot(train_sizes, train_scores.mean(axis=1), label='Train')
plt.plot(train_sizes, val_scores.mean(axis=1), label='Validation')
plt.xlabel('Nombre de samples')
plt.ylabel('Score')
plt.legend()
plt.show()

# Si les courbes convergent bas = UNDERFITTING
# Si les courbes divergent = OVERFITTING


[OK] PIPELINES

# === QU'EST-CE QU'UN PIPELINE? ===

# Pourquoi? Combiner plusieurs étapes en une seule

# Sans pipeline:
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X_train)
model = LogisticRegression()
model.fit(X_scaled, y_train)
X_test_scaled = scaler.transform(X_test)
predictions = model.predict(X_test_scaled)

# Avec pipeline:
from sklearn.pipeline import Pipeline

pipeline = Pipeline([
    ('scaler', StandardScaler()),
    ('model', LogisticRegression())
])

pipeline.fit(X_train, y_train)
predictions = pipeline.predict(X_test)

# Beaucoup plus clean et moins d'erreurs!


# === PIPELINE AVEC PREPROCESSING COMPLEXE ===

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.compose import ColumnTransformer

# Définir les transformations pour différentes colonnes:
numeric_features = ['Âge', 'Revenu']
categorical_features = ['Ville']

preprocessor = ColumnTransformer(
    transformers=[
        ('num', StandardScaler(), numeric_features),
        ('cat', OneHotEncoder(), categorical_features)
    ]
)

pipeline = Pipeline([
    ('preprocessor', preprocessor),
    ('model', RandomForestClassifier())
])

pipeline.fit(X_train, y_train)


# === PIPELINE AVEC GRID SEARCH ===

param_grid = {
    'model__n_estimators': [50, 100, 200],
    'model__max_depth': [5, 10, 15]
}

grid_search = GridSearchCV(pipeline, param_grid, cv=5)
grid_search.fit(X_train, y_train)


[OK] TECHNIQUES AVANCÉES

# === ENSEMBLE METHODS ===

# Pourquoi? Combiner plusieurs modèles pour des meilleures prédictions

# Méthode 1: VOTING

from sklearn.ensemble import VotingClassifier

model1 = LogisticRegression()
model2 = RandomForestClassifier()
model3 = SVC(probability=True)

voting = VotingClassifier(
    estimators=[('lr', model1), ('rf', model2), ('svc', model3)],
    voting='soft'  # 'soft' = moyenne des probabilités
)

voting.fit(X_train, y_train)


# Méthode 2: STACKING

from sklearn.ensemble import StackingClassifier

base_learners = [
    ('lr', LogisticRegression()),
    ('rf', RandomForestClassifier()),
    ('svc', SVC(probability=True))
]

meta_learner = LogisticRegression()

stacking = StackingClassifier(
    estimators=base_learners,
    final_estimator=meta_learner,
    cv=5
)

stacking.fit(X_train, y_train)


# === GRADIENT BOOSTING ===

# Pourquoi? Très puissant pour les compétitions Kaggle

from sklearn.ensemble import GradientBoostingClassifier

model = GradientBoostingClassifier(
    n_estimators=100,
    learning_rate=0.1,
    max_depth=5
)

model.fit(X_train, y_train)


# === XGBOOST ===

# Une version plus optimisée de Gradient Boosting

import xgboost as xgb

model = xgb.XGBClassifier(
    n_estimators=100,
    max_depth=5,
    learning_rate=0.1
)

model.fit(X_train, y_train)


[OK] EXAMPLE COMPLET: PRÉDIRE LES CHIFFRES MANUSCRITS

# === SCÉNARIO ===

# Utiliser le dataset MNIST
# - 70,000 images de chiffres (0-9)
# - Chaque image = 28x28 pixels
# - Prédire quel chiffre est l'image

# === ÉTAPE 1: CHARGER LES DONNÉES ===

from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split

digits = load_digits()
X = digits.data  # Images (8x8=64 pixels)
y = digits.target  # Chiffres (0-9)

print(f"Nombre de samples: {X.shape[0]}")
print(f"Nombre de features: {X.shape[1]}")
print(f"Nombre de classes: {len(set(y))}")


# === ÉTAPE 2: EXPLORER LES DONNÉES ===

import matplotlib.pyplot as plt

# Visualiser quelques images:
fig, axes = plt.subplots(2, 5, figsize=(10, 4))
for i, ax in enumerate(axes.flat):
    ax.imshow(digits.images[i], cmap='gray')
    ax.set_title(f"Chiffre: {digits.target[i]}")
    ax.axis('off')
plt.show()

# Vérifier le déséquilibre des classes:
print(pd.Series(y).value_counts().sort_index())


# === ÉTAPE 3: DIVISER LES DONNÉES ===

X_train, X_test, y_train, y_test = train_test_split(
    X, y,
    test_size=0.2,
    random_state=42,
    stratify=y
)

print(f"Train: {X_train.shape}, Test: {X_test.shape}")


# === ÉTAPE 4: NORMALISER ===

from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)


# === ÉTAPE 5: ENTRAÎNER PLUSIEURS MODÈLES ===

from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.svm import SVC
import xgboost as xgb

models = {
    'Logistic Regression': LogisticRegression(max_iter=1000),
    'Random Forest': RandomForestClassifier(n_estimators=100),
    'SVM': SVC(kernel='rbf'),
    'XGBoost': xgb.XGBClassifier(n_estimators=100, verbosity=0)
}

results = {}

for name, model in models.items():
    print(f"\nEntraînement {name}...")
    model.fit(X_train_scaled, y_train)
    
    # Évaluer:
    train_score = model.score(X_train_scaled, y_train)
    test_score = model.score(X_test_scaled, y_test)
    
    results[name] = {'train': train_score, 'test': test_score}
    
    print(f"  Train Accuracy: {train_score:.4f}")
    print(f"  Test Accuracy: {test_score:.4f}")


# === ÉTAPE 6: COMPARER LES MODÈLES ===

df_results = pd.DataFrame(results).T
print("\nComparaison des modèles:")
print(df_results)


# === ÉTAPE 7: HYPERTUNING DU MEILLEUR MODÈLE ===

from sklearn.model_selection import GridSearchCV

model = RandomForestClassifier()

param_grid = {
    'n_estimators': [50, 100, 200],
    'max_depth': [10, 20, None],
    'min_samples_split': [2, 5]
}

grid_search = GridSearchCV(model, param_grid, cv=5, n_jobs=-1)
grid_search.fit(X_train_scaled, y_train)

print(f"\nMeilleurs hyperparamètres: {grid_search.best_params_}")
print(f"Meilleur score CV: {grid_search.best_score_:.4f}")

best_model = grid_search.best_estimator_
test_score = best_model.score(X_test_scaled, y_test)
print(f"Test Accuracy: {test_score:.4f}")


# === ÉTAPE 8: ANALYSER LES ERREURS ===

from sklearn.metrics import confusion_matrix, classification_report

y_pred = best_model.predict(X_test_scaled)

print("\nClassification Report:")
print(classification_report(y_test, y_pred))

# Confusion Matrix:
cm = confusion_matrix(y_test, y_pred)

plt.figure(figsize=(10, 8))
plt.imshow(cm, cmap='Blues')
plt.colorbar()
plt.xlabel('Prédiction')
plt.ylabel('Réalité')
plt.title('Confusion Matrix')

# Ajouter les valeurs:
for i in range(10):
    for j in range(10):
        plt.text(j, i, str(cm[i, j]), ha='center', va='center', color='white' if cm[i, j] > cm.max()/2 else 'black')

plt.show()


# === ÉTAPE 9: VISUALISER DES PRÉDICTIONS INCORRECTES ===

incorrect_indices = np.where(y_pred != y_test)[0]

fig, axes = plt.subplots(2, 5, figsize=(10, 4))
for i, ax in enumerate(axes.flat):
    idx = incorrect_indices[i]
    ax.imshow(X_test[idx].reshape(8, 8), cmap='gray')
    ax.set_title(f"Réel: {y_test[idx]}, Prédit: {y_pred[idx]}")
    ax.axis('off')

plt.suptitle('Prédictions Incorrectes')
plt.show()


# === ÉTAPE 10: FAIRE DES PRÉDICTIONS ===

# Sur une nouvelle image:
new_image = X_test[0]
new_image_scaled = scaler.transform([new_image])

prediction = best_model.predict(new_image_scaled)
probability = best_model.predict_proba(new_image_scaled)

print(f"Prédiction: {prediction[0]}")
print(f"Probabilités: {probability[0]}")

# Visualiser:
plt.imshow(new_image.reshape(8, 8), cmap='gray')
plt.title(f"Prédiction: {prediction[0]}")
plt.show()


[OK] SAUVEGARDER ET CHARGER LES MODÈLES

# === POURQUOI SAUVEGARDER? ===

# - Ne pas avoir à ré-entraîner le modèle à chaque fois
# - Partager le modèle avec d'autres
# - Déployer en production


# === AVEC JOBLIB (RECOMMANDÉ) ===

import joblib

# Sauvegarder:
joblib.dump(best_model, 'mon_modele.pkl')

# Charger:
loaded_model = joblib.load('mon_modele.pkl')

# Faire une prédiction:
prediction = loaded_model.predict(X_test)


# === AVEC PICKLE ===

import pickle

# Sauvegarder:
with open('mon_modele.pkl', 'wb') as f:
    pickle.dump(best_model, f)

# Charger:
with open('mon_modele.pkl', 'rb') as f:
    loaded_model = pickle.load(f)


[OK] DÉPLOIEMENT EN PRODUCTION

# === CRÉER UNE FONCTION DE PRÉDICTION ===

def predict_digit(image_array):
    """
    Prédire un chiffre à partir d'un array 64D
    
    Args:
        image_array: Array numpy de 64 pixels
    
    Returns:
        prediction: Chiffre prédit (0-9)
        confidence: Confiance de la prédiction
    """
    
    # Normaliser:
    image_scaled = scaler.transform([image_array])
    
    # Prédire:
    prediction = best_model.predict(image_scaled)[0]
    probability = best_model.predict_proba(image_scaled)[0]
    confidence = probability[prediction]
    
    return prediction, confidence


# === CRÉER UNE API AVEC FLASK (OPTIONNEL) ===

from flask import Flask, request, jsonify
import numpy as np

app = Flask(__name__)

# Charger le modèle:
model = joblib.load('mon_modele.pkl')

@app.route('/predict', methods=['POST'])
def predict():
    data = request.json
    image = np.array(data['image'])
    
    prediction, confidence = predict_digit(image)
    
    return jsonify({
        'prediction': int(prediction),
        'confidence': float(confidence)
    })

if __name__ == '__main__':
    app.run(debug=True)

# Tester avec:
# curl -X POST http://localhost:5000/predict -H "Content-Type: application/json" -d '{"image": [0,1,2,...,63]}'


[OK] RESSOURCES ET BONNES PRATIQUES

# === BONNES PRATIQUES ===

# 1. TOUJOURS FAIRE TRAIN/TEST SPLIT
#    - Ne jamais évaluer sur les données d'entraînement!

# 2. NORMALISER LES DONNÉES
#    - Même si ce n'est pas toujours nécessaire, c'est une bonne habitude

# 3. VÉRIFIER L'OVERFITTING
#    - Comparer train accuracy vs test accuracy
#    - Si train >> test = OVERFITTING

# 4. UTILISER CROSS-VALIDATION
#    - Plus fiable qu'un simple train/test

# 5. DOCUMENTER TON CODE
#    - ML est compliqué, explique ce que tu fais

# 6. VERSIONNER TON CODE ET TES DONNÉES
#    - Utilise Git pour le code
#    - Documente les transformations des données

# 7. MESURER CE QUI IMPORTE
#    - Choisir la bonne métrique selon le problème
#    - Accuracy n'est pas toujours appropriée

# 8. COMMENCER SIMPLE
#    - Commencer par des modèles simples (Régression Logique)
#    - Augmenter la complexité si nécessaire

# 9. FAIRE DE L'EXPLORATORY DATA ANALYSIS (EDA)
#    - Visualiser les données avant de modéliser
#    - Comprendre les patterns et anomalies

# 10. ITÉRER, ITÉRER, ITÉRER
#     - ML est itératif
#     - Toujours chercher à améliorer


# === RESSOURCES ===

# Documentation:
# Scikit-learn: https://scikit-learn.org/
# TensorFlow: https://www.tensorflow.org/
# PyTorch: https://pytorch.org/

# Tutoriels:
# Kaggle Learn: https://www.kaggle.com/learn
# Fast.ai: https://www.fast.ai/
# Coursera ML: https://www.coursera.org/learn/machine-learning

# Datasets:
# Kaggle: https://www.kaggle.com/datasets
# UCI ML Repository: https://archive.ics.uci.edu/
# Google Dataset Search: https://datasetsearch.research.google.com/

# Communauté:
# Stack Overflow: https://stackoverflow.com/questions/tagged/scikit-learn
# Reddit: https://www.reddit.com/r/MachineLearning/
# Kaggle Discussions: https://www.kaggle.com/discussions

# Livres:
# "Hands-On Machine Learning" par Aurélien Géron
# "Introduction to Statistical Learning" (gratuit)
# "Deep Learning" par Goodfellow et al