pyphyschemtools 0.1.0__py3-none-any.whl → 0.1.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. pyphyschemtools/.__init__.py.swp +0 -0
  2. pyphyschemtools/.ipynb_checkpoints/Chem3D-checkpoint.py +835 -0
  3. pyphyschemtools/.ipynb_checkpoints/PeriodicTable-checkpoint.py +294 -0
  4. pyphyschemtools/.ipynb_checkpoints/aithermo-checkpoint.py +349 -0
  5. pyphyschemtools/.ipynb_checkpoints/core-checkpoint.py +120 -0
  6. pyphyschemtools/.ipynb_checkpoints/spectra-checkpoint.py +471 -0
  7. pyphyschemtools/.ipynb_checkpoints/survey-checkpoint.py +1048 -0
  8. pyphyschemtools/.ipynb_checkpoints/sympyUtilities-checkpoint.py +51 -0
  9. pyphyschemtools/.ipynb_checkpoints/tools4AS-checkpoint.py +964 -0
  10. pyphyschemtools/.readthedocs.yaml +23 -0
  11. pyphyschemtools/Chem3D.py +12 -8
  12. pyphyschemtools/ML.py +6 -4
  13. pyphyschemtools/PeriodicTable.py +9 -4
  14. pyphyschemtools/__init__.py +3 -3
  15. pyphyschemtools/aithermo.py +5 -6
  16. pyphyschemtools/core.py +7 -6
  17. pyphyschemtools/spectra.py +78 -58
  18. pyphyschemtools/survey.py +0 -449
  19. pyphyschemtools/sympyUtilities.py +9 -9
  20. pyphyschemtools/tools4AS.py +12 -8
  21. {pyphyschemtools-0.1.0.dist-info → pyphyschemtools-0.1.2.dist-info}/METADATA +2 -2
  22. {pyphyschemtools-0.1.0.dist-info → pyphyschemtools-0.1.2.dist-info}/RECORD +30 -20
  23. /pyphyschemtools/{icons-logos-banner → icons_logos_banner}/Logo_pyPhysChem_border.svg +0 -0
  24. /pyphyschemtools/{icons-logos-banner → icons_logos_banner}/__init__.py +0 -0
  25. /pyphyschemtools/{icons-logos-banner → icons_logos_banner}/logo.png +0 -0
  26. /pyphyschemtools/{icons-logos-banner → icons_logos_banner}/tools4pyPC_banner.png +0 -0
  27. /pyphyschemtools/{icons-logos-banner → icons_logos_banner}/tools4pyPC_banner.svg +0 -0
  28. {pyphyschemtools-0.1.0.dist-info → pyphyschemtools-0.1.2.dist-info}/WHEEL +0 -0
  29. {pyphyschemtools-0.1.0.dist-info → pyphyschemtools-0.1.2.dist-info}/licenses/LICENSE +0 -0
  30. {pyphyschemtools-0.1.0.dist-info → pyphyschemtools-0.1.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,964 @@
1
+ __author__ = "Romuald POTEAU"
2
+ __maintainer__ = "Romuald POTEAU"
3
+ __email__ = "romuald.poteau@univ-tlse3.fr"
4
+ __status__ = "Development"
5
+
6
+ ####################################################################################################################################
7
+ # F E U I L L E S D E S T Y L E
8
+ ####################################################################################################################################
9
+
10
+ from .visualID_Eng import fg, hl, bg
11
+
12
+
13
+ from IPython.display import HTML
14
+
15
+ def css_styling():
16
+ styles = open("./tools4AS.css", "r").read()
17
+ return HTML(styles)
18
+
19
+
20
+ ####################################################################################################################################
21
+ # F O N C T I O N S M A I S O N
22
+ ####################################################################################################################################
23
+
24
+ import numpy as np
25
+ import matplotlib.pyplot as plt
26
+ # importation de la libairie pandas
27
+ import pandas as pd
28
+ import dataframe_image as dfim
29
+ import seaborn as sns
30
+
31
+ def verifNotes(dfCC,labelNoteCC,nomCC,NI,absents='aabs'):
32
+ """
33
+ Args:
34
+ - dfCC = dataframe dont 1 colonne contient les notes de CC
35
+ - labelNoteCC = label de la colonne qui contient les notes
36
+ - nomCC = label de l'épreuve de CC (ex CC1), utilisé pour l'affichage
37
+ - NI = nombre d'inscrits dans le module
38
+ - absents:
39
+ * 'aabs' : analyser s'il y a des étudiants qui n'ont pas été pointés au CC (défaut)
40
+ * 'noabs' : ne pas analyser s'il y a des étudiants qui n'ont pas été pointés au CC
41
+ (ça n'a plus de sens une fois les fichiers concaténés)
42
+
43
+ Returns:
44
+ - la moyenne et la déviation standard de liste de notes contenues dans le dataframe dfCC
45
+ - le nombre d'étudiants qui n'ont pas composé au CC
46
+
47
+ Note:
48
+ Affiche le nombre d'étudiants avec le label 'ABS' et signale les
49
+ étudiants sans note ni label, ce qui nécessite une vérification du PV.
50
+
51
+ """
52
+ print()
53
+ #pd.set_option("display.max_rows", len(dfCC))
54
+ #display(dfCC[labelNoteCC])
55
+ if (absents == 'aabs'):
56
+ nABS = ((dfCC[labelNoteCC] == "ABS") | (dfCC[labelNoteCC] == "abs") | (dfCC[labelNoteCC] == "Abs") ).sum()
57
+ print(f"{hl.BOLD}{fg.BLUE}Etudiants de {nomCC} avec label 'ABS' = {nABS}{fg.OFF}")
58
+ nEMPTY = (dfCC[labelNoteCC].isna()).sum()
59
+ print(f"{hl.BOLD}{fg.BLUE}Etudiants de {nomCC} sans label ni note = {nEMPTY}{fg.OFF}")
60
+ if ((nEMPTY != 0) & (absents == 'aabs')):
61
+ print(f"{fg.RED}{hl.BOLD}Attention !!! Ça n'est pas normal. Vérifier la liste d'appel{fg.OFF}")
62
+ #pandas.to_numeric(arg, errors='raise', downcast=None)
63
+ #Convert argument to a numeric type
64
+ #errors{‘ignore’, ‘raise’, ‘coerce’}, default ‘raise’
65
+ # If ‘raise’, then invalid parsing will raise an exception.
66
+ # If ‘coerce’, then invalid parsing will be set as NaN.
67
+ # If ‘ignore’, then invalid parsing will return the input.
68
+ nCC_Absents = pd.to_numeric(dfCC[labelNoteCC], errors='coerce').isna().values.sum()
69
+ nCC_Notes = (~pd.to_numeric(dfCC[labelNoteCC], errors='coerce').isna()).values.sum()
70
+ av = pd.to_numeric(dfCC[labelNoteCC], errors='coerce').mean()
71
+ std = pd.to_numeric(dfCC[labelNoteCC], errors='coerce').std()
72
+ 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}")
73
+ print(f"Somme des copies notées & des absents = {nCC_Notes + nCC_Absents}")
74
+ print(f"{NI - nCC_Notes}/{NI} étudiants n'ont pas composé au {nomCC}")
75
+ print(f"{hl.BOLD}Moyenne = {av:.1f}, Écart-type = {std:.1f}{fg.OFF}")
76
+ return av, std, (NI - nCC_Notes)
77
+
78
+
79
+ def RenameDfHeader(dfCC,dfCCname,labelNoteCC,nomCC):
80
+ """
81
+ entrée :
82
+ - dfCC = dataframe dont 1 colonne contient les notes de CC
83
+ - dfCCname = nom (string) du dataframe. Il est recommandé d'utiliser f'{dfCC=}'.split('=')[0]
84
+ - labelNoteCC = label de la colonne qui contient les notes
85
+ - nomCC = label de l'épreuve de CC (ex CC1), utilisé pour l'affichage
86
+
87
+ sortie : aucune
88
+
89
+ la fonction change le nom 'labelNoteCC' en 'nomCC'
90
+
91
+ """
92
+ print(f"{hl.BOLD}{fg.BLUE}Normalisation du nom des colonnes de notes{fg.OFF}")
93
+ print(f"{hl.BOLD}Dataframe = {dfCCname}.{fg.OFF} {labelNoteCC} --> {nomCC}")
94
+ dfCC.rename(columns = {labelNoteCC:nomCC}, inplace=True)
95
+
96
+ def mentionD(row, Mention):
97
+ rowV = row[Mention]
98
+ CHIMIE1 = "L1 CHI"
99
+ CHIMIE2 = "L2 CHI"
100
+ PHYSIQUE1 = "L1 PHY"
101
+ PHYSIQUE2 = "L2 PHY"
102
+ PHYSIQUE3 = "L3 PHY"
103
+ MATHS1 = "L1 MAT"
104
+ MATHS2 = "L2 MAT"
105
+ MATHS3 = "L2 MAT"
106
+ MECA = "L1 MECA"
107
+ MIASHS = "L1 MIASHS"
108
+ EEA = "L1 EEA"
109
+ INFO1 = "L1 INFO"
110
+ INFO2 = "L2 INFO"
111
+ PC1 = "L1 PHYSIQUE CHIMIE"
112
+ PC2 = "L1 PC"
113
+ GC1 = "L1 GC"
114
+ GC2 = "L1 GENIE CIVIL"
115
+ MOBINT = "MOBILITE INTERNAT"
116
+ DUMMY = "DUMMY"
117
+ if (CHIMIE1 in rowV) | (CHIMIE2 in rowV):
118
+ return "CHIMIE"
119
+ elif ((PHYSIQUE1 in rowV) | (PHYSIQUE2 in rowV) | (PHYSIQUE3 in rowV)) & ~(PC1 in rowV):
120
+ return "PHYSIQUE"
121
+ elif (MATHS1 in rowV) | (MATHS2 in rowV) | (MATHS3 in rowV):
122
+ return "MATHS"
123
+ elif MIASHS in rowV:
124
+ return "MIASHS"
125
+ elif MECA in rowV:
126
+ return "MECA"
127
+ elif (INFO1 in rowV) | (INFO2 in rowV):
128
+ return "INFO"
129
+ elif EEA in rowV:
130
+ return "EEA"
131
+ elif (GC1 in rowV) | (GC2 in rowV):
132
+ return "GC"
133
+ elif (PC1 in rowV) | (PC2 in rowV):
134
+ return "PC"
135
+ elif MOBINT in rowV:
136
+ return "MobInt"
137
+ elif DUMMY in rowV:
138
+ return "DUMMY"
139
+ else:
140
+ print(f"Quelle est cette Mention ? {rowV}")
141
+ return
142
+
143
+ def parcours(row, Parcours):
144
+ rowV = row[Parcours]
145
+ CUPGE = "CUPGE"
146
+ SANTE = "OPT° SANTE"
147
+ ACCOMP = "ACCOMP"
148
+ DUMMY = "DUMMY"
149
+ if CUPGE in rowV:
150
+ return "CUPGE"
151
+ elif SANTE in rowV:
152
+ return "SANTE"
153
+ elif ACCOMP in rowV:
154
+ return "ACCOMPAGNEMENT"
155
+ elif DUMMY in rowV:
156
+ return "DUMMY"
157
+ else:
158
+ # print(f"Pas de parcours dans la mention {rowV}")
159
+ return "Standard"
160
+ return
161
+
162
+ def MentionAuModule(note, Seuil):
163
+ """
164
+ entrée :
165
+ - note = valeur numérique ou NaN
166
+ - Seuil = seuil de réussite
167
+ sortie :
168
+ - 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)
169
+ """
170
+ if (note >=0) and (note < Seuil):
171
+ m = "AJ"
172
+ elif (note >=10) and (note < 12):
173
+ m = "P"
174
+ elif (note>=12) and (note < 14):
175
+ m = "AB"
176
+ elif (note >= 14) and (note <16):
177
+ m = "B"
178
+ elif (note >=16) and (note <= 20) :
179
+ m = "TB"
180
+ elif (np.isnan(note)):
181
+ m = np.NaN
182
+ else:
183
+ print(note,"PB")
184
+ m = 'PB!!'
185
+ return m
186
+
187
+ def concat2ApoG(df2Complete, ID_ApoG, dfCC, Col2SaveInCC, IDCC, nomCC):
188
+ """
189
+ entrée :
190
+ - df2Complete = dataframe à compléter (merge = 'left')
191
+ - au premier appel, ce soit être le fichier de Référence
192
+ - ensuite c'est le fichier de notes lui-même, en cours d'update
193
+ - ID_ApoG = label de la colonne qui contient les numéros étudiants dans le fichier de référence
194
+ - dfCC = dataframe dont 1 colonne contient les notes de CC
195
+ - Col2SaveInCC = liste avec les en-têtes de colonnes qui contiennent les colonnes de dfCC à reporter dans dfNotes
196
+ - IDCC = label de la colonne qui contient les numéros étudiants dans le fichier de notes
197
+ - 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
198
+
199
+ sortie :
200
+ - le dataframe dfNotes. Contient la version concaténée du dataframe d'entrée df2complete et certaines colonnes du dataframe des notes dfCC
201
+ ([Col2SaveInCC + IDCC + nomCC])
202
+ - 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
203
+
204
+ affichages :
205
+ - liste des étudiants qui sont dans le fichier de notes et pas dans le fichier de référence
206
+ """
207
+
208
+ #display(df2Complete)
209
+ dfNotes = df2Complete.copy()
210
+
211
+ pd.set_option('display.max_rows', 12)
212
+ #print(f"{hl.BOLD}{fg.BLUE}Fichier de notes copié de l'export Apogée{fg.OFF}")
213
+ #display(dfNotes)
214
+ Col2SaveInCCExt = Col2SaveInCC.copy()
215
+ Col2SaveInCCExt.extend([IDCC])
216
+ Col2SaveInCCExt.extend([nomCC])
217
+ dfNotes = dfNotes.merge(dfCC[Col2SaveInCCExt], left_on=ID_ApoG, right_on=IDCC, how='left')
218
+ print(f"{hl.BOLD}{fg.BLUE}{nomCC} ajouté dans fichier de notes{fg.OFF}")
219
+ print(f"Les colonnes qui ont été ajoutées sont : {Col2SaveInCCExt}")
220
+ #display(dfNotes)
221
+
222
+ dfNotesTmp = df2Complete.copy()
223
+ dfNotesOuter = dfNotesTmp.merge(dfCC[Col2SaveInCCExt], left_on=ID_ApoG, right_on=IDCC, how='outer')
224
+ #display(dfNotesOuter)
225
+
226
+ dfnotFoundInReftmp = dfNotesOuter[dfNotesOuter[ID_ApoG].isna()]
227
+ if dfnotFoundInReftmp.shape[0] != 0:
228
+ print(f"{hl.BOLD}{fg.RED}Problème !! Ces étudiants du {nomCC} ne sont pas dans le dataframe de Référence{fg.OFF}")
229
+ print(f"{hl.BOLD}{fg.RED}Ils ne sont pas rajoutés dans ce dataframe, mais dans un dataframe dfnotFoundInRef{fg.OFF}")
230
+ display(dfnotFoundInReftmp)
231
+ else:
232
+ print(f"{hl.BOLD}{fg.GREEN}Tous les étudiants sont bien dans le dataframe de Référence{fg.OFF}")
233
+
234
+ return dfNotes, dfnotFoundInReftmp
235
+
236
+ def checkNoID_DuplicateID(df, dfname, ID, nom, NomPrenom):
237
+ import numpy as np
238
+ """
239
+ entrée :
240
+ - df = dataframe à analyser
241
+ - dfname = nom (string) du dataframe à analyser. Il est recommandé d'utiliser f'{dfCC=}'.split('=')[0]
242
+ - ID = label de la colonne qui contient les numéros étudiants dans df
243
+ - nom = label du dataframe, utilisé pour l'affichage
244
+ - NomPrenom = liste avec les en-têtes de colonnes qui contiennent les noms et les prénoms dans df
245
+
246
+ affichage : diagnostic et éventuellement la liste des étudiants sans numéros d'étudiant
247
+ """
248
+ print(f"{hl.BOLD}{fg.BLUE}Dataframe {dfname} (alias {nom}){fg.OFF}")
249
+ noID = df[df[ID].isnull()]
250
+ if (noID.shape[0] != 0):
251
+ print(f"{hl.BOLD}{fg.RED}Etudiants sans ID !{fg.OFF}")
252
+ display(noID.sort_values(by=NomPrenom[0], ascending = True))
253
+ else:
254
+ print(f"{hl.BOLD}{fg.GREEN}Etudiants sans ID ? Pas de problème{fg.OFF}")
255
+
256
+ values, counts = np.unique(df[ID].to_numpy(), return_counts=True)
257
+
258
+ duplicateID = False
259
+ for c in counts:
260
+ if c != 1: duplicateID = True
261
+ if (not duplicateID):
262
+ print(f"{hl.BOLD}{fg.GREEN}Doublon sur les ID ? Pas de problème{fg.OFF}")
263
+ else:
264
+ print(f"{hl.BOLD}{fg.RED}ID en double!{fg.OFF}")
265
+ for i, c in enumerate(counts):
266
+ if c != 1: print(f"{values[i]} x {c}")
267
+ return
268
+
269
+ def read_excel(xlFile,decimal,name):
270
+ """
271
+ entrée :
272
+ - xlFile = nom du ficher excel
273
+ - decimal = "." ou "," selon le cas
274
+ - name = label du dataframe, utilisé pour l'affichage
275
+ sortie :
276
+ - le dataframe qui contient l'intégralité du fichier excel
277
+ - le nombre de lignes de ce tableau (à l'exclusion de l'en-tête des colonnes)
278
+ affichage :
279
+ - statistiques descriptives (describe) de toutes les colonnes, y compris celles qui ne contiennent pas de valeurs numériques
280
+ """
281
+ print(f"{hl.BOLD}{fg.BLUE}{name}{fg.OFF}")
282
+ print(f"Reading... {xlFile}")
283
+ df=pd.read_excel(xlFile,decimal=decimal)
284
+
285
+ #pd.set_option('display.max_rows', 12)
286
+ #display(dfCC1)
287
+ display(df.describe(include='all'))
288
+ return df, df.shape[0]
289
+
290
+ def ReplaceABSByNan(df,nomCC):
291
+ """
292
+ entrée :
293
+ - dataframe qui contient les notes
294
+ - nom des colonnes qui contiennent les notes
295
+ sortie
296
+ - nouveau dataframe où toutes les notes des colonnes nomCC = ABS sont remplacées par des nan
297
+ """
298
+ dfcopy = df.copy()
299
+ for nom in nomCC:
300
+ # correction introduite le 24/01/2026
301
+ # dfcopy[nom].mask((dfcopy[nom] == "ABS") | (dfcopy[nom] == "abs") | (dfcopy[nom] == "Abs"), np.nan ,inplace=True)
302
+ # On convertit en chaîne, on met en majuscules, et on compare à "ABS"
303
+ # L'assignation directe (df[nom] = ...) évite le ChainedAssignmentError
304
+ dfcopy[nom] = dfcopy[nom].mask(dfcopy[nom].astype(str).str.upper() == "ABS", np.nan)
305
+ return dfcopy
306
+
307
+ # deprecated à cause de la nouvelle version de pandas (26/01/2026)
308
+ def ReplaceNanBy0OLD(df,nomCC):
309
+ """
310
+ entrée :
311
+ - df = dataframe avec les notes
312
+ - nomCC = liste des en-têtes de colonnes qui contiennt les notes
313
+ sortie :
314
+ - le dataframe avec Nan remplacé par 0 pour chaque étudiant qui a au moins une note de CC
315
+ """
316
+ dfCopy = df.copy()
317
+ for nom in nomCC:
318
+ nomCCred = nomCC.copy()
319
+ nomCCred.remove(nom)
320
+ mask = pd.DataFrame([False]*dfCopy.shape[0],index=dfCopy.index,columns=["mask"])
321
+ for nomred in nomCCred:
322
+ mask["mask"] = (mask["mask"] | dfCopy[nomred].notnull())
323
+ mask["mask"] = (mask["mask"] & dfCopy[nom].isna())
324
+ # correction le 24/01/2026
325
+ # dfCopy[nom].mask(mask["mask"],0.0,inplace=True)
326
+ dfCopy[nom] = dfCopy[nom].mask(mask["mask"], 0.0)
327
+ return dfCopy
328
+
329
+ def ReplaceNanBy0(df, nomCC):
330
+ dfCopy = df.copy()
331
+ for nom in nomCC:
332
+ # 1. On identifie les autres colonnes
333
+ autres_cols = [c for c in nomCC if c != nom]
334
+
335
+ # 2. On crée le masque :
336
+ # (Au moins une autre note n'est pas nulle) ET (La note actuelle est nulle)
337
+ condition = dfCopy[autres_cols].notnull().any(axis=1) & dfCopy[nom].isna()
338
+
339
+ # 3. Application directe (On s'assure que la colonne accepte les flottants)
340
+ dfCopy[nom] = dfCopy[nom].astype(float)
341
+ dfCopy[nom] = dfCopy[nom].mask(condition, 0.0)
342
+
343
+ return dfCopy
344
+
345
+ def dropColumnsByIndex(df,idropC):
346
+ """
347
+ entrée :
348
+ - df = dataframe dont on veut supprimer des colonnes
349
+ - idropC = indices des colonnes dont on veut se débarasser
350
+ sortie :
351
+ - dfCleaned = dataframe originel dont les colonnes indexées par idropC ont été supprimées
352
+ """
353
+ listC = list(df.columns)
354
+ dropC = [listC[idropC[i]] for i in range(len(idropC))]
355
+ print(f"On va se débarrasser des colonnes {dropC}")
356
+ dfCleaned = df.drop(dropC,axis=1)
357
+ return dfCleaned
358
+
359
+ def CreationDfADMAJGH(df,note,Seuil,prefix,MD,Parc,IDApoG):
360
+ """
361
+ entrée :
362
+ - df = dataframe avec les mentions/parcours/moyennes
363
+ - note = nom de la colonne qui contient la moyenne globale
364
+ - Seuil = seuil de réussite pour départager ADM & AJ
365
+ - prefix = préfixe du nom de fichier temporaire, incluant ou nom le nom d'un sous-répertoire de sauvegarde
366
+ - MD = nom de la colonne qui contient la dénomination simplifiée de la mention de diplôme ("MentionD")
367
+ - Parc = nom de la colonne qui contient le parcours
368
+ - IDApoG = nom de la colonne qui contient l'ID des étudiants
369
+
370
+ sortie :
371
+ - dfADM = dataframe des admis
372
+ - dfAJ = dataframe des ajournés
373
+ - dfGhosts = dataframe des fantômes (aka Ghosts ; i.e. n'ont passé aucun des CC)
374
+
375
+ affichage : stats rapides (describe & sum) de chacun des sous-ensembles ADM, AJ, Ghosts
376
+
377
+ sauvegarde : fichier excel tmp{Note}.xlsx avec 3 onglets (ADM, AJ, Ghosts)
378
+
379
+ """
380
+ print(f"{hl.BOLD}{bg.LIGHTRED}Construction des dataframes avec les ADM & les AJ sur la base de la {note} >= ou < à {Seuil}{fg.OFF}")
381
+ 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}")
382
+ #dataframe reçus
383
+ dfADM = df[df[note]>=Seuil]
384
+ #dataframe ajournés
385
+ dfAJ = df[df[note]<Seuil]
386
+ #dataframe fantômes
387
+ dfGhosts = df[df[note].isnull()]
388
+
389
+ Ftmp = prefix+'tmp'+note+'.xlsx'
390
+
391
+ exceltest = pd.ExcelWriter(Ftmp, engine='xlsxwriter')
392
+ dfADM.to_excel(exceltest, sheet_name='ADM')
393
+ dfAJ.to_excel(exceltest, sheet_name='AJ')
394
+ dfGhosts.to_excel(exceltest, sheet_name='Fantomes')
395
+ exceltest.close()
396
+
397
+ print(f"{hl.BOLD}{fg.BLUE}Total{fg.OFF}")
398
+ display(df.groupby(MD)[note].describe().style.format("{0:.1f}"))
399
+ print(f"{hl.BOLD}{fg.BLUE}Admis{fg.OFF}")
400
+ display(dfADM.groupby(MD)[note].describe().style.format("{0:.1f}"))
401
+ print(f"{hl.BOLD}{fg.BLUE}Ajournés{fg.OFF}")
402
+ display(dfAJ.groupby(MD)[note].describe().style.format("{0:.1f}"))
403
+
404
+ print(f"{hl.BOLD}{fg.BLUE}Total{fg.OFF}")
405
+ display(df.groupby(MD)[note].describe().sum())
406
+ print(f"{hl.BOLD}{fg.BLUE}Admis{fg.OFF}")
407
+ display(dfADM.groupby(MD)[note].describe().sum())
408
+ print(f"{hl.BOLD}{fg.BLUE}Ajournés{fg.OFF}")
409
+ display(dfAJ.groupby(MD)[note].describe().sum())
410
+ print(f"{hl.BOLD}{fg.BLUE}Fantômes{fg.OFF}")
411
+ display(dfGhosts.groupby(MD)[IDApoG].count())
412
+ return dfADM, dfAJ, dfGhosts
413
+
414
+ def StatsRéussiteParMentionOuParcours(dfT, dfADM, dfAJ, dfGhosts, Category, note):
415
+ """
416
+ entrée :
417
+ - dfT = dataframe qui contient toutes les notes, ainsi qu'au moins une catégorisation (exemple : Mention ou Parcours ou Section etc...)
418
+ - dfADM = dataframe qui contient uniquement les étudiants admis
419
+ - dfAJ = dataframe qui contient uniquement les étudiants ajournés (sans les fantômes)
420
+ - dfGhosts = dataframe qui contient la liste des fantômes
421
+ - Category = nom de la colonne sur laquelle on veut faire des analyses statistiques
422
+ - note = nom de la colonne qui contient la note qu'on veut analyser par catégorie (généralement la moyenne finale)
423
+
424
+ sortie :
425
+ - dfStats
426
+
427
+ affichage :
428
+ """
429
+ def incrementer(row,Category,Catunique,note,NN,Presents="Presents"):
430
+ """
431
+ conçu pour pour une analyse ligne par ligne
432
+ si Category = une des catégories de Catunique NN[de cette catégorie] +=1
433
+ nécessite au préalable
434
+ - de fabriquer la liste des catégories uniques (df[Category].unique())
435
+ - d'initialiser à 0 un tableau qui a la même dimension que Catunique
436
+
437
+ entrée :
438
+ - row = ligne d'un dataframe à analyser
439
+ - Category = nom de la colonne qui contient les catégories à comptabiliser
440
+ - Catunique = liste exhaustive des catégories du dataframe analysé
441
+ - note = nom de la colonne qui contient les notes
442
+ - NN = tableau dont la colonne qui correspond à l'une des catégories uniques pour cet étudiant est incrémenté de 1
443
+ - Presents (valeur par défaut = "Present") : compte les présents uniquement
444
+ sinon ce sont les fantômes qui sont comptés
445
+
446
+ """
447
+ i = 0
448
+ for C in Catunique:
449
+ if (((row[Category] == C) & (not np.isnan(row[note]))) & (Presents=="Presents")) |\
450
+ (((row[Category] == C) & (np.isnan(row[note]))) & (Presents!="Presents")):
451
+ NN[i] +=1
452
+ #print(i,row[CategoryInRow],C,NN[i],row[note],np.isnan(row[note]))
453
+ i += 1
454
+ return
455
+
456
+ print(f"{hl.BOLD}-- Statistiques uniquement sur les présents --{fg.OFF}")
457
+ CatUnique = dfT[Category].unique()
458
+ print(f"{Category} = {CatUnique}")
459
+ # création des tableaux pour comptabiliser les reçus (NRP), ajournés (NAJP) par parcours
460
+ # NTP contient le nombre total d'étudiants par parcours
461
+ NT = np.zeros(len(CatUnique))
462
+ NADM = np.zeros(len(CatUnique))
463
+ NAJ = np.zeros(len(CatUnique))
464
+ NGH = np.zeros(len(CatUnique))
465
+ dfT.apply(lambda row: incrementer(row, Category, CatUnique, note, NT), axis=1)
466
+ dfADM.apply(lambda row: incrementer(row, Category, CatUnique, note, NADM), axis=1)
467
+ dfAJ.apply(lambda row: incrementer(row, Category, CatUnique, note, NAJ), axis=1)
468
+ dfGhosts.apply(lambda row: incrementer(row, Category, CatUnique, note, NGH, "Fantomes"), axis=1)
469
+ dfADMAJ = pd.concat([dfADM,dfAJ])
470
+ # display(dfADMAJ.describe(include='all'))
471
+ # print(dfADMAJ[Moyenne].mean())
472
+
473
+ print(f" NT/Cat = {NT}")
474
+ print(f"{fg.GREEN}NADM/Cat = {NADM}{fg.OFF}")
475
+ print(f"{fg.RED} NAJ/Cat = {NAJ}{fg.OFF}")
476
+ print(f"{fg.LIGHTGRAY} NGH/Cat = {NGH}{fg.OFF}")
477
+
478
+ print(f"{hl.BOLD}{fg.GREEN}Reçus par {Category}{fg.OFF}")
479
+ i = 0
480
+ nADM = 0
481
+ nAJ = 0
482
+ nTOT = 0
483
+ nGH = 0
484
+ Moy = np.zeros(len(CatUnique))
485
+ StD = np.zeros(len(CatUnique))
486
+ Med = np.zeros(len(CatUnique))
487
+ MoyGlob = np.zeros(len(CatUnique))
488
+ MoyGlobT = 0
489
+ for C in CatUnique:
490
+ 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},"\
491
+ 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}")
492
+ # display(dfADM.loc[dfADM[Category] == C][note].sum())
493
+ Moy[i] = (dfADM.loc[dfADM[Category] == C][note].sum()+dfAJ.loc[dfAJ[Category] == C][note].sum())
494
+ MoyGlob[i] = Moy[i]
495
+ MoyGlobT += Moy[i]
496
+ Moy[i] = np.round(Moy[i] / (NADM[i]+NAJ[i]),2)
497
+ MoyGlob[i] = np.round(MoyGlob[i] / (NADM[i]+NAJ[i]+NGH[i]),2)
498
+ StD[i] = np.round(dfADMAJ.loc[dfADMAJ[Category] == C][note].std(),1)
499
+ Med[i] = np.round(dfADMAJ.loc[dfADMAJ[Category] == C][note].median(),1)
500
+ # print("Moyennes ",Moy[i],MoyGlob[i])
501
+ nADM += NADM[i]
502
+ nAJ += NAJ[i]
503
+ nGH += NGH[i]
504
+ nTOT += NT[i]
505
+ i+=1
506
+ print(f"{hl.BOLD}{fg.GREEN} ADM = {nADM:3.0f}{fg.OFF}")
507
+ print(f"{hl.BOLD}{fg.RED} AJ = {nAJ:3.0f}{fg.OFF}")
508
+ print(f"{hl.BOLD}{fg.BLACK} TOT = {nTOT:3.0f}{fg.OFF}")
509
+ print(f"{hl.BOLD}{fg.LIGHTGRAY}(+Fantomes = {nGH:3.0f}){fg.OFF}")
510
+ MoyT = np.round(dfADMAJ[note].mean(),2)
511
+ StDT = np.round(dfADMAJ[note].std(),1)
512
+ MedT = np.round(dfADMAJ[note].median(),1)
513
+ MoyGlobT = np.round(MoyGlobT/(nTOT+nGH),2)
514
+ 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]
515
+ defCol = ["Présents","ADM","AJ","Taux ADM (présents)","Moy.","StDev","Med.","Fantômes","Total","Taux ADM (tot.)","Moy."]
516
+ 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)
517
+ rowTotal = pd.DataFrame([rowTotal],index=["Total"],columns=dfStats.columns)
518
+ dfStats=pd.concat([dfStats,rowTotal])
519
+ dfStats.style.set_caption(f"Statistiques par {Category}")
520
+ return dfStats
521
+
522
+ def StatsRéussiteParMentionOuParcoursWithAb(dfT, dfADM, dfAJ, dfGhosts, dfAb, Category, note):
523
+ """
524
+ entrée :
525
+ - dfT = dataframe qui contient toutes les notes, ainsi qu'au moins une catégorisation (exemple : Mention ou Parcours ou Section etc...)
526
+ - dfADM = dataframe qui contient uniquement les étudiants admis
527
+ - dfAJ = dataframe qui contient uniquement les étudiants ajournés (sans les fantômes)
528
+ - dfGhosts = dataframe qui contient la liste des fantômes
529
+ - dfAb = dataframe qui contient la liste des étudiants qui ont abandonné
530
+ - Category = nom de la colonne sur laquelle on veut faire des analyses statistiques
531
+ - note = nom de la colonne qui contient la note qu'on veut analyser par catégorie (généralement la moyenne finale)
532
+
533
+ sortie :
534
+ - dfStats
535
+
536
+ affichage :
537
+ """
538
+ def incrementer(row,Category,Catunique,note,NN,Presents="Presents"):
539
+ """
540
+ conçu pour pour une analyse ligne par ligne
541
+ si Category = une des catégories de Catunique NN[de cette catégorie] +=1
542
+ nécessite au préalable
543
+ - de fabriquer la liste des catégories uniques (df[Category].unique())
544
+ - d'initialiser à 0 un tableau qui a la même dimension que Catunique
545
+
546
+ entrée :
547
+ - row = ligne d'un dataframe à analyser
548
+ - Category = nom de la colonne qui contient les catégories à comptabiliser
549
+ - Catunique = liste exhaustive des catégories du dataframe analysé
550
+ - note = nom de la colonne qui contient les notes
551
+ - NN = tableau dont la colonne qui correspond à l'une des catégories uniques pour cet étudiant est incrémenté de 1
552
+ - Presents (valeur par défaut = "Present") : compte les présents uniquement
553
+ sinon ce sont les fantômes qui sont comptés
554
+
555
+ """
556
+ i = 0
557
+ for C in Catunique:
558
+ if (((row[Category] == C) & (not np.isnan(row[note]))) & (Presents=="Presents")) |\
559
+ (((row[Category] == C) & (np.isnan(row[note]))) & (Presents!="Presents")):
560
+ NN[i] +=1
561
+ #print(i,row[CategoryInRow],C,NN[i],row[note],np.isnan(row[note]))
562
+ i += 1
563
+ return
564
+ def incrementerAbandons(row,Category,Catunique,NN):
565
+ i = 0
566
+ for C in Catunique:
567
+ if (row[Category] == C):
568
+ NN[i] +=1
569
+ i += 1
570
+ return
571
+
572
+ print(f"{hl.BOLD}-- Statistiques uniquement sur les présents --{fg.OFF}")
573
+ CatUnique = dfT[Category].unique()
574
+ print(f"{Category} = {CatUnique}")
575
+ # création des tableaux pour comptabiliser les reçus (NRP), ajournés (NAJP) par parcours
576
+ # NTP contient le nombre total d'étudiants par parcours
577
+ NT = np.zeros(len(CatUnique))
578
+ NADM = np.zeros(len(CatUnique))
579
+ NAJ = np.zeros(len(CatUnique))
580
+ NAb = np.zeros(len(CatUnique))
581
+ NGH = np.zeros(len(CatUnique))
582
+ dfT.apply(lambda row: incrementer(row, Category, CatUnique, note, NT), axis=1)
583
+ dfADM.apply(lambda row: incrementer(row, Category, CatUnique, note, NADM), axis=1)
584
+ dfAJ.apply(lambda row: incrementer(row, Category, CatUnique, note, NAJ), axis=1)
585
+ dfAb.apply(lambda row: incrementerAbandons(row, Category, CatUnique, NAb), axis=1)
586
+ dfGhosts.apply(lambda row: incrementer(row, Category, CatUnique, note, NGH, "Fantomes"), axis=1)
587
+ dfADMAJ = pd.concat([dfADM,dfAJ])
588
+ # display(dfADMAJ.describe(include='all'))
589
+ # print(dfADMAJ[Moyenne].mean())
590
+
591
+ print(f" NT/Cat = {NT}")
592
+ print(f"{fg.GREEN}NADM/Cat = {NADM}{fg.OFF}")
593
+ print(f"{fg.RED} NAJ/Cat = {NAJ}{fg.OFF}")
594
+ print(f"{fg.RED} NAb/Cat = {NAb}{fg.OFF}")
595
+ print(f"{fg.LIGHTGRAY} NGH/Cat = {NGH}{fg.OFF}")
596
+
597
+ print(f"{hl.BOLD}{fg.GREEN}Reçus par {Category}{fg.OFF}")
598
+ i = 0
599
+ nADM = 0
600
+ nAJ = 0
601
+ nAb = 0
602
+ nTOT = 0
603
+ nGH = 0
604
+ Moy = np.zeros(len(CatUnique))
605
+ StD = np.zeros(len(CatUnique))
606
+ Med = np.zeros(len(CatUnique))
607
+ MoyGlob = np.zeros(len(CatUnique))
608
+ MoyGlobT = 0
609
+ for C in CatUnique:
610
+ 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},"\
611
+ 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}")
612
+ # display(dfADM.loc[dfADM[Category] == C][note].sum())
613
+ Moy[i] = (dfADM.loc[dfADM[Category] == C][note].sum()+dfAJ.loc[dfAJ[Category] == C][note].sum())
614
+ MoyGlob[i] = Moy[i]
615
+ MoyGlobT += Moy[i]
616
+ Moy[i] = np.round(Moy[i] / (NADM[i]+NAJ[i]),2)
617
+ MoyGlob[i] = np.round(MoyGlob[i] / (NADM[i]+NAJ[i]+NGH[i]),2)
618
+ StD[i] = np.round(dfADMAJ.loc[dfADMAJ[Category] == C][note].std(),1)
619
+ Med[i] = np.round(dfADMAJ.loc[dfADMAJ[Category] == C][note].median(),1)
620
+ # print("Moyennes ",Moy[i],MoyGlob[i])
621
+ nADM += NADM[i]
622
+ nAJ += NAJ[i]
623
+ nAb += NAb[i]
624
+ nGH += NGH[i]
625
+ nTOT += NT[i]
626
+ i+=1
627
+ print(f"{hl.BOLD}{fg.GREEN} ADM = {nADM:3.0f}{fg.OFF}")
628
+ print(f"{hl.BOLD}{fg.RED} AJ = {nAJ:3.0f}{fg.OFF}")
629
+ print(f"{hl.BOLD}{fg.RED} dont Ab = {nAb:3.0f} (= abandons){fg.OFF}")
630
+ print(f"{hl.BOLD}{fg.BLACK} TOT = {nTOT:3.0f}{fg.OFF}")
631
+ print(f"{hl.BOLD}{fg.LIGHTGRAY}(+Fantomes = {nGH:3.0f}){fg.OFF}")
632
+ MoyT = np.round(dfADMAJ[note].mean(),2)
633
+ StDT = np.round(dfADMAJ[note].std(),1)
634
+ MedT = np.round(dfADMAJ[note].median(),1)
635
+ MoyGlobT = np.round(MoyGlobT/(nTOT+nGH),2)
636
+ 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]
637
+ defCol = ["Présents","ADM","AJ","dont Ab","Taux ADM (présents)","Moy.","StDev","Med.","Fantômes","Total","Taux ADM (tot.)","Moy."]
638
+ 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)
639
+ rowTotal = pd.DataFrame([rowTotal],index=["Total"],columns=dfStats.columns)
640
+ dfStats=pd.concat([dfStats,rowTotal])
641
+ dfStats.style.set_caption(f"Statistiques par {Category}")
642
+ return dfStats
643
+
644
+ def ApplySecondeChance(df,nomCC,nomCCSC,nomCCdelaSC):
645
+ """
646
+ modification du dataframe df
647
+
648
+ entrée :
649
+ - 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
650
+ - nomCC = nom de la colonne à laquelle on applique la seconde chance
651
+ - nomCCSC = nom de la nouvelle colonne qui contient la note du CC après application de la seconde chance
652
+ - nomCCdelaSC = nom de la colonne qui contient la note "seconde chance
653
+ sortie :
654
+ - moyenne après application de la seconde chance
655
+ - écart-type après application de la seconde chance
656
+ """
657
+ df[nomCCSC] = df[[nomCC,nomCCdelaSC]].max(axis=1)
658
+ moySC = pd.to_numeric(df[nomCCSC], errors='coerce').mean()
659
+ stdSC = pd.to_numeric(df[nomCCSC], errors='coerce').std()
660
+ return moySC, stdSC
661
+
662
+ def ComparaisonMoyennesDMCC(df,nomCC1,nomCC2,SeuilBadDMCC1,SeuilGoodDMCC1):
663
+ """
664
+ entrée :
665
+ - df = dataframe avec uniquement les étudiants AJ ou ADM, c'est-à-dire qu'il n'y a aucun fantôme
666
+ - nomCC1 = en-tête de la colonne de df qui contient la note du premier CC
667
+ - nomCC2 = en-tête de la colonne de df qui contient la note du 2nd CC
668
+ - SeuilBadDMCC1 = Seuil en-dessous duquel la note au nomCC1 est considérée comme médiocre
669
+ - SeuilGoodDMCC1 = Seuil au-deçà duquel la note au nomCC1 est considérée comme bonne
670
+ affichage :
671
+ - moyenne au nomCC2 de la cohorte d'étudiant(e)s
672
+ - en-dessous du SeuilBadDMCC1 au nomCC1
673
+ - au-dessus du SeuilGoodDMCC1 au nomCC1
674
+ - entre les deux seuils au nomCC1
675
+ - jointplot entre nomCC1 et nomCC2 uniquement pour les étudiant(e)s dont la note au CC1 est considérée comme médiocre
676
+ """
677
+ print(f"{hl.BOLD}Corrélation entre {fg.BLUE}{nomCC1}{fg.BLACK} et {fg.BLUE}{nomCC2}{fg.BLACK} ?{hl.OFF}")
678
+ dfBadCC1 = df[df[nomCC1] < SeuilBadDMCC1]
679
+ dfAvCC1 = df[(df[nomCC1] >= SeuilBadDMCC1) & (df[nomCC1] <= SeuilGoodDMCC1)]
680
+ dfGCC1 = df[df[nomCC1] > SeuilGoodDMCC1]
681
+ MoyenneAuCC2desBadCC1 = np.round(dfBadCC1[nomCC2].mean(),1)
682
+ MoyenneAuCC2desAvCC1 = np.round(dfAvCC1[nomCC2].mean(),1)
683
+ MoyenneAuCC2desGoodCC1 = np.round(dfGCC1[nomCC2].mean(),1)
684
+ MoyenneAuCC1 = np.round(df[nomCC1].mean(),1)
685
+ MoyenneAuCC2 = np.round(df[nomCC2].mean(),1)
686
+ print(f"Les étudiant(e)s qui ont eu moins de {SeuilBadDMCC1}/20 au {nomCC1} ont en moyenne {MoyenneAuCC2desBadCC1}/20 au {nomCC2}")
687
+ print(f"Les étudiant(e)s qui ont eu entre {SeuilBadDMCC1}/20 et {SeuilGoodDMCC1}/20 au {nomCC1} ont en moyenne {MoyenneAuCC2desAvCC1}/20 au {nomCC2}")
688
+ print(f"Les étudiant(e)s qui ont eu plus de {SeuilGoodDMCC1}/20 au {nomCC1} ont en moyenne {MoyenneAuCC2desGoodCC1}/20 au {nomCC2}")
689
+ print(f"Pour rappel, la moyenne au {nomCC1} = {MoyenneAuCC1}/20 et celle au {nomCC2} = {MoyenneAuCC2}/20")
690
+ sns.jointplot(x = nomCC1, y = nomCC2, data = dfBadCC1)
691
+ plt.show()
692
+ return
693
+
694
+ ####################################################################################################################################
695
+ # G R A P H E S
696
+ ####################################################################################################################################
697
+
698
+ def Histogrammes(df,nomCC,Moyenne,NomGraphique,w,moy,std,moyT,stdT,legende):
699
+ """
700
+ entrée :
701
+ - df = dataframe qui contient ID, Noms, Prénoms, notes de CC, et Moyenne pondérée
702
+ - nomCC = liste qui contient noms d'en-têtes des colonnes qui contiennent les notes de CC
703
+ - Moyenne = nom de l'en-tête de la colonne qui contient la moyenne
704
+ - NomGraphique = nom du fichier png qui va contenir la figure
705
+ - w = liste qui contient les coeffs des CC
706
+ - moy = liste qui contient la moyenne de chaque CC
707
+ - std = liste qui contient l'écart-type calculé pour chaque CC
708
+ - moyT = moyenne des 4 CC
709
+ - stdT = écart-type calculé pour la note globale
710
+ - legende = titre qui sera affiché sur l'histogramme principal
711
+ affichage :
712
+ - 1 petit histogramme /CC
713
+ - 1 grand histogramme avec la moyenne
714
+ sauvegarde :
715
+ - fichier graphique 'NomGraphique' avec 1 petit histogramme par CC + 1 grand histogramme avec la moyenne
716
+ """
717
+
718
+ import matplotlib.gridspec as gridspec
719
+
720
+ wt = sum(w)
721
+
722
+ fig = plt.figure(figsize=(18, 12))
723
+ plt.rcParams["font.size"] = (14) #font size
724
+ gs = gridspec.GridSpec(2, 4, height_ratios=[1, 1], width_ratios=[1, 1, 1, 1])
725
+
726
+ fontpropertiesT = {'family':'sans-serif', 'weight' : 'bold', 'size' : 16}
727
+ fontpropertiesA = {'family':'sans-serif', 'weight' : 'bold', 'size' : 18}
728
+
729
+ sns.set_style("whitegrid")
730
+ ax00=plt.subplot(gs[0,0])
731
+ fig00=sns.histplot(data=df, x=nomCC[0], discrete=True, kde=True, color="#3e95ff", alpha=1.0)
732
+ ax00.set_xlabel("note / 20",fontsize=16)
733
+ ax00.set_ylabel("Count",fontsize=16)
734
+ ax00.set_title(f"{nomCC[0]} ({w[0]}%). <N> = {moy[0]:.1f}, $\sigma$ = {std[0]:.1f}",color = "red", font=fontpropertiesT)
735
+
736
+ ax01=plt.subplot(gs[0,1])
737
+ fig01=sns.histplot(data=df, x=nomCC[1], discrete=True, kde=True, color="#3e95ff", alpha=1.0)
738
+ ax01.set_xlabel("note / 20",fontsize=16)
739
+ ax01.set_ylabel("Count",fontsize=16)
740
+ ax01.set_title(f"{nomCC[1]} ({w[1]}%) <N> = {moy[1]:.1f}, $\sigma$ = {std[1]:.1f}",color = "red", font=fontpropertiesT)
741
+
742
+ ax02=plt.subplot(gs[0,2])
743
+ fig02=sns.histplot(data=df, x=nomCC[2], discrete=True, kde=True, color="#3e95ff", alpha=1.0)
744
+ ax02.set_xlabel("note / 20",fontsize=16)
745
+ ax02.set_ylabel("Count",fontsize=16)
746
+ ax02.set_title(f"{nomCC[2]} ({w[2]}%) <N> = {moy[2]:.1f}, $\sigma$ = {std[2]:.1f}",color = "red", font=fontpropertiesT)
747
+
748
+ ax03=plt.subplot(gs[0,3])
749
+ fig03=sns.histplot(data=df, x=nomCC[3], discrete=True, kde=True, color="#3e95ff", alpha=1.0)
750
+ ax03.set_xlabel("note / 20",fontsize=16)
751
+ ax03.set_ylabel("Count",fontsize=16)
752
+ ax03.set_title(f"{nomCC[3]} ({w[3]}%) <N> = {moy[3]:.1f}, $\sigma$ = {std[3]:.1f}",color = "red", font=fontpropertiesT)
753
+
754
+ axTot=plt.subplot(gs[1,0:4])
755
+ figTot=sns.histplot(data=df, x=Moyenne, discrete=True, kde=True, color="#737cff", alpha=1.0, stat='count', label = legende)
756
+ axTot.set_xlabel("note / 20",fontsize=16)
757
+ axTot.set_ylabel("Count",fontsize=16)
758
+ axTot.set_xticks(np.arange(0,19,2))
759
+ axTot.set_title(f"Moyenne ({wt}%). <N> = {moyT:.1f}, $\sigma$ = {stdT:.1f}",color = "red", font=fontpropertiesT)
760
+ axTot.legend()
761
+
762
+ fig.savefig(NomGraphique, dpi=300)
763
+ plt.show()
764
+ return
765
+
766
+ def kdePlotByMentionEtParcours(df,Moyenne,Mention,Parcours,NomGraphique):
767
+ """
768
+ entrée :
769
+ - df = dataframe qui contient ID, Noms, Prénoms, notes de CC, Moyenne pondérée, Mention et Parcours
770
+ - Mention = nom de l'en-tête de la colonne qui contient la Mention de diplôme simplifiée d'un étudiant
771
+ - Parcours = nom de l'en-tête de la colonne qui contient le parcours suivi par un étudiant
772
+ affichage :
773
+ - 1 graphe avec des plots kde par Mention
774
+ - 1 graphe avec des plots kde par Parcours
775
+ sauvegarde :
776
+ - fichier graphique 'NomGraphique' avec les 2 graphes
777
+ """
778
+ import matplotlib.gridspec as gridspec
779
+ fig = plt.figure(figsize=(18, 12))
780
+ plt.rcParams["font.size"] = (14) #font size
781
+ gs = gridspec.GridSpec(2, 1, height_ratios=[1, 1], width_ratios=[1])
782
+
783
+ fontpropertiesT = {'family':'sans-serif', 'weight' : 'bold', 'size' : 16}
784
+ fontpropertiesA = {'family':'sans-serif', 'weight' : 'bold', 'size' : 18}
785
+
786
+ sns.set_style("whitegrid")
787
+ axP=plt.subplot(gs[0,0])
788
+ hue_order = np.sort(df[Mention].unique())
789
+ 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)
790
+ axP.set_xlabel("note / 20",fontsize=16)
791
+ axP.set_ylabel("Count",fontsize=16)
792
+ #axP.set_title(f"Total/Mention. <N> = {moy1:.1f}, $\sigma$ = {std1:.1f}",color = "red", font=fontpropertiesT)
793
+
794
+ axO=plt.subplot(gs[1,0])
795
+ hue_order = np.sort(df[Parcours].unique())
796
+ 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)
797
+ axO.set_xlabel("note / 20",fontsize=16)
798
+ axO.set_ylabel("Count",fontsize=16)
799
+ axO.set_xticks(np.arange(0,19,2))
800
+ #axO.set_title(f"Total/Parcours. <N> = {moyT:.1f}, $\sigma$ = {stdT:.1f}",color = "red", font=fontpropertiesT)
801
+
802
+ fig.savefig(NomGraphique, dpi=300)
803
+ plt.show()
804
+ return
805
+
806
+ def BoiteAMoustachesByMentionEtParcours(df, Moyenne, Mention, Parcours, NomGraphique):
807
+ import matplotlib.gridspec as gridspec
808
+
809
+ plt.style.use('seaborn-v0_8-white')
810
+ fig = plt.figure(figsize=(16, 12))
811
+ plt.rcParams["font.size"] = (14) #font size
812
+ nP = (df[Mention].unique().shape[0])
813
+ nO = (df[Parcours].unique().shape[0])
814
+ gs = gridspec.GridSpec(2, 2, height_ratios=[1,2*nO/nP], width_ratios=[1,1])
815
+
816
+ fontpropertiesT = {'family':'sans-serif', 'weight' : 'bold', 'size' : 16}
817
+ fontpropertiesA = {'family':'sans-serif', 'weight' : 'bold', 'size' : 18}
818
+
819
+ sns.set_style("whitegrid")
820
+ axP=plt.subplot(gs[:,0])
821
+ figP=sns.boxplot(data=df.sort_values(by = Mention), x=Moyenne, y=Mention, palette='tab20c')
822
+ axP.set_xlabel("note / 20",fontsize=16)
823
+ axP.set_ylabel("",fontsize=16)
824
+ axP.set_xticks(np.arange(0,20,2))
825
+ #axP.set_title(f"Total/Mention. <N> = {moy1:.1f}, $\sigma$ = {std1:.1f}",color = "red", font=fontpropertiesT)
826
+ plt.xticks(fontweight='bold',fontsize=16)
827
+ plt.yticks(fontweight='bold',fontsize=16)
828
+
829
+ axO=plt.subplot(gs[-1,-1])
830
+ figO=sns.boxplot(data=df.sort_values(by = Parcours), x=Moyenne, y=Parcours, palette='tab20c')
831
+ axO.set_xlabel("note / 20",fontsize=16)
832
+ axO.set_ylabel("",fontsize=16)
833
+ axO.yaxis.tick_right()
834
+ axO.set_xticks(np.arange(0,20,2))
835
+ plt.xticks(fontweight='bold',fontsize=16)
836
+ plt.yticks(fontweight='bold',fontsize=16)
837
+
838
+ plt.savefig(NomGraphique, dpi=300)
839
+ plt.show()
840
+ return
841
+
842
+
843
+ def BoiteAMoustachesByMentionEtParcoursHue(df, Moyenne, Mention, Parcours, NomGraphique):
844
+ import matplotlib.gridspec as gridspec
845
+
846
+ fig = plt.figure(figsize=(18, 12))
847
+ plt.style.use('seaborn-v0_8-whitegrid')
848
+ plt.rcParams["font.size"] = (14) #font size
849
+
850
+ fontpropertiesT = {'family':'sans-serif', 'weight' : 'bold', 'size' : 16}
851
+ fontpropertiesA = {'family':'sans-serif', 'weight' : 'bold', 'size' : 18}
852
+
853
+ sns.set_style("whitegrid")
854
+ hue_order = np.sort(df[Parcours].unique())
855
+ sns.boxplot(data=df.sort_values(by = Mention), x=Moyenne, y=Mention, hue=Parcours, palette='hsv', hue_order=hue_order)
856
+ plt.xlabel("note / 20",fontsize=16)
857
+ plt.ylabel("",fontsize=16)
858
+ plt.xticks(np.arange(0,20,2))
859
+ plt.xticks(fontweight='bold',fontsize=16)
860
+ plt.yticks(fontweight='bold',fontsize=16)
861
+ plt.legend(loc='lower left')
862
+ fig.savefig(NomGraphique, dpi=300)
863
+ plt.show()
864
+ return
865
+
866
+ def plotTauxADMasBars(dfwoSC,dfwSC,xCol,hueCol,NomGraphique):
867
+ """
868
+ entrée :
869
+ - dfwoSC = dataframe contenant les stats ADM/AJ/Ghosts avant l'application de la seconde chance
870
+ - dfwSC = dataframe contenant les stats ADM/AJ/Ghosts après l'application de la seconde chance
871
+ - xCol = nom de la colonne qui contient les taux de réussite
872
+ - hueCol = nom de la colonne qui contient les paramètres avant ou après seconde chance
873
+ - NomGraphique = nom du fichier png qui va être sauvé
874
+ affichage :
875
+ - bar plot de seaborn
876
+ sauvegarde :
877
+ fichier graphique 'NomGraphique' au format png
878
+ """
879
+ from matplotlib.colors import LinearSegmentedColormap
880
+ plt.style.use('seaborn-v0_8-whitegrid')
881
+ my_colors = ['#ff696b','#00aa7f']
882
+ my_cmap = LinearSegmentedColormap.from_list("mycolors",my_colors)
883
+ df = pd.concat([dfwoSC, dfwSC])
884
+ nbars=df.shape[0]
885
+ fig = plt.figure(figsize=(18, nbars*0.6))
886
+ plt.rcParams["font.size"] = (16) #font size
887
+ ax = sns.barplot(data=df,y=df.index.values,x = xCol,hue=hueCol,palette=my_colors)
888
+ ax.bar_label(ax.containers[0],fontweight='bold',fontsize=18)
889
+ ax.bar_label(ax.containers[1],fontweight='bold',fontsize=18)
890
+ plt.xticks(fontweight='bold',fontsize=14)
891
+ plt.yticks(fontweight='bold',fontsize=16)
892
+ plt.xlim(0,100)
893
+ fig.savefig(NomGraphique, dpi=300)
894
+ plt.show()
895
+ return
896
+
897
+ def StackedBarPop(df,ListCols,ylabel,NomGraphique):
898
+ """
899
+ entrée :
900
+ - df : dataframe contenant l'analyse statistique globale, dont les moyennes, effectifs, etc. i.e. le dataframe renvoyé par StatsRéussiteParMentionOuParcours()
901
+ - ListCols = liste avecles labels des colonnes contenant les valeurs numériques qu'on veut tracer comme un histogramme empilé
902
+ - ylabel = label de l'axe des y
903
+ - NomGraphique = nom du fichier png qui va être sauvé
904
+ affichage :
905
+ - bar plot empilé de pandas
906
+ sauvegarde :
907
+ fichier graphique 'NomGraphique' au format png
908
+ """
909
+ from matplotlib.colors import LinearSegmentedColormap
910
+ plt.style.use('seaborn-v0_8-whitegrid')
911
+ my_colors = ['#b95651','#01bec3']
912
+ my_cmap = LinearSegmentedColormap.from_list("mycolors",my_colors)
913
+ nbars=df.shape[0]
914
+ bplot = df[ListCols].plot(kind='bar',stacked=True, figsize=(nbars*1.5,10), fontsize=16, width=0.85, colormap=my_cmap, edgecolor='black')
915
+ bplot.set_ylabel(ylabel,fontdict={'fontsize':18})
916
+ bplot.bar_label(bplot.containers[0],label_type='center',color='w',fontweight='bold',fontsize=18)
917
+ bplot.bar_label(bplot.containers[1],padding=5,fontweight='bold',fontsize=18)
918
+ bplot.legend(fontsize=20)
919
+ plt.xticks(fontweight='bold')
920
+ plt.savefig(NomGraphique, dpi=300)
921
+ plt.show()
922
+ return
923
+
924
+ def StackedBarPopPO(dfRef,Mention,Parcours,NomGraphique):
925
+ """
926
+ entrée :
927
+ - dfRef : dataframe contenant la liste des étudiants avec leur Mention et leur otpion, tel que généré par mentionD()
928
+ - Mention = nom de la colonne qui contient le nom simplifié d'une Mention
929
+ - Parcours = nom de la colonne qui contient la version simplifiée d'un Parcours
930
+ - NomGraphique = nom du fichier png qui va être sauvé
931
+ affichage :
932
+ - histplot "hue" de seaborn, càd les effectifs par Parcours empilés pour chaque Mention
933
+ sauvegarde :
934
+ fichier graphique 'NomGraphique' au format png
935
+ """
936
+ plt.style.use('seaborn-v0_8-whitegrid')
937
+ nP = (dfRef[Mention].unique().shape[0])
938
+ fig = plt.figure(figsize=(nP*2,8))
939
+ hue_order = np.sort(dfRef[Parcours].unique())
940
+ hplot = sns.histplot(data=dfRef, x=Mention, hue=Parcours, multiple="stack", stat='count', palette='terrain', hue_order=hue_order)
941
+ # print(len(hplot.containers[0]))
942
+ ntot = np.zeros(len(hplot.containers[0]))
943
+ xtot = np.zeros(len(hplot.containers[0]))
944
+ # print(ntot)
945
+ for c in hplot.containers:
946
+ # print(c[0].get_height())
947
+ for i in range(nP):
948
+ # print(c[i])
949
+ x = c[i].get_x()
950
+ y = c[i].get_y()
951
+ n = c[i].get_height()
952
+ ntot[i] += n
953
+ xtot[i] = x
954
+ if (n!=0) : hplot.annotate(n, (x+0.5, y+n/2), size = 16, weight="bold",ha="center",va="center",color="black")
955
+ # print(ntot,xtot)
956
+ # print(ntot.max())
957
+ [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)]
958
+ plt.rcParams["font.size"] = (16) #font size
959
+ plt.xticks(fontweight='bold',fontsize=16)
960
+ plt.yticks(fontweight='bold',fontsize=16)
961
+ plt.savefig(NomGraphique, dpi=300)
962
+ plt.show()
963
+ return
964
+