🌐
🔍 100%
👁️

Chapitre 2 : Manipulation de Données Avancée (LINQ)

Module : Programmation .Net C#

Niveau : 4ème Génie Informatique (Spécialité Data Science)

Prérequis : TP1 validé & TP2 validé (Records, LINQ de base)

Introduction

Après avoir maîtrisé les fondamentaux de la syntaxe C# dans le Chapitre 1, le TP 2 vous a plongé dans le paradigme fonctionnel. Vous avez remplacé les boucles foreach manuelles par des requêtes LINQ (Language Integrated Query) fluides et expressives. Ce chapitre théorique consolide ces acquis et explore les mécanismes internes qui font de LINQ l'outil de prédilection pour la Data Science en .NET.

I. Les Données "Modernes" : Records et Immutabilité

Dans l'Activité 1 du TP 2, vous avez utilisé un record pour modéliser une transaction. Pourquoi ?

1. Class vs Record : La sémantique de valeur

En Data Science, une donnée est définie par son contenu, pas par son emplacement en mémoire. Si deux transactions ont le même ID, le même montant et la même date, elles sont identiques.

Class (Référence) Record (Valeur)
L'égalité vérifie si c'est le même objet en mémoire (pointeur). L'égalité vérifie si toutes les propriétés sont identiques.
Mutable par défaut. Immuable par défaut (Idéal pour le parallélisme).

2. L'Immutabilité et la copie non-destructive (`with`)

Travailler avec des données immuables évite les effets de bord indésirables. Si vous devez modifier une transaction, vous en créez une copie modifiée grâce au mot-clé with :

public record Transaction(int Id, double Montant);

var t1 = new Transaction(1, 100);
// t1.Montant = 200; // ERREUR ! Impossible de modifier.

// On crée une nouvelle version :
var t2 = t1 with { Montant = 200 }; 
// t1 existe toujours et vaut 100. t2 vaut 200.
> Transaction { Id = 1, Montant = 100 } > Transaction { Id = 1, Montant = 200 }

II. LINQ : Le SQL de .NET

LINQ (Language Integrated Query) n'est pas juste une librairie, c'est une syntaxe unifiée pour interroger n'importe quelle source de données (Objets mémoire, Bases de données SQL, XML, JSON...).

1. Le Pipeline de Données

Contrairement à l'approche impérative (boucles imbriquées), LINQ permet de décrire un pipeline de transformation. Il est crucial de comprendre que ce pipeline est différé (Lazy).

Source List<T> Définition de la Requête (Différé) Where Filtre GroupBy Regroupement OrderBy Tri Select Projection Exécution / Agrégation foreach / Sum / Count Résultat

Figure 1 : Le Pipeline de transformation LINQ et Agrégation

2. Décryptage des Opérateurs (Vu en TP)

A. Le Filtrage (`Where`)

Le filtre correspond à la clause WHERE du SQL. Il utilise un prédicat (une fonction qui renvoie vrai ou faux).

// Syntaxe Lambda : x => condition
var franceOnly = transactions.Where(t => t.Pays == "France");
> Transaction { Id = 1, Pays = "France", Montant = 1200.50 ... } > Transaction { Id = 3, Pays = "France", Montant = 450.00 ... }

B. Le Tri (`OrderBy` / `OrderByDescending`)

Pour ordonner une liste, on utilise OrderBy (Ascendant) ou OrderByDescending (Descendant). On peut enchaîner avec ThenBy pour des tris multi-niveaux.

// Tri simple (Montant croissant)
var petitPrix = transactions.OrderBy(t => t.Montant);

// Tri complexe : Par Pays (A-Z), puis par Montant décroissant
var complexe = transactions.OrderBy(t => t.Pays)
                          .ThenByDescending(t => t.Montant);
> Allemagne : 3200.00 > France : 1200.50 > France : 450.00

C. La Projection (`Select`)

La projection transforme la forme des données (SQL SELECT). Vous pouvez transformer un objet riche en une simple valeur, ou en un nouvel objet (Type Anonyme).

// On ne garde que les montants (List<double>)
var montants = transactions.Select(t => t.Montant);

// On crée un objet simplifié à la volée (Type Anonyme)
var rapport = transactions.Select(t => new { 
    Nom = t.Categorie, 
    PrixTTC = t.Montant * 1.2 
});
> 1200.50 > 3200.00 > { Name = "Hardware", PriceWithTax = 1440.6 } > { Name = "Service", PriceWithTax = 3840.0 }

D. Le Groupement (`GroupBy`)

C'est l'outil d'analyse par excellence. Il transforme une liste plate en une liste de "Groupes". Chaque groupe possède une Clé (`Key`) et une liste d'éléments.

var parPays = transactions.GroupBy(t => t.Pays);

foreach (var groupe in parPays) {
    Console.WriteLine($"Pays : {groupe.Key}");
    Console.WriteLine($"CA Total : {groupe.Sum(t => t.Montant)}"); // Affichage
}
Pays : France CA Total : 1650.50 Pays : Allemagne CA Total : 3200.00

3. Récupération d'Éléments Uniques (Element Operators)

Souvent, on ne veut pas une liste, mais un seul élément précis.

> Transaction { Id = 1, Montant = 1200.50 ... }

4. Autres Opérateurs Indispensables

Au-delà des bases, voici des méthodes très utiles pour le nettoyage de données :

> aDesGeants : False > toutEstPositif : True > Categories : "Hardware", "Service" (Doublons supprimés)

5. Les Jointures (`Join`)

Comme en SQL, vous pouvez croiser deux listes (ex: Transactions et Clients) via une clé commune. Voici un exemple complet avec définition des données :

// 1. Définition des structures
public record Client(int Id, string Nom);
public record Transaction(int Id, int ClientId, double Amount);

// 2. Alimentation des listes
var clients = new List<Client> {
    new(1, "Alice"),
    new(2, "Bob")
};

var transactions = new List<Transaction> {
    new(101, 1, 500),  // Achat d'Alice
    new(102, 2, 1200), // Achat de Bob
    new(103, 1, 300)   // Autre achat d'Alice
};

// 3. Requête Join
var historique = transactions.Join(clients,
    t => t.ClientId,      // Clé table 1 (FK)
    c => c.Id,            // Clé table 2 (PK)
    (t, c) => new {       // Projection du résultat
        Client = c.Nom,
        Montant = t.Amount,
        Description = $"{c.Nom} a payé {t.Amount}€"
    } 
);

// 4. Affichage
foreach (var h in historique) {
    Console.WriteLine(h.Description);
}
> Alice a payé 500€ > Bob a payé 1200€ > Alice a payé 300€

IV. Agrégation & Statistiques

LINQ propose des méthodes performantes pour calculer des valeurs scalaires à partir d'une collection :

V. Concepts Avancés : Pour aller plus loin

1. L'Exécution Différée (Lazy Evaluation)

C'est le point fort de LINQ. Lorsque vous écrivez var q = data.Where(...).Select(...), rien ne se passe. Aucune donnée n'est filtrée. LINQ construit simplement la requête en mémoire.

L'exécution réelle se produit uniquement au moment de la consommation :

Cela permet d'optimiser les performances en ne traitant que les données strictement nécessaires.

VI. Synthèse : La Corrélation LINQ vs SQL

Si vous venez du monde des bases de données, LINQ est très intuitif. C'est littéralement du "SQL pour le code".

Concept SQL Opérateur LINQ Description
SELECT .Select() Projection / Transformation
WHERE .Where() Filtrage
ORDER BY .OrderBy() / .OrderByDescending() Tri
GROUP BY .GroupBy() Regroupement
JOIN .Join() Jointure interne
TOP / LIMIT .Take() Restriction du nombre de résultats
DISTINCT .Distinct() Dédoublonnage
COUNT() .Count() Comptage
MIN / MAX / AVG .Min() / .Max() / .Average() Agrégation

Différence majeure : SQL est exécuté par le moteur de base de données (SGBD), tandis que LINQ to Objects est exécuté en mémoire par le CLR .NET.

VII. Bonnes Pratiques & Pièges à Éviter

LINQ est puissant, mais peut être lent si mal utilisé. Voici les règles d'or pour un ingénieur Data :

Chapter 2: Advanced Data Manipulation (LINQ)

Module: .Net C# Programming

Level: 4th Year Computer Engineering (Data Science Specialty)

Prerequisite: TP1 Validated & TP2 Validated (Records, Basic LINQ)

Introduction

After mastering the fundamentals of C# syntax in Chapter 1, Lab 2 (LAB 2) immersed you in the functional paradigm. You replaced manual foreach loops with fluid and expressive LINQ (Language Integrated Query) queries. This theoretical chapter consolidates these skills and explores the internal mechanisms that make LINQ the preferred tool for Data Science in .NET.

I. "Modern" Data: Records and Immutability

In Activity 1 of LAB 2, you used a record to model a transaction. Why?

1. Class vs Record: Value Semantics

In Data Science, a piece of data is defined by its content, not its location in memory. If two transactions have the same ID, same amount, and same date, they are identical.

Class (Reference) Record (Value)
Equality checks if it is the same object in memory (pointer). Equality checks if all properties are identical.
Mutable by default. Immutable by default (Ideal for parallelism).

2. Immutability and Non-Destructive Copy (`with`)

Working with immutable data avoids unwanted side effects. If you need to modify a transaction, you create a modified copy using the with keyword:

public record Transaction(int Id, double Amount);

var t1 = new Transaction(1, 100);
// t1.Amount = 200; // ERROR! Cannot modify.

// Create a new version:
var t2 = t1 with { Amount = 200 }; 
// t1 still exists and is 100. t2 is 200.
> Transaction { Id = 1, Amount = 100 } > Transaction { Id = 1, Amount = 200 }

II. LINQ: The SQL of .NET

LINQ (Language Integrated Query) isn't just a library; it's a unified syntax for querying any data source (In-memory objects, SQL databases, XML, JSON...).

1. The Data Pipeline

Unlike the imperative approach (nested loops), LINQ allows you to describe a transformation pipeline. It is crucial to understand that this pipeline is deferred (Lazy).

Source List<T> Query Definition (Lazy) Where Filter GroupBy Grouping OrderBy Sort Select Projection Execution / Aggregation foreach / Sum / Count Result

Figure 1: The LINQ Pipeline: Definition vs Execution

2. Deciphering Operators (Seen in LAB)

A. Filtering (`Where`)

The filter corresponds to the SQL WHERE clause. It uses a predicate (a function that returns true or false).

// Lambda Syntax: x => condition
var franceOnly = transactions.Where(t => t.Country == "France");
> Transaction { Id = 1, Country = "France", Amount = 1200.50 ... } > Transaction { Id = 3, Country = "France", Amount = 450.00 ... }

B. Sorting (`OrderBy` / `OrderByDescending`)

To sort a list, use OrderBy (Ascending) or OrderByDescending (Descending). You can chain with ThenBy for multi-level sorting.

// Simple Sort (Amount ascending)
var lowPrice = transactions.OrderBy(t => t.Amount);

// Complex Sort: By Country (A-Z), then by Amount descending
var complex = transactions.OrderBy(t => t.Country)
                          .ThenByDescending(t => t.Amount);
> Germany : 3200.00 > France : 1200.50 > France : 450.00

C. Projection (`Select`)

Projection transforms the shape of data (SQL SELECT). You can transform a rich object into a simple value, or into a new object (Anonymous Type).

// Keep only amounts (List<double>)
var amounts = transactions.Select(t => t.Amount);

// Create a simplified object on the fly (Anonymous Type)
var report = transactions.Select(t => new { 
    Name = t.Category, 
    PriceWithTax = t.Amount * 1.2 
});
> 1200.50 > 3200.00 > { Name = "Hardware", PriceWithTax = 1440.6 } > { Name = "Service", PriceWithTax = 3840.0 }

D. Grouping (`GroupBy`)

This is the ultimate analysis tool. It transforms a flat list into a list of "Groups". Each group has a Key and a list of elements.

var byCountry = transactions.GroupBy(t => t.Country);

foreach (var group in byCountry) {
    Console.WriteLine($"Country : {group.Key}");
    Console.WriteLine($"Total Revenue : {group.Sum(t => t.Amount)}"); // Display
}
Country : France Total Revenue : 1650.50 Country : Germany Total Revenue : 3200.00

3. Single Element Retrieval (Element Operators)

Often, we don't want a list, but a single specific element.

> Transaction { Id = 1, Amount = 1200.50 ... }

4. Other Essential Operators

Beyond the basics, here are very useful methods for data cleaning:

> hasBigWhales : False > allPositive : True > Categories : "Hardware", "Service" (Duplicates removed)

5. Joins (`Join`)

Like in SQL, you can merge two lists (e.g., Transactions and Customers) via a common key. Here is a complete example with data definition:

// 1. Structure Definitions
public record Client(int Id, string Name);
public record Transaction(int Id, int ClientId, double Amount);

// 2. List Population
var clients = new List<Client> {
    new(1, "Alice"),
    new(2, "Bob")
};

var transactions = new List<Transaction> {
    new(101, 1, 500),  // Alice's purchase
    new(102, 2, 1200), // Bob's purchase
    new(103, 1, 300)   // Another purchase by Alice
};

// 3. Join Query
var history = transactions.Join(clients,
    t => t.ClientId,      // Key table 1 (FK)
    c => c.Id,            // Key table 2 (PK)
    (t, c) => new {       // Result Projection
        Client = c.Name,
        Amount = t.Amount,
        Description = $"{c.Name} paid {t.Amount}€"
    } 
);

// 4. Display
foreach (var h in history) {
    Console.WriteLine(h.Description);
}
> Alice paid 500€ > Bob paid 1200€ > Alice paid 300€

IV. Aggregation & Statistics

LINQ offers performant methods to calculate scalar values from a collection:

V. Advanced Concepts: Going Further

1. Lazy Evaluation (Deferred Execution)

This is LINQ's greatest strength. When you write var q = data.Where(...).Select(...), nothing happens. No data is filtered. LINQ simply constructs the query in memory.

Actual execution happens only upon consumption:

This optimizes performance by processing only strictly necessary data.

VI. Synthesis: The LINQ vs SQL Correlation

If you come from the database world, LINQ is very intuitive. It is literally "SQL for code".

SQL Concept LINQ Operator Description
SELECT .Select() Projection / Transformation
WHERE .Where() Filtering
ORDER BY .OrderBy() / .OrderByDescending() Sorting
GROUP BY .GroupBy() Grouping
JOIN .Join() Inner Join
TOP / LIMIT .Take() Restrict result count
DISTINCT .Distinct() Deduplication
COUNT() .Count() Counting
MIN / MAX / AVG .Min() / .Max() / .Average() Aggregation

Major Difference: SQL is executed by the Database Management System (DBMS), whereas LINQ to Objects is executed in memory by the .NET CLR.

VII. Best Practices & Pitfalls

LINQ is powerful but can be slow if misused. Here are golden rules for a Data Engineer:

🎙️ Sélectionner une voix

🎙️ Select a voice