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