# -*- 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 Fri Sep 9 09:15:05 2016 @author: barasc """ from app.comp import moy_sem from app.comp.res_sem import load_formsemestre_results from app.pe.pe_semtag import SemestreTag import pandas as pd import numpy as np from app.pe.pe_trajectoire import Trajectoire from app.pe.pe_tabletags import TableTag class TrajectoireTag(TableTag): def __init__( self, trajectoire: Trajectoire, semestres_taggues: dict[int, SemestreTag] ): """Calcule les moyennes par tag d'une combinaison de semestres (trajectoires), identifiée par un nom d'aggrégat (par ex: '3S') et par un semestre terminal, pour extraire les classements par tag pour un groupe d'étudiants donnés. Le groupe d'étudiants est formé par ceux ayant tous participé au semestre terminal. Par ex: fusion d'un parcours ['S1', 'S2', 'S3'] donnant un nom_combinaison = '3S' """ TableTag.__init__(self) # La trajectoire associée self.trajectoire_id = trajectoire.trajectoire_id self.trajectoire = trajectoire # Le nom de la trajectoire tagguée (identique à la trajectoire) self.nom = self.get_repr() """Le formsemestre terminal et les semestres aggrégés""" self.formsemestre_terminal = trajectoire.semestre_final nt = load_formsemestre_results(self.formsemestre_terminal) self.semestres_aggreges = trajectoire.semestres_aggreges self.semestres_tags_aggreges = {} """Les semestres tags associés aux semestres aggrégés""" for frmsem_id in self.semestres_aggreges: try: self.semestres_tags_aggreges[frmsem_id] = semestres_taggues[frmsem_id] except: raise ValueError("Semestres taggués manquants") """Les étudiants (état civil + cursus connu)""" self.etuds = nt.etuds # assert self.etuds == trajectoire.suivi # manque-t-il des étudiants ? self.etudiants = {etud.etudid: etud.etat_civil for etud in self.etuds} """Les tags extraits de tous les semestres""" self.tags_sorted = self.do_taglist() """Construit le cube de notes""" self.notes_cube = self.compute_notes_cube() """Calcul les moyennes par tag sous forme d'un dataframe""" etudids = list(self.etudiants.keys()) self.notes = compute_tag_moy(self.notes_cube, etudids, self.tags_sorted) """Synthétise les moyennes/classements par tag""" self.moyennes_tags = {} for tag in self.tags_sorted: moy_gen_tag = self.notes[tag] class_gen_tag = moy_sem.comp_ranks_series(moy_gen_tag)[1] # en int self.moyennes_tags[tag] = { "notes": moy_gen_tag, "classements": class_gen_tag, "min": moy_gen_tag.min(), "max": moy_gen_tag.max(), "moy": moy_gen_tag.mean(), "nb_inscrits": len(moy_gen_tag), } def get_repr(self, verbose=False) -> str: """Renvoie une représentation textuelle (celle de la trajectoire sur laquelle elle est basée)""" return self.trajectoire.get_repr(verbose=verbose) def compute_notes_cube(self): """Construit le cube de notes (etudid x tags x semestre_aggregé) nécessaire au calcul des moyennes de l'aggrégat """ nb_tags = len(self.tags_sorted) nb_etudiants = len(self.etuds) nb_semestres = len(self.semestres_tags_aggreges) """Index du cube (etudids -> dim 0, tags -> dim 1)""" etudids = [etud.etudid for etud in self.etuds] tags = self.tags_sorted semestres_id = list(self.semestres_tags_aggreges.keys()) dfs = {} for frmsem_id in semestres_id: """Partant d'un dataframe vierge""" df = pd.DataFrame(np.nan, index=etudids, columns=tags) """Charge les notes du semestre tag""" notes = self.semestres_tags_aggreges[frmsem_id].notes """Les étudiants & les tags commun au dataframe final et aux notes du semestre)""" etudids_communs = df.index.intersection(notes.index) tags_communs = df.columns.intersection(notes.columns) """Injecte les notes par tag""" df.loc[etudids_communs, tags_communs] = notes.loc[ etudids_communs, tags_communs ] # Supprime tout ce qui n'est pas numérique for col in df.columns: df[col] = pd.to_numeric(df[col], errors="coerce") """Stocke le df""" dfs[frmsem_id] = df """Réunit les notes sous forme d'un cube etdids x tags x semestres""" semestres_x_etudids_x_tags = [dfs[fid].values for fid in dfs] etudids_x_tags_x_semestres = np.stack(semestres_x_etudids_x_tags, axis=-1) return etudids_x_tags_x_semestres def do_taglist(self): """Synthétise les tags à partir des semestres (taggués) aggrégés Returns: Une liste de tags triés par ordre alphabétique """ tags = [] for frmsem_id in self.semestres_tags_aggreges: tags.extend(self.semestres_tags_aggreges[frmsem_id].tags_sorted) return sorted(set(tags)) def compute_tag_moy(set_cube: np.array, etudids: list, tags: list): """Calcul de la moyenne par tag sur plusieurs semestres. La moyenne est un nombre (note/20), ou NaN si pas de notes disponibles *Remarque* : Adaptation de moy_ue.compute_ue_moys_apc au cas des moyennes de tag par aggrégat de plusieurs semestres. Args: set_cube: notes moyennes aux modules ndarray (etuds x modimpls x UEs), des floats avec des NaN etudids: liste des étudiants (dim. 0 du cube) tags: liste des tags (dim. 1 du cube) Returns: Un DataFrame avec pour columns les moyennes par tags, et pour rows les etudid """ nb_etuds, nb_tags, nb_semestres = set_cube.shape assert nb_etuds == len(etudids) assert nb_tags == len(tags) # Quelles entrées du cube contiennent des notes ? mask = ~np.isnan(set_cube) # Enlève les NaN du cube pour les entrées manquantes set_cube_no_nan = np.nan_to_num(set_cube, nan=0.0) # Les moyennes par tag with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN) etud_moy_tag = np.sum(set_cube_no_nan, axis=2) / np.sum(mask, axis=2) # Le dataFrame etud_moy_tag_df = pd.DataFrame( etud_moy_tag, index=etudids, # les etudids columns=tags, # les tags ) return etud_moy_tag_df