diff --git a/app/comp/moy_mod.py b/app/comp/moy_mod.py index 358f9cfd40..7f8aed3494 100644 --- a/app/comp/moy_mod.py +++ b/app/comp/moy_mod.py @@ -121,8 +121,8 @@ def df_load_modimpl_notes(moduleimpl_id: int) -> pd.DataFrame: def compute_module_moy( - evals_notes: pd.DataFrame, - evals_poids: pd.DataFrame, + evals_notes_df: pd.DataFrame, + evals_poids_df: pd.DataFrame, evaluations: list, ) -> pd.DataFrame: """Calcule les moyennes des étudiants dans ce module @@ -140,34 +140,35 @@ def compute_module_moy( ou NaN si les évaluations (dans lesquelles l'étudiant à des notes) ne donnent pas de coef vers cette UE. """ - nb_etuds = len(evals_notes) - nb_ues = evals_poids.shape[1] - etud_moy_module_arr = np.zeros((nb_etuds, nb_ues)) - evals_poids_arr = evals_poids.to_numpy().transpose() * [ - e.coefficient for e in evaluations - ] - # -> evals_poids_arr shape : (nb_ues, nb_evals) + nb_etuds, nb_evals = evals_notes_df.shape + nb_ues = evals_poids_df.shape[1] + assert evals_poids_df.shape[0] == nb_evals # compat notes/poids + evals_coefs = np.array([e.coefficient for e in evaluations], dtype=float).reshape( + -1, 1 + ) + evals_poids = evals_poids_df.values * evals_coefs + # -> evals_poids_arr shape : (nb_evals, nb_ues) + assert evals_poids.shape == (nb_evals, nb_ues) # Remet les notes sur 20 (sauf notes spéciales <= -1000): - evals_notes_arr = np.where(evals_notes.values > -1000, evals_notes.values, 0.0) / [ - e.note_max / 20.0 for e in evaluations - ] - for i in range(nb_etuds): - # note_vect: array [note_ue1, note_ue2, ...] de l'étudiant i - note_vect = evals_notes_arr[i] - # Les poids des évals pour cet étudiant: là où il a des notes non neutralisées - # Attention: les NaN (codant les absents) sont remplacés par des 0 dans - # evals_notes_arr mais pas dans evals_poids_etud_arr - # (la comparaison est toujours false face à un NaN) - evals_poids_etud_arr = np.where( - evals_notes.values[i] <= -1000, 0, evals_poids_arr - ) - # Calcule la moyenne pondérée sur les notes disponibles - with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN) - etud_moy_module_arr[i] = (note_vect * evals_poids_etud_arr).sum( - axis=1 - ) / evals_poids_etud_arr.sum(axis=1) - + evals_notes = np.where( + evals_notes_df.values > -1000, evals_notes_df.values, 0.0 + ) / [e.note_max / 20.0 for e in evaluations] + # Les poids des évals pour les étudiant: là où il a des notes non neutralisées + # Attention: les NaN (codant les absents) sont remplacés par des 0 dans + # evals_notes_arr mais pas dans evals_poids_etuds_arr + # (la comparaison est toujours false face à un NaN) + # shape: (nb_etuds, nb_evals, nb_ues) + poids_stacked = np.stack([evals_poids] * nb_etuds) + evals_poids_etuds = np.where( + np.stack([evals_notes_df.values] * nb_ues, axis=2) <= -1000.0, 0, poids_stacked + ) + # Calcule la moyenne pondérée sur les notes disponibles + evals_notes_stacked = np.stack([evals_notes] * nb_ues, axis=2) + with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN) + etud_moy_module = np.sum( + evals_poids_etuds * evals_notes_stacked, axis=1 + ) / np.sum(evals_poids_etuds, axis=1) etud_moy_module_df = pd.DataFrame( - etud_moy_module_arr, index=evals_notes.index, columns=evals_poids.columns + etud_moy_module, index=evals_notes_df.index, columns=evals_poids_df.columns ) return etud_moy_module_df diff --git a/tests/unit/test_but_modules.py b/tests/unit/test_but_modules.py index db9febcd26..7b55f45140 100644 --- a/tests/unit/test_but_modules.py +++ b/tests/unit/test_but_modules.py @@ -234,7 +234,7 @@ def test_module_moy_elem(test_client): "EVAL2": np.NaN, # et une ABS }, ] - evals_notes = pd.DataFrame( + evals_notes_df = pd.DataFrame( data, index=["etud1", "etud2", "etud3", "etud4", "etud5"] ) # Poids des évaluations (1 ligne / évaluation) @@ -242,13 +242,13 @@ def test_module_moy_elem(test_client): {"UE1": 1, "UE2": 0, "UE3": 0}, {"UE1": 2, "UE2": 5, "UE3": 0}, ] - evals_poids = pd.DataFrame(data, index=["EVAL1", "EVAL2"], dtype=float) + evals_poids_df = pd.DataFrame(data, index=["EVAL1", "EVAL2"], dtype=float) evaluations = [ Evaluation(note_max=20.0, coefficient=1.0), Evaluation(note_max=20.0, coefficient=1.0), ] etud_moy_module_df = moy_mod.compute_module_moy( - evals_notes.fillna(0.0), evals_poids, evaluations + evals_notes_df.fillna(0.0), evals_poids_df, evaluations ) NAN = 666.0 # pour pouvoir comparer NaN et NaN (car NaN != NaN) r = etud_moy_module_df.fillna(NAN)