diff --git a/recopytex/__init__.py b/recopytex/__init__.py index 050ae6a..ab1cdbe 100644 --- a/recopytex/__init__.py +++ b/recopytex/__init__.py @@ -1,15 +1,5 @@ #!/usr/bin/env python # encoding: utf-8 -NO_STUDENT_COLUMNS = [ - "Trimestre", - "Nom", - "Date", - "Exercice", - "Question", - "Competence", - "Domaine", - "Commentaire", - "Bareme", - "Niveau", -] +from .csv_extraction import flat_clear_csv +from .df_marks_manip import pp_q_scores diff --git a/recopytex/config.py b/recopytex/config.py new file mode 100644 index 0000000..c5eb096 --- /dev/null +++ b/recopytex/config.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python +# encoding: utf-8 + +NO_ST_COLUMNS = { + "term": "Trimestre", + "assessment": "Nom", + "date": "Date", + "exercise": "Exercice", + "question": "Question", + "competence": "Competence", + "theme": "Domaine", + "comment": "Commentaire", + "score_rate": "Bareme", + "is_leveled": "Est_nivele", +} + +COLUMNS = { + **NO_ST_COLUMNS, + "student": "Eleve", + "score": "Score", + "mark": "Note", + "level": "Niveau", + "normalized": "Normalise", +} + +VALIDSCORE = { + "NOTFILLED": "", # The item is not scored yet + "NOANSWER": ".", # Student gives no answer (this score will impact the fianl mark) + "ABS": "a", # Student has absent (this score won't be impact the final mark) +} diff --git a/recopytex/csv_extraction.py b/recopytex/csv_extraction.py index 8d3df1f..41f30d4 100644 --- a/recopytex/csv_extraction.py +++ b/recopytex/csv_extraction.py @@ -4,12 +4,12 @@ """ Extracting data from xlsx files """ import pandas as pd -from . import NO_STUDENT_COLUMNS +from .config import NO_ST_COLUMNS, COLUMNS, VALIDSCORE pd.set_option("Precision", 2) -def extract_students(df, no_student_columns=NO_STUDENT_COLUMNS): +def extract_students(df, no_student_columns=NO_ST_COLUMNS.values()): """ Extract the list of students from df :param df: the dataframe @@ -20,7 +20,7 @@ def extract_students(df, no_student_columns=NO_STUDENT_COLUMNS): return students -def flat_df_students(df, no_student_columns=NO_STUDENT_COLUMNS): +def flat_df_students(df, no_student_columns=NO_ST_COLUMNS.values()): """ Flat the ws for students :param df: the dataframe (one row per questions) @@ -29,7 +29,7 @@ def flat_df_students(df, no_student_columns=NO_STUDENT_COLUMNS): Columns of csv files: - - NO_STUDENT_COLUMNS + - NO_ST_COLUMNS meta data on questions - one for each students This function flat student's columns to "student" and "score" @@ -42,14 +42,14 @@ def flat_df_students(df, no_student_columns=NO_STUDENT_COLUMNS): df, id_vars=no_student_columns, value_vars=st, - var_name="student", - value_name="score", + var_name=COLUMNS["student"], + value_name=COLUMNS["score"], ) ) - return pd.concat(scores) + return pd.concat(scores).dropna(subset=[COLUMNS["score"]]) -def flat_clear_csv(csv_df, no_student_columns=NO_STUDENT_COLUMNS): +def flat_clear_csv(csv_df, no_student_columns=NO_ST_COLUMNS.values()): """ Flat and clear the dataframe extracted from csv :param csv_df: data frame read from csv @@ -59,12 +59,16 @@ def flat_clear_csv(csv_df, no_student_columns=NO_STUDENT_COLUMNS): """ df = flat_df_students(csv_df) - df.columns = df.columns.map(lambda x: x.lower()) + df[COLUMNS["question"]].fillna("", inplace=True) + df[COLUMNS["exercise"]].fillna("", inplace=True) + df[COLUMNS["comment"]].fillna("", inplace=True) + df[COLUMNS["competence"]].fillna("", inplace=True) - df["question"].fillna("", inplace=True) - df["exercice"].fillna("", inplace=True) - df["commentaire"].fillna("", inplace=True) - df["competence"].fillna("", inplace=True) + df[COLUMNS["score"]] = pd.to_numeric( + df[COLUMNS["score"]] + .replace(VALIDSCORE["NOANSWER"], -1) + .apply(lambda x: str(x).replace(",", ".")) + ) return df diff --git a/recopytex/df_marks_manip.py b/recopytex/df_marks_manip.py new file mode 100644 index 0000000..964d416 --- /dev/null +++ b/recopytex/df_marks_manip.py @@ -0,0 +1,205 @@ +#!/usr/bin/env python +# encoding: utf-8 + +import pandas as pd +import numpy as np +from math import ceil, floor +from .config import COLUMNS, VALIDSCORE + +# Values manipulations + + +def round_half_point(val): + try: + return 0.5 * ceil(2.0 * val) + except ValueError: + return val + except TypeError: + return val + + +def score_to_mark(x): + """ Compute the mark + + if the item is leveled then the score is multiply by the score_rate + otherwise it copies the score + + :param x: dictionnary with COLUMNS["is_leveled"], COLUMNS["score"] and COLUMNS["score_rate"] keys + + >>> d = {"Eleve":["E1"]*6 + ["E2"]*6, + ... COLUMNS["score_rate"]:[1]*2+[2]*2+[2]*2 + [1]*2+[2]*2+[2]*2, + ... COLUMNS["is_leveled"]:[0]*4+[1]*2 + [0]*4+[1]*2, + ... COLUMNS["score"]:[1, 0.33, 2, 1.5, 1, 3, 0.666, 1, 1.5, 1, 2, 3], + ... } + >>> df = pd.DataFrame(d) + >>> score_to_mark(df.loc[0]) + 1.0 + >>> score_to_mark(df.loc[10]) + 1.3333333333333333 + """ + # -1 is no answer + if x[COLUMNS["score"]] == -1: + return 0 + + if x[COLUMNS["is_leveled"]]: + if x[COLUMNS["score"]] not in [0, 1, 2, 3]: + raise ValueError(f"The evaluation is out of range: {x[COLUMNS['score']]} at {x}") + return x[COLUMNS["score"]] * x[COLUMNS["score_rate"]] / 3 + + if x[COLUMNS["score"]] > x[COLUMNS["score_rate"]]: + raise ValueError( + f"The score ({x['score']}) is greated than the rating scale ({x[COLUMNS['score_rate']]}) at {x}" + ) + return x[COLUMNS["score"]] + + +def score_to_level(x): + """ Compute the level (".",0,1,2,3). + + :param x: dictionnary with COLUMNS["is_leveled"], COLUMNS["score"] and COLUMNS["score_rate"] keys + + >>> d = {"Eleve":["E1"]*6 + ["E2"]*6, + ... COLUMNS["score_rate"]:[1]*2+[2]*2+[2]*2 + [1]*2+[2]*2+[2]*2, + ... COLUMNS["is_leveled"]:[0]*4+[1]*2 + [0]*4+[1]*2, + ... COLUMNS["score"]:[1, 0.33, np.nan, 1.5, 1, 3, 0.666, 1, 1.5, 1, 2, 3], + ... } + >>> df = pd.DataFrame(d) + >>> score_to_level(df.loc[0]) + 3 + >>> score_to_level(df.loc[1]) + 1 + >>> score_to_level(df.loc[2]) + 'na' + >>> score_to_level(df.loc[3]) + 3 + >>> score_to_level(df.loc[5]) + 3 + >>> score_to_level(df.loc[10]) + 2 + """ + # -1 is no answer + if x[COLUMNS["score"]] == -1: + return x[COLUMNS["score"]] + + if x[COLUMNS["is_leveled"]]: + return int(x[COLUMNS["score"]]) + else: + return int(ceil(x[COLUMNS["score"]] / x[COLUMNS["score_rate"]] * 3)) + + +# DataFrame columns manipulations + + +def compute_mark(df): + """ Add Mark column to df + + :param df: DataFrame with COLUMNS["score"], COLUMNS["is_leveled"] and COLUMNS["score_rate"] columns. + + >>> d = {"Eleve":["E1"]*6 + ["E2"]*6, + ... COLUMNS["score_rate"]:[1]*2+[2]*2+[2]*2 + [1]*2+[2]*2+[2]*2, + ... COLUMNS["is_leveled"]:[0]*4+[1]*2 + [0]*4+[1]*2, + ... COLUMNS["score"]:[1, 0.33, 2, 1.5, 1, 3, 0.666, 1, 1.5, 1, 2, 3], + ... } + >>> df = pd.DataFrame(d) + >>> compute_mark(df) + 0 1.00 + 1 0.33 + 2 2.00 + 3 1.50 + 4 0.67 + 5 2.00 + 6 0.67 + 7 1.00 + 8 1.50 + 9 1.00 + 10 1.33 + 11 2.00 + dtype: float64 + """ + return df[[COLUMNS["score"], COLUMNS["is_leveled"], COLUMNS["score_rate"]]].apply( + score_to_mark, axis=1 + ) + + +def compute_level(df): + """ Add Mark column to df + + :param df: DataFrame with COLUMNS["score"], COLUMNS["is_leveled"] and COLUMNS["score_rate"] columns. + + >>> d = {"Eleve":["E1"]*6 + ["E2"]*6, + ... COLUMNS["score_rate"]:[1]*2+[2]*2+[2]*2 + [1]*2+[2]*2+[2]*2, + ... COLUMNS["is_leveled"]:[0]*4+[1]*2 + [0]*4+[1]*2, + ... COLUMNS["score"]:[np.nan, 0.33, 2, 1.5, 1, 3, 0.666, 1, 1.5, 1, 2, 3], + ... } + >>> df = pd.DataFrame(d) + >>> compute_level(df) + 0 na + 1 1 + 2 3 + 3 3 + 4 1 + 5 3 + 6 2 + 7 3 + 8 3 + 9 2 + 10 2 + 11 3 + dtype: object + """ + return df[[COLUMNS["score"], COLUMNS["is_leveled"], COLUMNS["score_rate"]]].apply( + score_to_level, axis=1 + ) + + +def compute_normalized(df): + """ Compute the normalized mark (Mark / score_rate) + + :param df: DataFrame with "Mark" and COLUMNS["score_rate"] columns + + >>> d = {"Eleve":["E1"]*6 + ["E2"]*6, + ... COLUMNS["score_rate"]:[1]*2+[2]*2+[2]*2 + [1]*2+[2]*2+[2]*2, + ... COLUMNS["is_leveled"]:[0]*4+[1]*2 + [0]*4+[1]*2, + ... COLUMNS["score"]:[1, 0.33, 2, 1.5, 1, 3, 0.666, 1, 1.5, 1, 2, 3], + ... } + >>> df = pd.DataFrame(d) + >>> df["Mark"] = compute_marks(df) + >>> compute_normalized(df) + 0 1.00 + 1 0.33 + 2 1.00 + 3 0.75 + 4 0.33 + 5 1.00 + 6 0.67 + 7 1.00 + 8 0.75 + 9 0.50 + 10 0.67 + 11 1.00 + dtype: float64 + """ + return df[COLUMNS["mark"]] / df[COLUMNS["score_rate"]] + + +# Postprocessing question scores + + +def pp_q_scores(df): + """ Postprocessing questions scores dataframe + + :param df: questions-scores dataframe + :return: same data frame with mark, level and normalize columns + """ + assign = { + COLUMNS["mark"]: compute_mark, + COLUMNS["level"]: compute_level, + COLUMNS["normalized"]: compute_normalized, + } + return df.assign(**assign) + + +# ----------------------------- +# Reglages pour 'vim' +# vim:set autoindent expandtab tabstop=4 shiftwidth=4: +# cursor: 16 del diff --git a/templates/tpl_evaluation.ipynb b/templates/tpl_evaluation.ipynb index 026c8be..31cb48e 100644 --- a/templates/tpl_evaluation.ipynb +++ b/templates/tpl_evaluation.ipynb @@ -2,21 +2,22 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, + "execution_count": 20, "metadata": {}, "outputs": [], "source": [ "from IPython.display import Markdown as md\n", - "from IPython.display import DisplayHandle\n", + "from IPython.display import display\n", "import pandas as pd\n", "from pathlib import Path\n", "from datetime import datetime\n", - "from recopytex.csv_extraction import flat_clear_csv" + "from recopytex import flat_clear_csv, pp_q_scores\n", + "%matplotlib inline" ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 21, "metadata": { "tags": [ "parameters" @@ -32,7 +33,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 22, "metadata": {}, "outputs": [ { @@ -57,7 +58,18 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 23, + "metadata": {}, + "outputs": [], + "source": [ + "stack_scores = pd.read_csv(csv_file)\n", + "scores = flat_clear_csv(stack_scores).dropna(subset=[\"Score\"])\n", + "scores = pp_q_scores(scores)" + ] + }, + { + "cell_type": "code", + "execution_count": 24, "metadata": {}, "outputs": [ { @@ -81,125 +93,320 @@ " \n", " \n", " \n", - " trimestre\n", - " nom\n", - " date\n", - " exercice\n", - " question\n", - " competence\n", - " domaine\n", - " commentaire\n", - " bareme\n", - " niveau\n", - " student\n", - " score\n", + " \n", + " Note\n", + " Bareme\n", + " \n", + " \n", + " Exercice\n", + " Eleve\n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " 0\n", - " 1\n", - " DM1\n", - " 15/09/16\n", - " 1\n", - " 1.1\n", - " Cal\n", - " Prio\n", - " \n", - " 1.0\n", - " 1\n", - " ABDOU Asmahane\n", - " 2\n", + " 1\n", + " ABDOU Asmahane\n", + " 3.67\n", + " 6.0\n", " \n", " \n", - " 1\n", - " 1\n", - " DM1\n", - " 15/09/16\n", - " 1\n", - " 1.2\n", - " Cal\n", - " Prio\n", - " \n", - " 1.0\n", - " 1\n", - " ABDOU Asmahane\n", - " 3\n", + " ABOU Roihim\n", + " 0.00\n", + " 6.0\n", " \n", " \n", - " 2\n", - " 1\n", - " DM1\n", - " 15/09/16\n", - " 1\n", - " 1.3\n", - " Cal\n", - " Prio\n", - " \n", - " 1.0\n", - " 1\n", - " ABDOU Asmahane\n", - " 2\n", + " AHMED BOINALI Kouraichia\n", + " 1.33\n", + " 6.0\n", " \n", " \n", - " 3\n", - " 1\n", - " DM1\n", - " 15/09/16\n", - " 1\n", - " 1.4\n", - " Cal\n", - " Prio\n", - " \n", - " 1.0\n", - " 1\n", - " ABDOU Asmahane\n", - " 2\n", + " AHMED Rahada\n", + " 2.67\n", + " 6.0\n", " \n", " \n", - " 4\n", - " 1\n", - " DM1\n", - " 15/09/16\n", - " 1\n", - " 1.5\n", - " Cal\n", - " Prio\n", - " \n", - " 1.0\n", - " 1\n", - " ABDOU Asmahane\n", - " 2\n", + " ALI SAID Anchourati\n", + " 0.00\n", + " 6.0\n", " \n", " \n", "\n", "" ], "text/plain": [ - " trimestre nom date exercice question competence domaine commentaire \\\n", - "0 1 DM1 15/09/16 1 1.1 Cal Prio \n", - "1 1 DM1 15/09/16 1 1.2 Cal Prio \n", - "2 1 DM1 15/09/16 1 1.3 Cal Prio \n", - "3 1 DM1 15/09/16 1 1.4 Cal Prio \n", - "4 1 DM1 15/09/16 1 1.5 Cal Prio \n", - "\n", - " bareme niveau student score \n", - "0 1.0 1 ABDOU Asmahane 2 \n", - "1 1.0 1 ABDOU Asmahane 3 \n", - "2 1.0 1 ABDOU Asmahane 2 \n", - "3 1.0 1 ABDOU Asmahane 2 \n", - "4 1.0 1 ABDOU Asmahane 2 " + " Note Bareme\n", + "Exercice Eleve \n", + "1 ABDOU Asmahane 3.67 6.0\n", + " ABOU Roihim 0.00 6.0\n", + " AHMED BOINALI Kouraichia 1.33 6.0\n", + " AHMED Rahada 2.67 6.0\n", + " ALI SAID Anchourati 0.00 6.0" ] }, - "execution_count": 4, + "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "stack_scores = pd.read_csv(csv_file)\n", - "scores = flat_clear_csv(stack_scores)\n", - "scores.head()" + "exercises_scores = scores.groupby([\"Exercice\", \"Eleve\"]).agg({\"Note\": \"sum\", \"Bareme\": \"sum\"})\n", + "exercises_scores.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
NoteBareme
Eleve
ABDOU Asmahane5.0012.0
ABOU Roihim0.0012.0
AHMED BOINALI Kouraichia2.6712.0
AHMED Rahada6.3312.0
ALI SAID Anchourati0.0012.0
ASSANE Noussouraniya4.6712.0
BACAR Issiaka0.0012.0
BACAR Samina3.6712.0
CHAIHANE Said5.3312.0
COMBO Houzaimati5.0012.0
DAOUD Anzilati5.1712.0
DAOUD Talaenti5.6712.0
DARKAOUI Rachma5.6712.0
DHAKIOINE Nabaouya1.0012.0
DJANFAR Soioutinour5.3312.0
DRISSA Ibrahim0.0012.0
HACHIM SIDI Assani7.0012.0
HAFIDHUI Zalifa5.6712.0
HOUMADI Marie6.6712.0
HOUMADI Sania5.3312.0
MAANDHUI Halouoi7.0012.0
MASSONDI Nasma7.3312.0
SAIDALI Irichad5.0012.0
\n", + "
" + ], + "text/plain": [ + " Note Bareme\n", + "Eleve \n", + "ABDOU Asmahane 5.00 12.0\n", + "ABOU Roihim 0.00 12.0\n", + "AHMED BOINALI Kouraichia 2.67 12.0\n", + "AHMED Rahada 6.33 12.0\n", + "ALI SAID Anchourati 0.00 12.0\n", + "ASSANE Noussouraniya 4.67 12.0\n", + "BACAR Issiaka 0.00 12.0\n", + "BACAR Samina 3.67 12.0\n", + "CHAIHANE Said 5.33 12.0\n", + "COMBO Houzaimati 5.00 12.0\n", + "DAOUD Anzilati 5.17 12.0\n", + "DAOUD Talaenti 5.67 12.0\n", + "DARKAOUI Rachma 5.67 12.0\n", + "DHAKIOINE Nabaouya 1.00 12.0\n", + "DJANFAR Soioutinour 5.33 12.0\n", + "DRISSA Ibrahim 0.00 12.0\n", + "HACHIM SIDI Assani 7.00 12.0\n", + "HAFIDHUI Zalifa 5.67 12.0\n", + "HOUMADI Marie 6.67 12.0\n", + "HOUMADI Sania 5.33 12.0\n", + "MAANDHUI Halouoi 7.00 12.0\n", + "MASSONDI Nasma 7.33 12.0\n", + "SAIDALI Irichad 5.00 12.0" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "assessment_scores = scores.groupby([\"Eleve\"]).agg({\"Note\": \"sum\", \"Bareme\": \"sum\"})\n", + "assessment_scores" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "count 23.00\n", + "mean 4.33\n", + "std 2.45\n", + "min 0.00\n", + "25% 3.17\n", + "50% 5.17\n", + "75% 5.67\n", + "max 7.33\n", + "Name: Note, dtype: float64" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "assessment_scores[\"Note\"].describe()" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "assessment_scores[\"Note\"].plot.kde()\n", + "assessment_scores[\"Note\"].plot.kde()" ] }, {