# -*- mode: python -*- # -*- coding: utf-8 -*- ############################################################################## # # Gestion scolarite IUT # # Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Emmanuel Viennet emmanuel.viennet@viennet.net # ############################################################################## ############################################################################## # Module "Avis de poursuite d'étude" # conçu et développé par Cléo Baras (IUT de Grenoble) ############################################################################## """ Created on Thu Sep 8 09:36:33 2016 @author: barasc """ import datetime import numpy as np from app import ScoValueError from app.comp.moy_sem import comp_ranks_series from app.pe import pe_affichage from app.pe.pe_affichage import SANS_NOTE from app.scodoc import sco_utils as scu import pandas as pd TAGS_RESERVES = ["but"] class MoyenneTag: def __init__(self, tag: str, notes: pd.Series): """Classe centralisant la synthèse des moyennes/classements d'une série d'étudiants à un tag donné, en stockant un dictionnaire : `` { "notes": la Serie pandas des notes (float), "classements": la Serie pandas des classements (float), "min": la note minimum, "max": la note maximum, "moy": la moyenne, "nb_inscrits": le nombre d'étudiants ayant une note, } `` Args: tag: Un tag note: Une série de notes (moyenne) sous forme d'un pd.Series() """ self.tag = tag """Le tag associé à la moyenne""" self.etudids = list(notes.index) # calcul à venir """Les id des étudiants""" self.inscrits_ids = notes[notes.notnull()].index.to_list() """Les id des étudiants dont la moyenne est non nulle""" self.df: pd.DataFrame = self.comp_moy_et_stat(notes) """Le dataframe retraçant les moyennes/classements/statistiques""" self.synthese = self.to_dict() """La synthèse (dictionnaire) des notes/classements/statistiques""" def __eq__(self, other): """Egalité de deux MoyenneTag lorsque leur tag sont identiques""" return self.tag == other.tag def comp_moy_et_stat(self, notes: pd.Series) -> dict: """Calcule et structure les données nécessaires au PE pour une série de notes (souvent une moyenne par tag) dans un dictionnaire spécifique. Partant des notes, sont calculés les classements (en ne tenant compte que des notes non nulles). Args: notes: Une série de notes (avec des éventuels NaN) Returns: Un dictionnaire stockant les notes, les classements, le min, le max, la moyenne, le nb de notes (donc d'inscrits) """ df = pd.DataFrame( np.nan, index=self.etudids, columns=[ "note", "classement", "rang", "min", "max", "moy", "nb_etuds", "nb_inscrits", ], ) # Supprime d'éventuelles chaines de caractères dans les notes notes = pd.to_numeric(notes, errors="coerce") df["note"] = notes # Les nb d'étudiants & nb d'inscrits df["nb_etuds"] = len(self.etudids) df.loc[self.inscrits_ids, "nb_inscrits"] = len(self.inscrits_ids) # Le classement des inscrits notes_non_nulles = notes[self.inscrits_ids] (class_str, class_int) = comp_ranks_series(notes_non_nulles) df.loc[self.inscrits_ids, "classement"] = class_int # Le rang (classement/nb_inscrit) df["rang"] = df["rang"].astype(str) df.loc[self.inscrits_ids, "rang"] = ( df.loc[self.inscrits_ids, "classement"].astype(int).astype(str) + "/" + df.loc[self.inscrits_ids, "nb_inscrits"].astype(int).astype(str) ) # Les stat (des inscrits) df.loc[self.inscrits_ids, "min"] = notes.min() df.loc[self.inscrits_ids, "max"] = notes.max() df.loc[self.inscrits_ids, "moy"] = notes.mean() return df def to_dict(self) -> dict: """Renvoie un dictionnaire de synthèse des moyennes/classements/statistiques""" synthese = { "notes": self.df["note"], "classements": self.df["classement"], "min": self.df["min"].mean(), "max": self.df["max"].mean(), "moy": self.df["moy"].mean(), "nb_inscrits": self.df["nb_inscrits"].mean(), } return synthese def get_notes(self): """Série des notes, arrondies à 2 chiffres après la virgule""" return self.df["note"].round(2) def get_rangs_inscrits(self) -> pd.Series: """Série des rangs classement/nbre_inscrit""" return self.df["rang"] def get_min(self) -> pd.Series: """Série des min""" return self.df["min"].round(2) def get_max(self) -> pd.Series: """Série des max""" return self.df["max"].round(2) def get_moy(self) -> pd.Series: """Série des moy""" return self.df["moy"].round(2) def get_note_for_df(self, etudid: int): """Note d'un étudiant donné par son etudid""" return round(self.df["note"].loc[etudid], 2) def get_min_for_df(self) -> float: """Min renseigné pour affichage dans un df""" return round(self.synthese["min"], 2) def get_max_for_df(self) -> float: """Max renseigné pour affichage dans un df""" return round(self.synthese["max"], 2) def get_moy_for_df(self) -> float: """Moyenne renseignée pour affichage dans un df""" return round(self.synthese["moy"], 2) def get_class_for_df(self, etudid: int) -> str: """Classement ramené au nombre d'inscrits, pour un étudiant donné par son etudid""" classement = self.df["rang"].loc[etudid] if not pd.isna(classement): return classement else: return pe_affichage.SANS_NOTE def is_significatif(self) -> bool: """Indique si la moyenne est significative (c'est-à-dire à des notes)""" return self.synthese["nb_inscrits"] > 0 class TableTag(object): def __init__(self): """Classe centralisant différentes méthodes communes aux SemestreTag, TrajectoireTag, AggregatInterclassTag """ pass # ----------------------------------------------------------------------------------------------------------- def get_all_tags(self): """Liste des tags de la table, triée par ordre alphabétique, extraite des clés du dictionnaire ``moyennes_tags`` connues (tags en doublon possible). Returns: Liste de tags triés par ordre alphabétique """ return sorted(list(self.moyennes_tags.keys())) def df_moyennes_et_classements(self) -> pd.DataFrame: """Renvoie un dataframe listant toutes les moyennes, et les classements des étudiants pour tous les tags. Est utilisé pour afficher le détail d'un tableau taggué (semestres, trajectoires ou aggrégat) Returns: Le dataframe des notes et des classements """ etudiants = self.etudiants df = pd.DataFrame.from_dict(etudiants, orient="index", columns=["nom"]) tags_tries = self.get_all_tags() for tag in tags_tries: moy_tag = self.moyennes_tags[tag] df = df.join(moy_tag.synthese["notes"].rename(f"Moy {tag}")) df = df.join(moy_tag.synthese["classements"].rename(f"Class {tag}")) return df def df_notes(self) -> pd.DataFrame | None: """Renvoie un dataframe (etudid x tag) listant toutes les moyennes par tags Returns: Un dataframe etudids x tag (avec tag par ordre alphabétique) """ tags_tries = self.get_all_tags() if tags_tries: dict_series = {} for tag in tags_tries: # Les moyennes associés au tag moy_tag = self.moyennes_tags[tag] dict_series[tag] = moy_tag.synthese["notes"] df = pd.DataFrame(dict_series) return df