forked from ScoDoc/ScoDoc
214 lines
7.7 KiB
Python
214 lines
7.7 KiB
Python
# -*- 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
|
|
]
|
|
|
|
"""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
|