Traitement des coeffs (état intermédiaire)

This commit is contained in:
Cléo Baras 2024-02-26 10:29:45 +01:00
parent 21a794a760
commit 57d616da1a
6 changed files with 187 additions and 74 deletions

View File

@ -46,9 +46,12 @@ import app.pe.pe_comp as pe_comp
from app.pe.moys import pe_tabletags, pe_moytag
class RCSTag(pe_tabletags.TableTag):
class RCSemXTag(pe_tabletags.TableTag):
def __init__(
self, rcsemx: pe_rcsemx.RCSemX, sxstags: dict[(str, int) : pe_sxtag.SxTag]
self,
rcsemx: pe_rcsemx.RCSemX,
sxstags: dict[(str, int) : pe_sxtag.SxTag],
semXs_suivis: dict[int, dict],
):
"""Calcule les moyennes par tag (orientées compétences)
d'un regroupement de SxTag
@ -59,14 +62,19 @@ class RCSTag(pe_tabletags.TableTag):
Args:
rcsemx: Le RCSemX (identifié par un nom et l'id de son semestre terminal)
sxstags: Les données sur les SemX taggués
semXs_suivis: Les données indiquant quels SXTags sont à prendre en compte
pour chaque étudiant
"""
pe_tabletags.TableTag.__init__(self)
self.rcs_id: tuple(str, int) = rcsemx.rcs_id
"""Identifiant du RCSTag (identique au RCSemX sur lequel il s'appuie)"""
"""Identifiant du RCSemXTag (identique au RCSemX sur lequel il s'appuie)"""
self.rcsemx: pe_rcsemx.RCSemX = rcsemx
"""RCSemX associé au RCSTag"""
"""Le regroupement RCSemX associé au RCSemXTag"""
self.semXs_suivis = semXs_suivis
"""Les semXs suivis par les étudiants"""
self.nom = self.get_repr()
"""Représentation textuelle du RSCtag"""
@ -80,20 +88,21 @@ class RCSTag(pe_tabletags.TableTag):
# Affichage pour debug
pe_affichage.pe_print(f"*** {self.get_repr(verbose=True)}")
# Les données aggrégés (RCRCF + SxTags
# Les données aggrégés (RCRCF + SxTags)
self.semXs_aggreges: dict[(str, int) : pe_rcsemx.RCSemX] = rcsemx.semXs_aggreges
"""Les SemX aggrégés"""
self.sxstags = {}
self.sxstags_aggreges = {}
"""Les SxTag associés aux SemX aggrégés"""
try:
for rcf_id in self.semXs_aggreges:
self.sxstags[rcf_id] = sxstags[rcf_id]
self.sxstags_aggreges[rcf_id] = sxstags[rcf_id]
except:
raise ValueError("Semestres SxTag manquants")
self.sxtags_connus = sxstags # Tous les sxstags connus
# Les étudiants (etuds, états civils & etudis)
sems_dans_aggregat = pe_rcs.TYPES_RCS[self.rcs_id[0]]["aggregat"]
sxtag_final = self.sxstags[(sems_dans_aggregat[-1], self.rcs_id[1])]
sems_dans_aggregat = rcsemx.aggregat
sxtag_final = self.sxstags_aggreges[(sems_dans_aggregat[-1], self.rcs_id[1])]
self.etuds = sxtag_final.etuds
"""Les étudiants (extraits du semestre final)"""
self.add_etuds(self.etuds)
@ -123,28 +132,48 @@ class RCSTag(pe_tabletags.TableTag):
"""Synthétise les moyennes/classements par tag (qu'ils soient personnalisé ou de compétences)"""
for tag in self.tags_sorted:
pe_affichage.pe_print(f"--> Moyennes du tag 👜{tag}")
# Traitement des inscriptions aux semX(tags)
# ******************************************
# Cube d'inscription (etudids_sorted x compétences_sorted x sxstags)
# indiquant quel sxtag est valide pour chaque étudiant
inscriptions_df, inscriptions_cube = self.compute_inscriptions_comps_cube(
tag, self.etudids_sorted, self.competences_sorted, self.sxstags_aggreges
)
# Traitement des notes
# ********************
# Cube de notes (etudids_sorted x compétences_sorted x sxstags)
notes_df, notes_cube = self.compute_notes_comps_cube(
tag, self.etudids_sorted, self.competences_sorted, self.sxstags
tag, self.etudids_sorted, self.competences_sorted, self.sxstags_aggreges
)
# Calcule des moyennes/coeffs sous forme d'un dataframe"""
# Calcule les moyennes sous forme d'un dataframe en les "aggrégant"
# compétence par compétence
moys_competences = compute_notes_competences(
notes_cube, self.etudids_sorted, self.competences_sorted
notes_cube,
inscriptions_cube,
self.etudids_sorted,
self.competences_sorted,
)
# Cube de coeffs pour la moyenne générale,
# traduisant les inscriptions des étudiants aux UEs (etudids_sorted x compétences_sorted x sxstags)
# Traitement des coeffs pour la moyenne générale
# ***********************************************
# Df des coeffs sur tous les SxTags aggrégés
coeffs_df, coeffs_cube = self.compute_coeffs_comps_cube(
tag,
self.etudids_sorted,
self.competences_sorted,
self.sxstags,
self.sxstags_aggreges,
)
# Calcule la synthèse des coefficients à prendre en compte pour la moyenne
# générale
# Synthèse des coefficients à prendre en compte pour la moyenne générale
matrice_coeffs_moy_gen = compute_coeffs_competences(
coeffs_cube, notes_cube, self.etudids_sorted, self.competences_sorted
coeffs_cube,
inscriptions_cube,
notes_cube,
self.etudids_sorted,
self.competences_sorted,
)
self.__aff_profil_coeffs(matrice_coeffs_moy_gen)
pe_affichage.aff_profil_coeffs(matrice_coeffs_moy_gen)
# Mémorise les moyennes et les coeff associés
self.moyennes_tags[tag] = pe_moytag.MoyennesTag(
@ -162,9 +191,11 @@ class RCSTag(pe_tabletags.TableTag):
"""Renvoie une représentation textuelle (celle de la trajectoire sur laquelle elle
est basée)"""
if verbose:
return self.rcsemx.get_repr(verbose=verbose)
return f"{self.__class__.__name__} basé sur " + self.rcsemx.get_repr(
verbose=verbose
)
else:
return f"{self.__class__.__name__} ({self.rcs_id})"
return f"{self.__class__.__name__} {self.rcs_id}"
def compute_notes_comps_cube(
self,
@ -287,6 +318,57 @@ class RCSTag(pe_tabletags.TableTag):
return coeffs_dfs, coeffs_etudids_x_comps_x_sxtag
def compute_inscriptions_comps_cube(
self,
tag,
etudids_sorted: list[int],
competences_sorted: list[str],
sxstags: dict[(str, int) : pe_sxtag.SxTag],
):
"""Pour un tag donné, construit
le cube etudid x competences x SxTag traduisant quels sxtags est à prendre
en compte pour chaque étudiant.
Contient des 0 et des 1 pour indiquer la prise en compte.
Args:
tag: Le tag visé
etudids_sorted: Les etudis triés
competences_sorted: Les compétences triées
sxstags: Les SxTag à réunir
"""
# Initialisation
inscriptions_dfs = {}
for sxtag_id, sxtag in sxstags.items():
# Partant d'un dataframe vierge
inscription_df = pd.DataFrame(
0, index=etudids_sorted, columns=competences_sorted
)
# Stocke les dfs
inscriptions_dfs[sxtag_id] = inscription_df
for etudid in etudids_sorted:
for sem in self.rcsemx.aggregat:
if etudid in self.semXs_suivis:
semx_suivi = self.semXs_suivis[etudid][sem]
if semx_suivi:
semx_suivi_id = semx_suivi.rcs_id
if semx_suivi_id not in self.sxtags_connus:
pe_affichage.pe_print(
f"Un SxTag est manquant : {semx_suivi_id}"
)
if semx_suivi_id in inscriptions_dfs:
# Si le sxtag est l'un des siens
inscriptions_dfs[semx_suivi_id].loc[etudid, :] = 1
"""Réunit les inscriptions sous forme d'un cube etudids x competences x semestres"""
sxtag_x_etudids_x_comps = [inscriptions_dfs[sxtag_id] for sxtag_id in sxstags]
inscriptions_etudids_x_comps_x_sxtag = np.stack(
sxtag_x_etudids_x_comps, axis=-1
)
return inscriptions_dfs, inscriptions_etudids_x_comps_x_sxtag
def _do_taglist(self) -> list[str]:
"""Synthétise les tags à partir des Sxtags aggrégés.
@ -294,8 +376,8 @@ class RCSTag(pe_tabletags.TableTag):
Liste de tags triés par ordre alphabétique
"""
tags = []
for frmsem_id in self.sxstags:
tags.extend(self.sxstags[frmsem_id].tags_sorted)
for frmsem_id in self.sxstags_aggreges:
tags.extend(self.sxstags_aggreges[frmsem_id].tags_sorted)
return sorted(set(tags))
def _do_acronymes_to_competences(self) -> dict[str:str]:
@ -307,7 +389,7 @@ class RCSTag(pe_tabletags.TableTag):
Un dictionnaire {'acronyme_ue' : 'compétences'}
"""
dict_competences = {}
for sxtag_id, sxtag in self.sxstags.items():
for sxtag_id, sxtag in self.sxstags_aggreges.items():
dict_competences |= sxtag.acronymes_ues_to_competences
return dict_competences
@ -324,60 +406,43 @@ class RCSTag(pe_tabletags.TableTag):
pe_affichage.pe_print(f"--> Compétences :")
pe_affichage.pe_print("\n".join(aff_comp))
def __aff_profil_coeffs(self, matrice_coeffs_moy_gen):
"""Extrait de la matrice des coeffs, les différents types d'inscription
et de coefficients (appelés profil) des étudiants et les affiche
(pour debug)
"""
# Les profils des coeffs d'UE (pour debug)
profils = []
for i in matrice_coeffs_moy_gen.index:
val = matrice_coeffs_moy_gen.loc[i].fillna("-")
val = " | ".join([str(v) for v in val])
if val not in profils:
profils += [val]
# L'affichage
if len(profils) > 1:
profils_aff = "\n" + "\n".join([" " * 10 + prof for prof in profils])
else:
profils_aff = "\n".join(profils)
pe_affichage.pe_print(
f" > Moyenne calculée avec pour coeffs (de compétences) : {profils_aff}"
)
def compute_coeffs_competences(
coeff_cube: np.array,
inscriptions: np.array,
set_cube: np.array,
etudids_sorted: list,
competences_sorted: list,
):
"""Calcule les coeffs à utiliser pour la moyenne générale (toutes compétences
confondues), en fonction des notes (set_cube) aggrégées.
confondues), en fonction des inscriptions.
Args:
coeffs_cube: coeffs impliqués dans la moyenne générale (semestres par semestres)
set_cube: notes moyennes aux modules ndarray
(etuds x UEs|compétences x sxtags), des floats avec des NaN
inscriptions: inscriptions aux UES|Compétences ndarray
(etuds x UEs|compétences x sxtags), des 0 ou des 1
set_cube: les notes
etudids_sorted: liste des étudiants (dim. 0 du cube)
competences_sorted: list
competences_sorted: list (dim. 1 du cube)
Returns:
Un DataFrame de coefficients (etudids_sorted x compétences_sorted)
"""
nb_etuds, nb_comps, nb_semestres = set_cube.shape
nb_etuds, nb_comps, nb_semestres = inscriptions.shape
assert nb_etuds == len(etudids_sorted)
assert nb_comps == len(competences_sorted)
# Applique le masque des inscriptions aux coeffs et aux notes
coeffs_significatifs = coeff_cube * inscriptions
set_cube_significatif = set_cube * inscriptions
# Quelles entrées du cube contiennent des notes ?
mask = ~np.isnan(set_cube)
mask = ~np.isnan(set_cube_significatif)
# Enlève les NaN du cube de notes pour les entrées manquantes
coeffs_cube_no_nan = np.nan_to_num(coeff_cube, nan=0.0)
coeffs_cube_no_nan = np.nan_to_num(coeffs_significatifs, nan=0.0)
# Retire les coefficients associées à des données sans notes
# Retire les coefficients associés à des données sans notes
coeffs_cube_no_nan = coeffs_cube_no_nan * mask
# Somme les coefficients (correspondant à des notes)
@ -395,6 +460,7 @@ def compute_coeffs_competences(
def compute_notes_competences(
set_cube: np.array,
inscriptions: np.array,
etudids_sorted: list,
competences_sorted: list,
):
@ -406,11 +472,12 @@ def compute_notes_competences(
par aggrégat de plusieurs semestres.
Args:
set_cube: notes moyennes aux modules ndarray
set_cube: notes moyennes aux compétences ndarray
(etuds x UEs|compétences x sxtags), des floats avec des NaN
inscriptions: inscrptions aux compétences ndarray
(etuds x UEs|compétences x sxtags), des 0 et des 1
etudids_sorted: liste des étudiants (dim. 0 du cube)
competences_sorted: list
tags: liste des tags (dim. 1 du cube)
competences_sorted: list (dim. 1 du cube)
Returns:
Un DataFrame avec pour columns les moyennes par tags,
et pour rows les etudid
@ -419,11 +486,14 @@ def compute_notes_competences(
assert nb_etuds == len(etudids_sorted)
assert nb_comps == len(competences_sorted)
# Applique le masque d'inscriptions
set_cube_significatif = set_cube * inscriptions
# Quelles entrées du cube contiennent des notes ?
mask = ~np.isnan(set_cube)
mask = ~np.isnan(set_cube_significatif)
# 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)
set_cube_no_nan = np.nan_to_num(set_cube_significatif, nan=0.0)
# Les moyennes par tag
with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN)

View File

@ -9,7 +9,7 @@
from flask import g
from app import log
PE_DEBUG = False
PE_DEBUG = True
# On stocke les logs PE dans g.scodoc_pe_log
@ -41,3 +41,41 @@ def pe_get_log() -> str:
# Affichage dans le tableur pe en cas d'absence de notes
SANS_NOTE = "-"
def aff_profil_coeffs(matrice_coeffs_moy_gen, with_index=False):
"""Affiche les différents types de coefficients (appelés profil)
d'une matrice_coeffs_moy_gen (pour debug)
"""
# Les profils des coeffs d'UE (pour debug)
profils = []
index_a_profils = {}
for i in matrice_coeffs_moy_gen.index:
val = matrice_coeffs_moy_gen.loc[i].fillna("-")
val = " | ".join([str(v) for v in val])
if val not in profils:
profils += [val]
index_a_profils[val] = [str(i)]
else:
index_a_profils[val] += [str(i)]
# L'affichage
if len(profils) > 1:
if with_index:
elmts = [
" " * 10
+ prof
+ " (par ex. "
+ ", ".join(index_a_profils[prof][:10])
+ ")"
for prof in profils
]
else:
elmts = [" " * 10 + prof for prof in profils]
profils_aff = "\n" + "\n".join(elmts)
else:
profils_aff = "\n".join(profils)
pe_print(
f" > Moyenne calculée avec pour coeffs (de compétences) : {profils_aff}"
)

View File

@ -306,9 +306,11 @@ class JuryPE(object):
)
pe_affichage.pe_print("1) Calcul des moyennes des RCSTag")
self.rcss_tags = {}
self.rcsstags = {}
for rcs_id, rcsemx in self.rcss_jury.rcsemxs.items():
self.rcss_tags[rcs_id] = pe_rcstag.RCSTag(rcsemx, self.sxtags)
self.rcsstags[rcs_id] = pe_rcstag.RCSemXTag(
rcsemx, self.sxtags, self.rcss_jury.semXs_suivis
)
# Intègre le bilan des trajectoires tagguées au zip final
pe_affichage.pe_print("2) Bilan")
@ -317,7 +319,7 @@ class JuryPE(object):
output, engine="openpyxl"
) as writer:
onglets = []
for rcs_tag in self.rcss_tags.values():
for rcs_tag in self.rcsstags.values():
onglet = rcs_tag.get_repr(verbose=False)
if rcs_tag.is_significatif():
df = rcs_tag.to_df()
@ -374,7 +376,7 @@ class JuryPE(object):
pe_moytag.CODE_MOY_COMPETENCES,
etudiants_diplomes,
self.rcss_jury.rcsemxs,
self.rcss_tags,
self.rcsstags,
self.rcss_jury.rcsemxs_suivis,
)
self.interclasstags[pe_moytag.CODE_MOY_COMPETENCES][nom_rcs] = interclass

View File

@ -87,18 +87,21 @@ class RCS:
tous se terminant par un (form)semestre final.
"""
def __init__(self, nom_rcs: str, semestre_final: FormSemestre):
self.nom: str = nom_rcs
def __init__(self, nom: str, semestre_final: FormSemestre):
self.nom: str = nom
"""Nom du RCS"""
assert self.nom in TOUS_LES_RCS, "Le nom d'un RCS doit être un aggrégat"
self.aggregat: list[str] = TYPES_RCS[nom]["aggregat"]
"""Aggrégat (liste des nom des semestres aggrégés)"""
self.formsemestre_final: FormSemestre = semestre_final
"""(Form)Semestre final du RCS"""
self.rang_final = self.formsemestre_final.semestre_id
"""Rang du formsemestre final"""
self.rcs_id: (str, int) = (nom_rcs, semestre_final.formsemestre_id)
self.rcs_id: (str, int) = (nom, semestre_final.formsemestre_id)
"""Identifiant du RCS sous forme (nom_rcs, id du semestre_terminal)"""
self.fid_final: int = self.formsemestre_final.formsemestre_id

View File

@ -26,12 +26,12 @@ class RCSemX(pe_rcs.RCS):
incluant des infos sur les redoublements).
Args:
nom_rcs: Un nom du RCS (par ex: '5S')
nom: Un nom du RCS (par ex: '5S')
semestre_final: Le semestre final du RCS
"""
def __init__(self, nom_rcs: str, semestre_final: FormSemestre):
pe_rcs.RCS.__init__(self, nom_rcs, semestre_final)
def __init__(self, nom: str, semestre_final: FormSemestre):
pe_rcs.RCS.__init__(self, nom, semestre_final)
self.semXs_aggreges: dict[(str, int) : pe_sxtag.SxTag] = {}
"""Les semX à aggréger"""

View File

@ -25,12 +25,12 @@ class Trajectoire(pe_rcs.RCS):
* des S1+S2+(année de césure)+S3 si césure, ...
Args:
nom_rcs: Un nom du RCS (par ex: '5S')
nom: Un nom du RCS (par ex: '5S')
semestre_final: Le formsemestre final du RCS
"""
def __init__(self, nom_rcs: str, semestre_final: FormSemestre):
pe_rcs.RCS.__init__(self, nom_rcs, semestre_final)
def __init__(self, nom: str, semestre_final: FormSemestre):
pe_rcs.RCS.__init__(self, nom, semestre_final)
self.semestres_aggreges: dict[int:FormSemestre] = {}
"""Formsemestres regroupés dans le RCS"""