Exercice Pandas

Mise en Oeuvre de Pandas pour générer des bulletins de Notes

L’objectif de cette application est de :

  • Récupérer un dataset ( un fichier contenant des notes d’étudiants dans différentes matières ) à partir d’un fichier .ods
  • Modifier ce dataset afin de remplacer :
    • Les absences injustifiées ( ABI ) par la note 0
    • Les absences justifiées ( ABJ ) par NaN
  • Calculer les moyennes par matière
  • Calculer la moyenne de chaque Etudiant
  • Créer un bulletin pour chaque étudiant, contenant ses notes, sa moyenne, et la moyenne de chaque matière

Récupération du Dataset

L’ensemble des notes est disponible dans le fichier suivant :

bulletin.ods

import numpy as np
import matplotlib.pyplot as plt
import pandas as pd

# conda install conda-forge::odfpy
df = pd.read_excel('bulletin.ods')
df

Nom Prenom Français Histoire-geo Maths Physique Anglais Biologie
0 Mailloux Gérard 16.00 17 16 17 16.0 17
1 Boileau Eustache 18.00 ABJ 10 ABI 16.0 20
2 Tisserand Raoul 4.00 5 2 10 1.0 5
3 Patenaude Michel 18.50 8 9.5 18 3.0 19
4 Bussiere Eloise 7.00 1 12 6 9.5 1
5 St-Pierre René 10.00 9 ABJ 11 12.0 13
6 Therriault Maurice 10.00 20 14 19 20.0 13
7 Guimond Noelle 3.00 10.5 7 9 3.0 17
8 Demers Pascal 4.00 2 ABI 15.5 4.0 7.5
9 Souplet Anita 10.00 12 19 5 12.0 11.5
10 Saurel Pauline 8.00 12 ABI 7 13.0 14
11 Mailly Henri 9.00 3 3 3 1.0 8
12 Lachance Romane 6.00 17 14 12 11.0 ABJ
13 Viens Daniel 4.00 6 ABJ 15 4.0 20
14 Narcisse Hélène 6.00 11 15 8 5.0 15
15 Camus Merlin 15.00 12 13 13.5 11.0 10
16 Savoie Leonie 8.00 5 7 3 10.0 0
17 Gervais Claude 19.25 1 13 18 2.0 0
18 Grojean Stanislas 12.00 12.5 10 14 15.0 ABI
19 Samson Micheline 14.00 6 6 10 17.0 13
# Ajout d'une colonne 'Moyenne globale'  
df['Moyenne globale'] = 0.0

matieres = df.columns[2:-1]  # Matières après nom et prénom, en excluant 'Moyenne globale'

print(matieres)

# Remplacement des absences par les valeurs correspondantes
df.replace("ABI", 0.0, inplace=True)     # Absence injustifiée --> 0
df.replace("ABJ", None, inplace=True)

# Conversion des colonnes de notes en float
for mat in matieres:
     df[mat] = pd.to_numeric(df[mat], errors='coerce')

print(df.head())
Index(['Français', 'Histoire-geo', 'Maths', 'Physique', 'Anglais', 'Biologie'], dtype='object')
         Nom    Prenom  Français  Histoire-geo  Maths  Physique  Anglais  \
0   Mailloux   Gérard       16.0          17.0   16.0      17.0     16.0   
1    Boileau  Eustache      18.0           NaN   10.0       0.0     16.0   
2  Tisserand     Raoul       4.0           5.0    2.0      10.0      1.0   
3  Patenaude    Michel      18.5           8.0    9.5      18.0      3.0   
4   Bussiere    Eloise       7.0           1.0   12.0       6.0      9.5   

   Biologie  Moyenne globale  
0      17.0              0.0  
1      20.0              0.0  
2       5.0              0.0  
3      19.0              0.0  
4       1.0              0.0  

Calcul des moyennes par matière

matieres_moyennes = df[matieres].mean(axis=0, skipna=True)
print(matieres_moyennes)
print(type(matieres_moyennes))
Français        10.087500
Histoire-geo     8.947368
Maths            9.472222
Physique        10.700000
Anglais          9.275000
Biologie        10.736842
dtype: float64
<class 'pandas.core.series.Series'>
# Autre possibilité : utilisation de numpy  
d_average = {} # Définition d'un dictionnaire

for mat in matieres:
    notes = df[mat].values  # Récupération des valeurs des colonnes
    average = np.nanmean(notes)   # Absence justifiée : on ignore la note dans le calcul
    d_average[mat] = average 

print('d_average:', d_average)
d_average: {'Français': 10.0875, 'Histoire-geo': 8.947368421052632, 'Maths': 9.472222222222221, 'Physique': 10.7, 'Anglais': 9.275, 'Biologie': 10.736842105263158}

Calcul de la moyenne de chaque Etudiant

df['Moyenne globale'] = df[matieres].mean(axis=1, skipna=True)
print(df.head())
         Nom    Prenom  Français  Histoire-geo  Maths  Physique  Anglais  \
0   Mailloux   Gérard       16.0          17.0   16.0      17.0     16.0   
1    Boileau  Eustache      18.0           NaN   10.0       0.0     16.0   
2  Tisserand     Raoul       4.0           5.0    2.0      10.0      1.0   
3  Patenaude    Michel      18.5           8.0    9.5      18.0      3.0   
4   Bussiere    Eloise       7.0           1.0   12.0       6.0      9.5   

   Biologie  Moyenne globale  
0      17.0        16.500000  
1      20.0        12.800000  
2       5.0         4.500000  
3      19.0        12.666667  
4       1.0         6.083333  
# Autre possibilité : utilisation de numpy  

for i in df.index : # df. index = RangeIndex(start=0, stop=20, step=1)
    notes = df.iloc[i,2:-1]         # ensemble des notes d'un étudiant
    average = np.nanmean(notes)   # Absence justifiée : on ignore la note dans le calcul
    df.loc[i,'Moyenne globale'] = average # Accès à un élément du dataset

print(df.head())
         Nom    Prenom  Français  Histoire-geo  Maths  Physique  Anglais  \
0   Mailloux   Gérard       16.0          17.0   16.0      17.0     16.0   
1    Boileau  Eustache      18.0           NaN   10.0       0.0     16.0   
2  Tisserand     Raoul       4.0           5.0    2.0      10.0      1.0   
3  Patenaude    Michel      18.5           8.0    9.5      18.0      3.0   
4   Bussiere    Eloise       7.0           1.0   12.0       6.0      9.5   

   Biologie  Moyenne globale  
0      17.0        16.500000  
1      20.0        12.800000  
2       5.0         4.500000  
3      19.0        12.666667  
4       1.0         6.083333  

Creation des bulletins individuels

import os

os.makedirs("bulletins", exist_ok=True)

df.replace({None: "ABJ"}, inplace=True) # on remplace NaN par ABJ

for index, row in df.iterrows(): # Contient toutes les colonnes et leurs valeurs pour une ligne donnée, sous forme de Series
    identifiant = f"{row['Prenom']}_{row['Nom']}"
    file_path = os.path.join("bulletins", f"{identifiant}.txt") # creation du path
    
    with open(file_path, "w", encoding="utf-8") as f:
                f.write(f"Bulletin de {row['Prenom']} {row['Nom']}\n")
                f.write("=" * 30 + "\n\n")
    
                for mat in matieres:  # Exclut les colonnes prénom, nom et moyenne générale
                     f.write(f"{mat}: {row[mat]:.2f}\n")
        
                f.write("\n")
    
                f.write(f"Moyenne générale: {row['Moyenne globale']:.2f}\n")
                f.write("\n")    
                f.write("Moyennes des matières:\n")
                
                f.write("\n")               
                for mat, average in d_average.items():
                    f.write(f"{mat}: {average:.2f}\n")
# Sauvegarde du nouveau dataset dans un fichier .ods
from pyexcel_ods import save_data
from collections import OrderedDict

data = OrderedDict()
data["Sheet1"] = [df.columns.tolist()] + df.values.tolist()  # Ajoutez les colonnes et les lignes du DataFrame

save_data("bulletin_update.ods", data)