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",
+ " Note | \n",
+ " Bareme | \n",
+ "
\n",
+ " \n",
+ " Eleve | \n",
+ " | \n",
+ " | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " ABDOU Asmahane | \n",
+ " 5.00 | \n",
+ " 12.0 | \n",
+ "
\n",
+ " \n",
+ " ABOU Roihim | \n",
+ " 0.00 | \n",
+ " 12.0 | \n",
+ "
\n",
+ " \n",
+ " AHMED BOINALI Kouraichia | \n",
+ " 2.67 | \n",
+ " 12.0 | \n",
+ "
\n",
+ " \n",
+ " AHMED Rahada | \n",
+ " 6.33 | \n",
+ " 12.0 | \n",
+ "
\n",
+ " \n",
+ " ALI SAID Anchourati | \n",
+ " 0.00 | \n",
+ " 12.0 | \n",
+ "
\n",
+ " \n",
+ " ASSANE Noussouraniya | \n",
+ " 4.67 | \n",
+ " 12.0 | \n",
+ "
\n",
+ " \n",
+ " BACAR Issiaka | \n",
+ " 0.00 | \n",
+ " 12.0 | \n",
+ "
\n",
+ " \n",
+ " BACAR Samina | \n",
+ " 3.67 | \n",
+ " 12.0 | \n",
+ "
\n",
+ " \n",
+ " CHAIHANE Said | \n",
+ " 5.33 | \n",
+ " 12.0 | \n",
+ "
\n",
+ " \n",
+ " COMBO Houzaimati | \n",
+ " 5.00 | \n",
+ " 12.0 | \n",
+ "
\n",
+ " \n",
+ " DAOUD Anzilati | \n",
+ " 5.17 | \n",
+ " 12.0 | \n",
+ "
\n",
+ " \n",
+ " DAOUD Talaenti | \n",
+ " 5.67 | \n",
+ " 12.0 | \n",
+ "
\n",
+ " \n",
+ " DARKAOUI Rachma | \n",
+ " 5.67 | \n",
+ " 12.0 | \n",
+ "
\n",
+ " \n",
+ " DHAKIOINE Nabaouya | \n",
+ " 1.00 | \n",
+ " 12.0 | \n",
+ "
\n",
+ " \n",
+ " DJANFAR Soioutinour | \n",
+ " 5.33 | \n",
+ " 12.0 | \n",
+ "
\n",
+ " \n",
+ " DRISSA Ibrahim | \n",
+ " 0.00 | \n",
+ " 12.0 | \n",
+ "
\n",
+ " \n",
+ " HACHIM SIDI Assani | \n",
+ " 7.00 | \n",
+ " 12.0 | \n",
+ "
\n",
+ " \n",
+ " HAFIDHUI Zalifa | \n",
+ " 5.67 | \n",
+ " 12.0 | \n",
+ "
\n",
+ " \n",
+ " HOUMADI Marie | \n",
+ " 6.67 | \n",
+ " 12.0 | \n",
+ "
\n",
+ " \n",
+ " HOUMADI Sania | \n",
+ " 5.33 | \n",
+ " 12.0 | \n",
+ "
\n",
+ " \n",
+ " MAANDHUI Halouoi | \n",
+ " 7.00 | \n",
+ " 12.0 | \n",
+ "
\n",
+ " \n",
+ " MASSONDI Nasma | \n",
+ " 7.33 | \n",
+ " 12.0 | \n",
+ "
\n",
+ " \n",
+ " SAIDALI Irichad | \n",
+ " 5.00 | \n",
+ " 12.0 | \n",
+ "
\n",
+ " \n",
+ "
\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": [
+ "