3  Visualisation de séries de données et flots de données

📊 Projet Java : Analyse de Notes d’Étudiants avec Streams et XChart

深度求索 (Shēndù Qiúsuǒ) vous propose un projet d’analyse de notes d’étudiants qui utilise les Streams Java pour le traitement de données et XChart pour la visualisation graphique.

3.1 🎯 Objectifs du Projet

  • Manipuler les Streams Java (filter, map, reduce, collect)
  • Générer des visualisations avec XChart
  • Répondre à des questions analytiques sur un jeu de données

3.2 📦 Structure du Projet

ProjetNotesEtudiants/
├── src/
│   ├── Etudiant.java
│   ├── Matiere.java
│   ├── AnalyseurNotes.java
│   └── Main.java
├── data/
│   └── notes.csv
└── charts/
    └── (dossiers pour les graphiques générés)

3.3 📝 Code Source

3.3.1 1. Classe Etudiant

public class Etudiant {
    private String nom;
    private String prenom;
    private Map<Matiere, Double> notes;
    
    public Etudiant(String nom, String prenom, Map<Matiere, Double> notes) {
        this.nom = nom;
        this.prenom = prenom;
        this.notes = notes;
    }

    public double getMoyenne() {
        return notes.values().stream()
            .mapToDouble(Double::doubleValue)
            .average()
            .orElse(0.0);
    }
    
    // Getters et autres méthodes...
}

3.3.2 2. Classe Matiere (énumération)

public enum Matiere {
    MATHEMATIQUES, PHYSIQUE, INFORMATIQUE, FRANCAIS, HISTOIRE;
}

3.3.3 3. Classe principale avec Streams et XChart

import org.knowm.xchart.*;
import java.util.*;
import java.util.stream.Collectors;

public class AnalyseurNotes {
    private List<Etudiant> etudiants;
    
    public AnalyseurNotes(List<Etudiant> etudiants) {
        this.etudiants = etudiants;
    }
    
    // Question 1: Moyenne générale par étudiant (diagramme en barres)
    public void afficherMoyennesParEtudiant() {
        Map<String, Double> moyennes = etudiants.stream()
            .collect(Collectors.toMap(
                e -> e.getPrenom() + " " + e.getNom(),
                Etudiant::getMoyenne
            ));
        
        CategoryChart chart = new CategoryChartBuilder()
            .width(800).height(600)
            .title("Moyennes par étudiant")
            .xAxisTitle("Étudiants").yAxisTitle("Moyenne")
            .build();
        
        chart.addSeries("Moyennes", 
            new ArrayList<>(moyennes.keySet()),
            new ArrayList<>(moyennes.values()));
        
        new SwingWrapper<>(chart).displayChart();
    }
    
    // Question 2: Moyenne par matière (diagramme en barres)
    public void afficherMoyennesParMatiere() {
        Map<Matiere, Double> moyennes = Arrays.stream(Matiere.values())
            .collect(Collectors.toMap(
                matiere -> matiere,
                matiere -> etudiants.stream()
                    .mapToDouble(e -> e.getNote(matiere))
                    .average()
                    .orElse(0.0)
            ));
        
        CategoryChart chart = new CategoryChartBuilder()
            .width(800).height(600)
            .title("Moyennes par matière")
            .xAxisTitle("Matières").yAxisTitle("Moyenne")
            .build();
        
        chart.addSeries("Moyennes", 
            Arrays.stream(Matiere.values()).map(Enum::name).collect(Collectors.toList()),
            new ArrayList<>(moyennes.values()));
        
        new SwingWrapper<>(chart).displayChart();
    }
    
    // Question 3: Répartition des mentions (diagramme circulaire)
    public void afficherRepartitionMentions() {
        Map<String, Long> mentions = etudiants.stream()
            .collect(Collectors.groupingBy(
                e -> {
                    double moyenne = e.getMoyenne();
                    if (moyenne >= 16) return "Très bien";
                    if (moyenne >= 14) return "Bien";
                    if (moyenne >= 12) return "Assez bien";
                    return "Passable";
                },
                Collectors.counting()
            ));
        
        PieChart chart = new PieChartBuilder()
            .width(800).height(600)
            .title("Répartition des mentions")
            .build();
        
        mentions.forEach((mention, count) -> 
            chart.addSeries(mention + " (" + count + ")", count));
        
        new SwingWrapper<>(chart).displayChart();
    }
    
    // Question 4: Corrélation entre deux matières (nuage de points)
    public void afficherCorrelation(Matiere m1, Matiere m2) {
        List<Double> notesM1 = etudiants.stream()
            .map(e -> e.getNote(m1))
            .collect(Collectors.toList());
        
        List<Double> notesM2 = etudiants.stream()
            .map(e -> e.getNote(m2))
            .collect(Collectors.toList());
        
        XYChart chart = new XYChartBuilder()
            .width(800).height(600)
            .title("Correlation entre " + m1 + " et " + m2)
            .xAxisTitle(m1.name()).yAxisTitle(m2.name())
            .build();
        
        XYSeries series = chart.addSeries("Notes", notesM1, notesM2);
        series.setMarker(SeriesMarkers.CIRCLE);
        series.setLineStyle(SeriesLines.NONE);

        chart.getStyler().setDefaultSeriesRenderStyle(XYSeriesRenderStyle.Scatter);
        chart.getStyler().setMarkerSize(10);
        
        new SwingWrapper<>(chart).displayChart();
    }
}

3.3.4 4. Classe Main pour l’exécution

import java.util.Arrays;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        // Création des données d'exemple
        List<Etudiant> etudiants = Arrays.asList(
            creerEtudiant("Dupont", "Jean", 15.0, 12.0, 18.0, 14.0, 13.0),
            creerEtudiant("Martin", "Marie", 18.0, 16.0, 17.0, 15.0, 14.0),
            creerEtudiant("Bernard", "Pierre", 10.0, 11.0, 9.0, 12.0, 8.0),
            creerEtudiant("Dubois", "Sophie", 14.0, 13.0, 15.0, 16.0, 12.0),
            creerEtudiant("Moreau", "Luc", 12.0, 14.0, 11.0, 13.0, 15.0)
        );
        
        AnalyseurNotes analyseur = new AnalyseurNotes(etudiants);
        
        // Réponses aux questions avec visualisation
        analyseur.afficherMoyennesParEtudiant();
        analyseur.afficherMoyennesParMatiere();
        analyseur.afficherRepartitionMentions();
        analyseur.afficherCorrelation(Matiere.MATHEMATIQUES, Matiere.PHYSIQUE);
    }
    
    private static Etudiant creerEtudiant(String nom, String prenom, 
                                         double maths, double physique, 
                                         double info, double francais, 
                                         double histoire) {
        Map<Matiere, Double> notes = new HashMap<>();
        //...                                  
        Etudiant etudiant = new Etudiant(nom, prenom, notes);
        
        return etudiant;
    }
}

3.4 🔍 Questions et indications pour l’implémentation

  1. Quelle est la moyenne de chaque étudiant ?
    • Utilisation de Collectors.toMap pour créer une association étudiant→moyenne
    • Visualisation avec un diagramme en barres
  2. Quelle est la moyenne par matière ?
    • Utilisation de Arrays.stream() sur l’énumération des matières
    • Combinaison avec Collectors.toMap et mapToDouble
    • Visualisation avec un diagramme en barres
  3. Comment se répartissent les mentions ?
    • Utilisation de Collectors.groupingBy avec une fonction de classification
    • Comptage avec Collectors.counting()
    • Visualisation avec un diagramme circulaire
  4. Existe-t-il une corrélation entre deux matières ?
    • Extraction des notes avec map et collect
    • Visualisation avec un nuage de points

3.5 🛠️ Compétences Développées

  • Streams API : filter, map, reduce, collect, groupingBy, toMap
  • XChart : Création de différents types de graphiques
  • Programmation fonctionnelle avec lambda expressions
  • Manipulation de collections et traitement de données

3.6 📊 Extensions Possibles

  1. Ajouter la lecture depuis un fichier CSV
  2. Implémenter des calculs statistiques plus avancés
  3. Ajouter des fonctionnalités d’export des graphiques
  4. Créer une interface utilisateur interactive

Ce projet permet de manipuler les concepts clés des Streams Java tout en produisant des visualisations concrètes avec XChart, idéal pour comprendre l’utilité du traitement de données en programmation.

3.7 Annexe : les flots (streams)

3.7.1 Les collecteurs

🧠 Comprendre les Collectors dans les Streams Java

3.7.1.1 🎯 C’est quoi un Collector ?

Imaginez que vous avez un flux d’eau (un Stream) avec des objets qui flottent. Un Collector, c’est comme un récipient qui va permettre de récupérer ces objets de la manière souhaitée :

  • Dans une liste (comme un panier),
  • Dans un ensemble (comme un tamis qui enlève les doublons),
  • Dans un dictionnaire (comme des étagères triées),
  • Ou même les compter ou les additionner,

3.7.1.2 🧰 Les Collectors de base

3.7.1.2.1 1. Collecter dans une Liste
List<String> noms = Arrays.asList("Alice", "Bob", "Alice", "Charlie");
List<String> listeNoms = noms.stream()
                            .collect(Collectors.toList());
// Résultat: ["Alice", "Bob", "Alice", "Charlie"]
3.7.1.2.2 2. Collecter dans un Set (enlève les doublons)
Set<String> nomsUniques = noms.stream()
                             .collect(Collectors.toSet());
// Résultat: ["Alice", "Bob", "Charlie"]
3.7.1.2.3 3. Collecter dans une Map
Map<String, Integer> mapLongueurs = noms.stream()
                                       .collect(Collectors.toMap(
                                           nom -> nom,           // Clé
                                           nom -> nom.length()   // Valeur
                                       ));
// Résultat: {"Alice"=5, "Bob"=3, "Charlie"=7}

3.7.1.3 🎪 Les Collectors avancés

3.7.1.3.1 4. Grouper des éléments
Map<Integer, List<String>> parLongueur = noms.stream()
                                            .collect(Collectors.groupingBy(
                                                nom -> nom.length()
                                            ));
// Résultat: {3=["Bob"], 5=["Alice", "Alice"], 7=["Charlie"]}
3.7.1.3.2 5. Compter les éléments
Map<String, Long> comptage = noms.stream()
                                .collect(Collectors.groupingBy(
                                    nom -> nom,
                                    Collectors.counting()
                                ));
// Résultat: {"Alice"=2, "Bob"=1, "Charlie"=1}
3.7.1.3.3 6. Joindre des chaînes
String tousLesNoms = noms.stream()
                        .collect(Collectors.joining(", "));
// Résultat: "Alice, Bob, Alice, Charlie"

3.7.1.4 🔧 Comment ça marche ?

Un Collector a 3 composants principaux :

  1. Supplier : Crée le conteneur vide (ex: ArrayList::new)
  2. Accumulator : Ajoute un élément au conteneur (ex: List::add)
  3. Combiner : Fusionne deux conteneurs (ex: List::addAll)

3.7.1.5 🎓 Analogie avec la vie réelle

Collector Analogie
toList() Mettre des courses dans un caddie
toSet() Ranger des livres uniques dans une bibliothèque
groupingBy() Classer des vêtements par couleur
joining() Enfiler des perles sur un fil

3.7.1.6 💡 Exemple complet

import java.util.*;
import java.util.stream.*;

public class ExempleCollector {
    public static void main(String[] args) {
        List<String> fruits = Arrays.asList("pomme", "banane", "orange", "pomme", "kiwi");
        
        // 1. Liste sans modification
        List<String> liste = fruits.stream()
                                 .collect(Collectors.toList());
        
        // 2. Ensemble sans doublons
        Set<String> ensemble = fruits.stream()
                                   .collect(Collectors.toSet());
        
        // 3. Grouper par longueur
        Map<Integer, List<String>> parTaille = fruits.stream()
                                                   .collect(Collectors.groupingBy(
                                                       fruit -> fruit.length()
                                                   ));
        
        // 4. Compter les occurrences
        Map<String, Long> compteur = fruits.stream()
                                         .collect(Collectors.groupingBy(
                                             fruit -> fruit,
                                             Collectors.counting()
                                         ));
        
        System.out.println("Liste: " + liste);
        System.out.println("Ensemble: " + ensemble);
        System.out.println("Par taille: " + parTaille);
        System.out.println("Compteur: " + compteur);
    }
}

Résultat :

Liste: [pomme, banane, orange, pomme, kiwi]
Ensemble: [banane, orange, pomme, kiwi]
Par taille: {4=[kiwi], 5=[pomme, pomme], 6=[banane, orange]}
Compteur: {banane=1, orange=1, pomme=2, kiwi=1}

3.7.1.7 🚀 Pourquoi c’est utile ?

  1. Plus concis : Moins de code que les boucles traditionnelles
  2. Plus lisible : Le code exprime l’intention clairement
  3. Plus efficace : Optimisé pour les traitements parallèles
  4. Plus flexible : Combinaison possible de multiples opérations

Les Collectors transforment les Streams d’éléments éphémères en structures de données permanentes et organisées, comme une usine qui transformerait des matières premières en produits finis ! 🏭➡️📦