__author__ = "Romuald POTEAU"
__maintainer__ = "Romuald POTEAU"
__email__ = "romuald.poteau@univ-tlse3.fr"
__status__ = "Development"
####################################################################################################################################
# F E U I L L E S D E S T Y L E
####################################################################################################################################
from .visualID_Eng import fg, hl, bg
from IPython.display import HTML
[docs]
def css_styling():
styles = open("./tools4AS.css", "r").read()
return HTML(styles)
####################################################################################################################################
# F O N C T I O N S M A I S O N
####################################################################################################################################
import numpy as np
import matplotlib.pyplot as plt
# importation de la libairie pandas
import pandas as pd
import dataframe_image as dfim
import seaborn as sns
[docs]
def verifNotes(dfCC,labelNoteCC,nomCC,NI,absents='aabs'):
"""
Args:
- dfCC = dataframe dont 1 colonne contient les notes de CC
- labelNoteCC = label de la colonne qui contient les notes
- nomCC = label de l'épreuve de CC (ex CC1), utilisé pour l'affichage
- NI = nombre d'inscrits dans le module
- absents:
* 'aabs' : analyser s'il y a des étudiants qui n'ont pas été pointés au CC (défaut)
* 'noabs' : ne pas analyser s'il y a des étudiants qui n'ont pas été pointés au CC
(ça n'a plus de sens une fois les fichiers concaténés)
Returns:
- la moyenne et la déviation standard de liste de notes contenues dans le dataframe dfCC
- le nombre d'étudiants qui n'ont pas composé au CC
Note:
Affiche le nombre d'étudiants avec le label 'ABS' et signale les
étudiants sans note ni label, ce qui nécessite une vérification du PV.
"""
print()
#pd.set_option("display.max_rows", len(dfCC))
#display(dfCC[labelNoteCC])
if (absents == 'aabs'):
nABS = ((dfCC[labelNoteCC] == "ABS") | (dfCC[labelNoteCC] == "abs") | (dfCC[labelNoteCC] == "Abs") ).sum()
print(f"{hl.BOLD}{fg.BLUE}Etudiants de {nomCC} avec label 'ABS' = {nABS}{fg.OFF}")
nEMPTY = (dfCC[labelNoteCC].isna()).sum()
print(f"{hl.BOLD}{fg.BLUE}Etudiants de {nomCC} sans label ni note = {nEMPTY}{fg.OFF}")
if ((nEMPTY != 0) & (absents == 'aabs')):
print(f"{fg.RED}{hl.BOLD}Attention !!! Ça n'est pas normal. Vérifier la liste d'appel{fg.OFF}")
#pandas.to_numeric(arg, errors='raise', downcast=None)
#Convert argument to a numeric type
#errors{‘ignore’, ‘raise’, ‘coerce’}, default ‘raise’
# If ‘raise’, then invalid parsing will raise an exception.
# If ‘coerce’, then invalid parsing will be set as NaN.
# If ‘ignore’, then invalid parsing will return the input.
nCC_Absents = pd.to_numeric(dfCC[labelNoteCC], errors='coerce').isna().values.sum()
nCC_Notes = (~pd.to_numeric(dfCC[labelNoteCC], errors='coerce').isna()).values.sum()
av = pd.to_numeric(dfCC[labelNoteCC], errors='coerce').mean()
std = pd.to_numeric(dfCC[labelNoteCC], errors='coerce').std()
print(f"{fg.BLUE}Nombre d'étudiants sur les listes du {nomCC} = {len(dfCC)}. Nombre de notes vs. nombre d'absents = {nCC_Notes} vs. {nCC_Absents}{fg.OFF}")
print(f"Somme des copies notées & des absents = {nCC_Notes + nCC_Absents}")
print(f"{NI - nCC_Notes}/{NI} étudiants n'ont pas composé au {nomCC}")
print(f"{hl.BOLD}Moyenne = {av:.1f}, Écart-type = {std:.1f}{fg.OFF}")
return av, std, (NI - nCC_Notes)
[docs]
def mentionD(row, Mention):
rowV = row[Mention]
CHIMIE1 = "L1 CHI"
CHIMIE2 = "L2 CHI"
PHYSIQUE1 = "L1 PHY"
PHYSIQUE2 = "L2 PHY"
PHYSIQUE3 = "L3 PHY"
MATHS1 = "L1 MAT"
MATHS2 = "L2 MAT"
MATHS3 = "L2 MAT"
MECA = "L1 MECA"
MIASHS = "L1 MIASHS"
EEA = "L1 EEA"
INFO1 = "L1 INFO"
INFO2 = "L2 INFO"
PC1 = "L1 PHYSIQUE CHIMIE"
PC2 = "L1 PC"
GC1 = "L1 GC"
GC2 = "L1 GENIE CIVIL"
MOBINT = "MOBILITE INTERNAT"
DUMMY = "DUMMY"
if (CHIMIE1 in rowV) | (CHIMIE2 in rowV):
return "CHIMIE"
elif ((PHYSIQUE1 in rowV) | (PHYSIQUE2 in rowV) | (PHYSIQUE3 in rowV)) & ~(PC1 in rowV):
return "PHYSIQUE"
elif (MATHS1 in rowV) | (MATHS2 in rowV) | (MATHS3 in rowV):
return "MATHS"
elif MIASHS in rowV:
return "MIASHS"
elif MECA in rowV:
return "MECA"
elif (INFO1 in rowV) | (INFO2 in rowV):
return "INFO"
elif EEA in rowV:
return "EEA"
elif (GC1 in rowV) | (GC2 in rowV):
return "GC"
elif (PC1 in rowV) | (PC2 in rowV):
return "PC"
elif MOBINT in rowV:
return "MobInt"
elif DUMMY in rowV:
return "DUMMY"
else:
print(f"Quelle est cette Mention ? {rowV}")
return
[docs]
def parcours(row, Parcours):
rowV = row[Parcours]
CUPGE = "CUPGE"
SANTE = "OPT° SANTE"
ACCOMP = "ACCOMP"
DUMMY = "DUMMY"
if CUPGE in rowV:
return "CUPGE"
elif SANTE in rowV:
return "SANTE"
elif ACCOMP in rowV:
return "ACCOMPAGNEMENT"
elif DUMMY in rowV:
return "DUMMY"
else:
# print(f"Pas de parcours dans la mention {rowV}")
return "Standard"
return
[docs]
def MentionAuModule(note, Seuil):
"""
entrée :
- note = valeur numérique ou NaN
- Seuil = seuil de réussite
sortie :
- m = mention au module (AJ, P, AB, B, TB ou PB!! dans le cas où la colonne contiendrait une valeur numérique non comprise entre 0 et 20, ou bien toute autre contenu (caractères etc)
"""
if (note >=0) and (note < Seuil):
m = "AJ"
elif (note >=10) and (note < 12):
m = "P"
elif (note>=12) and (note < 14):
m = "AB"
elif (note >= 14) and (note <16):
m = "B"
elif (note >=16) and (note <= 20) :
m = "TB"
elif (np.isnan(note)):
m = np.NaN
else:
print(note,"PB")
m = 'PB!!'
return m
[docs]
def concat2ApoG(df2Complete, ID_ApoG, dfCC, Col2SaveInCC, IDCC, nomCC):
"""
entrée :
- df2Complete = dataframe à compléter (merge = 'left')
- au premier appel, ce soit être le fichier de Référence
- ensuite c'est le fichier de notes lui-même, en cours d'update
- ID_ApoG = label de la colonne qui contient les numéros étudiants dans le fichier de référence
- dfCC = dataframe dont 1 colonne contient les notes de CC
- Col2SaveInCC = liste avec les en-têtes de colonnes qui contiennent les colonnes de dfCC à reporter dans dfNotes
- IDCC = label de la colonne qui contient les numéros étudiants dans le fichier de notes
- nomCC = à ce stade, c'est aussi bien le label de la colonne qui contient les notes que le label de l'épreuve de CC (ex CC1), utilisé pour l'affichage
sortie :
- le dataframe dfNotes. Contient la version concaténée du dataframe d'entrée df2complete et certaines colonnes du dataframe des notes dfCC
([Col2SaveInCC + IDCC + nomCC])
- un dataframe dfnotFoundInRef qui contient la liste des étudiants qui sont dans le fichier de notes et pas dans le fichier de référence
affichages :
- liste des étudiants qui sont dans le fichier de notes et pas dans le fichier de référence
"""
#display(df2Complete)
dfNotes = df2Complete.copy()
pd.set_option('display.max_rows', 12)
#print(f"{hl.BOLD}{fg.BLUE}Fichier de notes copié de l'export Apogée{fg.OFF}")
#display(dfNotes)
Col2SaveInCCExt = Col2SaveInCC.copy()
Col2SaveInCCExt.extend([IDCC])
Col2SaveInCCExt.extend([nomCC])
dfNotes = dfNotes.merge(dfCC[Col2SaveInCCExt], left_on=ID_ApoG, right_on=IDCC, how='left')
print(f"{hl.BOLD}{fg.BLUE}{nomCC} ajouté dans fichier de notes{fg.OFF}")
print(f"Les colonnes qui ont été ajoutées sont : {Col2SaveInCCExt}")
#display(dfNotes)
dfNotesTmp = df2Complete.copy()
dfNotesOuter = dfNotesTmp.merge(dfCC[Col2SaveInCCExt], left_on=ID_ApoG, right_on=IDCC, how='outer')
#display(dfNotesOuter)
dfnotFoundInReftmp = dfNotesOuter[dfNotesOuter[ID_ApoG].isna()]
if dfnotFoundInReftmp.shape[0] != 0:
print(f"{hl.BOLD}{fg.RED}Problème !! Ces étudiants du {nomCC} ne sont pas dans le dataframe de Référence{fg.OFF}")
print(f"{hl.BOLD}{fg.RED}Ils ne sont pas rajoutés dans ce dataframe, mais dans un dataframe dfnotFoundInRef{fg.OFF}")
display(dfnotFoundInReftmp)
else:
print(f"{hl.BOLD}{fg.GREEN}Tous les étudiants sont bien dans le dataframe de Référence{fg.OFF}")
return dfNotes, dfnotFoundInReftmp
[docs]
def checkNoID_DuplicateID(df, dfname, ID, nom, NomPrenom):
import numpy as np
"""
entrée :
- df = dataframe à analyser
- dfname = nom (string) du dataframe à analyser. Il est recommandé d'utiliser f'{dfCC=}'.split('=')[0]
- ID = label de la colonne qui contient les numéros étudiants dans df
- nom = label du dataframe, utilisé pour l'affichage
- NomPrenom = liste avec les en-têtes de colonnes qui contiennent les noms et les prénoms dans df
affichage : diagnostic et éventuellement la liste des étudiants sans numéros d'étudiant
"""
print(f"{hl.BOLD}{fg.BLUE}Dataframe {dfname} (alias {nom}){fg.OFF}")
noID = df[df[ID].isnull()]
if (noID.shape[0] != 0):
print(f"{hl.BOLD}{fg.RED}Etudiants sans ID !{fg.OFF}")
display(noID.sort_values(by=NomPrenom[0], ascending = True))
else:
print(f"{hl.BOLD}{fg.GREEN}Etudiants sans ID ? Pas de problème{fg.OFF}")
values, counts = np.unique(df[ID].to_numpy(), return_counts=True)
duplicateID = False
for c in counts:
if c != 1: duplicateID = True
if (not duplicateID):
print(f"{hl.BOLD}{fg.GREEN}Doublon sur les ID ? Pas de problème{fg.OFF}")
else:
print(f"{hl.BOLD}{fg.RED}ID en double!{fg.OFF}")
for i, c in enumerate(counts):
if c != 1: print(f"{values[i]} x {c}")
return
[docs]
def read_excel(xlFile,decimal,name):
"""
entrée :
- xlFile = nom du ficher excel
- decimal = "." ou "," selon le cas
- name = label du dataframe, utilisé pour l'affichage
sortie :
- le dataframe qui contient l'intégralité du fichier excel
- le nombre de lignes de ce tableau (à l'exclusion de l'en-tête des colonnes)
affichage :
- statistiques descriptives (describe) de toutes les colonnes, y compris celles qui ne contiennent pas de valeurs numériques
"""
print(f"{hl.BOLD}{fg.BLUE}{name}{fg.OFF}")
print(f"Reading... {xlFile}")
df=pd.read_excel(xlFile,decimal=decimal)
#pd.set_option('display.max_rows', 12)
#display(dfCC1)
display(df.describe(include='all'))
return df, df.shape[0]
[docs]
def ReplaceABSByNan(df,nomCC):
"""
entrée :
- dataframe qui contient les notes
- nom des colonnes qui contiennent les notes
sortie
- nouveau dataframe où toutes les notes des colonnes nomCC = ABS sont remplacées par des nan
"""
dfcopy = df.copy()
for nom in nomCC:
# correction introduite le 24/01/2026
# dfcopy[nom].mask((dfcopy[nom] == "ABS") | (dfcopy[nom] == "abs") | (dfcopy[nom] == "Abs"), np.nan ,inplace=True)
# On convertit en chaîne, on met en majuscules, et on compare à "ABS"
# L'assignation directe (df[nom] = ...) évite le ChainedAssignmentError
dfcopy[nom] = dfcopy[nom].mask(dfcopy[nom].astype(str).str.upper() == "ABS", np.nan)
return dfcopy
# deprecated à cause de la nouvelle version de pandas (26/01/2026)
[docs]
def ReplaceNanBy0OLD(df,nomCC):
"""
entrée :
- df = dataframe avec les notes
- nomCC = liste des en-têtes de colonnes qui contiennt les notes
sortie :
- le dataframe avec Nan remplacé par 0 pour chaque étudiant qui a au moins une note de CC
"""
dfCopy = df.copy()
for nom in nomCC:
nomCCred = nomCC.copy()
nomCCred.remove(nom)
mask = pd.DataFrame([False]*dfCopy.shape[0],index=dfCopy.index,columns=["mask"])
for nomred in nomCCred:
mask["mask"] = (mask["mask"] | dfCopy[nomred].notnull())
mask["mask"] = (mask["mask"] & dfCopy[nom].isna())
# correction le 24/01/2026
# dfCopy[nom].mask(mask["mask"],0.0,inplace=True)
dfCopy[nom] = dfCopy[nom].mask(mask["mask"], 0.0)
return dfCopy
[docs]
def ReplaceNanBy0(df, nomCC):
dfCopy = df.copy()
for nom in nomCC:
# 1. On identifie les autres colonnes
autres_cols = [c for c in nomCC if c != nom]
# 2. On crée le masque :
# (Au moins une autre note n'est pas nulle) ET (La note actuelle est nulle)
condition = dfCopy[autres_cols].notnull().any(axis=1) & dfCopy[nom].isna()
# 3. Application directe (On s'assure que la colonne accepte les flottants)
dfCopy[nom] = dfCopy[nom].astype(float)
dfCopy[nom] = dfCopy[nom].mask(condition, 0.0)
return dfCopy
[docs]
def dropColumnsByIndex(df,idropC):
"""
entrée :
- df = dataframe dont on veut supprimer des colonnes
- idropC = indices des colonnes dont on veut se débarasser
sortie :
- dfCleaned = dataframe originel dont les colonnes indexées par idropC ont été supprimées
"""
listC = list(df.columns)
dropC = [listC[idropC[i]] for i in range(len(idropC))]
print(f"On va se débarrasser des colonnes {dropC}")
dfCleaned = df.drop(dropC,axis=1)
return dfCleaned
[docs]
def CreationDfADMAJGH(df,note,Seuil,prefix,MD,Parc,IDApoG):
"""
entrée :
- df = dataframe avec les mentions/parcours/moyennes
- note = nom de la colonne qui contient la moyenne globale
- Seuil = seuil de réussite pour départager ADM & AJ
- prefix = préfixe du nom de fichier temporaire, incluant ou nom le nom d'un sous-répertoire de sauvegarde
- MD = nom de la colonne qui contient la dénomination simplifiée de la mention de diplôme ("MentionD")
- Parc = nom de la colonne qui contient le parcours
- IDApoG = nom de la colonne qui contient l'ID des étudiants
sortie :
- dfADM = dataframe des admis
- dfAJ = dataframe des ajournés
- dfGhosts = dataframe des fantômes (aka Ghosts ; i.e. n'ont passé aucun des CC)
affichage : stats rapides (describe & sum) de chacun des sous-ensembles ADM, AJ, Ghosts
sauvegarde : fichier excel tmp{Note}.xlsx avec 3 onglets (ADM, AJ, Ghosts)
"""
print(f"{hl.BOLD}{bg.LIGHTRED}Construction des dataframes avec les ADM & les AJ sur la base de la {note} >= ou < à {Seuil}{fg.OFF}")
print(f"{hl.BOLD}{bg.LIGHTRED}Construction également d'un dataframe 'Ghosts', c'est-à-dire avec les fantômes i.e. n'ont passé aucun des CC{fg.OFF}")
#dataframe reçus
dfADM = df[df[note]>=Seuil]
#dataframe ajournés
dfAJ = df[df[note]<Seuil]
#dataframe fantômes
dfGhosts = df[df[note].isnull()]
Ftmp = prefix+'tmp'+note+'.xlsx'
exceltest = pd.ExcelWriter(Ftmp, engine='xlsxwriter')
dfADM.to_excel(exceltest, sheet_name='ADM')
dfAJ.to_excel(exceltest, sheet_name='AJ')
dfGhosts.to_excel(exceltest, sheet_name='Fantomes')
exceltest.close()
print(f"{hl.BOLD}{fg.BLUE}Total{fg.OFF}")
display(df.groupby(MD)[note].describe().style.format("{0:.1f}"))
print(f"{hl.BOLD}{fg.BLUE}Admis{fg.OFF}")
display(dfADM.groupby(MD)[note].describe().style.format("{0:.1f}"))
print(f"{hl.BOLD}{fg.BLUE}Ajournés{fg.OFF}")
display(dfAJ.groupby(MD)[note].describe().style.format("{0:.1f}"))
print(f"{hl.BOLD}{fg.BLUE}Total{fg.OFF}")
display(df.groupby(MD)[note].describe().sum())
print(f"{hl.BOLD}{fg.BLUE}Admis{fg.OFF}")
display(dfADM.groupby(MD)[note].describe().sum())
print(f"{hl.BOLD}{fg.BLUE}Ajournés{fg.OFF}")
display(dfAJ.groupby(MD)[note].describe().sum())
print(f"{hl.BOLD}{fg.BLUE}Fantômes{fg.OFF}")
display(dfGhosts.groupby(MD)[IDApoG].count())
return dfADM, dfAJ, dfGhosts
[docs]
def StatsRéussiteParMentionOuParcours(dfT, dfADM, dfAJ, dfGhosts, Category, note):
"""
entrée :
- dfT = dataframe qui contient toutes les notes, ainsi qu'au moins une catégorisation (exemple : Mention ou Parcours ou Section etc...)
- dfADM = dataframe qui contient uniquement les étudiants admis
- dfAJ = dataframe qui contient uniquement les étudiants ajournés (sans les fantômes)
- dfGhosts = dataframe qui contient la liste des fantômes
- Category = nom de la colonne sur laquelle on veut faire des analyses statistiques
- note = nom de la colonne qui contient la note qu'on veut analyser par catégorie (généralement la moyenne finale)
sortie :
- dfStats
affichage :
"""
def incrementer(row,Category,Catunique,note,NN,Presents="Presents"):
"""
conçu pour pour une analyse ligne par ligne
si Category = une des catégories de Catunique NN[de cette catégorie] +=1
nécessite au préalable
- de fabriquer la liste des catégories uniques (df[Category].unique())
- d'initialiser à 0 un tableau qui a la même dimension que Catunique
entrée :
- row = ligne d'un dataframe à analyser
- Category = nom de la colonne qui contient les catégories à comptabiliser
- Catunique = liste exhaustive des catégories du dataframe analysé
- note = nom de la colonne qui contient les notes
- NN = tableau dont la colonne qui correspond à l'une des catégories uniques pour cet étudiant est incrémenté de 1
- Presents (valeur par défaut = "Present") : compte les présents uniquement
sinon ce sont les fantômes qui sont comptés
"""
i = 0
for C in Catunique:
if (((row[Category] == C) & (not np.isnan(row[note]))) & (Presents=="Presents")) |\
(((row[Category] == C) & (np.isnan(row[note]))) & (Presents!="Presents")):
NN[i] +=1
#print(i,row[CategoryInRow],C,NN[i],row[note],np.isnan(row[note]))
i += 1
return
print(f"{hl.BOLD}-- Statistiques uniquement sur les présents --{fg.OFF}")
CatUnique = dfT[Category].unique()
print(f"{Category} = {CatUnique}")
# création des tableaux pour comptabiliser les reçus (NRP), ajournés (NAJP) par parcours
# NTP contient le nombre total d'étudiants par parcours
NT = np.zeros(len(CatUnique))
NADM = np.zeros(len(CatUnique))
NAJ = np.zeros(len(CatUnique))
NGH = np.zeros(len(CatUnique))
dfT.apply(lambda row: incrementer(row, Category, CatUnique, note, NT), axis=1)
dfADM.apply(lambda row: incrementer(row, Category, CatUnique, note, NADM), axis=1)
dfAJ.apply(lambda row: incrementer(row, Category, CatUnique, note, NAJ), axis=1)
dfGhosts.apply(lambda row: incrementer(row, Category, CatUnique, note, NGH, "Fantomes"), axis=1)
dfADMAJ = pd.concat([dfADM,dfAJ])
# display(dfADMAJ.describe(include='all'))
# print(dfADMAJ[Moyenne].mean())
print(f" NT/Cat = {NT}")
print(f"{fg.GREEN}NADM/Cat = {NADM}{fg.OFF}")
print(f"{fg.RED} NAJ/Cat = {NAJ}{fg.OFF}")
print(f"{fg.LIGHTGRAY} NGH/Cat = {NGH}{fg.OFF}")
print(f"{hl.BOLD}{fg.GREEN}Reçus par {Category}{fg.OFF}")
i = 0
nADM = 0
nAJ = 0
nTOT = 0
nGH = 0
Moy = np.zeros(len(CatUnique))
StD = np.zeros(len(CatUnique))
Med = np.zeros(len(CatUnique))
MoyGlob = np.zeros(len(CatUnique))
MoyGlobT = 0
for C in CatUnique:
print(f"{hl.BOLD}{C:>20}{fg.OFF} = {hl.BOLD}{fg.GREEN}{100*NADM[i]/NT[i]:.1f} %{fg.OFF} ({fg.RED}AJ : {NAJ[i]:3.0f}{fg.OFF},"\
f" {fg.GREEN}ADM : {NADM[i]:3.0f}{fg.OFF}, Tot : {NT[i]:3.0f} {fg.LIGHTGRAY}+Fantômes : {NGH[i]:3.0f}){fg.OFF}")
# display(dfADM.loc[dfADM[Category] == C][note].sum())
Moy[i] = (dfADM.loc[dfADM[Category] == C][note].sum()+dfAJ.loc[dfAJ[Category] == C][note].sum())
MoyGlob[i] = Moy[i]
MoyGlobT += Moy[i]
Moy[i] = np.round(Moy[i] / (NADM[i]+NAJ[i]),2)
MoyGlob[i] = np.round(MoyGlob[i] / (NADM[i]+NAJ[i]+NGH[i]),2)
StD[i] = np.round(dfADMAJ.loc[dfADMAJ[Category] == C][note].std(),1)
Med[i] = np.round(dfADMAJ.loc[dfADMAJ[Category] == C][note].median(),1)
# print("Moyennes ",Moy[i],MoyGlob[i])
nADM += NADM[i]
nAJ += NAJ[i]
nGH += NGH[i]
nTOT += NT[i]
i+=1
print(f"{hl.BOLD}{fg.GREEN} ADM = {nADM:3.0f}{fg.OFF}")
print(f"{hl.BOLD}{fg.RED} AJ = {nAJ:3.0f}{fg.OFF}")
print(f"{hl.BOLD}{fg.BLACK} TOT = {nTOT:3.0f}{fg.OFF}")
print(f"{hl.BOLD}{fg.LIGHTGRAY}(+Fantomes = {nGH:3.0f}){fg.OFF}")
MoyT = np.round(dfADMAJ[note].mean(),2)
StDT = np.round(dfADMAJ[note].std(),1)
MedT = np.round(dfADMAJ[note].median(),1)
MoyGlobT = np.round(MoyGlobT/(nTOT+nGH),2)
rowTotal = [NT.sum(),NADM.sum(),NAJ.sum(),np.round(100*NADM.sum()/NT.sum(),1),MoyT,StDT,MedT,NGH.sum(),NT.sum()+NGH.sum(),np.round(100*NADM.sum()/(NT.sum()+NGH.sum()),1),MoyGlobT]
defCol = ["Présents","ADM","AJ","Taux ADM (présents)","Moy.","StDev","Med.","Fantômes","Total","Taux ADM (tot.)","Moy."]
dfStats=pd.DataFrame(zip(NT,NADM,NAJ,np.round(100*NADM/NT,1),Moy,StD,Med,NGH,NT+NGH,np.round(100*NADM/(NT+NGH),1),MoyGlob),index=CatUnique,columns=defCol)
rowTotal = pd.DataFrame([rowTotal],index=["Total"],columns=dfStats.columns)
dfStats=pd.concat([dfStats,rowTotal])
dfStats.style.set_caption(f"Statistiques par {Category}")
return dfStats
[docs]
def StatsRéussiteParMentionOuParcoursWithAb(dfT, dfADM, dfAJ, dfGhosts, dfAb, Category, note):
"""
entrée :
- dfT = dataframe qui contient toutes les notes, ainsi qu'au moins une catégorisation (exemple : Mention ou Parcours ou Section etc...)
- dfADM = dataframe qui contient uniquement les étudiants admis
- dfAJ = dataframe qui contient uniquement les étudiants ajournés (sans les fantômes)
- dfGhosts = dataframe qui contient la liste des fantômes
- dfAb = dataframe qui contient la liste des étudiants qui ont abandonné
- Category = nom de la colonne sur laquelle on veut faire des analyses statistiques
- note = nom de la colonne qui contient la note qu'on veut analyser par catégorie (généralement la moyenne finale)
sortie :
- dfStats
affichage :
"""
def incrementer(row,Category,Catunique,note,NN,Presents="Presents"):
"""
conçu pour pour une analyse ligne par ligne
si Category = une des catégories de Catunique NN[de cette catégorie] +=1
nécessite au préalable
- de fabriquer la liste des catégories uniques (df[Category].unique())
- d'initialiser à 0 un tableau qui a la même dimension que Catunique
entrée :
- row = ligne d'un dataframe à analyser
- Category = nom de la colonne qui contient les catégories à comptabiliser
- Catunique = liste exhaustive des catégories du dataframe analysé
- note = nom de la colonne qui contient les notes
- NN = tableau dont la colonne qui correspond à l'une des catégories uniques pour cet étudiant est incrémenté de 1
- Presents (valeur par défaut = "Present") : compte les présents uniquement
sinon ce sont les fantômes qui sont comptés
"""
i = 0
for C in Catunique:
if (((row[Category] == C) & (not np.isnan(row[note]))) & (Presents=="Presents")) |\
(((row[Category] == C) & (np.isnan(row[note]))) & (Presents!="Presents")):
NN[i] +=1
#print(i,row[CategoryInRow],C,NN[i],row[note],np.isnan(row[note]))
i += 1
return
def incrementerAbandons(row,Category,Catunique,NN):
i = 0
for C in Catunique:
if (row[Category] == C):
NN[i] +=1
i += 1
return
print(f"{hl.BOLD}-- Statistiques uniquement sur les présents --{fg.OFF}")
CatUnique = dfT[Category].unique()
print(f"{Category} = {CatUnique}")
# création des tableaux pour comptabiliser les reçus (NRP), ajournés (NAJP) par parcours
# NTP contient le nombre total d'étudiants par parcours
NT = np.zeros(len(CatUnique))
NADM = np.zeros(len(CatUnique))
NAJ = np.zeros(len(CatUnique))
NAb = np.zeros(len(CatUnique))
NGH = np.zeros(len(CatUnique))
dfT.apply(lambda row: incrementer(row, Category, CatUnique, note, NT), axis=1)
dfADM.apply(lambda row: incrementer(row, Category, CatUnique, note, NADM), axis=1)
dfAJ.apply(lambda row: incrementer(row, Category, CatUnique, note, NAJ), axis=1)
dfAb.apply(lambda row: incrementerAbandons(row, Category, CatUnique, NAb), axis=1)
dfGhosts.apply(lambda row: incrementer(row, Category, CatUnique, note, NGH, "Fantomes"), axis=1)
dfADMAJ = pd.concat([dfADM,dfAJ])
# display(dfADMAJ.describe(include='all'))
# print(dfADMAJ[Moyenne].mean())
print(f" NT/Cat = {NT}")
print(f"{fg.GREEN}NADM/Cat = {NADM}{fg.OFF}")
print(f"{fg.RED} NAJ/Cat = {NAJ}{fg.OFF}")
print(f"{fg.RED} NAb/Cat = {NAb}{fg.OFF}")
print(f"{fg.LIGHTGRAY} NGH/Cat = {NGH}{fg.OFF}")
print(f"{hl.BOLD}{fg.GREEN}Reçus par {Category}{fg.OFF}")
i = 0
nADM = 0
nAJ = 0
nAb = 0
nTOT = 0
nGH = 0
Moy = np.zeros(len(CatUnique))
StD = np.zeros(len(CatUnique))
Med = np.zeros(len(CatUnique))
MoyGlob = np.zeros(len(CatUnique))
MoyGlobT = 0
for C in CatUnique:
print(f"{hl.BOLD}{C:>20}{fg.OFF} = {hl.BOLD}{fg.GREEN}{100*NADM[i]/NT[i]:.1f} %{fg.OFF} ({fg.RED}AJ : {NAJ[i]:3.0f} dont {NAb[i]:3.0f} Ab{fg.OFF},"\
f" {fg.GREEN}ADM : {NADM[i]:3.0f}{fg.OFF}, Tot : {NT[i]:3.0f} {fg.LIGHTGRAY}+Fantômes : {NGH[i]:3.0f}){fg.OFF}")
# display(dfADM.loc[dfADM[Category] == C][note].sum())
Moy[i] = (dfADM.loc[dfADM[Category] == C][note].sum()+dfAJ.loc[dfAJ[Category] == C][note].sum())
MoyGlob[i] = Moy[i]
MoyGlobT += Moy[i]
Moy[i] = np.round(Moy[i] / (NADM[i]+NAJ[i]),2)
MoyGlob[i] = np.round(MoyGlob[i] / (NADM[i]+NAJ[i]+NGH[i]),2)
StD[i] = np.round(dfADMAJ.loc[dfADMAJ[Category] == C][note].std(),1)
Med[i] = np.round(dfADMAJ.loc[dfADMAJ[Category] == C][note].median(),1)
# print("Moyennes ",Moy[i],MoyGlob[i])
nADM += NADM[i]
nAJ += NAJ[i]
nAb += NAb[i]
nGH += NGH[i]
nTOT += NT[i]
i+=1
print(f"{hl.BOLD}{fg.GREEN} ADM = {nADM:3.0f}{fg.OFF}")
print(f"{hl.BOLD}{fg.RED} AJ = {nAJ:3.0f}{fg.OFF}")
print(f"{hl.BOLD}{fg.RED} dont Ab = {nAb:3.0f} (= abandons){fg.OFF}")
print(f"{hl.BOLD}{fg.BLACK} TOT = {nTOT:3.0f}{fg.OFF}")
print(f"{hl.BOLD}{fg.LIGHTGRAY}(+Fantomes = {nGH:3.0f}){fg.OFF}")
MoyT = np.round(dfADMAJ[note].mean(),2)
StDT = np.round(dfADMAJ[note].std(),1)
MedT = np.round(dfADMAJ[note].median(),1)
MoyGlobT = np.round(MoyGlobT/(nTOT+nGH),2)
rowTotal = [NT.sum(),NADM.sum(),NAJ.sum(),NAb.sum(),np.round(100*NADM.sum()/NT.sum(),1),MoyT,StDT,MedT,NGH.sum(),NT.sum()+NGH.sum(),np.round(100*NADM.sum()/(NT.sum()+NGH.sum()),1),MoyGlobT]
defCol = ["Présents","ADM","AJ","dont Ab","Taux ADM (présents)","Moy.","StDev","Med.","Fantômes","Total","Taux ADM (tot.)","Moy."]
dfStats=pd.DataFrame(zip(NT,NADM,NAJ,NAb,np.round(100*NADM/NT,1),Moy,StD,Med,NGH,NT+NGH,np.round(100*NADM/(NT+NGH),1),MoyGlob),index=CatUnique,columns=defCol)
rowTotal = pd.DataFrame([rowTotal],index=["Total"],columns=dfStats.columns)
dfStats=pd.concat([dfStats,rowTotal])
dfStats.style.set_caption(f"Statistiques par {Category}")
return dfStats
[docs]
def ApplySecondeChance(df,nomCC,nomCCSC,nomCCdelaSC):
"""
modification du dataframe df
entrée :
- df = dataframe auquel on va ajouter une nouvelle colonne nomCCSC qui va contenir la note de la colonne nomCC soit celle de la colonne nomCCdelaSC, si cell-ci est supérieure à la notede nomCC
- nomCC = nom de la colonne à laquelle on applique la seconde chance
- nomCCSC = nom de la nouvelle colonne qui contient la note du CC après application de la seconde chance
- nomCCdelaSC = nom de la colonne qui contient la note "seconde chance
sortie :
- moyenne après application de la seconde chance
- écart-type après application de la seconde chance
"""
df[nomCCSC] = df[[nomCC,nomCCdelaSC]].max(axis=1)
moySC = pd.to_numeric(df[nomCCSC], errors='coerce').mean()
stdSC = pd.to_numeric(df[nomCCSC], errors='coerce').std()
return moySC, stdSC
[docs]
def ComparaisonMoyennesDMCC(df,nomCC1,nomCC2,SeuilBadDMCC1,SeuilGoodDMCC1):
"""
entrée :
- df = dataframe avec uniquement les étudiants AJ ou ADM, c'est-à-dire qu'il n'y a aucun fantôme
- nomCC1 = en-tête de la colonne de df qui contient la note du premier CC
- nomCC2 = en-tête de la colonne de df qui contient la note du 2nd CC
- SeuilBadDMCC1 = Seuil en-dessous duquel la note au nomCC1 est considérée comme médiocre
- SeuilGoodDMCC1 = Seuil au-deçà duquel la note au nomCC1 est considérée comme bonne
affichage :
- moyenne au nomCC2 de la cohorte d'étudiant(e)s
- en-dessous du SeuilBadDMCC1 au nomCC1
- au-dessus du SeuilGoodDMCC1 au nomCC1
- entre les deux seuils au nomCC1
- jointplot entre nomCC1 et nomCC2 uniquement pour les étudiant(e)s dont la note au CC1 est considérée comme médiocre
"""
print(f"{hl.BOLD}Corrélation entre {fg.BLUE}{nomCC1}{fg.BLACK} et {fg.BLUE}{nomCC2}{fg.BLACK} ?{hl.OFF}")
dfBadCC1 = df[df[nomCC1] < SeuilBadDMCC1]
dfAvCC1 = df[(df[nomCC1] >= SeuilBadDMCC1) & (df[nomCC1] <= SeuilGoodDMCC1)]
dfGCC1 = df[df[nomCC1] > SeuilGoodDMCC1]
MoyenneAuCC2desBadCC1 = np.round(dfBadCC1[nomCC2].mean(),1)
MoyenneAuCC2desAvCC1 = np.round(dfAvCC1[nomCC2].mean(),1)
MoyenneAuCC2desGoodCC1 = np.round(dfGCC1[nomCC2].mean(),1)
MoyenneAuCC1 = np.round(df[nomCC1].mean(),1)
MoyenneAuCC2 = np.round(df[nomCC2].mean(),1)
print(f"Les étudiant(e)s qui ont eu moins de {SeuilBadDMCC1}/20 au {nomCC1} ont en moyenne {MoyenneAuCC2desBadCC1}/20 au {nomCC2}")
print(f"Les étudiant(e)s qui ont eu entre {SeuilBadDMCC1}/20 et {SeuilGoodDMCC1}/20 au {nomCC1} ont en moyenne {MoyenneAuCC2desAvCC1}/20 au {nomCC2}")
print(f"Les étudiant(e)s qui ont eu plus de {SeuilGoodDMCC1}/20 au {nomCC1} ont en moyenne {MoyenneAuCC2desGoodCC1}/20 au {nomCC2}")
print(f"Pour rappel, la moyenne au {nomCC1} = {MoyenneAuCC1}/20 et celle au {nomCC2} = {MoyenneAuCC2}/20")
sns.jointplot(x = nomCC1, y = nomCC2, data = dfBadCC1)
plt.show()
return
####################################################################################################################################
# G R A P H E S
####################################################################################################################################
[docs]
def Histogrammes(df,nomCC,Moyenne,NomGraphique,w,moy,std,moyT,stdT,legende):
"""
entrée :
- df = dataframe qui contient ID, Noms, Prénoms, notes de CC, et Moyenne pondérée
- nomCC = liste qui contient noms d'en-têtes des colonnes qui contiennent les notes de CC
- Moyenne = nom de l'en-tête de la colonne qui contient la moyenne
- NomGraphique = nom du fichier png qui va contenir la figure
- w = liste qui contient les coeffs des CC
- moy = liste qui contient la moyenne de chaque CC
- std = liste qui contient l'écart-type calculé pour chaque CC
- moyT = moyenne des 4 CC
- stdT = écart-type calculé pour la note globale
- legende = titre qui sera affiché sur l'histogramme principal
affichage :
- 1 petit histogramme /CC
- 1 grand histogramme avec la moyenne
sauvegarde :
- fichier graphique 'NomGraphique' avec 1 petit histogramme par CC + 1 grand histogramme avec la moyenne
"""
import matplotlib.gridspec as gridspec
wt = sum(w)
fig = plt.figure(figsize=(18, 12))
plt.rcParams["font.size"] = (14) #font size
gs = gridspec.GridSpec(2, 4, height_ratios=[1, 1], width_ratios=[1, 1, 1, 1])
fontpropertiesT = {'family':'sans-serif', 'weight' : 'bold', 'size' : 16}
fontpropertiesA = {'family':'sans-serif', 'weight' : 'bold', 'size' : 18}
sns.set_style("whitegrid")
ax00=plt.subplot(gs[0,0])
fig00=sns.histplot(data=df, x=nomCC[0], discrete=True, kde=True, color="#3e95ff", alpha=1.0)
ax00.set_xlabel("note / 20",fontsize=16)
ax00.set_ylabel("Count",fontsize=16)
ax00.set_title(f"{nomCC[0]} ({w[0]}%). <N> = {moy[0]:.1f}, $\sigma$ = {std[0]:.1f}",color = "red", font=fontpropertiesT)
ax01=plt.subplot(gs[0,1])
fig01=sns.histplot(data=df, x=nomCC[1], discrete=True, kde=True, color="#3e95ff", alpha=1.0)
ax01.set_xlabel("note / 20",fontsize=16)
ax01.set_ylabel("Count",fontsize=16)
ax01.set_title(f"{nomCC[1]} ({w[1]}%) <N> = {moy[1]:.1f}, $\sigma$ = {std[1]:.1f}",color = "red", font=fontpropertiesT)
ax02=plt.subplot(gs[0,2])
fig02=sns.histplot(data=df, x=nomCC[2], discrete=True, kde=True, color="#3e95ff", alpha=1.0)
ax02.set_xlabel("note / 20",fontsize=16)
ax02.set_ylabel("Count",fontsize=16)
ax02.set_title(f"{nomCC[2]} ({w[2]}%) <N> = {moy[2]:.1f}, $\sigma$ = {std[2]:.1f}",color = "red", font=fontpropertiesT)
ax03=plt.subplot(gs[0,3])
fig03=sns.histplot(data=df, x=nomCC[3], discrete=True, kde=True, color="#3e95ff", alpha=1.0)
ax03.set_xlabel("note / 20",fontsize=16)
ax03.set_ylabel("Count",fontsize=16)
ax03.set_title(f"{nomCC[3]} ({w[3]}%) <N> = {moy[3]:.1f}, $\sigma$ = {std[3]:.1f}",color = "red", font=fontpropertiesT)
axTot=plt.subplot(gs[1,0:4])
figTot=sns.histplot(data=df, x=Moyenne, discrete=True, kde=True, color="#737cff", alpha=1.0, stat='count', label = legende)
axTot.set_xlabel("note / 20",fontsize=16)
axTot.set_ylabel("Count",fontsize=16)
axTot.set_xticks(np.arange(0,19,2))
axTot.set_title(f"Moyenne ({wt}%). <N> = {moyT:.1f}, $\sigma$ = {stdT:.1f}",color = "red", font=fontpropertiesT)
axTot.legend()
fig.savefig(NomGraphique, dpi=300)
plt.show()
return
[docs]
def kdePlotByMentionEtParcours(df,Moyenne,Mention,Parcours,NomGraphique):
"""
entrée :
- df = dataframe qui contient ID, Noms, Prénoms, notes de CC, Moyenne pondérée, Mention et Parcours
- Mention = nom de l'en-tête de la colonne qui contient la Mention de diplôme simplifiée d'un étudiant
- Parcours = nom de l'en-tête de la colonne qui contient le parcours suivi par un étudiant
affichage :
- 1 graphe avec des plots kde par Mention
- 1 graphe avec des plots kde par Parcours
sauvegarde :
- fichier graphique 'NomGraphique' avec les 2 graphes
"""
import matplotlib.gridspec as gridspec
fig = plt.figure(figsize=(18, 12))
plt.rcParams["font.size"] = (14) #font size
gs = gridspec.GridSpec(2, 1, height_ratios=[1, 1], width_ratios=[1])
fontpropertiesT = {'family':'sans-serif', 'weight' : 'bold', 'size' : 16}
fontpropertiesA = {'family':'sans-serif', 'weight' : 'bold', 'size' : 18}
sns.set_style("whitegrid")
axP=plt.subplot(gs[0,0])
hue_order = np.sort(df[Mention].unique())
figP=sns.kdeplot(data=df.sort_values(by = Mention), x=Moyenne, alpha=0.4, hue=Mention, bw_adjust=1, cut=0, fill=True, linewidth=3, hue_order=hue_order)
axP.set_xlabel("note / 20",fontsize=16)
axP.set_ylabel("Count",fontsize=16)
#axP.set_title(f"Total/Mention. <N> = {moy1:.1f}, $\sigma$ = {std1:.1f}",color = "red", font=fontpropertiesT)
axO=plt.subplot(gs[1,0])
hue_order = np.sort(df[Parcours].unique())
figO=sns.kdeplot(data=df.sort_values(by = Parcours), x=Moyenne, alpha=0.4, hue=Parcours, bw_adjust=1, cut=0, fill=True, linewidth=3, hue_order=hue_order)
axO.set_xlabel("note / 20",fontsize=16)
axO.set_ylabel("Count",fontsize=16)
axO.set_xticks(np.arange(0,19,2))
#axO.set_title(f"Total/Parcours. <N> = {moyT:.1f}, $\sigma$ = {stdT:.1f}",color = "red", font=fontpropertiesT)
fig.savefig(NomGraphique, dpi=300)
plt.show()
return
[docs]
def BoiteAMoustachesByMentionEtParcours(df, Moyenne, Mention, Parcours, NomGraphique):
import matplotlib.gridspec as gridspec
plt.style.use('seaborn-v0_8-white')
fig = plt.figure(figsize=(16, 12))
plt.rcParams["font.size"] = (14) #font size
nP = (df[Mention].unique().shape[0])
nO = (df[Parcours].unique().shape[0])
gs = gridspec.GridSpec(2, 2, height_ratios=[1,2*nO/nP], width_ratios=[1,1])
fontpropertiesT = {'family':'sans-serif', 'weight' : 'bold', 'size' : 16}
fontpropertiesA = {'family':'sans-serif', 'weight' : 'bold', 'size' : 18}
sns.set_style("whitegrid")
axP=plt.subplot(gs[:,0])
figP=sns.boxplot(data=df.sort_values(by = Mention), x=Moyenne, y=Mention, palette='tab20c')
axP.set_xlabel("note / 20",fontsize=16)
axP.set_ylabel("",fontsize=16)
axP.set_xticks(np.arange(0,20,2))
#axP.set_title(f"Total/Mention. <N> = {moy1:.1f}, $\sigma$ = {std1:.1f}",color = "red", font=fontpropertiesT)
plt.xticks(fontweight='bold',fontsize=16)
plt.yticks(fontweight='bold',fontsize=16)
axO=plt.subplot(gs[-1,-1])
figO=sns.boxplot(data=df.sort_values(by = Parcours), x=Moyenne, y=Parcours, palette='tab20c')
axO.set_xlabel("note / 20",fontsize=16)
axO.set_ylabel("",fontsize=16)
axO.yaxis.tick_right()
axO.set_xticks(np.arange(0,20,2))
plt.xticks(fontweight='bold',fontsize=16)
plt.yticks(fontweight='bold',fontsize=16)
plt.savefig(NomGraphique, dpi=300)
plt.show()
return
[docs]
def BoiteAMoustachesByMentionEtParcoursHue(df, Moyenne, Mention, Parcours, NomGraphique):
import matplotlib.gridspec as gridspec
fig = plt.figure(figsize=(18, 12))
plt.style.use('seaborn-v0_8-whitegrid')
plt.rcParams["font.size"] = (14) #font size
fontpropertiesT = {'family':'sans-serif', 'weight' : 'bold', 'size' : 16}
fontpropertiesA = {'family':'sans-serif', 'weight' : 'bold', 'size' : 18}
sns.set_style("whitegrid")
hue_order = np.sort(df[Parcours].unique())
sns.boxplot(data=df.sort_values(by = Mention), x=Moyenne, y=Mention, hue=Parcours, palette='hsv', hue_order=hue_order)
plt.xlabel("note / 20",fontsize=16)
plt.ylabel("",fontsize=16)
plt.xticks(np.arange(0,20,2))
plt.xticks(fontweight='bold',fontsize=16)
plt.yticks(fontweight='bold',fontsize=16)
plt.legend(loc='lower left')
fig.savefig(NomGraphique, dpi=300)
plt.show()
return
[docs]
def plotTauxADMasBars(dfwoSC,dfwSC,xCol,hueCol,NomGraphique):
"""
entrée :
- dfwoSC = dataframe contenant les stats ADM/AJ/Ghosts avant l'application de la seconde chance
- dfwSC = dataframe contenant les stats ADM/AJ/Ghosts après l'application de la seconde chance
- xCol = nom de la colonne qui contient les taux de réussite
- hueCol = nom de la colonne qui contient les paramètres avant ou après seconde chance
- NomGraphique = nom du fichier png qui va être sauvé
affichage :
- bar plot de seaborn
sauvegarde :
fichier graphique 'NomGraphique' au format png
"""
from matplotlib.colors import LinearSegmentedColormap
plt.style.use('seaborn-v0_8-whitegrid')
my_colors = ['#ff696b','#00aa7f']
my_cmap = LinearSegmentedColormap.from_list("mycolors",my_colors)
df = pd.concat([dfwoSC, dfwSC])
nbars=df.shape[0]
fig = plt.figure(figsize=(18, nbars*0.6))
plt.rcParams["font.size"] = (16) #font size
ax = sns.barplot(data=df,y=df.index.values,x = xCol,hue=hueCol,palette=my_colors)
ax.bar_label(ax.containers[0],fontweight='bold',fontsize=18)
ax.bar_label(ax.containers[1],fontweight='bold',fontsize=18)
plt.xticks(fontweight='bold',fontsize=14)
plt.yticks(fontweight='bold',fontsize=16)
plt.xlim(0,100)
fig.savefig(NomGraphique, dpi=300)
plt.show()
return
[docs]
def StackedBarPop(df,ListCols,ylabel,NomGraphique):
"""
entrée :
- df : dataframe contenant l'analyse statistique globale, dont les moyennes, effectifs, etc. i.e. le dataframe renvoyé par StatsRéussiteParMentionOuParcours()
- ListCols = liste avecles labels des colonnes contenant les valeurs numériques qu'on veut tracer comme un histogramme empilé
- ylabel = label de l'axe des y
- NomGraphique = nom du fichier png qui va être sauvé
affichage :
- bar plot empilé de pandas
sauvegarde :
fichier graphique 'NomGraphique' au format png
"""
from matplotlib.colors import LinearSegmentedColormap
plt.style.use('seaborn-v0_8-whitegrid')
my_colors = ['#b95651','#01bec3']
my_cmap = LinearSegmentedColormap.from_list("mycolors",my_colors)
nbars=df.shape[0]
bplot = df[ListCols].plot(kind='bar',stacked=True, figsize=(nbars*1.5,10), fontsize=16, width=0.85, colormap=my_cmap, edgecolor='black')
bplot.set_ylabel(ylabel,fontdict={'fontsize':18})
bplot.bar_label(bplot.containers[0],label_type='center',color='w',fontweight='bold',fontsize=18)
bplot.bar_label(bplot.containers[1],padding=5,fontweight='bold',fontsize=18)
bplot.legend(fontsize=20)
plt.xticks(fontweight='bold')
plt.savefig(NomGraphique, dpi=300)
plt.show()
return
[docs]
def StackedBarPopPO(dfRef,Mention,Parcours,NomGraphique):
"""
entrée :
- dfRef : dataframe contenant la liste des étudiants avec leur Mention et leur otpion, tel que généré par mentionD()
- Mention = nom de la colonne qui contient le nom simplifié d'une Mention
- Parcours = nom de la colonne qui contient la version simplifiée d'un Parcours
- NomGraphique = nom du fichier png qui va être sauvé
affichage :
- histplot "hue" de seaborn, càd les effectifs par Parcours empilés pour chaque Mention
sauvegarde :
fichier graphique 'NomGraphique' au format png
"""
plt.style.use('seaborn-v0_8-whitegrid')
nP = (dfRef[Mention].unique().shape[0])
fig = plt.figure(figsize=(nP*2,8))
hue_order = np.sort(dfRef[Parcours].unique())
hplot = sns.histplot(data=dfRef, x=Mention, hue=Parcours, multiple="stack", stat='count', palette='terrain', hue_order=hue_order)
# print(len(hplot.containers[0]))
ntot = np.zeros(len(hplot.containers[0]))
xtot = np.zeros(len(hplot.containers[0]))
# print(ntot)
for c in hplot.containers:
# print(c[0].get_height())
for i in range(nP):
# print(c[i])
x = c[i].get_x()
y = c[i].get_y()
n = c[i].get_height()
ntot[i] += n
xtot[i] = x
if (n!=0) : hplot.annotate(n, (x+0.5, y+n/2), size = 16, weight="bold",ha="center",va="center",color="black")
# print(ntot,xtot)
# print(ntot.max())
[hplot.annotate(ntot[i], (xtot[i]+0.5, ntot[i]+4), size = 20, weight="bold",ha="center",va="center",color="#407ba6") for i in range(nP)]
plt.rcParams["font.size"] = (16) #font size
plt.xticks(fontweight='bold',fontsize=16)
plt.yticks(fontweight='bold',fontsize=16)
plt.savefig(NomGraphique, dpi=300)
plt.show()
return