# Python et Pandas pour la Manipulation de Données
## Exercices
Les exercices sont structurés par niveau de difficulté, allant de l'application directe à des problèmes plus complexes nécessitant une réflexion approfondie et la combinaison de plusieurs concepts.
### Partie 1 : Exercices très basiques (Application directe du cours)
Ces exercices visent à vous familiariser avec les structures de données fondamentales de `pandas`: les `Series` et les `DataFrames`.
#### Exercice 1 : Création d'une Série Pandas
Créez une `pandas.Series` à partir de la liste Python suivante et affichez-la.
```python
temperatures = [22, 25, 19, 28, 23]
```
#### Exercice 2 : Création d'un DataFrame simple
Créez un `pandas.DataFrame` à partir du dictionnaire suivant. Le DataFrame doit représenter des informations sur des étudiants. Affichez le DataFrame et ses informations générales (types de colonnes, nombre de valeurs non nulles).
```python
data_etudiants = {
'Nom': ['Alice', 'Bob', 'Charlie'],
'Age': [20, 21, 19],
'Moyenne': [15.5, 12.0, 17.8]
}
```
### Partie 2 : Exercices de niveau normal (Combinaison de concepts)
Ces exercices vous demanderont de combiner plusieurs opérations de sélection, filtrage et agrégation de base.
#### Exercice 3 : Sélection et Filtrage de Données
En utilisant le DataFrame créé à l'Exercice 2, effectuez les opérations suivantes :
1. Sélectionnez et affichez uniquement les colonnes 'Nom' et 'Moyenne'.
2. Filtrez et affichez les étudiants dont la moyenne est supérieure à 15.
#### Exercice 4 : Statistiques Descriptives de Base
Considérez le DataFrame suivant représentant les ventes mensuelles d'un produit.
Calculez et affichez la moyenne, la médiane et l'écart type des ventes.
```python
import pandas as pd
ventes = pd.DataFrame({
'Mois': ['Jan', 'Fev', 'Mar', 'Avr', 'Mai'],
'Chiffre_Affaires': [12000, 15000, 11000, 18000, 13000]
})
```
#### Exercice 5 : Ajout d'une Colonne Calculée
En utilisant le DataFrame des ventes de l'Exercice 4, ajoutez une nouvelle colonne nommée 'Commission' qui représente 5% du 'Chiffre_Affaires'. Affichez le DataFrame mis à jour.
> [!definition] Commission
> La commission est une rémunération basée sur un pourcentage du chiffre d'affaires réalisé. Ici, elle est calculée comme $C = 0.05 \times CA$, où $C$ est la commission et $CA$ est le chiffre d'affaires.
#### Exercice 6 : Tri et Réinitialisation de l'Index
Considérez le DataFrame suivant :
```python
import pandas as pd
notes = pd.DataFrame({
'Etudiant': ['Eve', 'Frank', 'Grace', 'Heidi'],
'Note_Maths': [14, 11, 18, 15],
'Note_Physique': [12, 13, 17, 16]
})
```
1. Triez le DataFrame par 'Note_Maths' en ordre décroissant.
2. Après le tri, réinitialisez l'index du DataFrame sans ajouter l'ancien index comme colonne. Affichez le DataFrame final.
### Partie 3 : Exercices plus élaborés (Problèmes complexes)
Ces exercices vous demanderont de combiner plusieurs techniques de `pandas` pour résoudre des problèmes de données plus réalistes.
#### Exercice 7 : Agrégation avec Groupement (Groupby)
Vous disposez d'un DataFrame simulant des données de transactions.
```python
import pandas as pd
transactions = pd.DataFrame({
'ID_Client': [1, 2, 1, 3, 2, 1, 3, 4],
'Produit': ['A', 'B', 'A', 'C', 'B', 'D', 'C', 'A'],
'Montant': [100, 200, 150, 50, 220, 300, 70, 120]
})
```
1. Calculez le montant total dépensé par chaque client.
2. Calculez le montant moyen dépensé par produit.
Affichez les résultats de chaque agrégation.
#### Exercice 8 : Gestion des Valeurs Manquantes et Conversion de Type
Considérez le DataFrame suivant avec des valeurs manquantes et des types de données potentiellement incorrects.
```python
import pandas as pd
data_qualite = pd.DataFrame({
'ID_Capteur': [101, 102, 101, 103, 102, None, 104],
'Temperature': [25.5, 24.1, None, 26.0, 23.8, 25.0, 27.2],
'Humidite': [70, 72, 68, None, 75, 71, 69],
'Statut_Capteur': ['OK', 'OK', 'Erreur', 'OK', 'OK', 'OK', 'OK']
})
```
1. Identifiez le nombre de valeurs manquantes par colonne.
2. Remplacez les valeurs manquantes de la colonne 'Temperature' par la moyenne des températures non manquantes.
3. Supprimez les lignes où 'Humidite' est manquante.
4. Convertissez la colonne 'ID_Capteur' en type entier (`int`).
Affichez le DataFrame après chaque étape de modification.
> [!warning] Attention aux types!
> La conversion d'une colonne contenant des valeurs manquantes (`NaN`) en entier peut échouer car `NaN` est un type flottant. Il faut s'assurer que toutes les valeurs manquantes sont traitées avant la conversion si l'on veut un type entier pur. `pandas` peut parfois gérer cela en convertissant en `Int64` (avec un I majuscule), qui supporte `NaN`.
#### Exercice 9 : Fusion de DataFrames (Merge)
Vous avez deux DataFrames : un pour les informations sur les employés et un autre pour leurs départements.
```python
import pandas as pd
employes = pd.DataFrame({
'ID_Employe': [1, 2, 3, 4],
'Nom': ['Alice', 'Bob', 'Charlie', 'David'],
'ID_Departement': [101, 102, 101, 103]
})
departements = pd.DataFrame({
'ID_Departement': [101, 102, 103],
'Nom_Departement': ['R&D', 'Ventes', 'Marketing']
})
```
Fusionnez ces deux DataFrames pour obtenir un seul DataFrame contenant le nom de l'employé et le nom de son département. Affichez le DataFrame résultant.
> [!tip] Jointure de clés
> La fusion de DataFrames est une opération fondamentale en Data Science, similaire aux jointures en SQL. Elle permet de combiner des informations provenant de différentes sources basées sur une ou plusieurs clés communes.
#### Exercice 10 : Analyse de Performances d'Étudiants
Vous avez un DataFrame contenant les notes de plusieurs étudiants dans différentes matières.
```python
import pandas as pd
notes_etudiants = pd.DataFrame({
'Etudiant': ['Alice', 'Bob', 'Charlie', 'David', 'Eve', 'Alice', 'Bob', 'Charlie'],
'Matiere': ['Maths', 'Maths', 'Maths', 'Physique', 'Physique', 'Chimie', 'Chimie', 'Chimie'],
'Note': [16, 12, 18, 15, 14, 17, 10, 13]
})
```
Effectuez les analyses suivantes :
1. Calculez la note moyenne de chaque étudiant sur toutes les matières.
2. Identifiez la matière dans laquelle chaque étudiant a obtenu sa meilleure note.
3. Calculez la note moyenne pour chaque matière.
4. Trouvez l'étudiant ayant la meilleure note globale (moyenne sur toutes les matières).
Affichez les résultats de chaque étape.
## Corrigés Détaillés
Voici les solutions pas à pas pour chaque exercice, avec des explications et des commentaires pour chaque étape.
### Partie 1 : Exercices très basiques (Application directe du cours) - Corrigés
#### Exercice 1 : Création d'une Série Pandas
**Problème :** Créez une `pandas.Series` à partir de la liste Python `temperatures = [22, 25, 19, 28, 23]` et affichez-la.
**Corrigé :**
```python
import pandas as pd
temperatures = [22, 25, 19, 28, 23]
# Création de la Série
serie_temperatures = pd.Series(temperatures)
# Affichage de la Série
print("Série des températures :")
print(serie_temperatures)
```
> [!example] Résultat attendu
> ```
> Série des températures :
> 0 22
> 1 25
> 2 19
> 3 28
> 4 23
> dtype: int64
> ```
> [!note] Explication
> La fonction `pd.Series()` est le constructeur de base pour créer une `Series`. Elle prend en argument une liste, un tableau NumPy, un dictionnaire, ou même une valeur scalaire. Par défaut, `pandas` attribue un index numérique (de 0 à N-1) si aucun index n'est spécifié. `dtype: int64` indique que les éléments de la série sont des entiers de 64 bits.
#### Exercice 2 : Création d'un DataFrame simple
**Problème :** Créez un `pandas.DataFrame` à partir du dictionnaire `data_etudiants`. Affichez le DataFrame et ses informations générales.
```python
data_etudiants = {
'Nom': ['Alice', 'Bob', 'Charlie'],
'Age': [20, 21, 19],
'Moyenne': [15.5, 12.0, 17.8]
}
```
**Corrigé :**
```python
import pandas as pd
data_etudiants = {
'Nom': ['Alice', 'Bob', 'Charlie'],
'Age': [20, 21, 19],
'Moyenne': [15.5, 12.0, 17.8]
}
# Création du DataFrame
df_etudiants = pd.DataFrame(data_etudiants)
# Affichage du DataFrame
print("DataFrame des étudiants :")
print(df_etudiants)
print("\nInformations générales du DataFrame :")
df_etudiants.info()
```
> [!example] Résultat attendu
> ```
> DataFrame des étudiants :
> Nom Age Moyenne
> 0 Alice 20 15.5
> 1 Bob 21 12.0
> 2 Charlie 19 17.8
>
> Informations générales du DataFrame :
> <class 'pandas.core.frame.DataFrame'>
> RangeIndex: 3 entries, 0 to 2
> Data columns (total 3 columns):
> # Column Non-Null Count Dtype
> --- ------ -------------- -----
> 0 Nom 3 non-null object
> 1 Age 3 non-null int64
> 2 Moyenne 3 non-null float64
> dtypes: float64(1), int64(1), object(1)
> memory usage: 152.0+ bytes
> ```
> [!definition] DataFrame
> Un `DataFrame` est une structure de données tabulaire bidimensionnelle, avec des étiquettes d'axes (lignes et colonnes). Il peut être vu comme une collection de `Series` partageant le même index. Chaque colonne d'un `DataFrame` est une `Series`.
> [!note] Explication
> La fonction `pd.DataFrame()` est le constructeur de base pour créer un `DataFrame`. Un dictionnaire où les clés sont les noms des colonnes et les valeurs sont des listes (ou des Series) est une manière très courante de le créer.
> La méthode `.info()` est extrêmement utile pour obtenir un résumé rapide du DataFrame, y compris le nombre d'entrées, le nombre de valeurs non nulles par colonne (important pour détecter les données manquantes), et le type de données (`dtype`) de chaque colonne.
### Partie 2 : Exercices de niveau normal (Combinaison de concepts) - Corrigés
#### Exercice 3 : Sélection et Filtrage de Données
**Problème :** En utilisant le DataFrame `df_etudiants` de l'Exercice 2, effectuez les opérations suivantes :
1. Sélectionnez et affichez uniquement les colonnes 'Nom' et 'Moyenne'.
2. Filtrez et affichez les étudiants dont la moyenne est supérieure à 15.
**Corrigé :**
```python
import pandas as pd
data_etudiants = {
'Nom': ['Alice', 'Bob', 'Charlie'],
'Age': [20, 21, 19],
'Moyenne': [15.5, 12.0, 17.8]
}
df_etudiants = pd.DataFrame(data_etudiants)
# 1. Sélection des colonnes 'Nom' et 'Moyenne'
colonnes_selectionnees = df_etudiants[['Nom', 'Moyenne']]
print("DataFrame avec les colonnes 'Nom' et 'Moyenne' :")
print(colonnes_selectionnees)
# 2. Filtrage des étudiants avec une moyenne supérieure à 15
etudiants_hautes_moyennes = df_etudiants[df_etudiants['Moyenne'] > 15]
print("\nÉtudiants dont la moyenne est supérieure à 15 :")
print(etudiants_hautes_moyennes)
```
> [!example] Résultat attendu
> ```
> DataFrame avec les colonnes 'Nom' et 'Moyenne' :
> Nom Moyenne
> 0 Alice 15.5
> 1 Bob 12.0
> 2 Charlie 17.8
>
> Étudiants dont la moyenne est supérieure à 15 :
> Nom Age Moyenne
> 0 Alice 20 15.5
> 2 Charlie 19 17.8
> ```
> [!note] Explication
> * **Sélection de colonnes :** Pour sélectionner plusieurs colonnes, on passe une liste de noms de colonnes à l'opérateur d'indexation `[]` du DataFrame. `df[['col1', 'col2']]`.
> * **Filtrage de lignes :** Le filtrage s'effectue en passant une condition booléenne à l'opérateur d'indexation `[]`. La condition `df_etudiants['Moyenne'] > 15` retourne une `Series` de `True`/`False`. `pandas` sélectionne ensuite uniquement les lignes où cette `Series` est `True`.
#### Exercice 4 : Statistiques Descriptives de Base
**Problème :** Considérez le DataFrame `ventes`. Calculez et affichez la moyenne, la médiane et l'écart type des ventes.
```python
import pandas as pd
ventes = pd.DataFrame({
'Mois': ['Jan', 'Fev', 'Mar', 'Avr', 'Mai'],
'Chiffre_Affaires': [12000, 15000, 11000, 18000, 13000]
})
```
**Corrigé :**
```python
import pandas as pd
ventes = pd.DataFrame({
'Mois': ['Jan', 'Fev', 'Mar', 'Avr', 'Mai'],
'Chiffre_Affaires': [12000, 15000, 11000, 18000, 13000]
})
# Calcul de la moyenne
moyenne_ventes = ventes['Chiffre_Affaires'].mean()
print(f"Moyenne du Chiffre d'Affaires : {moyenne_ventes:.2f}")
# Calcul de la médiane
mediane_ventes = ventes['Chiffre_Affaires'].median()
print(f"Médiane du Chiffre d'Affaires : {mediane_ventes:.2f}")
# Calcul de l'écart type
ecart_type_ventes = ventes['Chiffre_Affaires'].std()
print(f"Écart type du Chiffre d'Affaires : {ecart_type_ventes:.2f}")
```
> [!example] Résultat attendu
> ```
> Moyenne du Chiffre d'Affaires : 13800.00
> Médiane du Chiffre d'Affaires : 13000.00
> Écart type du Chiffre d'Affaires : 2774.88
> ```
> [!note] Explication
> `pandas` offre des méthodes statistiques intégrées directement sur les `Series` (qui sont les colonnes d'un DataFrame).
> * `.mean()` : Calcule la moyenne arithmétique.
> * `.median()` : Calcule la valeur médiane (la valeur centrale d'un ensemble de données triées).
> * `.std()` : Calcule l'écart type, une mesure de la dispersion des données autour de la moyenne.
> Ces fonctions sont très efficaces et gèrent automatiquement les valeurs manquantes par défaut.
#### Exercice 5 : Ajout d'une Colonne Calculée
**Problème :** En utilisant le DataFrame des ventes de l'Exercice 4, ajoutez une nouvelle colonne nommée 'Commission' qui représente 5% du 'Chiffre_Affaires'. Affichez le DataFrame mis à jour.
**Corrigé :**
```python
import pandas as pd
ventes = pd.DataFrame({
'Mois': ['Jan', 'Fev', 'Mar', 'Avr', 'Mai'],
'Chiffre_Affaires': [12000, 15000, 11000, 18000, 13000]
})
# Ajout de la colonne 'Commission'
ventes['Commission'] = ventes['Chiffre_Affaires'] * 0.05
print("DataFrame des ventes avec la colonne 'Commission' :")
print(ventes)
```
> [!example] Résultat attendu
> ```
> DataFrame des ventes avec la colonne 'Commission' :
> Mois Chiffre_Affaires Commission
> 0 Jan 12000 600.0
> 1 Fev 15000 750.0
> 2 Mar 11000 550.0
> 3 Avr 18000 900.0
> 4 Mai 13000 650.0
> ```
> [!note] Explication
> L'ajout d'une nouvelle colonne est très simple en `pandas`. Il suffit d'assigner une `Series` (ou une valeur scalaire) à une nouvelle clé de colonne. Ici, nous effectuons une opération vectorisée : `ventes['Chiffre_Affaires'] * 0.05` calcule 5% pour chaque valeur de la colonne 'Chiffre_Affaires' en une seule opération rapide.
#### Exercice 6 : Tri et Réinitialisation de l'Index
**Problème :** Considérez le DataFrame `notes`.
1. Triez le DataFrame par 'Note_Maths' en ordre décroissant.
2. Après le tri, réinitialisez l'index du DataFrame sans ajouter l'ancien index comme colonne. Affichez le DataFrame final.
```python
import pandas as pd
notes = pd.DataFrame({
'Etudiant': ['Eve', 'Frank', 'Grace', 'Heidi'],
'Note_Maths': [14, 11, 18, 15],
'Note_Physique': [12, 13, 17, 16]
})
```
**Corrigé :**
```python
import pandas as pd
notes = pd.DataFrame({
'Etudiant': ['Eve', 'Frank', 'Grace', 'Heidi'],
'Note_Maths': [14, 11, 18, 15],
'Note_Physique': [12, 13, 17, 16]
})
print("DataFrame original :")
print(notes)
# 1. Tri par 'Note_Maths' en ordre décroissant
notes_triees = notes.sort_values(by='Note_Maths', ascending=False)
print("\nDataFrame trié par 'Note_Maths' (décroissant) :")
print(notes_triees)
# 2. Réinitialisation de l'index
notes_triees_reindexees = notes_triees.reset_index(drop=True)
print("\nDataFrame trié et index réinitialisé :")
print(notes_triees_reindexees)
```
> [!example] Résultat attendu
> ```
> DataFrame original :
> Etudiant Note_Maths Note_Physique
> 0 Eve 14 12
> 1 Frank 11 13
> 2 Grace 18 17
> 3 Heidi 15 16
>
> DataFrame trié par 'Note_Maths' (décroissant) :
> Etudiant Note_Maths Note_Physique
> 2 Grace 18 17
> 3 Heidi 15 16
> 0 Eve 14 12
> 1 Frank 11 13
>
> DataFrame trié et index réinitialisé :
> Etudiant Note_Maths Note_Physique
> 0 Grace 18 17
> 1 Heidi 15 16
> 2 Eve 14 12
> 3 Frank 11 13
> ```
> [!note] Explication
> * **Tri :** La méthode `.sort_values()` permet de trier un DataFrame. L'argument `by` spécifie la ou les colonnes à utiliser pour le tri. L'argument `ascending=False` indique un tri décroissant.
> * **Réinitialisation de l'index :** Après un tri ou un filtrage, l'index du DataFrame peut devenir non séquentiel. `.reset_index()` réinitialise l'index à la séquence par défaut (0, 1, 2...). L'argument `drop=True` est crucial ici : il empêche l'ancien index d'être ajouté comme une nouvelle colonne dans le DataFrame. Si `drop=False` (valeur par défaut), l'ancien index serait conservé dans une colonne nommée 'index'.
### Partie 3 : Exercices plus élaborés (Problèmes complexes) - Corrigés
#### Exercice 7 : Agrégation avec Groupement (Groupby)
**Problème :** Vous disposez d'un DataFrame simulant des données de transactions.
1. Calculez le montant total dépensé par chaque client.
2. Calculez le montant moyen dépensé par produit.
Affichez les résultats de chaque agrégation.
```python
import pandas as pd
transactions = pd.DataFrame({
'ID_Client': [1, 2, 1, 3, 2, 1, 3, 4],
'Produit': ['A', 'B', 'A', 'C', 'B', 'D', 'C', 'A'],
'Montant': [100, 200, 150, 50, 220, 300, 70, 120]
})
```
**Corrigé :**
```python
import pandas as pd
transactions = pd.DataFrame({
'ID_Client': [1, 2, 1, 3, 2, 1, 3, 4],
'Produit': ['A', 'B', 'A', 'C', 'B', 'D', 'C', 'A'],
'Montant': [100, 200, 150, 50, 220, 300, 70, 120]
})
print("DataFrame des transactions original :")
print(transactions)
# 1. Montant total dépensé par chaque client
total_par_client = transactions.groupby('ID_Client')['Montant'].sum()
print("\nMontant total dépensé par chaque client :")
print(total_par_client)
# 2. Montant moyen dépensé par produit
moyenne_par_produit = transactions.groupby('Produit')['Montant'].mean()
print("\nMontant moyen dépensé par produit :")
print(moyenne_par_produit)
```
> [!example] Résultat attendu
> ```
> DataFrame des transactions original :
> ID_Client Produit Montant
> 0 1 A 100
> 1 2 B 200
> 2 1 A 150
> 3 3 C 50
> 4 2 B 220
> 5 1 D 300
> 6 3 C 70
> 7 4 A 120
>
> Montant total dépensé par chaque client :
> ID_Client
> 1 550
> 2 420
> 3 120
> 4 120
> Name: Montant, dtype: int64
>
> Montant moyen dépensé par produit :
> Produit
> A 123.333333
> B 210.000000
> C 60.000000
> D 300.000000
> Name: Montant, dtype: float64
> ```
> [!note] Explication
> * **`groupby()` :** C'est une fonction extrêmement puissante en `pandas`. Elle permet de regrouper les lignes d'un DataFrame en fonction des valeurs d'une ou plusieurs colonnes. Le processus de `groupby` peut être conceptualisé en trois étapes :
> 1. **Split (division) :** Les données sont divisées en groupes basés sur une clé (ici, 'ID_Client' ou 'Produit').
> 2. **Apply (application) :** Une fonction est appliquée indépendamment à chaque groupe (ici, `sum()` ou `mean()`).
> 3. **Combine (combinaison) :** Les résultats des applications sont combinés en une structure de données unique.
> * Syntaxe : `df.groupby('colonne_de_groupement')['colonne_a_agreger'].fonction_agregation()`
#### Exercice 8 : Gestion des Valeurs Manquantes et Conversion de Type
**Problème :** Considérez le DataFrame `data_qualite` avec des valeurs manquantes et des types de données potentiellement incorrects.
1. Identifiez le nombre de valeurs manquantes par colonne.
2. Remplacez les valeurs manquantes de la colonne 'Temperature' par la moyenne des températures non manquantes.
3. Supprimez les lignes où 'Humidite' est manquante.
4. Convertissez la colonne 'ID_Capteur' en type entier (`int`).
Affichez le DataFrame après chaque étape de modification.
```python
import pandas as pd
data_qualite = pd.DataFrame({
'ID_Capteur': [101, 102, 101, 103, 102, None, 104],
'Temperature': [25.5, 24.1, None, 26.0, 23.8, 25.0, 27.2],
'Humidite': [70, 72, 68, None, 75, 71, 69],
'Statut_Capteur': ['OK', 'OK', 'Erreur', 'OK', 'OK', 'OK', 'OK']
})
```
**Corrigé :**
```python
import pandas as pd
import numpy as np # Pour None qui est NaN dans pandas
data_qualite = pd.DataFrame({
'ID_Capteur': [101, 102, 101, 103, 102, None, 104],
'Temperature': [25.5, 24.1, None, 26.0, 23.8, 25.0, 27.2],
'Humidite': [70, 72, 68, None, 75, 71, 69],
'Statut_Capteur': ['OK', 'OK', 'Erreur', 'OK', 'OK', 'OK', 'OK']
})
print("DataFrame original :")
print(data_qualite)
print("\nInformations originales :")
data_qualite.info()
# 1. Identification des valeurs manquantes
print("\nNombre de valeurs manquantes par colonne :")
print(data_qualite.isnull().sum())
# 2. Remplacer les valeurs manquantes de 'Temperature' par la moyenne
mean_temp = data_qualite['Temperature'].mean()
data_qualite['Temperature'].fillna(mean_temp, inplace=True)
print(f"\nDataFrame après remplacement des NaN de 'Temperature' par la moyenne ({mean_temp:.2f}) :")
print(data_qualite)
# 3. Supprimer les lignes où 'Humidite' est manquante
data_qualite.dropna(subset=['Humidite'], inplace=True)
print("\nDataFrame après suppression des lignes avec NaN dans 'Humidite' :")
print(data_qualite)
print("\nInformations après suppression des NaN dans 'Humidite' :")
data_qualite.info()
# 4. Convertir 'ID_Capteur' en type entier
# Il faut d'abord s'assurer qu'il n'y a plus de NaN dans ID_Capteur si l'on veut un int pur.
# Ici, le NaN dans ID_Capteur a été supprimé par l'étape 3.
data_qualite['ID_Capteur'] = data_qualite['ID_Capteur'].astype(int)
print("\nDataFrame après conversion de 'ID_Capteur' en entier :")
print(data_qualite)
print("\nInformations finales :")
data_qualite.info()
```
> [!example] Résultat attendu (partiel)
> ```
> DataFrame original :
> ID_Capteur Temperature Humidite Statut_Capteur
> 0 101.0 25.5 70.0 OK
> 1 102.0 24.1 72.0 OK
> 2 101.0 NaN 68.0 Erreur
> 3 103.0 26.0 NaN OK
> 4 102.0 23.8 75.0 OK
> 5 NaN 25.0 71.0 OK
> 6 104.0 27.2 69.0 OK
>
> Informations originales :
> <class 'pandas.core.frame.DataFrame'>
> RangeIndex: 7 entries, 0 to 6
> Data columns (total 4 columns):
> # Column Non-Null Count Dtype
> --- ------ -------------- -----
> 0 ID_Capteur 6 non-null float64
> 1 Temperature 6 non-null float64
> 2 Humidite 6 non-null float64
> 3 Statut_Capteur 7 non-null object
> dtypes: float64(3), object(1)
> memory usage: 320.0+ bytes
>
> Nombre de valeurs manquantes par colonne :
> ID_Capteur 1
> Temperature 1
> Humidite 1
> Statut_Capteur 0
> dtype: int64
>
> DataFrame après remplacement des NaN de 'Temperature' par la moyenne (25.27) :
> ID_Capteur Temperature Humidite Statut_Capteur
> 0 101.0 25.500000 70.0 OK
> 1 102.0 24.100000 72.0 OK
> 2 101.0 25.266667 68.0 Erreur
> 3 103.0 26.000000 NaN OK
> 4 102.0 23.800000 75.0 OK
> 5 NaN 25.000000 71.0 OK
> 6 104.0 27.200000 69.0 OK
>
> DataFrame après suppression des lignes avec NaN dans 'Humidite' :
> ID_Capteur Temperature Humidite Statut_Capteur
> 0 101.0 25.500000 70.0 OK
> 1 102.0 24.100000 72.0 OK
> 2 101.0 25.266667 68.0 Erreur
> 4 102.0 23.800000 75.0 OK
> 5 NaN 25.000000 71.0 OK
> 6 104.0 27.200000 69.0 OK
>
> Informations après suppression des NaN dans 'Humidite' :
> <class 'pandas.core.frame.DataFrame'>
> Index: 6 entries, 0 to 6
> Data columns (total 4 columns):
> # Column Non-Null Count Dtype
> --- ------ -------------- -----
> 0 ID_Capteur 5 non-null float64
> 1 Temperature 6 non-null float64
> 2 Humidite 6 non-null float64
> 3 Statut_Capteur 6 non-null object
> dtypes: float64(3), object(1)
> memory usage: 240.0+ bytes
>
> DataFrame après conversion de 'ID_Capteur' en entier :
> ID_Capteur Temperature Humidite Statut_Capteur
> 0 101 25.500000 70.0 OK
> 1 102 24.100000 72.0 OK
> 2 101 25.266667 68.0 Erreur
> 4 102 23.800000 75.0 OK
> 6 104 27.200000 69.0 OK
>
> Informations finales :
> <class 'pandas.core.frame.DataFrame'>
> Index: 5 entries, 0 to 6
> Data columns (total 4 columns):
> # Column Non-Null Count Dtype
> --- ------ -------------- -----
> 0 ID_Capteur 5 non-null int64
> 1 Temperature 5 non-null float64
> 2 Humidite 5 non-null float64
> 3 Statut_Capteur 5 non-null object
> dtypes: float64(2), int64(1), object(1)
> memory usage: 240.0+ bytes
> ```
> [!note] Explication
> * **`isnull().sum()` :** `df.isnull()` retourne un DataFrame de booléens indiquant la présence de `NaN`. `.sum()` appliqué à ce DataFrame compte le nombre de `True` (donc de `NaN`) par colonne.
> * **`fillna()` :** Permet de remplacer les valeurs manquantes. Ici, nous utilisons la moyenne de la colonne comme valeur de remplacement. L'argument `inplace=True` modifie le DataFrame directement sans avoir besoin de réaffecter le résultat.
> * **`dropna()` :** Permet de supprimer les lignes ou colonnes contenant des valeurs manquantes. `subset=['Humidite']` indique de ne considérer que les `NaN` dans la colonne 'Humidite' pour la suppression. Encore une fois, `inplace=True` pour une modification directe.
> * **`astype()` :** Permet de convertir le type de données d'une colonne. Il est crucial que la colonne ne contienne pas de `NaN` si vous voulez la convertir en un type entier natif de Python (`int`). Si des `NaN` subsistent, `pandas` pourrait convertir en un type `Int64` (avec un `I` majuscule) qui est un type entier supportant les valeurs manquantes, ou lever une erreur si le type visé ne les supporte pas. Ici, la suppression des `NaN` dans `ID_Capteur` (indirectement par `dropna` sur 'Humidite') a rendu la conversion en `int` possible.
#### Exercice 9 : Fusion de DataFrames (Merge)
**Problème :** Vous avez deux DataFrames : `employes` et `departements`. Fusionnez-les pour obtenir un seul DataFrame contenant le nom de l'employé et le nom de son département. Affichez le DataFrame résultant.
```python
import pandas as pd
employes = pd.DataFrame({
'ID_Employe': [1, 2, 3, 4],
'Nom': ['Alice', 'Bob', 'Charlie', 'David'],
'ID_Departement': [101, 102, 101, 103]
})
departements = pd.DataFrame({
'ID_Departement': [101, 102, 103],
'Nom_Departement': ['R&D', 'Ventes', 'Marketing']
})
```
**Corrigé :**
```python
import pandas as pd
employes = pd.DataFrame({
'ID_Employe': [1, 2, 3, 4],
'Nom': ['Alice', 'Bob', 'Charlie', 'David'],
'ID_Departement': [101, 102, 101, 103]
})
departements = pd.DataFrame({
'ID_Departement': [101, 102, 103],
'Nom_Departement': ['R&D', 'Ventes', 'Marketing']
})
print("DataFrame Employés :")
print(employes)
print("\nDataFrame Départements :")
print(departements)
# Fusion des DataFrames
# La colonne commune pour la jointure est 'ID_Departement'
df_fusionne = pd.merge(employes, departements, on='ID_Departement', how='inner')
print("\nDataFrame fusionné :")
print(df_fusionne)
```
> [!example] Résultat attendu
> ```
> DataFrame Employés :
> ID_Employe Nom ID_Departement
> 0 1 Alice 101
> 1 2 Bob 102
> 2 3 Charlie 101
> 3 4 David 103
>
> DataFrame Départements :
> ID_Departement Nom_Departement
> 0 101 R&D
> 1 102 Ventes
> 2 103 Marketing
>
> DataFrame fusionné :
> ID_Employe Nom ID_Departement Nom_Departement
> 0 1 Alice 101 R&D
> 1 3 Charlie 101 R&D
> 2 2 Bob 102 Ventes
> 3 4 David 103 Marketing
> ```
> [!note] Explication
> * **`pd.merge()` :** C'est la fonction principale pour combiner des DataFrames basés sur des colonnes communes.
> * `left` et `right` : Les deux DataFrames à fusionner (`employes` et `departements`).
> * `on='ID_Departement'` : Spécifie la colonne ou les colonnes à utiliser comme clé de jointure. Les valeurs de cette colonne doivent correspondre dans les deux DataFrames.
> * `how='inner'` : Spécifie le type de jointure. Un `inner` join (jointure interne) ne conserve que les lignes où la clé de jointure est présente dans *les deux* DataFrames. D'autres types de jointures incluent `'left'`, `'right'`, et `'outer'`.
#### Exercice 10 : Analyse de Performances d'Étudiants
**Problème :** Vous avez un DataFrame `notes_etudiants`.
1. Calculez la note moyenne de chaque étudiant sur toutes les matières.
2. Identifiez la matière dans laquelle chaque étudiant a obtenu sa meilleure note.
3. Calculez la note moyenne pour chaque matière.
4. Trouvez l'étudiant ayant la meilleure note globale (moyenne sur toutes les matières).
```python
import pandas as pd
notes_etudiants = pd.DataFrame({
'Etudiant': ['Alice', 'Bob', 'Charlie', 'David', 'Eve', 'Alice', 'Bob', 'Charlie'],
'Matiere': ['Maths', 'Maths', 'Maths', 'Physique', 'Physique', 'Chimie', 'Chimie', 'Chimie'],
'Note': [16, 12, 18, 15, 14, 17, 10, 13]
})
```
**Corrigé :**
```python
import pandas as pd
notes_etudiants = pd.DataFrame({
'Etudiant': ['Alice', 'Bob', 'Charlie', 'David', 'Eve', 'Alice', 'Bob', 'Charlie'],
'Matiere': ['Maths', 'Maths', 'Maths', 'Physique', 'Physique', 'Chimie', 'Chimie', 'Chimie'],
'Note': [16, 12, 18, 15, 14, 17, 10, 13]
})
print("DataFrame des notes des étudiants original :")
print(notes_etudiants)
# 1. Note moyenne de chaque étudiant
moyenne_par_etudiant = notes_etudiants.groupby('Etudiant')['Note'].mean()
print("\nNote moyenne de chaque étudiant :")
print(moyenne_par_etudiant)
# 2. Matière avec la meilleure note pour chaque étudiant
# Pour cela, nous devons trouver l'index de la note maximale pour chaque groupe d'étudiant
idx_max_notes = notes_etudiants.groupby('Etudiant')['Note'].idxmax()
meilleures_notes_par_etudiant = notes_etudiants.loc[idx_max_notes, ['Etudiant', 'Matiere', 'Note']]
print("\nMatière avec la meilleure note pour chaque étudiant :")
print(meilleures_notes_par_etudiant)
# 3. Note moyenne pour chaque matière
moyenne_par_matiere = notes_etudiants.groupby('Matiere')['Note'].mean()
print("\nNote moyenne pour chaque matière :")
print(moyenne_par_matiere)
# 4. Étudiant ayant la meilleure note globale (moyenne sur toutes les matières)
meilleur_etudiant_global = moyenne_par_etudiant.idxmax()
note_meilleur_etudiant = moyenne_par_etudiant.max()
print(f"\nL'étudiant avec la meilleure note globale est '{meilleur_etudiant_global}' avec une moyenne de {note_meilleur_etudiant:.2f}.")
```
> [!example] Résultat attendu (partiel)
> ```
> DataFrame des notes des étudiants original :
> Etudiant Matiere Note
> 0 Alice Maths 16
> 1 Bob Maths 12
> 2 Charlie Maths 18
> 3 David Physique 15
> 4 Eve Physique 14
> 5 Alice Chimie 17
> 6 Bob Chimie 10
> 7 Charlie Chimie 13
>
> Note moyenne de chaque étudiant :
> Etudiant
> Alice 16.5
> Bob 11.0
> Charlie 15.5
> David 15.0
> Eve 14.0
> Name: Note, dtype: float64
>
> Matière avec la meilleure note pour chaque étudiant :
> Etudiant Matiere Note
> 0 Alice Chimie 17
> 1 Bob Maths 12
> 2 Charlie Maths 18
> 3 David Physique 15
> 4 Eve Physique 14
>
> Note moyenne pour chaque matière :
> Matiere
> Chimie 13.333333
> Maths 15.333333
> Physique 14.500000
> Name: Note, dtype: float64
>
> L'étudiant avec la meilleure note globale est 'Alice' avec une moyenne de 16.50.
> ```
> [!note] Explication
> * **`groupby().mean()` :** Pour la moyenne par étudiant et par matière, on utilise la combinaison `groupby()` puis `.mean()` sur la colonne 'Note'.
> * **`idxmax()` avec `groupby()` :** Pour trouver la matière avec la meilleure note par étudiant, nous utilisons `groupby('Etudiant')['Note'].idxmax()`. Cette méthode retourne l'index du DataFrame original pour la ligne qui contient la valeur maximale de 'Note' *pour chaque groupe d'étudiant*. Ensuite, nous utilisons `.loc` pour sélectionner ces lignes spécifiques du DataFrame original, ce qui nous donne l'étudiant, la matière et la note correspondante.
> * **`idxmax()` sur une Série :** Pour trouver l'étudiant avec la meilleure note globale, nous appliquons `idxmax()` directement sur la `Series` `moyenne_par_etudiant` qui contient les moyennes de chaque étudiant. Cela retourne l'index (ici, le nom de l'étudiant) correspondant à la valeur maximale. `max()` retourne la valeur maximale elle-même.
---
Félicitations pour avoir complété ces exercices ! Vous avez maintenant une base solide en manipulation de données avec `pandas`, ce qui vous sera invaluable pour les prochaines étapes de votre parcours en Data Science.