Site de Jean-Michel RICHER

Maître de Conférences en Informatique à l'Université d'Angers

Ce site est en cours de reconstruction certains liens peuvent ne pas fonctionner ou certaines images peuvent ne pas s'afficher.


1. Application : moyenne et ecart-type, fichier CSV

1.1. Problématique

1.1.1. Description du problème

On dispose d'un fichier de notes obtenues par des étudiants pour les mathématiques et l'informatique. On désire

  • calculer la moyenne et l'écart-type pour les mathématiques et l'informatique
  • exporter les notes ainsi que moyenne et écart-type en HTML et LaTeX

Si le fichier contient une centaine de lignes cela peut prendre du temps d'exporter le fichier en faisant celà à la main, s'il s'agit de milliers de lignes, c'est inenvisageable.

Les données initiales sont les suivantes (fichier notes.csv) :

Etudiant","Math","Info"
"Jean Bon",   12, 17
"Marc Decafer" ,  7, 10
"Henri Quatre", 12,  11
"Marie Ney" , 16, 18
"Santa Claus", 19, 20
"Pascal Line", 13, 13
"Noël Blanc", 14,  13
"Charlotte Ofrèze", 11, 15
"Sarah Longe", 14, 19
"Charles Magne", 13, 8
"Anne Atoll", 7,  15

Il s'agit donc d'un fichier .csv doté d'un header (première ligne) qui contient la description des colonnes.

1.1.2. Analyse du problème

  • il faut pouvoir charger le fichier .csv, réaliser les calculs et les ajouter à la fin du fichier
  • il faut pouvoir traduire les données sous forme de table en HTML et en LaTeX

1.2. Comment faire en ligne de commande ?

1.2.1. Première étape

Supprimer le header et les lignes vides et créer un nouveau fichier notes_sans_header.csv :

richer@solaris\$sed '1d' notes.csv | grep -v "^$" > notes_sans_header.csv

La commande sed est utilisée pour supprimer la première ligne '1d' (1 delete), puis on utilise grep pour supprimer les lignes vides. On pourrait égalemet supprimer les lignes qui ne contiendraient que des espaces.

1.2.2. Deuxième étape

Écrire le fichier transforme.awk, il s'agit d'un fichier awk qui est un langage de traitement par lignes de fichiers plats proche du C.

Afficher le code    ens/l1/python1/exemple_1/transforme.awk
  1. BEGIN {
  2.   # set field separator to commad ','
  3.   FS=","
  4. }
  5. /.*/ {  
  6.   cols=NF;
  7.   for(i=2; i<=NF; i++) {
  8.     sum[i] += $i;
  9.     sumsq[i] += $i*$i;
  10.   }
  11.   count++;
  12.   print($0);
  13. }
  14. END {
  15.   printf("Moyenne");
  16.   average = 0
  17.   for(i=2; i<=cols; i++) {
  18.     average = sum[i]/count;
  19.     printf(", %.2f", average);
  20.   }
  21.   printf("\n");
  22.   # commentaire
  23.   printf("Ecart-type");
  24.   for(i=2; i<=cols; i++) {
  25.     average = sum[i]/count;
  26.     variance = (sumsq[i] / count) - (average * average)
  27.     printf(", %.2f", sqrt(variance) );
  28.   }
  29.   printf("\n");  
  30. }
  31.  
  32.  

1.2.3. Troisième étape

Exécuter le script awk sur le fichier .csv sans header :

richer@solaris\$awk -f transforme.awk notes_sans_header.csv > donnees.csv
"Jean Bon",   12, 17
"Marc Decafer" ,  7, 10
"Henri Quatre", 12,  11
"Marie Ney" , 16, 18
"Santa Claus", 19, 20
"Pascal Line", 13, 13
"Noël Blanc", 14,  13
"Charlotte Ofrèze", 11, 15
"Sarah Longe", 14, 19
"Charles Magne", 13, 8
"Anne Atoll", 7,  15
Moyenne, 12.55, 14.45
Ecart-type, 3.34, 3.68

1.2.4. Quatrième étape

Créer d'autres scripts awk pour transformer un fichier .csv en tableau HTML ou en LaTeX

1.3. Comment faire avec C++ ?

Voici le code C++ qui permet de faire ce qui est demandé, il s'agit d'un programme de 140 lignes donc assez court mais qui demande une bonne maîtrise de la STL.

Afficher le code    ens/l1/python1/exemple_1/traite_donnees.cpp
  1. #include <iostream>
  2. #include <fstream>
  3. #include <vector>
  4. #include <string>
  5. #include <sstream>
  6. #include <cmath>
  7. #include <iomanip>
  8. #include <numeric>
  9.  
  10. using namespace std;
  11.  
  12. // Structure pour stocker les données du tableau
  13. struct Table {
  14.     vector<string> headers;
  15.     vector<vector<string>> rows;
  16. };
  17.  
  18. // Fonction pour découper une ligne CSV en colonnes
  19. vector<string> split(const string& s, char delimiter) {
  20.     vector<string> tokens;
  21.     string token;
  22.     istringstream tokenStream(s);
  23.     while (getline(tokenStream, token, delimiter)) {
  24.         tokens.push_back(token);
  25.     }
  26.     return tokens;
  27. }
  28.  
  29. // Fonction pour lire le CSV
  30. Table readCSV(const string& filename) {
  31.     Table table;
  32.     ifstream file(filename);
  33.     string line;
  34.  
  35.     if (getline(file, line)) {
  36.      
  37.         table.headers = split(line, ',');
  38.     }
  39.  
  40.     while (getline(file, line)) {
  41.       if (line.length() == 0) continue;
  42.         table.rows.push_back(split(line, ','));
  43.     }
  44.     return table;
  45. }
  46.  
  47. // Fonction pour calculer les stats et ajouter les lignes
  48. void addStatistics(Table& table) {
  49.     int numCols = table.headers.size();
  50.     int numRows = table.rows.size();
  51.  
  52.     vector<string> meanRow(numCols, "-");
  53.     vector<string> stdDevRow(numCols, "-");
  54.  
  55.     // On commence à i=1 si la colonne 0 contient des noms/textes
  56.     // Ici on tente le calcul sur toutes les colonnes
  57.     for (int j = 0; j < numCols; ++j) {
  58.         vector<double> values;
  59.         for (int i = 0; i < numRows; ++i) {
  60.             try {
  61.                 values.push_back(stod(table.rows[i][j]));
  62.             } catch (...) {
  63.                 // Ignore les valeurs non numériques
  64.             }
  65.         }
  66.  
  67.         if (!values.empty()) {
  68.             double sum = accumulate(values.begin(), values.end(), 0.0);
  69.             double mean = sum / values.size();
  70.  
  71.             double sq_sum = 0;
  72.             for (double v : values) sq_sum += (v - mean) * (v - mean);
  73.             double stdDev = sqrt(sq_sum / values.size());
  74.  
  75.             stringstream ssMean, ssStd;
  76.             ssMean << fixed << setprecision(2) << mean;
  77.             ssStd << fixed << setprecision(2) << stdDev;
  78.             meanRow[j] = ssMean.str();
  79.             stdDevRow[j] = ssStd.str();
  80.         }
  81.     }
  82.  
  83.     meanRow[0] = "MOYENNE";
  84.     stdDevRow[0] = "ECART-TYPE";
  85.     table.rows.push_back(meanRow);
  86.     table.rows.push_back(stdDevRow);
  87. }
  88.  
  89. // Export HTML
  90. void saveToHTML(const Table& table, const string& filename) {
  91.     ofstream file(filename);
  92.     file << "<html><body><table border='1'><thead><tr>";
  93.     for (const auto& h : table.headers) file << "<th>" << h << "</th>";
  94.     file << "</tr></thead><tbody>";
  95.     for (const auto& row : table.rows) {
  96.         file << "<tr>";
  97.         for (const auto& cell : row) file << "<td>" << cell << "</td>";
  98.         file << "</tr>";
  99.     }
  100.     file << "</tbody></table></body></html>";
  101. }
  102.  
  103. // Export LaTeX
  104. void saveToLaTeX(const Table& table, const string& filename) {
  105.     ofstream file(filename);
  106.     file << "\\begin{tabular}{" << string(table.headers.size(), 'l') << "}\n\\hline\n";
  107.     for (size_t i = 0; i < table.headers.size(); ++i) {
  108.         file << table.headers[i] << (i == table.headers.size() - 1 ? "" : " & ");
  109.     }
  110.     file << " \\\\\n\\hline\n";
  111.     for (const auto& row : table.rows) {
  112.         for (size_t i = 0; i < row.size(); ++i) {
  113.             file << row[i] << (i == row.size() - 1 ? "" : " & ");
  114.         }
  115.         file << " \\\\\n";
  116.     }
  117.     file << "\\hline\n\\end{tabular}";
  118. }
  119.  
  120. int main(int argc, char *argv[]) {
  121.     string inputFile = "donnees.csv";
  122.    
  123.     if (argc > 1) {
  124.       inputFile = argv[1];
  125.     }
  126.     Table table = readCSV(inputFile);
  127.     if (table.headers.empty()) {
  128.         cout << "Erreur de lecture ou fichier vide." << endl;
  129.         return 1;
  130.     }
  131.  
  132.     addStatistics(table);
  133.    
  134.     saveToHTML(table, "resultat.html");
  135.     saveToLaTeX(table, "resultat.tex");
  136.  
  137.     cout << "Traitement terminé. Fichiers resultat.html et resultat.tex générés." << endl;
  138.  
  139.     return 0;
  140. }
  141.  

1.4. Comment faire avec Python ?

Le programme python qui permet de répondre au problème posé est composé d'une trentaine de lignes et fait appel à des fonctionnalités déjà existantes notamment celles du package pandas.

1.4.1. Chargement des données

On utilise le package pandas qui permet de gérer les fichiers .csv.

  1. import pandas as pd
  2.  
  3. # lecture du fichier de données sous forme d'un data frame
  4. df = pd.read_csv('notes.csv')
  5.  
  6. # affiche le data frame
  7. print(df)

Le package pandas lit le fichier .csv et le stocke en mémoire sous forme d'un data frame qui est une matrice de données. Le résultat de l'affichage est :

            Etudiant  Math  Info
0           Jean Bon    12    17
1      Marc Decafer      7    10
2       Henri Quatre    12    11
3         Marie Ney     16    18
4        Santa Claus    19    20
5        Pascal Line    13    13
6         Noël Blanc    14    13
7   Charlotte Ofrèze    11    15
8        Sarah Longe    14    19
9      Charles Magne    13     8
10        Anne Atoll     7    15

On voit que les lignes commencent à 0 et que la première ligne est considérée comme un header qui contient le nom des colonnes.

1.4.2. Calcul et ajout de la moyenne et de l'écart-type

Grâces aux instructions suivantes on calcule la moyenne (mean) et on l'ajoute à la fin du data frame, puis on fait de même pour l'écart-type (std = standard deviation) :

  1. # calculer la moyenne de chaque colonne et ajouter à la fin du data frame
  2. # attention il faut donner un nom à la ligne à ajouter ici 'moyenne'
  3. df.loc['moyenne'] = df.mean(numeric_only=True)
  4. df.iloc[-1,0] = "Moyenne"
  5.  
  6. # calculer l'écart-type et ajouter à la fin du data frame
  7. # attention il faut donner un nom à la ligne à ajouter ici 'ecart-type'
  8. df.loc['ecart-type'] = df.std(numeric_only=True)
  9. df.iloc[-1,0] = "Ecart-type"
  10.  
  11. # affichage du nouveau data frame
  12. print(df)

La méthode loc associée au data frame permet de sélectionner des lignes et des colonnes, son format est :

df.loc[sélection_des_lignes, sélection_des_colonnes]

Le data frame modifié est alors :

                    Etudiant       Math       Info
0                   Jean Bon  12.000000  17.000000
1              Marc Decafer    7.000000  10.000000
2               Henri Quatre  12.000000  11.000000
3                 Marie Ney   16.000000  18.000000
4                Santa Claus  19.000000  20.000000
5                Pascal Line  13.000000  13.000000
6                 Noël Blanc  14.000000  13.000000
7           Charlotte Ofrèze  11.000000  15.000000
8                Sarah Longe  14.000000  19.000000
9              Charles Magne  13.000000   8.000000
10                Anne Atoll   7.000000  15.000000
moyenne              Moyenne  12.545455  14.454545
ecart-type        Ecart-type   3.340213   3.677045

1.4.3. Export vers HTML

On utilise la méthode to_html associée au data frame :

  1. # export vers HTML
  2. html_table = df.to_html(index = False, header = True, float_format='{:.2f}'.format)
  3. with open("tableau.html", "w") as f:
  4.     f.write(html_table)
  • Le paramètre index permet d'indiquer si on désire inclure les numéros de lignes
  • Le paramètre header indique d'utiliser les noms de colonnes (header)
  • Le paramètre float_format indique d'afficher les nombres flottants avec deux chiffres après la virgule

Le résultat obtenu ressemble (application du style CSS de ce site) à ce qui suit :

Etudiant Math Info
Jean Bon 12.00 17.00
Marc Decafer 7.00 10.00
Henri Quatre 12.00 11.00
Marie Ney 16.00 18.00
Santa Claus 19.00 20.00
Pascal Line 13.00 13.00
Noël Blanc 14.00 13.00
Charlotte Ofrèze 11.00 15.00
Sarah Longe 14.00 19.00
Charles Magne 13.00 8.00
Anne Atoll 7.00 15.00
Moyenne 12.55 14.45
Ecart-type 3.34 3.68

1.4.4. Export vers LaTeX

On utilise la méthode to_latex associée au data frame :

  1. # export vers LaTeX
  2. latex_code = df.to_latex(index = False, caption="Comparaison des modèles", label="tab:modeles")
  3. with open("tableau.tex", "w") as f:
  4.     f.write(latex_code)
  • Le paramètre index permet d'indiquer si on désire inclure les numéros de lignes
  • Le paramètre caption représente la légende du tableau
  • Le paramètre label définit un identifiant qui peut être réutilisé pour faire référence au tableau

Le résultat obtenu est :

\begin{table}
\caption{Comparaison des modèles}
\label{tab:modeles}
\begin{tabular}{lrr}
\toprule
Etudiant & Math & Info \\\\
\midrule
Jean Bon      & 12.000000 & 17.000000 \\\\
Marc Decafer  & 7.000000 & 10.000000 \\\\
Henri Quatre  & 12.000000 & 11.000000 \\\\
Marie Ney     & 16.000000 & 18.000000 \\\\
Santa Claus   & 19.000000 & 20.000000 \\\\
Pascal Line   & 13.000000 & 13.000000 \\\\
Noël Blanc    & 14.000000 & 13.000000 \\\\
Charlotte Ofrèze & 11.000000 & 15.000000 \\\\
Sarah Longe   & 14.000000 & 19.000000 \\\\
Charles Magne & 13.000000 & 8.000000 \\\\
Anne Atoll    & 7.000000 & 15.000000 \\\\
Moyenne       & 12.545455 & 14.454545 \\\\
Ecart-type    & 3.340213 & 3.677045 \\\\
\bottomrule
\end{tabular}
\end{table}

Le fichier résultat après compilation et ajout de packages LaTeX est tableau.pdf.

1.5. En résumé

Avec Python on dispose de packages opérationnels qui permettent de faire rapidement ce que l'on veut, il suffit de leur procurer les paramètres adéquats.

Avec le Shell ou les scripts awk, ou encore le C++, on a plus de lignes de code à écrire.