# -*- 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.models import UniteEns 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 from app.scodoc.codes_cursus import UE_SPORT TAGS_RESERVES = ["but"] class MoyenneTag: def __init__( self, tag: str, ues: list[UniteEns], notes_ues: pd.DataFrame, notes_gen: pd.Series, ): """Classe centralisant la synthèse des moyennes/classements d'une série d'étudiants à un tag donné, en stockant : `` { "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 ues: La liste des UEs ayant servie au calcul de la moyenne notes_ues: Les moyennes (etudid x ues) aux différentes UEs et pour le tag notes_gen: Une série de notes (moyenne) sous forme d'un pd.Series() (toutes UEs confondues) """ self.tag = tag """Le tag associé à la moyenne""" self.etudids = list(notes_gen.index) # calcul à venir """Les id des étudiants""" self.ues: list[UniteEns] = ues """Les UEs sur lesquelles sont calculées les moyennes""" self.df_ues: dict[int, pd.DataFrame] = {} """Les dataframes retraçant les moyennes/classements/statistiques des étudiants aux UEs""" for ue in self.ues: # if ue.type != UE_SPORT: notes = notes_ues[ue.id] self.df_ues[ue.id] = self.comp_moy_et_stat(notes) self.inscrits_ids = notes_gen[notes_gen.notnull()].index.to_list() """Les id des étudiants dont la moyenne générale est non nulle""" self.df_gen: pd.DataFrame = self.comp_moy_et_stat(notes_gen) """Le dataframe retraçant les moyennes/classements/statistiques général""" 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 (pouvant être une moyenne d'un tag à une UE ou une moyenne générale d'un 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) # Les étudiants dont la note n'est pas nulle inscrits_ids = notes[notes.notnull()].index.to_list() df.loc[inscrits_ids, "nb_inscrits"] = len(inscrits_ids) # Le classement des inscrits notes_non_nulles = notes[inscrits_ids] (class_str, class_int) = comp_ranks_series(notes_non_nulles) df.loc[inscrits_ids, "classement"] = class_int # Le rang (classement/nb_inscrit) df["rang"] = df["rang"].astype(str) df.loc[inscrits_ids, "rang"] = ( df.loc[inscrits_ids, "classement"].astype(int).astype(str) + "/" + df.loc[inscrits_ids, "nb_inscrits"].astype(int).astype(str) ) # Les stat (des inscrits) df.loc[inscrits_ids, "min"] = notes.min() df.loc[inscrits_ids, "max"] = notes.max() df.loc[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_gen["note"], "classements": self.df_gen["classement"], "min": self.df_gen["min"].mean(), "max": self.df_gen["max"].mean(), "moy": self.df_gen["moy"].mean(), "nb_inscrits": self.df_gen["nb_inscrits"].mean(), } return synthese def get_notes(self): """Série des notes, arrondies à 2 chiffres après la virgule""" return self.df_gen["note"].round(2) def get_rangs_inscrits(self) -> pd.Series: """Série des rangs classement/nbre_inscrit""" return self.df_gen["rang"] def get_min(self) -> pd.Series: """Série des min""" return self.df_gen["min"].round(2) def get_max(self) -> pd.Series: """Série des max""" return self.df_gen["max"].round(2) def get_moy(self) -> pd.Series: """Série des moy""" return self.df_gen["moy"].round(2) def get_note_for_df(self, etudid: int): """Note d'un étudiant donné par son etudid""" return round(self.df_gen["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_gen["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