Update opolka/ScoDoc from ScoDoc/ScoDoc #2

Merged
opolka merged 1272 commits from ScoDoc/ScoDoc:master into master 2024-05-27 09:11:04 +02:00
6 changed files with 224 additions and 96 deletions
Showing only changes of commit b8cb592ac9 - Show all commits

View File

@ -50,6 +50,7 @@ from zipfile import ZipFile
import numpy as np
import pandas as pd
from app.pe import pe_semtag
from app.pe.pe_affichage import NOM_STAT_PROMO, SANS_NOTE, NOM_STAT_GROUPE
import app.pe.pe_affichage as pe_affichage
from app.pe.pe_etudiant import * # TODO A éviter -> pe_etudiant.
@ -96,7 +97,9 @@ class JuryPE(object):
pe_affichage.pe_print("*** Aucun étudiant diplômé")
else:
self._gen_xls_diplomes(zipfile)
self._gen_rcss()
self._gen_xls_resultats_semestres_taggues(zipfile)
self._gen_xls_semestres_taggues(zipfile)
# self._gen_xls_rcss_tags(zipfile)
# self._gen_xls_interclassements_rcss(zipfile)
# self._gen_xls_synthese_jury_par_tag(zipfile)
@ -142,12 +145,53 @@ class JuryPE(object):
output, engine="openpyxl"
) as writer:
for res_sem_tag in self.res_sems_tags.values():
onglet = res_sem_tag.get_repr()
onglet = res_sem_tag.get_repr(verbose=False)
df = res_sem_tag.df_moyennes_et_classements()
# écriture dans l'onglet
df.to_excel(writer, onglet, index=True, header=True)
output.seek(0)
self.add_file_to_zip(
zipfile,
f"resultats_semestres_taggues_{self.diplome}.xlsx",
output.read(),
path="details",
)
def _gen_rcss(self):
"""Génère les RCS (attribut `rcss_jury`), combinaisons de semestres suivis par les étudiants au sens
d'un nom de RCS (par ex: '3S').
"""
pe_affichage.pe_print(
"*** Génère les RCS (différentes combinaisons de semestres) des étudiants"
)
self.rcss_jury = pe_rcs.RCSsJuryPE(self.diplome)
self.rcss_jury.cree_rcss(self.etudiants)
def _gen_xls_semestres_taggues(self, zipfile: ZipFile):
"""Génère les semestres taggués en s'appuyant sur les RCS de type Sx (pour
identifier les redoublements impactant les semestres taggués).
"""
# Génère les moyennes des RCS de type Sx
pe_affichage.pe_print("*** Calcule les moyennes de semestres = RCS de type Sx")
self.sems_tags = {}
for rcs_id, rcs in self.rcss_jury.rcss.items():
if rcs.nom.startswith("S"):
self.sems_tags[rcs_id] = pe_semtag.SemTag(rcs, self.res_sems_tags)
# Intègre le bilan des semestres taggués au zip final
output = io.BytesIO()
with pd.ExcelWriter( # pylint: disable=abstract-class-instantiated
output, engine="openpyxl"
) as writer:
for sem_tag in self.sems_tags.values():
onglet = sem_tag.get_repr(verbose=False)
df = sem_tag.df_moyennes_et_classements()
# écriture dans l'onglet
df.to_excel(writer, onglet, index=True, header=True)
output.seek(0)
self.add_file_to_zip(
zipfile,
f"semestres_taggues_{self.diplome}.xlsx",
@ -156,11 +200,7 @@ class JuryPE(object):
)
def _gen_xls_rcss_tags(self, zipfile: ZipFile):
"""Génère :
* les RCS (combinaisons de semestres suivis par les étudiants au sens
d'un aggrégat (par ex: '3S'))
* les RCS tagguées des RCS, en calculant les moyennes et les classements par tag
"""Génère les RCS tagguées des RCS, en calculant les moyennes et les classements par tag
pour chacune.
Stocke le résultat dans self.rccs_tag, un dictionnaire de
@ -183,14 +223,9 @@ class JuryPE(object):
"""
pe_affichage.pe_print(
"*** Génère les trajectoires (différentes combinaisons de semestres) des étudiants"
)
self.rcss_jury = pe_rcs.RCSsJuryPE(self.diplome)
self.rcss_jury.cree_rcss(self.etudiants)
# Génère les moyennes par tags des trajectoires
pe_affichage.pe_print("*** Calcule les moyennes par tag des RCS possibles")
# Génère les moyennes des RCS de type Sx
pe_affichage.pe_print("*** Calcule les moyennes des RCS de type Sx")
self.rcss_tags = {}
for rcs_id, rcs in self.rcss_jury.rcss.items():
@ -597,9 +632,6 @@ def compute_resultats_semestres_tag(etudiants: EtudiantsJuryPE) -> dict:
return semestres_tags
def compute_interclassements(
etudiants: EtudiantsJuryPE,
trajectoires_jury_pe: pe_rcs.RCSsJuryPE,

View File

@ -1,9 +1,11 @@
import numpy as np
import pandas as pd
from app import comp
from app.comp.moy_sem import comp_ranks_series
from app.models import UniteEns
from app.pe import pe_affichage
from app.scodoc.codes_cursus import UE_SPORT
class Moyenne:
@ -157,8 +159,9 @@ class MoyennesTag:
def __init__(
self,
tag: str,
ues: list[UniteEns],
ues: dict[int, UniteEns],
notes_ues: pd.DataFrame,
ues_inscr_parcours_df: pd.DataFrame
# notes_gen: pd.Series,
):
"""Classe centralisant la synthèse des moyennes/classements d'une série
@ -169,21 +172,39 @@ class MoyennesTag:
tag: Un tag
ues: La liste des UEs ayant servie au calcul de la moyenne
notes_ues: Les moyennes (etudid x acronymes_ues) aux différentes UEs et pour le tag
ues_inscr_parcours_df: Les inscriptions des etudid au UE
# notes_gen: Une série de notes (moyenne) sous forme d'un pd.Series() (toutes UEs confondues)
"""
self.tag = tag
"""Le tag associé aux moyennes"""
# Les UE
self.ues: dict[int, UniteEns] = {ue.id: ue for ue in ues}
self.ues: dict[int, UniteEns] = ues
"""Les UEs sur lesquelles sont calculées les moyennes"""
colonnes = list(notes_ues.columns)
acronymes = [self.ues[ue_id].acronyme for ue_id in colonnes]
assert len(set(acronymes)) == len(colonnes), \
"Deux UEs ne peuvent pas avoir le même acronyme"
acronymes: list[str] = [self.ues[ue_id].acronyme for ue_id in self.ues]
assert len(set(acronymes)) == len(
colonnes
), "Deux UEs ne peuvent pas avoir le même acronyme"
# Les inscriptions des etudids aux UEs
self.ues_inscr_parcours_df: pd.DataFrame = ues_inscr_parcours_df
"""Les inscriptions des etudids au UE en fonction de leur parcours"""
# Les coefficients à appliquer aux UEs pour la moyenne générale = ECTS
self.ects = self.ues_inscr_parcours_df.fillna(0.0) * [
ue.ects
for ue in self.ues.values() # if ue.type != UE_SPORT <= déjà supprimé
]
# Les profils d'ects (pour debug)
profils_ects = []
for val in list(self.ects.values):
if tuple(val) not in profils_ects:
profils_ects.append(tuple(val))
# Les moyennes par UE
self.notes_ues = notes_ues
self.notes_ues: pd.DataFrame = notes_ues
"""Les notes aux UEs (dataframe)"""
self.notes_ues.columns = acronymes # remplace les ue.id par leur acronyme
self.moys_ues: dict[int, pd.DataFrame] = {}
@ -193,11 +214,42 @@ class MoyennesTag:
self.moys_ues[ue.acronyme] = Moyenne(notes)
# Les moyennes générales
notes_gen = self.compute_moy_gen(self.notes_ues, self.ects)
self.notes_gen = notes_gen
"""Les notes générales (moyenne toutes UEs confonudes)"""
self.moy_gen = Moyenne(notes_gen)
"""Le dataframe retraçant les moyennes/classements/statistiques général"""
pe_affichage.pe_print(f"> MoyTag pour {tag} avec")
pe_affichage.pe_print(f" - ues={acronymes}")
pe_affichage.pe_print(f" - ects={profils_ects}")
def __eq__(self, other):
"""Egalité de deux MoyenneTag lorsque leur tag sont identiques"""
return self.tag == other.tag
def compute_moy_gen(
self, moy_ues: pd.DataFrame, coeff_ues: pd.DataFrame
) -> pd.Series:
"""Calcule la moyenne générale (toutes UE confondus)
pour le tag considéré, en pondérant les notes obtenues au UE
par les crédits ECTS.
Args:
moy_ues: Les moyennes etudids x acronymes_ues
coeff_ues: Les coeff etudids x ueids
"""
# Calcule la moyenne générale dans le semestre (pondérée par le ECTS)
try:
moy_gen_tag = comp.moy_sem.compute_sem_moys_apc_using_ects(
moy_ues,
coeff_ues,
# formation_id=self.formsemestre.formation_id,
skip_empty_ues=True,
)
except TypeError as e:
raise TypeError("Pb dans le calcul de la moyenne toutes UEs confondues")
return moy_gen_tag

View File

@ -94,7 +94,7 @@ class RCSTag(TableTag):
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}
self.etats_civils = {etud.etudid: etud.nomprenom for etud in self.etuds}
self.tags_sorted = self.do_taglist()
"""Tags extraits de tous les semestres"""
@ -102,7 +102,7 @@ class RCSTag(TableTag):
self.notes_cube = self.compute_notes_cube()
"""Cube de notes"""
etudids = list(self.etudiants.keys())
etudids = list(self.etats_civils.keys())
self.notes = compute_tag_moy(self.notes_cube, etudids, self.tags_sorted)
"""Calcul les moyennes par tag sous forme d'un dataframe"""

View File

@ -70,19 +70,15 @@ class ResSemTag(TableTag):
self.formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
# Le nom du res_semestre taggué
self.nom = self.get_repr(mode="long")
self.nom = self.get_repr(verbose=True)
pe_affichage.pe_print(
f"--> Résultats de Semestre taggués {self.nom}"
)
pe_affichage.pe_print(f"--> Résultats de semestre taggués {self.nom}")
# Les résultats du semestre
self.nt = load_formsemestre_results(self.formsemestre)
# Les étudiants
self.etuds = self.nt.etuds
self.etudiants = {etud.etudid: etud.etat_civil for etud in self.etuds}
self.etudids = list(self.etudiants.keys())
# Les étudiants (etuds, états civils & etudis)
self.add_etuds(self.nt.etuds)
# Les notes, les modules implémentés triés, les étudiants, les coeffs,
# récupérés notamment de py:mod:`res_but`
@ -113,8 +109,13 @@ class ResSemTag(TableTag):
moy_ues_tag = self.compute_moy_ues_tag(infos_tag)
# moy_gen_tag = self.compute_moy_gen_tag(moy_ues_tag)
ues_dict = {ue.id: ue for ue in ues_hors_sport}
self.moyennes_tags[tag] = MoyennesTag(
tag, ues_hors_sport, moy_ues_tag # moy_gen_tag
tag,
ues_dict,
moy_ues_tag,
self.ues_inscr_parcours_df
# moy_gen_tag
)
# Ajoute les d'UE moyennes générales de BUT pour le semestre considéré
@ -128,8 +129,10 @@ class ResSemTag(TableTag):
)
# moy_ues = self.nt.etud_moy_ue[ue_id]
# moy_gen_but = self.nt.etud_moy_gen
ues_dict = {ue.id: ue for ue in ues_hors_sport}
self.moyennes_tags["but"] = MoyennesTag(
"but", ues_hors_sport, df_ues #, moy_gen_but
"but", ues_dict, df_ues, self.ues_inscr_parcours_df # , moy_gen_but
)
self.tags_sorted = self.get_all_tags()
@ -144,11 +147,11 @@ class ResSemTag(TableTag):
# f" => Traitement des tags {', '.join(self.tags_sorted)}"
# )
def get_repr(self, mode="long"):
def get_repr(self, verbose=False):
"""Nom affiché pour le semestre taggué"""
if mode == "short":
if verbose:
return f"{self.formsemestre} ({self.formsemestre_id})"
else: # mode == "long"
else:
return pe_etudiant.nom_semestre_etape(self.formsemestre, avec_fid=True)
def compute_moy_ues_tag(self, info_tag: dict[int, dict]) -> pd.DataFrame:
@ -192,25 +195,6 @@ class ResSemTag(TableTag):
)
return moyennes_ues_tag
def compute_moy_gen_tag(self, moy_ues_tag: pd.DataFrame) -> pd.Series:
"""Calcule la moyenne générale (toutes UE confondus)
pour le tag considéré, en les pondérant par les crédits ECTS.
"""
# Les ects
ects = self.ues_inscr_parcours_df.fillna(0.0) * [
ue.ects for ue in self.ues if ue.type != UE_SPORT
]
# Calcule la moyenne générale dans le semestre (pondérée par le ECTS)
moy_gen_tag = comp.moy_sem.compute_sem_moys_apc_using_ects(
moy_ues_tag,
ects,
formation_id=self.formsemestre.formation_id,
skip_empty_ues=True,
)
return moy_gen_tag
def _get_tags_dict(self):
"""Renvoie les tags personnalisés (déduits des modules du semestre)
et les tags automatiques ('but'), et toutes leurs informations,
@ -272,8 +256,6 @@ class ResSemTag(TableTag):
raise ScoValueError(message)
def get_moduleimpl(modimpl_id) -> dict:
"""Renvoie l'objet modimpl dont l'id est modimpl_id"""
modimpl = db.session.get(ModuleImpl, modimpl_id)

View File

@ -49,7 +49,7 @@ from app.pe.pe_moytag import MoyennesTag
class SemTag(TableTag):
def __init__(self, rcs: RCS, semestres_taggues: dict[int, ResSemTag]):
def __init__(self, rcs: RCS, res_sems_tags: dict[int, ResSemTag]):
"""Calcule les moyennes/classements par tag à un RCS d'un seul semestre
(ici semestre) de type 'Sx' (par ex. 'S1', 'S2', ...) :
@ -62,7 +62,7 @@ class SemTag(TableTag):
Args:
rcs: Un RCS (identifié par un nom et l'id de son semestre terminal)
semestres_taggues: Les données sur les semestres taggués
res_sems_tags: Les données sur les résultats des semestres taggués
"""
TableTag.__init__(self)
@ -88,27 +88,20 @@ class SemTag(TableTag):
self.semestres_aggreges = rcs.semestres_aggreges
"""Les semestres aggrégés"""
self.semestres_tags_aggreges = {}
"""Les semestres tags associés aux semestres aggrégés"""
self.res_sems_tags = {}
"""Les résultats des semestres taggués (limités aux semestres aggrégés)"""
try:
for frmsem_id in self.semestres_aggreges:
self.semestres_tags_aggreges[frmsem_id] = semestres_taggues[frmsem_id]
self.res_sems_tags[frmsem_id] = res_sems_tags[frmsem_id]
except:
raise ValueError("Semestres taggués manquants")
raise ValueError("Résultats des semestres taggués manquants")
# Les données des étudiants
self.etuds = nt.etuds
"""Les étudiants"""
self.etudids = [etud.etudid for etud in self.etuds]
"""Les etudids"""
self.etats_civils = {
etudid: self.etuds[etudid].etat_civil for etudid in self.etudids
}
"""Les états civils"""
# Les étudiants (etuds, états civils & etudis)
self.add_etuds(nt.etuds)
# Les tags
self.tags_sorted = self.comp_tags_list()
"""Tags extraits du semestre terminal de l'aggrégat"""
"""Tags (extraits uniquement du semestre terminal de l'aggrégat)"""
# Les UEs
self.ues = self.comp_ues(tag="but")
@ -116,20 +109,33 @@ class SemTag(TableTag):
"""UEs extraites du semestre terminal de l'aggrégat (avec
check de concordance sur les UE des semestres_aggrégés)"""
# Les inscriptions aux UEs
self.ues_inscr_parcours_df = self.comp_ues_inscr_parcours(tag="but")
"""Les inscriptions aux UEs (extraites uniquement du semestre terminal)"""
self.moyennes_tags: dict[str, MoyennesTag] = {}
"""Moyennes/classements par tag (qu'ils soient personnalisés ou automatiques)"""
self.notes: dict[str, pd.DataFrame] = {}
"""Les notes aux différents tags"""
self.moyennes_tags: dict[str, pd.DataFrame] = {}
"""Les notes aux UEs dans différents tags"""
# Masque des inscriptions
inscr_mask = self.ues_inscr_parcours_df.to_numpy()
for tag in self.tags_sorted:
# Cube de note
notes_cube = self.compute_notes_ues_cube(tag, self.acronymes_ues)
notes_cube = self.compute_notes_ues_cube(tag, self.acronymes_ues_sorted)
# Calcule des moyennes sous forme d'un dataframe"""
self.notes[tag] = compute_notes_ues(notes_cube, self.etudids, self.acronymes_ues)
moys_ues = compute_notes_ues(
notes_cube,
self.etudids,
self.acronymes_ues_sorted,
inscr_mask,
)
# Les moyennes
self.moyennes_tags[tag] = MoyennesTag(tag, self.notes[tag])
self.moyennes_tags[tag] = MoyennesTag(tag,
self.ues,
moys_ues,
self.ues_inscr_parcours_df)
def __eq__(self, other):
"""Egalité de 2 RCS taggués sur la base de leur identifiant"""
@ -147,7 +153,7 @@ class SemTag(TableTag):
# Index du cube (etudids -> dim 0, ues -> dim 1, semestres -> dim2)
etudids = [etud.etudid for etud in self.etuds]
# acronymes_ues = sorted([ue.acronyme for ue in self.ues.values()])
semestres_id = list(self.semestres_tags_aggreges.keys())
semestres_id = list(self.res_sems_tags.keys())
dfs = {}
@ -156,10 +162,12 @@ class SemTag(TableTag):
df = pd.DataFrame(np.nan, index=etudids, columns=acronymes_ues_sorted)
# Charge les notes du semestre tag
sem_tag = self.semestres_tags_aggreges[frmsem_id]
sem_tag = self.res_sems_tags[frmsem_id]
moys_tag = sem_tag.moyennes_tags[tag]
notes = moys_tag.notes_ues # dataframe etudids x ues
acronymes_ues_sem = list(notes.columns) # les acronymes des UEs du semestre tag
acronymes_ues_sem = list(
notes.columns
) # les acronymes des UEs du semestre tag
# UEs communes à celles du SemTag (celles du dernier semestre du RCS)
ues_communes = list(set(acronymes_ues_sorted) & set(acronymes_ues_sem))
@ -168,7 +176,9 @@ class SemTag(TableTag):
etudids_communs = df.index.intersection(notes.index)
# Recopie
df.loc[etudids_communs, ues_communes] = notes.loc[etudids_communs, ues_communes]
df.loc[etudids_communs, ues_communes] = notes.loc[
etudids_communs, ues_communes
]
# Supprime tout ce qui n'est pas numérique
for col in df.columns:
@ -182,7 +192,7 @@ class SemTag(TableTag):
etudids_x_ues_x_semestres = np.stack(semestres_x_etudids_x_ues, axis=-1)
return etudids_x_ues_x_semestres
def comp_tags_list(self):
def comp_tags_list(self) -> list[str]:
"""Récupère les tag du semestre taggué associé au semestre final du RCS
Returns:
@ -190,7 +200,7 @@ class SemTag(TableTag):
"""
tags = []
dernier_frmid = self.formsemestre_terminal.formsemestre_id
dernier_semestre_tag = self.semestres_tags_aggreges[dernier_frmid]
dernier_semestre_tag = self.res_sems_tags[dernier_frmid]
tags = dernier_semestre_tag.tags_sorted
pe_affichage.pe_print(f"* Tags : {', '.join(tags)}")
return tags
@ -203,12 +213,29 @@ class SemTag(TableTag):
Un dictionnaire donnant les UEs
"""
dernier_frmid = self.formsemestre_terminal.formsemestre_id
dernier_semestre_tag = self.semestres_tags_aggreges[dernier_frmid]
dernier_semestre_tag = self.res_sems_tags[dernier_frmid]
moy_tag = dernier_semestre_tag.moyennes_tags[tag]
return moy_tag.ues # les UEs
def comp_ues_inscr_parcours(self, tag="but") -> pd.DataFrame:
"""Récupère les informations d'inscription des étudiants aux UEs : ne
conserve que les UEs du semestre terminal (pour les redoublants)
def compute_notes_ues(set_cube: np.array, etudids: list, acronymes_ues: list):
Returns:
Un dataFrame etudids x UE indiquant si un étudiant est inscrit à une UE
"""
dernier_frmid = self.formsemestre_terminal.formsemestre_id
dernier_semestre_tag = self.res_sems_tags[dernier_frmid]
moy_tag = dernier_semestre_tag.moyennes_tags[tag]
return moy_tag.ues_inscr_parcours_df
def compute_notes_ues(
set_cube: np.array,
etudids: list,
acronymes_ues: list,
inscr_mask: np.array,
):
"""Calcule la moyenne par UEs à un tag donné en prenant la note maximum (UE
par UE) obtenue par un étudiant à un semestre.
@ -217,18 +244,27 @@ def compute_notes_ues(set_cube: np.array, etudids: list, acronymes_ues: list):
(semestre_ids x etudids x UEs), des floats avec des NaN
etudids: liste des étudiants (dim. 0 du cube)
acronymes_ues: liste des acronymes des ues (dim. 1 du cube)
inscr_mask: masque etudids x UE traduisant les inscriptions des
étudiants aux UE (du semestre terminal)
Returns:
Un DataFrame avec pour columns les moyennes par ues,
et pour rows les etudid
"""
nb_etuds, nb_ues, nb_semestres = set_cube.shape
nb_etuds_mask, nb_ues_mask = inscr_mask.shape
assert nb_etuds == len(etudids)
assert nb_ues == len(acronymes_ues)
assert nb_etuds == nb_etuds_mask
assert nb_ues == nb_ues_mask
# Quelles entrées du cube contiennent des notes ?
mask = ~np.isnan(set_cube)
# Enlève les NaN du cube pour les entrées manquantes
# Entrées à garder dans le cube en fonction du mask d'inscription
inscr_mask_3D = np.stack([inscr_mask]*nb_semestres, axis=-1)
set_cube = set_cube*inscr_mask_3D
# Enlève les NaN du cube pour les entrées manquantes : NaN -> -1.0
set_cube_no_nan = np.nan_to_num(set_cube, nan=-1.0)
# Les moyennes par ues

View File

@ -39,6 +39,8 @@ Created on Thu Sep 8 09:36:33 2016
import pandas as pd
from app.models import Identite
TAGS_RESERVES = ["but"]
@ -47,7 +49,23 @@ class TableTag(object):
"""Classe centralisant différentes méthodes communes aux
SemestreTag, TrajectoireTag, AggregatInterclassTag
"""
pass
# Les étudiants
self.etuds: list[Identite] = None # A venir
"""Les étudiants"""
self.etats_civils: dict[int, Identite] = None
"""Les états civils"""
self.etudids: list[int] = None
"""Les etudids"""
def add_etuds(self, etuds: list[Identite]):
"""Mémorise les informations sur les étudiants
Args:
etuds: la liste des identités de l'étudiant
"""
self.etuds = etuds
self.etats_civils = {etud.etudid: etud.etat_civil for etud in self.etuds}
self.etudids = list(self.etats_civils.keys())
def get_all_tags(self):
"""Liste des tags de la table, triée par ordre alphabétique,
@ -70,15 +88,23 @@ class TableTag(object):
Le dataframe des notes et des classements
"""
etudiants = self.etudiants
etudiants = self.etats_civils
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]
for acronyme in moy_tag.moys_ues:
moy = moy_tag.moys_ues[acronyme] # une moyenne
df = df.join(moy.synthese["notes"].rename(f"Moy {tag}-{acronyme}"))
df = df.join(
moy.synthese["classements"].rename(f"Class {tag}-{acronyme}")
)
moy_gen = moy_tag.moy_gen
df = df.join(moy_gen.synthese["notes"].rename(f"Moy {tag}"))
df = df.join(moy_gen.synthese["classements"].rename(f"Class {tag}"))
df = df.join(moy_gen.synthese["notes"].rename(f"Moy {tag} (gen)"))
df = df.join(moy_gen.synthese["classements"].rename(f"Class {tag} (gen)"))
df.sort_values(by=['nom'])
return df