ScoDoc/app/pe/pe_rcstag.py

271 lines
10 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.res_sem import load_formsemestre_results
from app.pe import pe_affichage
from app.pe.pe_ressemtag import ResSemBUTTag
import pandas as pd
import numpy as np
from app.pe.pe_rcs import RCS, RCRCF
from app.pe.pe_sxtag import SxTag
from app.pe.pe_tabletags import TableTag
from app.pe.pe_moytag import MoyennesTag
class RCSTag(TableTag):
def __init__(self, rcrcf: RCS, sxstags: dict[(str, int): SxTag]):
"""Calcule les moyennes par tag (orientées compétences)
d'un regroupement de SxTag
(RCRCF), 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 même semestre terminal.
Args:
rcs: Un RCS (identifié par un nom et l'id de son semestre terminal)
sxstags: Les données sur les RCF taggués
"""
TableTag.__init__(self)
self.rcs_id: tuple(str, int) = rcrcf.rcs_id
"""Identifiant du RCS taggué (identique au RCS sur lequel il s'appuie)"""
self.rcrcf: RCRCF = rcrcf
"""RCRCF associé au RCS taggué"""
self.nom = self.get_repr()
"""Représentation textuelle du RCS taggué"""
self.formsemestre_terminal = rcrcf.formsemestre_final
"""Le formsemestre terminal"""
# Les résultats du formsemestre terminal
nt = load_formsemestre_results(self.formsemestre_terminal)
self.rcfs_aggreges = rcrcf.rcfs_aggreges
"""Les RCFs aggrégés"""
self.sxstags = {}
"""Les SxTag associés aux RCF aggrégés"""
try:
for rcf_id in self.rcfs_aggreges:
self.sxstags[rcf_id] = sxstags[rcf_id]
except:
raise ValueError("Semestres SxTag manquants")
# Les étudiants (etuds, états civils & etudis)
self.etuds = nt.etuds
self.add_etuds(nt.etuds)
# Les compétences (extraites de tous les Sxtags)
self.competences_sorted = self.do_complist()
"""Compétences extraites de tous les SxTag aggrégés"""
# Les tags
self.tags_sorted = self.do_taglist()
"""Tags extraits de tous les SxTag aggrégés"""
# Les moyennes
self.moyennes_tags: dict[str, MoyennesTag] = {}
"""Synthétise les moyennes/classements par tag (qu'ils soient personnalisé ou de compétences)"""
for tag in self.tags_sorted:
# Cube de note
notes_cube, coeffs_cube = self.compute_notes_comps_cube(tag)
# Calcule des moyennes/coeffs sous forme d'un dataframe"""
moys_competences, coeffs_competences = compute_notes_competences(
notes_cube, coeffs_cube, self.etudids, self.competences_sorted
)
# Les moyennes
self.moyennes_tags[tag] = MoyennesTag(tag, moys_competences,
coeffs_competences)
def __eq__(self, other):
"""Egalité de 2 RCS taggués sur la base de leur identifiant"""
return self.rcs_id == other.sxtag_id
def get_repr(self, verbose=False) -> str:
"""Renvoie une représentation textuelle (celle de la trajectoire sur laquelle elle
est basée)"""
return self.rcrcf.get_repr(verbose=verbose)
def compute_notes_comps_cube(self, tag):
"""Pour un tag donné, construit :
* le cube de notes (etudid x competences x SxTag) nécessaire au calcul des moyennes,
en remplaçant les données d'UE (obtenus du SxTag) par les compétences
* le cube de coeffs (etudid x competences x SxTag) (traduisant les inscriptions)
appliqué au calcul des différents SxTag
"""
# 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]
competences_sorted = self.competences_sorted
sxstags_ids = list(self.sxstags.keys())
notes_dfs = {}
coeffs_dfs = {}
for sxtag_id, sxtag in sxstags_ids.item():
# Partant d'un dataframe vierge
notes_df = pd.DataFrame(np.nan, index=etudids, columns=competences_sorted)
coeffs_df = pd.DataFrame(np.nan, index=etudids, columns=competences_sorted)
moys_tag = sxtag.moyennes_tags[tag]
# Charge les notes et les coeffs du semestre tag
notes = moys_tag.matrice_notes.copy() # avec une copie
coeffs = moys_tag.matrice_coeffs.copy() # les coeffs
# Traduction des UE en compétences
association_ues_comp = moys_tag.competences
ues_columns_df = notes.columns
comp_associes_aux_ues = [association_ues_comp[ue] for ue in ues_columns_df]
notes.columns = comp_associes_aux_ues
coeffs.columns = comp_associes_aux_ues
# Compétences communes
comp_communes = list(set(competences_sorted) & set(comp_associes_aux_ues))
# Etudiants communs
etudids_communs = notes_df.index.intersection(notes.index)
# Recopie des notes et des coeffs
notes_df.loc[etudids_communs, comp_communes] = notes.loc[
etudids_communs, comp_communes
]
coeffs_df.loc[etudids_communs, comp_communes] = coeffs.loc[
etudids_communs, comp_communes
]
# Supprime tout ce qui n'est pas numérique
for col in notes_df.columns:
notes_df[col] = pd.to_numeric(notes_df[col], errors="coerce")
# Stocke les dfs
notes_dfs[sxtag_id] = notes_df
coeffs_dfs[sxtag_id] = coeffs_df
"""Réunit les notes sous forme d'un cube etudids x competences x semestres"""
sxtag_x_etudids_x_comps = [notes_dfs[fid].values for fid in notes_dfs]
notes_etudids_x_comps_x_sxtag = np.stack(sxtag_x_etudids_x_comps, axis=-1)
"""Réunit les coeffs sous forme d'un cube etudids x competences x semestres"""
sxtag_x_etudids_x_comps = [coeffs_dfs[fid].values for fid in notes_dfs]
coeffs_etudids_x_comps_x_sxtag = np.stack(sxtag_x_etudids_x_comps, axis=-1)
return notes_etudids_x_comps_x_sxtag, coeffs_etudids_x_comps_x_sxtag
def do_taglist(self):
"""Synthétise les tags à partir des Sxtags aggrégés
Returns:
Une liste de tags triés par ordre alphabétique
"""
tags = []
for frmsem_id in self.sxstags:
tags.extend(self.sxstags[frmsem_id].tags_sorted)
pe_affichage.pe_print(f"* Tags : {', '.join(tags)}")
return sorted(set(tags))
def do_complist(self):
"""Synthétise les compétences à partir des Sxtags aggrégés"""
competences = []
for sxtag_id, sxtag in self.sxstags:
comp = sxtag.moyennes_tags["but"].competences
competences.extend(comp)
return sorted(set(competences))
def compute_notes_competences(
set_cube: np.array, coeff_cube: np.array, etudids: list, competences: list
):
"""Calcule:
* la moyenne par compétences à un tag donné sur plusieurs semestres (partant du set_cube).
* la somme des coeffs à utiliser pour la moyenne générale.
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 UEs|compétences x sxtags), des floats avec des NaN
coeffs_cube: somme des coeffs impliqués dans la moyennes
etudids: liste des étudiants (dim. 0 du cube)
competences: list
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_comps, nb_semestres = set_cube.shape
assert nb_etuds == len(etudids)
assert nb_comps == len(competences)
# Quelles entrées du cube contiennent des notes ?
mask = ~np.isnan(set_cube)
# Enlève les NaN du cube de notes pour les entrées manquantes
set_cube_no_nan = np.nan_to_num(set_cube, nan=0.0)
coeffs_cube_no_nan = no.nan_to_num(coeff_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)
# La somme des coeffs
coeff_tag = np.sum(coeffs_cube_no_nan, axis=2)
# Le dataFrame des notes moyennes
etud_moy_tag_df = pd.DataFrame(
etud_moy_tag,
index=etudids, # les etudids
columns=competences, # les competences
)
etud_moy_tag_df.fillna(np.nan)
coeffs_df = pd.DataFrame(coeff_tag, index=etudids, columns=competences)
coeffs_df.fillna(np.nan)
return etud_moy_tag_df, coeffs_df