madamasterclass.com

📔 Manipulation de tables

Exploration et manipulation de tables


Dans ce cours, nous allons découvrir la manipulation de tables de données en informatique. Nous verrons le format CSV, comment importer et exploiter des données en Python, ainsi que les techniques d'interrogation et de tri. Ces compétences sont essentielles pour analyser et traiter des données dans de nombreux domaines.

Les données tabulaires (sous forme de tableaux) sont omniprésentes dans notre monde numérique. Savoir les manipuler efficacement est une compétence fondamentale en informatique moderne.

Manipulation de tables de données

1. Le format CSV (Comma-Separated Values)
  • Format texte : Simple et universel pour stocker des données tabulaires
  • Structure : Chaque ligne = un enregistrement, chaque colonne séparée par une virgule
  • Première ligne : Généralement les en-têtes (noms des colonnes)
📋 Mémo rapide : CSV = format de fichier pour tableaux de données séparées par des virgules
   📄 Exemple de fichier CSV (eleves.csv) :
   
   nom,prenom,age,classe,moyenne
   Dupont,Marie,16,1NSI,15.5
   Martin,Paul,17,1NSI,12.8
   Durand,Sophie,16,1NSI,17.2
   Moreau,Lucas,17,1NSI,14.1
   

2. Importation des données en Python
  • ✦ Plusieurs méthodes possibles pour lire un fichier CSV
  • ✦ Utilisation du module csv ou de listes Python
Méthode Avantages Inconvénients
Lecture manuelle Simple, contrôle total Gestion des cas particuliers
Module csv Robuste, gère les virgules dans les données À importer
Pandas (avancé) Très puissant Bibliothèque externe
   🐍 Méthode 1 : Lecture simple avec split()
   
   def lire_csv_simple(nom_fichier):
       with open(nom_fichier, 'r', encoding='utf-8') as fichier:
           lignes = fichier.readlines()
           entetes = lignes[0].strip().split(',')
           donnees = []
           for ligne in lignes[1:]:
               valeurs = ligne.strip().split(',')
               donnees.append(valeurs)
       return entetes, donnees
   
   # Utilisation
   entetes, eleves = lire_csv_simple('eleves.csv')
   print(entetes)  # ['nom', 'prenom', 'age', 'classe', 'moyenne']
   
   🐍 Méthode 2 : Module csv (recommandée)
   
   import csv
   
   def lire_csv_module(nom_fichier):
       with open(nom_fichier, 'r', newline='', encoding='utf-8') as fichier:
           lecteur = csv.reader(fichier)
           entetes = next(lecteur)  # Première ligne = en-têtes
           donnees = []
           for ligne in lecteur:
               donnees.append(ligne)
       return entetes, donnees
   
   # Lecture avec dictionnaires
   def lire_csv_dict(nom_fichier):
       with open(nom_fichier, 'r', newline='', encoding='utf-8') as fichier:
           lecteur = csv.DictReader(fichier)
           return list(lecteur)
   
   eleves_dict = lire_csv_dict('eleves.csv')
   # Chaque élève = {'nom': 'Dupont', 'prenom': 'Marie', ...}
   
3. Structure des données en mémoire
⚡ Deux représentations principales : Liste de listes OU Liste de dictionnaires
Représentation Structure Avantages
Liste de listes [['Dupont','Marie','16'], ...] Simple, rapide
Liste de dictionnaires [{'nom':'Dupont', 'prenom':'Marie'}, ...] Plus lisible, accès par nom
4. Interrogation des données
  • ✦ Recherche d'informations spécifiques dans les données
  • ✦ Filtrage selon des critères
   🔍 Exemples d'interrogation :
   
   # Rechercher tous les élèves d'une classe
   def eleves_classe(eleves, classe_recherchee):
       resultats = []
       for eleve in eleves:
           if eleve['classe'] == classe_recherchee:
               resultats.append(eleve)
       return resultats
   
   # Rechercher les élèves avec une moyenne > seuil
   def eleves_moyenne_superieure(eleves, seuil):
       resultats = []
       for eleve in eleves:
           if float(eleve['moyenne']) > seuil:
               resultats.append(eleve)
       return resultats
   
   # Compter les élèves par classe
   def compter_par_classe(eleves):
       compteurs = {}
       for eleve in eleves:
           classe = eleve['classe']
           compteurs[classe] = compteurs.get(classe, 0) + 1
       return compteurs
   
5. Tri des données
🔧 Fonction sorted() : Crée une nouvelle liste triée
🔧 Méthode .sort() : Modifie la liste existante
   📊 Tri simple par un critère :
   
   # Tri par nom (ordre alphabétique)
   eleves_tries_nom = sorted(eleves, key=lambda x: x['nom'])
   
   # Tri par moyenne (ordre décroissant)
   eleves_tries_moyenne = sorted(eleves, 
                                key=lambda x: float(x['moyenne']), 
                                reverse=True)
   
   # Tri par âge (ordre croissant)
   eleves_tries_age = sorted(eleves, key=lambda x: int(x['age']))
   
   # Fonction de tri personnalisée
   def trier_par_moyenne(eleves):
       return sorted(eleves, key=lambda x: float(x['moyenne']), reverse=True)
   
6. Tri selon plusieurs critères
  • ✦ Tri principal puis tri secondaire en cas d'égalité
  • ✦ Utilisation de tuples pour définir l'ordre de priorité
   🎯 Tri multi-critères avec tuples :
   
   # Tri par classe puis par moyenne décroissante
   eleves_tries = sorted(eleves, 
                        key=lambda x: (x['classe'], -float(x['moyenne'])))
   
   # Tri par nom puis prénom (ordre alphabétique)
   eleves_tries = sorted(eleves, 
                        key=lambda x: (x['nom'], x['prenom']))
   
   # Tri complexe : classe, puis moyenne (desc), puis nom
   def tri_complexe(eleve):
       return (eleve['classe'], 
               -float(eleve['moyenne']),  # - pour ordre décroissant
               eleve['nom'])
   
   eleves_tries = sorted(eleves, key=tri_complexe)
   
   # Fonction générique de tri multi-critères
   def trier_multi_criteres(eleves, criteres):
       """
       criteres = [('classe', False), ('moyenne', True), ('nom', False)]
       False = croissant, True = décroissant
       """
       def cle_tri(eleve):
           cle = []
           for nom_critere, decroissant in criteres:
               valeur = eleve[nom_critere]
               # Conversion si nécessaire
               if nom_critere in ['age', 'moyenne']:
                   valeur = float(valeur)
               if decroissant:
                   valeur = -valeur if isinstance(valeur, (int, float)) else valeur
               cle.append(valeur)
           return tuple(cle)
       
       return sorted(eleves, key=cle_tri)
   
7. Fonctions utiles pour l'analyse
Fonction Utilité Exemple
Moyenne Calculer la moyenne d'une colonne sum(notes)/len(notes)
Min/Max Trouver les valeurs extrêmes min(ages), max(ages)
Groupement Regrouper par catégorie Élèves par classe
Filtrage Sélectionner selon critères Moyenne > 15
   📈 Fonctions d'analyse statistique :
   
   def calculer_statistiques(eleves, colonne):
       """Calcule les statistiques d'une colonne numérique"""
       valeurs = [float(eleve[colonne]) for eleve in eleves]
       return {
           'moyenne': sum(valeurs) / len(valeurs),
           'min': min(valeurs),
           'max': max(valeurs),
           'etendue': max(valeurs) - min(valeurs)
       }
   
   def grouper_par(eleves, critere):
       """Groupe les élèves selon un critère"""
       groupes = {}
       for eleve in eleves:
           cle = eleve[critere]
           if cle not in groupes:
               groupes[cle] = []
           groupes[cle].append(eleve)
       return groupes
   
   # Utilisation
   stats_moyennes = calculer_statistiques(eleves, 'moyenne')
   groupes_classe = grouper_par(eleves, 'classe')
   
⚠️ Attention aux types : Les données CSV sont toujours des chaînes ! Pensez à convertir (int(), float()) avant les calculs.
8. Exemple d'application complète
   🎓 Application : Système de gestion d'élèves
   
   import csv
   
   class GestionEleves:
       def __init__(self, fichier_csv):
           self.eleves = self.charger_donnees(fichier_csv)
       
       def charger_donnees(self, fichier):
           with open(fichier, 'r', newline='', encoding='utf-8') as f:
               return list(csv.DictReader(f))
       
       def rechercher(self, critere, valeur):
           return [e for e in self.eleves if e[critere] == valeur]
       
       def trier(self, critere, decroissant=False):
           if critere in ['age', 'moyenne']:
               return sorted(self.eleves, 
                           key=lambda x: float(x[critere]), 
                           reverse=decroissant)
           return sorted(self.eleves, 
                        key=lambda x: x[critere], 
                        reverse=decroissant)
       
       def statistiques_classe(self):
           stats = {}
           for eleve in self.eleves:
               classe = eleve['classe']
               if classe not in stats:
                   stats[classe] = {'count': 0, 'moyennes': []}
               stats[classe]['count'] += 1
               stats[classe]['moyennes'].append(float(eleve['moyenne']))
           
           # Calcul des moyennes par classe
           for classe, data in stats.items():
               data['moyenne_classe'] = sum(data['moyennes']) / len(data['moyennes'])
           
           return stats
   
   # Utilisation
   gestion = GestionEleves('eleves.csv')
   meilleurs = gestion.trier('moyenne', decroissant=True)[:3]
   stats = gestion.statistiques_classe()
   
9. Résumé des points essentiels
💡 Points clés à retenir :
  • 🔸 CSV = format simple et universel pour les données tabulaires
  • 🔸 Module csv recommandé pour l'importation robuste
  • 🔸 Liste de dictionnaires = structure pratique en Python
  • 🔸 Fonction sorted() avec key= pour les tris personnalisés
  • 🔸 Tuples pour les tris multi-critères
  • 🔸 Toujours convertir les types avant les calculs numériques
10. Conclusion

La manipulation de tables de données est une compétence essentielle en informatique. Maîtriser l'importation CSV, l'interrogation et le tri des données vous permettra d'analyser efficacement de nombreux jeux de données. Ces techniques constituent la base de l'analyse de données et vous préparent aux outils plus avancés comme les bases de données et les bibliothèques spécialisées.

Manipulation des Tables CSV en Python

Exercice 1: Lecture et affichage de données CSV ★ ★ ☆ ☆ ☆

Objectif pédagogique : Maîtriser la lecture d'un fichier CSV et l'affichage des premières lignes.

Énoncé :
Vous disposez d'un fichier CSV etudiants.csv contenant : nom, âge, note, ville
1. Lisez le fichier avec la bibliothèque csv de Python.
Conseil : Utilisez csv.DictReader pour une manipulation plus facile.

2. Affichez les 3 premières lignes de données. Astuce : Utilisez une boucle avec un compteur pour limiter l'affichage.

Correction détaillée :

Question 1 :
import csv

with open('etudiants.csv', 'r', encoding='utf-8') as fichier:
    lecteur = csv.DictReader(fichier)
    donnees = list(lecteur)

Question 2 :
for i, ligne in enumerate(donnees):
    if i < 3:
        print(f"Nom: {ligne['nom']}, Âge: {ligne['âge']}, Note: {ligne['note']}, Ville: {ligne['ville']}")
    else:
        break


Exercice 2: Interrogation simple des données ★ ★ ★ ☆ ☆

Objectif pédagogique : Filtrer et rechercher des données selon des critères spécifiques.

Énoncé :
À partir du même fichier CSV :
1. Trouvez tous les étudiants ayant une note supérieure à 15.
Rappel : Les données CSV sont lues comme des chaînes, pensez à la conversion.

2. Comptez le nombre d'étudiants par ville. Procédure : Utilisez un dictionnaire pour compter les occurrences.

Étapes pas à pas :

Question 1 :
etudiants_excellents = []
for etudiant in donnees:
    if float(etudiant['note']) > 15:
        etudiants_excellents.append(etudiant)

print(f"Nombre d'étudiants avec note > 15 : {len(etudiants_excellents)}")

Question 2 :
compteur_villes = {}
for etudiant in donnees:
    ville = etudiant['ville']
    compteur_villes[ville] = compteur_villes.get(ville, 0) + 1

for ville, count in compteur_villes.items():
    print(f"{ville}: {count} étudiant(s)")


Exercice 3: Tri simple des données ★ ★ ★ ★ ☆

Objectif pédagogique : Maîtriser le tri des données CSV selon différents critères.

Énoncé :
1. Triez les étudiants par note décroissante.
2. Triez les étudiants par ordre alphabétique des noms.
3. Affichez les 5 meilleurs étudiants (note la plus élevée).

Attention : Utilisez la fonction sorted() avec le paramètre key pour spécifier le critère de tri.
Solution complète :

Question 1 : Tri par note décroissante
etudiants_par_note = sorted(donnees, key=lambda x: float(x['note']), reverse=True)
for etudiant in etudiants_par_note:
    print(f"{etudiant['nom']}: {etudiant['note']}")

Question 2 : Tri alphabétique
etudiants_alpha = sorted(donnees, key=lambda x: x['nom'])
for etudiant in etudiants_alpha:
    print(f"{etudiant['nom']}")

Question 3 : Top 5
top_5 = etudiants_par_note[:5]
print("Top 5 des meilleurs étudiants :")
for i, etudiant in enumerate(top_5, 1):
    print(f"{i}. {etudiant['nom']}: {etudiant['note']}")


Exercice 4: Tri selon plusieurs critères ★ ★ ★ ★ ★

Objectif pédagogique : Effectuer des tris complexes avec plusieurs critères de priorité.

Énoncé :
1. Triez les étudiants par ville (ordre alphabétique), puis par note décroissante au sein de chaque ville.
2. Créez une fonction qui permet de trier selon n'importe quelle combinaison de critères.
3. Testez votre fonction en triant par âge croissant, puis par nom alphabétique.

Méthode : Utilisez un tuple dans la fonction key pour définir plusieurs critères de tri.
Analyse approfondie :

Question 1 : Tri multi-critères
etudiants_ville_note = sorted(donnees, 
                               key=lambda x: (x['ville'], -float(x['note'])))
for etudiant in etudiants_ville_note:
    print(f"{etudiant['ville']} - {etudiant['nom']}: {etudiant['note']}")

Question 2 : Fonction générique
def trier_donnees(donnees, criteres):
    """
    criteres: liste de tuples (champ, reverse)
    exemple: [('ville', False), ('note', True)]
    """
    def cle_tri(item):
        result = []
        for champ, reverse in criteres:
            valeur = item[champ]
            if champ in ['note', 'âge']:
                valeur = float(valeur)
            if reverse:
                valeur = -valeur if isinstance(valeur, (int, float)) else valeur
            result.append(valeur)
        return tuple(result)
    
    return sorted(donnees, key=cle_tri)

Question 3 : Test de la fonction
criteres = [('âge', False), ('nom', False)]
result = trier_donnees(donnees, criteres)
for etudiant in result:
    print(f"{etudiant['âge']} ans - {etudiant['nom']}")


Exercice 5: Interrogation avancée avec pandas ★ ★ ★ ★ ★

Objectif pédagogique : Utiliser pandas pour des manipulations avancées de données CSV.

Énoncé :
Utilisez la bibliothèque pandas pour :
1. Lire le fichier CSV et calculer la moyenne des notes par ville.
2. Filtrer les étudiants âgés de 18 à 25 ans avec une note > 12.
3. Créer un tableau croisé dynamique montrant la répartition âge/ville.

Rappel :groupby() pour les regroupements
query() pour les filtres complexes
pivot_table() pour les tableaux croisés
Explications :

Question 1 : Moyenne par ville
import pandas as pd

df = pd.read_csv('etudiants.csv')
moyenne_par_ville = df.groupby('ville')['note'].mean()
print("Moyenne des notes par ville :")
print(moyenne_par_ville)

Question 2 : Filtrage complexe
etudiants_filtres = df.query('18 <= âge <= 25 and note > 12')
print(f"Nombre d'étudiants correspondants : {len(etudiants_filtres)}")
print(etudiants_filtres[['nom', 'âge', 'note', 'ville']])

Question 3 : Tableau croisé
tableau_croise = pd.pivot_table(df, 
                                values='nom', 
                                index='âge', 
                                columns='ville', 
                                aggfunc='count', 
                                fill_value=0)
print("Répartition âge/ville :")
print(tableau_croise)


Application Complète de la Manipulation des Tables CSV

Exercice 1: Importation et structure des données ★ ★ ☆ ☆ ☆

Objectif pédagogique : Maîtriser l'importation CSV avec le module csv et comprendre les structures de données.

Énoncé :
Vous disposez d'un fichier produits.csv avec les colonnes : nom, prix, categorie, stock
1. Implémentez deux fonctions : une avec csv.reader() (liste de listes) et une avec csv.DictReader().
Conseil : Comparez les deux approches et leurs avantages respectifs.

2. Affichez la structure obtenue et testez l'accès aux données. Astuce : Montrez comment accéder au prix du premier produit dans chaque structure.

Correction détaillée :

Question 1 : Deux méthodes d'importation
import csv

# Méthode 1 : csv.reader() - Liste de listes
def lire_csv_listes(nom_fichier):
    with open(nom_fichier, 'r', newline='', encoding='utf-8') as fichier:
        lecteur = csv.reader(fichier)
        entetes = next(lecteur)
        donnees = []
        for ligne in lecteur:
            donnees.append(ligne)
    return entetes, donnees

# Méthode 2 : csv.DictReader() - Liste de dictionnaires  
def lire_csv_dict(nom_fichier):
    with open(nom_fichier, 'r', newline='', encoding='utf-8') as fichier:
        lecteur = csv.DictReader(fichier)
        return list(lecteur)

Question 2 : Test des structures
# Test méthode 1
entetes, produits_listes = lire_csv_listes('produits.csv')
print("Structure liste:", entetes)
print("Premier produit:", produits_listes[0])
print("Prix du premier:", produits_listes[0][1])  # Index 1 pour prix

# Test méthode 2
produits_dict = lire_csv_dict('produits.csv')
print("Premier produit dict:", produits_dict[0])
print("Prix du premier:", produits_dict[0]['prix'])  # Accès par nom
Avantage dictionnaires : Code plus lisible et moins d'erreurs d'index


Exercice 2: Interrogation avancée et analyse statistique ★ ★ ★ ☆ ☆

Objectif pédagogique : Développer des fonctions d'analyse et de statistiques sur les données.

Énoncé :
À partir du fichier produits :
1. Créez une fonction calculer_statistiques(produits, colonne) qui retourne moyenne, min, max et étendue.
Rappel : Attention à la conversion des types pour les calculs numériques.

2. Implémentez grouper_par_categorie(produits) qui retourne un dictionnaire avec les produits regroupés par catégorie. Procédure : Parcourir les produits et les classer dans des listes selon leur catégorie.

Étapes pas à pas :

Question 1 : Fonction statistiques
def calculer_statistiques(produits, colonne):
    """Calcule les statistiques d'une colonne numérique"""
    valeurs = []
    for produit in produits:
        if colonne in ['prix', 'stock']:
            valeurs.append(float(produit[colonne]))
    
    if not valeurs:
        return None
    
    return {
        'moyenne': sum(valeurs) / len(valeurs),
        'min': min(valeurs),
        'max': max(valeurs),
        'etendue': max(valeurs) - min(valeurs),
        'nombre': len(valeurs)
    }

# Utilisation
stats_prix = calculer_statistiques(produits, 'prix')
print(f"Prix moyen: {stats_prix['moyenne']:.2f}€")

Question 2 : Groupement par catégorie
def grouper_par_categorie(produits):
    """Regroupe les produits par catégorie"""
    groupes = {}
    for produit in produits:
        categorie = produit['categorie']
        if categorie not in groupes:
            groupes[categorie] = []
        groupes[categorie].append(produit)
    return groupes

# Test
groupes = grouper_par_categorie(produits)
for cat, liste_produits in groupes.items():
    print(f"{cat}: {len(liste_produits)} produit(s)")


Exercice 3: Système de tri avancé ★ ★ ★ ★ ☆

Objectif pédagogique : Implémenter un système de tri flexible avec plusieurs critères et ordres personnalisés.

Énoncé :
1. Créez une fonction trier_produits(produits, critere, decroissant=False) qui gère tous les types de colonnes.
2. Implémentez tri_multi_criteres(produits, criteres) où critères est une liste de tuples (nom_colonne, decroissant).
3. Testez en triant par catégorie (croissant) puis prix (décroissant).

Attention : Gérez les conversions de types automatiquement selon le type de colonne (texte vs numérique).
Solution complète :

Question 1 : Tri simple intelligent
def trier_produits(produits, critere, decroissant=False):
    """Tri intelligent selon le type de colonne"""
    def cle_tri(produit):
        valeur = produit[critere]
        # Conversion automatique pour colonnes numériques
        if critere in ['prix', 'stock']:
            return float(valeur)
        return valeur.lower()  # Tri insensible à la casse pour texte
    
    return sorted(produits, key=cle_tri, reverse=decroissant)

# Test
produits_par_prix = trier_produits(produits, 'prix', decroissant=True)

Question 2 : Tri multi-critères
def tri_multi_criteres(produits, criteres):
    """
    criteres: liste de tuples (nom_colonne, decroissant)
    Exemple: [('categorie', False), ('prix', True)]
    """
    def cle_tri(produit):
        cle = []
        for nom_colonne, decroissant in criteres:
            valeur = produit[nom_colonne]
            
            # Conversion selon le type
            if nom_colonne in ['prix', 'stock']:
                valeur = float(valeur)
                if decroissant:
                    valeur = -valeur  # Négation pour ordre décroissant
            else:
                valeur = valeur.lower()
                # Pour texte décroissant, on inverse avec un artifice
            
            cle.append(valeur)
        return tuple(cle)
    
    return sorted(produits, key=cle_tri)

# Test
criteres = [('categorie', False), ('prix', True)]
produits_tries = tri_multi_criteres(produits, criteres)
for p in produits_tries[:5]:
    print(f"{p['categorie']} - {p['nom']}: {p['prix']}€")

Question 3 : Version améliorée pour texte décroissant
def tri_multi_criteres_ameliore(produits, criteres):
    # Tri en plusieurs passes pour gérer texte décroissant
    result = produits.copy()
    
    # Tri dans l'ordre inverse des critères
    for nom_colonne, decroissant in reversed(criteres):
        if nom_colonne in ['prix', 'stock']:
            result = sorted(result, 
                          key=lambda x: float(x[nom_colonne]), 
                          reverse=decroissant)
        else:
            result = sorted(result, 
                          key=lambda x: x[nom_colonne].lower(), 
                          reverse=decroissant)
    
    return result


Exercice 4: Classe de gestion complète ★ ★ ★ ★ ★

Objectif pédagogique : Créer une classe complète pour la gestion de données CSV avec toutes les fonctionnalités.

Énoncé :
Créez une classe GestionCSV avec les méthodes suivantes :
1. __init__(fichier) : Charge les données
2. filtrer(critere, valeur) : Filtre selon un critère
3. rechercher_avancee(**kwargs) : Recherche avec plusieurs critères
4. exporter_filtre(donnees, nom_fichier) : Sauvegarde un sous-ensemble

Méthode : Utilisez **kwargs pour permettre des recherches flexibles comme rechercher_avancee(categorie="Electronique", prix_min=100).
Analyse approfondie :

Classe complète GestionCSV
import csv

class GestionCSV:
    def __init__(self, fichier):
        """Initialise avec le chargement des données"""
        self.fichier = fichier
        self.donnees = self.charger_donnees()
        self.colonnes_numeriques = ['prix', 'stock']  # Configurable
    
    def charger_donnees(self):
        """Charge les données du fichier CSV"""
        with open(self.fichier, 'r', newline='', encoding='utf-8') as f:
            return list(csv.DictReader(f))
    
    def filtrer(self, critere, valeur):
        """Filtre selon un critère exact"""
        return [item for item in self.donnees if item[critere] == str(valeur)]
    
    def rechercher_avancee(self, **kwargs):
        """Recherche avec critères multiples et opérateurs"""
        resultats = self.donnees.copy()
        
        for cle, valeur in kwargs.items():
            if cle.endswith('_min'):
                # Critère minimum (ex: prix_min)
                colonne = cle[:-4]
                if colonne in self.colonnes_numeriques:
                    resultats = [r for r in resultats 
                               if float(r[colonne]) >= float(valeur)]
            elif cle.endswith('_max'):
                # Critère maximum (ex: prix_max)
                colonne = cle[:-4]
                if colonne in self.colonnes_numeriques:
                    resultats = [r for r in resultats 
                               if float(r[colonne]) <= float(valeur)]
            elif cle.endswith('_contient'):
                # Recherche textuelle (ex: nom_contient)
                colonne = cle[:-9]
                resultats = [r for r in resultats 
                           if str(valeur).lower() in r[colonne].lower()]
            else:
                # Critère exact
                resultats = [r for r in resultats if r[cle] == str(valeur)]
        
        return resultats
    
    def exporter_filtre(self, donnees, nom_fichier):
        """Exporte un sous-ensemble des données"""
        if not donnees:
            print("Aucune donnée à exporter")
            return
        
        with open(nom_fichier, 'w', newline='', encoding='utf-8') as f:
            writer = csv.DictWriter(f, fieldnames=donnees[0].keys())
            writer.writeheader()
            writer.writerows(donnees)
        
        print(f"Exporté {len(donnees)} lignes vers {nom_fichier}")
    
    def statistiques_globales(self):
        """Retourne des statistiques générales"""
        stats = {}
        for col in self.colonnes_numeriques:
            valeurs = [float(item[col]) for item in self.donnees]
            stats[col] = {
                'moyenne': sum(valeurs) / len(valeurs),
                'min': min(valeurs),
                'max': max(valeurs)
            }
        return stats

Utilisation de la classe
# Initialisation
gestion = GestionCSV('produits.csv')

# Recherches avancées
chers = gestion.rechercher_avancee(prix_min=50, categorie="Electronique")
recherche = gestion.rechercher_avancee(nom_contient="phone", prix_max=800)

# Export des résultats
gestion.exporter_filtre(chers, 'produits_chers_electronique.csv')

# Statistiques
stats = gestion.statistiques_globales()
print(f"Prix moyen: {stats['prix']['moyenne']:.2f}€")


Exercice 5: Analyse comparative et reporting ★ ★ ★ ★ ★

Objectif pédagogique : Développer un système d'analyse comparative entre plusieurs fichiers CSV et génération de rapports.

Énoncé :
Vous avez deux fichiers : produits_2023.csv et produits_2024.csv
1. Créez une fonction qui compare les prix entre les deux années et identifie les évolutions.
2. Générez un rapport textuel avec les produits ajoutés, supprimés et modifiés.
3. Calculez les statistiques d'évolution par catégorie (hausse/baisse moyenne des prix).

Rappel : • Utilisez les noms de produits comme clé de comparaison
• Gérez les cas où un produit n'existe que dans l'un des fichiers
• Formatez les rapports pour une lecture claire
Système d'analyse comparative :

Fonction de comparaison principale
def comparer_annees(fichier_ancien, fichier_nouveau):
    """Compare deux fichiers CSV de produits"""
    import csv
    
    # Chargement des données
    def charger_dict_produits(fichier):
        with open(fichier, 'r', newline='', encoding='utf-8') as f:
            lecteur = csv.DictReader(f)
            return {row['nom']: row for row in lecteur}
    
    produits_2023 = charger_dict_produits(fichier_ancien)
    produits_2024 = charger_dict_produits(fichier_nouveau)
    
    # Analyse des changements
    rapport = {
        'ajoutes': [],
        'supprimes': [],
        'modifies': [],
        'inchanges': []
    }
    
    # Produits de 2024
    for nom, produit_2024 in produits_2024.items():
        if nom not in produits_2023:
            rapport['ajoutes'].append(produit_2024)
        else:
            produit_2023 = produits_2023[nom]
            if produit_2023['prix'] != produit_2024['prix']:
                rapport['modifies'].append({
                    'nom': nom,
                    'ancien_prix': float(produit_2023['prix']),
                    'nouveau_prix': float(produit_2024['prix']),
                    'evolution': float(produit_2024['prix']) - float(produit_2023['prix']),
                    'categorie': produit_2024['categorie']
                })
            else:
                rapport['inchanges'].append(produit_2024)
    
    # Produits supprimés (présents en 2023 mais pas en 2024)
    for nom, produit in produits_2023.items():
        if nom not in produits_2024:
            rapport['supprimes'].append(produit)
    
    return rapport

Génération du rapport textuel
def generer_rapport_textuel(rapport, nom_fichier_rapport):
    """Génère un rapport textuel détaillé"""
    with open(nom_fichier_rapport, 'w', encoding='utf-8') as f:
        f.write("RAPPORT D'ÉVOLUTION DES PRODUITS 2023-2024\n")
        f.write("=" * 50 + "\n\n")
        
        # Statistiques générales
        f.write(f"📊 STATISTIQUES GÉNÉRALES\n")
        f.write(f"Produits ajoutés: {len(rapport['ajoutes'])}\n")
        f.write(f"Produits supprimés: {len(rapport['supprimes'])}\n")
        f.write(f"Produits modifiés: {len(rapport['modifies'])}\n")
        f.write(f"Produits inchangés: {len(rapport['inchanges'])}\n\n")
        
        # Détail des modifications
        if rapport['modifies']:
            f.write("💰 ÉVOLUTIONS DE PRIX\n")
            f.write("-" * 30 + "\n")
            for modif in rapport['modifies']:
                evolution = modif['evolution']
                signe = "📈" if evolution > 0 else "📉"
                f.write(f"{signe} {modif['nom']}: ")
                f.write(f"{modif['ancien_prix']:.2f}€ → {modif['nouveau_prix']:.2f}€ ")
                f.write(f"({evolution:+.2f}€)\n")
        
        # Nouveaux produits
        if rapport['ajoutes']:
            f.write(f"\n✅ NOUVEAUX PRODUITS ({len(rapport['ajoutes'])})\n")
            f.write("-" * 30 + "\n")
            for produit in rapport['ajoutes']:
                f.write(f"• {produit['nom']} - {produit['prix']}€ ")
                f.write(f"({produit['categorie']})\n")
        
        # Produits supprimés
        if rapport['supprimes']:
            f.write(f"\n❌ PRODUITS SUPPRIMÉS ({len(rapport['supprimes'])})\n")
            f.write("-" * 30 + "\n")
            for produit in rapport['supprimes']:
                f.write(f"• {produit['nom']} - {produit['prix']}€\n")
    
    print(f"Rapport généré: {nom_fichier_rapport}")

Statistiques par catégorie
def analyser_evolution_categories(rapport):
    """Analyse les évolutions de prix par catégorie"""
    stats_categories = {}
    
    for modif in rapport['modifies']:
        cat = modif['categorie']
        if cat not in stats_categories:
            stats_categories[cat] = {
                'evolutions': [],
                'hausse_count': 0,
                'baisse_count': 0
            }
        
        evolution = modif['evolution']
        stats_categories[cat]['evolutions'].append(evolution)
        
        if evolution > 0:
            stats_categories[cat]['hausse_count'] += 1
        else:
            stats_categories[cat]['baisse_count'] += 1
    
    # Calcul des moyennes
    for cat, data in stats_categories.items():
        evolutions = data['evolutions']
        data['evolution_moyenne'] = sum(evolutions) / len(evolutions)
        data['total_modifies'] = len(evolutions)
    
    return stats_categories

# Utilisation complète
rapport = comparer_annees('produits_2023.csv', 'produits_2024.csv')
generer_rapport_textuel(rapport, 'rapport_evolution.txt')
stats_cat = analyser_evolution_categories(rapport)

print("Évolution par catégorie:")
for cat, stats in stats_cat.items():
    print(f"{cat}: {stats['evolution_moyenne']:+.2f}€ en moyenne")


5 Nouveaux Exercices - Manipulation Avancée des CSV avec Listes de Dictionnaires

Exercice 6: Système de gestion d'employés ★ ★ ★ ☆ ☆

Objectif pédagogique : Manipuler des données RH avec calculs de salaires et gestion des départements.

Contexte :
Vous travaillez pour une entreprise et devez analyser le fichier employes.csv contenant : nom, prenom, departement, salaire_base, heures_sup, taux_horaire

Énoncé :
1. Créez une fonction calculer_salaire_total(employes) qui ajoute une clé 'salaire_total' à chaque dictionnaire d'employé.
Formule : salaire_total = salaire_base + (heures_sup × taux_horaire)

2. Implémentez top_employes_par_departement(employes, n=3) qui retourne les n meilleurs salaires par département.
Conseil : Utilisez les listes de dictionnaires pour conserver toutes les informations.

Solution complète :

Question 1 : Calcul des salaires totaux
import csv

def charger_employes(fichier):
    """Charge les employés depuis le CSV"""
    with open(fichier, 'r', newline='', encoding='utf-8') as f:
        return list(csv.DictReader(f))

def calculer_salaire_total(employes):
    """Ajoute le salaire total à chaque employé"""
    for employe in employes:
        salaire_base = float(employe['salaire_base'])
        heures_sup = float(employe['heures_sup'])
        taux_horaire = float(employe['taux_horaire'])
        
        # Ajout de la nouvelle clé au dictionnaire
        employe['salaire_total'] = salaire_base + (heures_sup * taux_horaire)
    
    return employes

# Test
employes = charger_employes('employes.csv')
employes = calculer_salaire_total(employes)
print(f"Premier employé: {employes[0]['prenom']} {employes[0]['nom']}")
print(f"Salaire total: {employes[0]['salaire_total']:.2f}€")

Question 2 : Top employés par département
def top_employes_par_departement(employes, n=3):
    """Retourne les n meilleurs salaires par département"""
    # Groupement par département
    departements = {}
    for employe in employes:
        dept = employe['departement']
        if dept not in departements:
            departements[dept] = []
        departements[dept].append(employe)
    
    # Tri et sélection des top n pour chaque département
    top_par_dept = {}
    for dept, liste_employes in departements.items():
        # Tri par salaire total décroissant
        employes_tries = sorted(liste_employes, 
                               key=lambda x: x['salaire_total'], 
                               reverse=True)
        top_par_dept[dept] = employes_tries[:n]
    
    return top_par_dept

# Utilisation
top_employes = top_employes_par_departement(employes, 3)
for dept, top_liste in top_employes.items():
    print(f"\nTop 3 - {dept}:")
    for i, emp in enumerate(top_liste, 1):
        print(f"  {i}. {emp['prenom']} {emp['nom']}: {emp['salaire_total']:.2f}€")


Exercice 7: Analyse de ventes par région ★ ★ ★ ☆ ☆

Objectif pédagogique : Analyser des données de ventes avec agrégations complexes et calculs de performances.

Contexte :
Une chaîne de magasins vous fournit ventes.csv avec : magasin, region, vendeur, date, produit, quantite, prix_unitaire

Énoncé :
1. Créez enrichir_ventes(ventes) qui ajoute les clés 'chiffre_affaires' et 'trimestre' à chaque vente.
Astuce : chiffre_affaires = quantite × prix_unitaire, trimestre basé sur le mois

2. Implémentez performance_vendeurs(ventes) qui retourne pour chaque vendeur : total_ca, nb_ventes, ca_moyen, meilleur_mois.
Défi : Exploitez les dictionnaires pour stocker plusieurs métriques par vendeur.

Analyse détaillée :

Question 1 : Enrichissement des ventes
from datetime import datetime

def enrichir_ventes(ventes):
    """Enrichit chaque vente avec CA et trimestre"""
    for vente in ventes:
        # Calcul du chiffre d'affaires
        quantite = float(vente['quantite'])
        prix_unitaire = float(vente['prix_unitaire'])
        vente['chiffre_affaires'] = quantite * prix_unitaire
        
        # Calcul du trimestre depuis la date
        date_vente = datetime.strptime(vente['date'], '%Y-%m-%d')
        mois = date_vente.month
        
        if mois <= 3:
            vente['trimestre'] = 'Q1'
        elif mois <= 6:
            vente['trimestre'] = 'Q2'
        elif mois <= 9:
            vente['trimestre'] = 'Q3'
        else:
            vente['trimestre'] = 'Q4'
    
    return ventes

# Test d'enrichissement
ventes = charger_ventes('ventes.csv')
ventes_enrichies = enrichir_ventes(ventes)
print(f"Première vente enrichie:")
print(f"CA: {ventes_enrichies[0]['chiffre_affaires']:.2f}€")
print(f"Trimestre: {ventes_enrichies[0]['trimestre']}")

Question 2 : Performance des vendeurs
def performance_vendeurs(ventes):
    """Analyse la performance de chaque vendeur"""
    performances = {}
    
    for vente in ventes:
        vendeur = vente['vendeur']
        ca = vente['chiffre_affaires']
        date_vente = datetime.strptime(vente['date'], '%Y-%m-%d')
        mois = date_vente.strftime('%Y-%m')
        
        # Initialisation du vendeur s'il n'existe pas
        if vendeur not in performances:
            performances[vendeur] = {
                'total_ca': 0,
                'nb_ventes': 0,
                'ventes_par_mois': {},
                'region': vente['region']  # Info supplémentaire
            }
        
        # Mise à jour des métriques
        perf = performances[vendeur]
        perf['total_ca'] += ca
        perf['nb_ventes'] += 1
        
        # Suivi par mois
        if mois not in perf['ventes_par_mois']:
            perf['ventes_par_mois'][mois] = 0
        perf['ventes_par_mois'][mois] += ca
    
    # Calculs finaux pour chaque vendeur
    for vendeur, perf in performances.items():
        perf['ca_moyen'] = perf['total_ca'] / perf['nb_ventes']
        
        # Meilleur mois
        meilleur_mois = max(perf['ventes_par_mois'].items(), 
                           key=lambda x: x[1])
        perf['meilleur_mois'] = meilleur_mois[0]
        perf['ca_meilleur_mois'] = meilleur_mois[1]
    
    return performances

# Utilisation et affichage
perfs = performance_vendeurs(ventes_enrichies)
for vendeur, stats in perfs.items():
    print(f"\n🏆 {vendeur} ({stats['region']}):")
    print(f"   CA Total: {stats['total_ca']:,.2f}€")
    print(f"   Nb ventes: {stats['nb_ventes']}")
    print(f"   CA moyen: {stats['ca_moyen']:.2f}€")
    print(f"   Meilleur mois: {stats['meilleur_mois']} ({stats['ca_meilleur_mois']:,.2f}€)")


Exercice 8: Système de recommandation de films ★ ★ ★ ★ ☆

Objectif pédagogique : Construire un système de recommandation basé sur les ratings utilisateurs avec analyse de similarité.

Contexte :
Une plateforme de streaming vous fournit ratings.csv avec : user_id, film_titre, genre, note, date_visionnage, duree_minutes

Énoncé :
1. Créez profil_utilisateur(ratings, user_id) qui retourne le profil complet d'un utilisateur avec ses préférences de genre.
Inclure : genres favoris, note moyenne, films notés, temps total de visionnage

2. Implémentez recommander_films(ratings, user_id, nb_recommandations=5) qui suggère des films basés sur les utilisateurs similaires.
Méthode : Trouver les utilisateurs avec des goûts similaires et recommander leurs films bien notés.

Algorithme suggéré : Calculer la similarité entre utilisateurs basée sur les genres préférés et les notes moyennes.
Système de recommandation complet :

Question 1 : Profil utilisateur détaillé
def profil_utilisateur(ratings, user_id):
    """Génère le profil complet d'un utilisateur"""
    ratings_user = [r for r in ratings if r['user_id'] == user_id]
    
    if not ratings_user:
        return None
    
    # Calculs de base
    notes = [float(r['note']) for r in ratings_user]
    durees = [int(r['duree_minutes']) for r in ratings_user]
    
    # Analyse des genres
    compteur_genres = {}
    notes_par_genre = {}
    
    for rating in ratings_user:
        genre = rating['genre']
        note = float(rating['note'])
        
        # Comptage des genres
        compteur_genres[genre] = compteur_genres.get(genre, 0) + 1
        
        # Notes par genre
        if genre not in notes_par_genre:
            notes_par_genre[genre] = []
        notes_par_genre[genre].append(note)
    
    # Genres favoris (par fréquence et note moyenne)
    genres_avec_stats = []
    for genre, count in compteur_genres.items():
        note_moy_genre = sum(notes_par_genre[genre]) / len(notes_par_genre[genre])
        genres_avec_stats.append({
            'genre': genre,
            'count': count,
            'note_moyenne': note_moy_genre,
            'score_preference': count * note_moy_genre  # Score combiné
        })
    
    # Tri par score de préférence
    genres_favoris = sorted(genres_avec_stats, 
                           key=lambda x: x['score_preference'], 
                           reverse=True)
    
    return {
        'user_id': user_id,
        'nb_films_notes': len(ratings_user),
        'note_moyenne': sum(notes) / len(notes),
        'temps_total_minutes': sum(durees),
        'temps_total_heures': sum(durees) / 60,
        'genres_favoris': genres_favoris[:3],  # Top 3
        'meilleur_film': max(ratings_user, key=lambda x: float(x['note'])),
        'dernier_visionnage': max(ratings_user, key=lambda x: x['date_visionnage'])
    }

Question 2 : Système de recommandation
def calculer_similarite_utilisateurs(profil1, profil2):
    """Calcule la similarité entre deux profils utilisateur"""
    if not profil1 or not profil2:
        return 0
    
    # Similarité basée sur les notes moyennes
    diff_notes = abs(profil1['note_moyenne'] - profil2['note_moyenne'])
    sim_notes = max(0, 1 - diff_notes / 5)  # Normalisation sur 5
    
    # Similarité des genres favoris
    genres1 = set(g['genre'] for g in profil1['genres_favoris'])
    genres2 = set(g['genre'] for g in profil2['genres_favoris'])
    
    if genres1 and genres2:
        intersection = len(genres1.intersection(genres2))
        union = len(genres1.union(genres2))
        sim_genres = intersection / union if union > 0 else 0
    else:
        sim_genres = 0
    
    # Score de similarité combiné
    return (sim_notes * 0.4) + (sim_genres * 0.6)

def recommander_films(ratings, user_id, nb_recommandations=5):
    """Recommande des films basés sur des utilisateurs similaires"""
    # Profil de l'utilisateur cible
    profil_cible = profil_utilisateur(ratings, user_id)
    if not profil_cible:
        return []
    
    # Films déjà vus par l'utilisateur
    films_vus = set(r['film_titre'] for r in ratings if r['user_id'] == user_id)
    
    # Calcul de similarité avec tous les autres utilisateurs
    autres_users = list(set(r['user_id'] for r in ratings if r['user_id'] != user_id))
    similarities = []
    
    for other_user in autres_users:
        profil_autre = profil_utilisateur(ratings, other_user)
        if profil_autre:
            sim_score = calculer_similarite_utilisateurs(profil_cible, profil_autre)
            if sim_score > 0.3:  # Seuil de similarité
                similarities.append((other_user, sim_score))
    
    # Tri par similarité décroissante
    similarities.sort(key=lambda x: x[1], reverse=True)
    users_similaires = similarities[:10]  # Top 10 utilisateurs similaires
    
    # Collecte des recommandations
    recommandations = {}
    for similar_user, sim_score in users_similaires:
        user_ratings = [r for r in ratings if r['user_id'] == similar_user]
        
        for rating in user_ratings:
            film = rating['film_titre']
            note = float(rating['note'])
            
            # Seulement les films bien notés (>= 4) et non vus
            if note >= 4 and film not in films_vus:
                if film not in recommandations:
                    recommandations[film] = {
                        'score_total': 0,
                        'nb_recommandeurs': 0,
                        'genre': rating['genre'],
                        'duree_minutes': rating['duree_minutes']
                    }
                
                # Score pondéré par la similarité
                recommandations[film]['score_total'] += note * sim_score
                recommandations[film]['nb_recommandeurs'] += 1
    
    # Calcul du score final et tri
    films_recommandes = []
    for film, data in recommandations.items():
        if data['nb_recommandeurs'] >= 2:  # Au moins 2 recommandeurs
            score_final = data['score_total'] / data['nb_recommandeurs']
            films_recommandes.append({
                'film': film,
                'score': score_final,
                'genre': data['genre'],
                'duree_minutes': data['duree_minutes'],
                'nb_recommandeurs': data['nb_recommandeurs']
            })
    
    # Tri par score et retour des top recommandations
    films_recommandes.sort(key=lambda x: x['score'], reverse=True)
    return films_recommandes[:nb_recommandations]

# Utilisation complète
profil = profil_utilisateur(ratings, 'user_123')
print(f"Profil de {profil['user_id']}:")
print(f"Films notés: {profil['nb_films_notes']}")
print(f"Note moyenne: {profil['note_moyenne']:.2f}")
print(f"Genres favoris: {[g['genre'] for g in profil['genres_favoris']]}")

recommandations = recommander_films(ratings, 'user_123', 5)
print(f"\n🎬 Recommandations pour user_123:")
for i, rec in enumerate(recommandations, 1):
    print(f"{i}. {rec['film']} ({rec['genre']}) - Score: {rec['score']:.2f}")


Exercice 9: Analyse financière de portefeuille ★ ★ ★ ★ ☆

Objectif pédagogique : Analyser des données financières complexes avec calculs de rendements et gestion de risque.

Contexte :
Une société de gestion vous confie l'analyse de transactions.csv contenant : client_id, date, action_type, symbole, quantite, prix, frais
(action_type: 'BUY' ou 'SELL')

Énoncé :
1. Créez calculer_portefeuille_actuel(transactions) qui retourne pour chaque client son portefeuille actuel (positions et valeurs).
Logique : BUY ajoute des actions, SELL les retire

2. Implémentez analyser_performance_client(transactions, client_id, prix_actuels) qui calcule les plus/moins-values réalisées et latentes.
Challenge : Gérer les achats/ventes multiples avec méthode FIFO (First In, First Out)

Données supplémentaires : prix_actuels est un dictionnaire {symbole: prix_actuel} pour calculer la valeur de marché.
Système d'analyse financière avancé :

Question 1 : Calcul des portefeuilles actuels
from datetime import datetime
from collections import defaultdict
import pandas as pd

def calculer_portefeuille_actuel(transactions):
    """Calcule le portefeuille actuel de chaque client"""
    portefeuilles = defaultdict(lambda: defaultdict(lambda: {'quantite': 0, 'historique': []}))
    
    # Tri chronologique des transactions
    transactions_triees = sorted(transactions, 
                                key=lambda x: datetime.strptime(x['date'], '%Y-%m-%d'))
    
    for transaction in transactions_triees:
        client = transaction['client_id']
        symbole = transaction['symbole']
        quantite = int(transaction['quantite'])
        prix = float(transaction['prix'])
        frais = float(transaction['frais'])
        action = transaction['action_type']
        
        position = portefeuilles[client][symbole]
        
        if action == 'BUY':
            # Achat : augmente la position
            position['quantite'] += quantite
            position['historique'].append({
                'type': 'BUY',
                'quantite': quantite,
                'prix': prix,
                'frais': frais,
                'date': transaction['date']
            })
        
        elif action == 'SELL':
            # Vente : diminue la position
            position['quantite'] -= quantite
            position['historique'].append({
                'type': 'SELL',
                'quantite': quantite,
                'prix': prix,
                'frais': frais,
                'date': transaction['date']
            })
    
    # Nettoyage : suppression des positions nulles
    portefeuilles_nettoyes = {}
    for client, positions in portefeuilles.items():
        portefeuilles_nettoyes[client] = {
            symbole: data for symbole, data in positions.items() 
            if data['quantite'] > 0
        }
    
    return portefeuilles_nettoyes


def analyser_performance_client(transactions, client_id, prix_actuels):
    """Analyse complète de performance d'un client avec méthode FIFO"""
    
    # Filtrage des transactions du client
    trans_client = [t for t in transactions if t['client_id'] == client_id]
    trans_client = sorted(trans_client, 
                         key=lambda x: datetime.strptime(x['date'], '%Y-%m-%d'))
    
    # Structure pour gérer les positions FIFO
    positions = defaultdict(list)  # Liste des achats par symbole
    plus_values_realisees = []
    frais_totaux = 0
    
    for transaction in trans_client:
        symbole = transaction['symbole']
        quantite = int(transaction['quantite'])
        prix = float(transaction['prix'])
        frais = float(transaction['frais'])
        action = transaction['action_type']
        date = transaction['date']
        
        frais_totaux += frais
        
        if action == 'BUY':
            # Ajout à la pile FIFO
            positions[symbole].append({
                'quantite': quantite,
                'prix': prix,
                'date': date
            })
        
        elif action == 'SELL':
            # Vente FIFO : on vend les plus anciens achats en premier
            quantite_a_vendre = quantite
            
            while quantite_a_vendre > 0 and positions[symbole]:
                achat = positions[symbole][0]
                
                if achat['quantite'] <= quantite_a_vendre:
                    # Vente complète de cet achat
                    quantite_vendue = achat['quantite']
                    plus_value = (prix - achat['prix']) * quantite_vendue
                    
                    plus_values_realisees.append({
                        'symbole': symbole,
                        'quantite': quantite_vendue,
                        'prix_achat': achat['prix'],
                        'prix_vente': prix,
                        'plus_value': plus_value,
                        'date_achat': achat['date'],
                        'date_vente': date
                    })
                    
                    quantite_a_vendre -= quantite_vendue
                    positions[symbole].pop(0)  # Retrait de la pile
                
                else:
                    # Vente partielle
                    plus_value = (prix - achat['prix']) * quantite_a_vendre
                    
                    plus_values_realisees.append({
                        'symbole': symbole,
                        'quantite': quantite_a_vendre,
                        'prix_achat': achat['prix'],
                        'prix_vente': prix,
                        'plus_value': plus_value,
                        'date_achat': achat['date'],
                        'date_vente': date
                    })
                    
                    achat['quantite'] -= quantite_a_vendre
                    quantite_a_vendre = 0
    
    # Calcul des plus-values latentes (positions actuelles)
    plus_values_latentes = []
    valeur_portefeuille = 0
    cout_total_positions = 0
    
    for symbole, achats in positions.items():
        if symbole in prix_actuels:
            prix_actuel = prix_actuels[symbole]
            
            for achat in achats:
                quantite = achat['quantite']
                prix_achat = achat['prix']
                
                valeur_actuelle = quantite * prix_actuel
                cout_achat = quantite * prix_achat
                plus_value_latente = valeur_actuelle - cout_achat
                
                plus_values_latentes.append({
                    'symbole': symbole,
                    'quantite': quantite,
                    'prix_achat': prix_achat,
                    'prix_actuel': prix_actuel,
                    'plus_value_latente': plus_value_latente,
                    'valeur_actuelle': valeur_actuelle,
                    'date_achat': achat['date']
                })
                
                valeur_portefeuille += valeur_actuelle
                cout_total_positions += cout_achat
    
    # Calculs de synthèse
    total_plus_values_realisees = sum(pv['plus_value'] for pv in plus_values_realisees)
    total_plus_values_latentes = sum(pv['plus_value_latente'] for pv in plus_values_latentes)
    
    return {
        'client_id': client_id,
        'plus_values_realisees': plus_values_realisees,
        'plus_values_latentes': plus_values_latentes,
        'resume': {
            'total_pv_realisees': total_plus_values_realisees,
            'total_pv_latentes': total_plus_values_latentes,
            'total_pv_globales': total_plus_values_realisees + total_plus_values_latentes,
            'valeur_portefeuille': valeur_portefeuille,
            'cout_total_positions': cout_total_positions,
            'frais_totaux': frais_totaux,
            'rendement_net': ((total_plus_values_realisees + total_plus_values_latentes - frais_totaux) / cout_total_positions * 100) if cout_total_positions > 0 else 0
        }
    }


def analyser_risque_portefeuille(transactions, prix_actuels, volatilites):
    """Analyse de risque avancée pour tous les portefeuilles"""
    portefeuilles = calculer_portefeuille_actuel(transactions)
    analyses_risque = {}
    
    for client_id in portefeuilles.keys():
        performance = analyser_performance_client(transactions, client_id, prix_actuels)
        
        # Calcul de la répartition du portefeuille
        positions_actuelles = {}
        valeur_totale = performance['resume']['valeur_portefeuille']
        
        for pv_latente in performance['plus_values_latentes']:
            symbole = pv_latente['symbole']
            valeur = pv_latente['valeur_actuelle']
            
            if symbole in positions_actuelles:
                positions_actuelles[symbole] += valeur
            else:
                positions_actuelles[symbole] = valeur
        
        # Calcul du risque (écart-type pondéré)
        risque_portefeuille = 0
        if valeur_totale > 0:
            for symbole, valeur in positions_actuelles.items():
                poids = valeur / valeur_totale
                volatilite = volatilites.get(symbole, 0.2)  # 20% par défaut
                risque_portefeuille += (poids * volatilite) ** 2
            
            risque_portefeuille = (risque_portefeuille ** 0.5) * 100
        
        # Ratio de Sharpe simplifié (rendement / risque)
        rendement = performance['resume']['rendement_net']
        ratio_sharpe = rendement / risque_portefeuille if risque_portefeuille > 0 else 0
        
        analyses_risque[client_id] = {
            'risque_portefeuille': risque_portefeuille,
            'rendement_net': rendement,
            'ratio_sharpe': ratio_sharpe,
            'diversification': len(positions_actuelles),
            'concentration_max': (max(positions_actuelles.values()) / valeur_totale * 100) if valeur_totale > 0 else 0
        }
    
    return analyses_risque


def generer_rapport_complet(transactions, prix_actuels, volatilites=None):
    """Génère un rapport complet d'analyse de portefeuille"""
    if volatilites is None:
        # Volatilités par défaut pour l'exemple
        volatilites = {
            'AAPL': 0.25, 'GOOGL': 0.30, 'MSFT': 0.22, 'TSLA': 0.45,
            'AMZN': 0.28, 'META': 0.35, 'NVDA': 0.40
        }
    
    print("="*80)
    print("📊 RAPPORT D'ANALYSE DE PORTEFEUILLE - SOCIÉTÉ DE GESTION")
    print("="*80)
    
    # Analyse globale
    portefeuilles = calculer_portefeuille_actuel(transactions)
    analyses_risque = analyser_risque_portefeuille(transactions, prix_actuels, volatilites)
    
    print(f"\n📈 RÉSUMÉ EXÉCUTIF")
    print(f"Nombre de clients analysés: {len(portefeuilles)}")
    
    # Analyse par client
    for client_id in portefeuilles.keys():
        print(f"\n" + "="*60)
        print(f"💼 CLIENT: {client_id}")
        print("="*60)
        
        # Performance détaillée
        performance = analyser_performance_client(transactions, client_id, prix_actuels)
        resume = performance['resume']
        
        print(f"💰 PERFORMANCE FINANCIÈRE:")
        print(f"  • Valeur du portefeuille: {resume['valeur_portefeuille']:,.2f} €")
        print(f"  • Plus-values réalisées: {resume['total_pv_realisees']:,.2f} €")
        print(f"  • Plus-values latentes: {resume['total_pv_latentes']:,.2f} €")
        print(f"  • Frais totaux: {resume['frais_totaux']:,.2f} €")
        print(f"  • Rendement net: {resume['rendement_net']:.2f}%")
        
        # Analyse de risque
        if client_id in analyses_risque:
            risque = analyses_risque[client_id]
            print(f"\n⚠️  ANALYSE DE RISQUE:")
            print(f"  • Risque du portefeuille: {risque['risque_portefeuille']:.2f}%")
            print(f"  • Ratio de Sharpe: {risque['ratio_sharpe']:.2f}")
            print(f"  • Diversification: {risque['diversification']} titres")
            print(f"  • Concentration max: {risque['concentration_max']:.1f}%")
        
        # Détail des positions actuelles
        print(f"\n📋 POSITIONS ACTUELLES:")
        portefeuille_client = portefeuilles[client_id]
        for symbole, data in portefeuille_client.items():
            if symbole in prix_actuels:
                valeur = data['quantite'] * prix_actuels[symbole]
                print(f"  • {symbole}: {data['quantite']} actions → {valeur:,.2f} €")
        
        # Top 3 des meilleures plus-values réalisées
        if performance['plus_values_realisees']:
            print(f"\n🏆 TOP 3 MEILLEURES OPÉRATIONS:")
            top_operations = sorted(performance['plus_values_realisees'], 
                                  key=lambda x: x['plus_value'], reverse=True)[:3]
            for i, op in enumerate(top_operations, 1):
                print(f"  {i}. {op['symbole']}: +{op['plus_value']:,.2f} € "
                      f"({op['quantite']} actions à {op['prix_vente']:.2f} €)")
    
    return {
        'portefeuilles': portefeuilles,
        'analyses_performance': {client_id: analyser_performance_client(transactions, client_id, prix_actuels) 
                               for client_id in portefeuilles.keys()},
        'analyses_risque': analyses_risque
    }


# ========================================
# EXEMPLE D'UTILISATION ET TESTS
# ========================================

if __name__ == "__main__":
    # Données de test
    transactions_exemple = [
        {'client_id': 'C001', 'date': '2023-01-15', 'action_type': 'BUY', 'symbole': 'AAPL', 'quantite': 100, 'prix': 150.0, 'frais': 10.0},
        {'client_id': 'C001', 'date': '2023-02-01', 'action_type': 'BUY', 'symbole': 'GOOGL', 'quantite': 50, 'prix': 2800.0, 'frais': 15.0},
        {'client_id': 'C001', 'date': '2023-03-15', 'action_type': 'SELL', 'symbole': 'AAPL', 'quantite': 30, 'prix': 165.0, 'frais': 8.0},
        {'client_id': 'C001', 'date': '2023-04-01', 'action_type': 'BUY', 'symbole': 'MSFT', 'quantite': 75, 'prix': 280.0, 'frais': 12.0},
        
        {'client_id': 'C002', 'date': '2023-01-20', 'action_type': 'BUY', 'symbole': 'TSLA', 'quantite': 80, 'prix': 200.0, 'frais': 20.0},
        {'client_id': 'C002', 'date': '2023-02-15', 'action_type': 'BUY', 'symbole': 'NVDA', 'quantite': 40, 'prix': 450.0, 'frais': 18.0},
        {'client_id': 'C002', 'date': '2023-03-01', 'action_type': 'SELL', 'symbole': 'TSLA', 'quantite': 20, 'prix': 250.0, 'frais': 10.0},
    ]
    
    prix_actuels_exemple = {
        'AAPL': 175.0,
        'GOOGL': 2900.0,
        'MSFT': 320.0,
        'TSLA': 240.0,
        'NVDA': 520.0
    }
    
    # Test des fonctions
    print("🧪 TESTS DES FONCTIONS")
    print("-" * 50)
    
    # Test 1: Calcul des portefeuilles actuels
    print("✅ Test 1: Portefeuilles actuels")
    portefeuilles = calculer_portefeuille_actuel(transactions_exemple)
    for client, positions in portefeuilles.items():
        print(f"  {client}: {len(positions)} positions")
    
    # Test 2: Analyse de performance
    print("\n✅ Test 2: Analyse de performance C001")
    perf_c001 = analyser_performance_client(transactions_exemple, 'C001', prix_actuels_exemple)
    print(f"  Plus-values réalisées: {perf_c001['resume']['total_pv_realisees']:.2f} €")
    print(f"  Plus-values latentes: {perf_c001['resume']['total_pv_latentes']:.2f} €")
    
    # Test 3: Rapport complet
    print("\n" + "="*80)
    print("📊 GÉNÉRATION DU RAPPORT COMPLET")
    print("="*80)
    
    rapport = generer_rapport_complet(transactions_exemple, prix_actuels_exemple)


Forum(s) associé(s)

L'Art de la Philosophie : Interprétations et Débats

08 Apr, 2016

Explorez comment l'art et la philosophie s'entrelacent pour questionner notre perception de la réalité et de l'esthétique.

Read more.

Voyage à Travers la Liberté : Concepts et Dilemmes

27 Jan, 2014

Plongez dans les débats philosophiques sur la liberté, ses implications éthiques et les défis contemporains qui l'entourent.

Read more.

La Quête de la Vérité : Philosophies et Perspectives

30 Feb, 2015

Découvrez les différentes approches philosophiques de la vérité et comment elles influencent notre compréhension du monde.

Read more.
Page: