Renaming to Repytex
This commit is contained in:
15
Repytex/tools/__init__.py
Normal file
15
Repytex/tools/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
|
||||
from .extract import extract_flat_marks, get_class_ws, list_classes
|
||||
from .df_marks_manip import digest_flat_df#, students_pov
|
||||
#from .eval_tools import select_eval, get_present_absent, keep_only_presents
|
||||
from .plottings import radar_graph
|
||||
|
||||
|
||||
|
||||
# -----------------------------
|
||||
# Reglages pour 'vim'
|
||||
# vim:set autoindent expandtab tabstop=4 shiftwidth=4:
|
||||
# cursor: 16 del
|
||||
69
Repytex/tools/bareme.py
Normal file
69
Repytex/tools/bareme.py
Normal file
@@ -0,0 +1,69 @@
|
||||
#! /usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim:fenc=utf-8
|
||||
#
|
||||
# Copyright © 2017 lafrite <lafrite@Poivre>
|
||||
#
|
||||
# Distributed under terms of the MIT license.
|
||||
|
||||
"""
|
||||
Manipulating rating scale of an evaluation.
|
||||
|
||||
Those functions are made to be applied over eval_df
|
||||
"""
|
||||
|
||||
from .df_marks_manip import round_half_point, compute_mark_barem
|
||||
|
||||
__all__ = []
|
||||
|
||||
def new_scale_min(x):
|
||||
""" Change the scale by selecting min between scale and mark """
|
||||
return min(x["Mark_old"], x["Bareme"])
|
||||
|
||||
def new_scale_proportionnal(x):
|
||||
""" Changing the scale proportionally """
|
||||
return round_half_point(x["Mark_old"] * x["Bareme"] / x["Bareme_old"])
|
||||
|
||||
def tranform_scale(eval_df, new_scale, method):
|
||||
""" Change the rating scale of the exam
|
||||
|
||||
It backups Bareme, Mark, Mark_barem columns adding "_old". The backup is done once then it is ignored.
|
||||
|
||||
It changes Bareme value to new_scale, applies method to marks and remake mark_bareme
|
||||
|
||||
:param eval_df: dataframe on evaluations
|
||||
:param new_scale: replacement scale value
|
||||
:param method: "min", "prop" or a function on eval_df rows
|
||||
:returns: the transformed eval_df
|
||||
|
||||
"""
|
||||
df = eval_df.copy()
|
||||
for c in ["Bareme", "Mark", "Mark_barem"]:
|
||||
try:
|
||||
df[c+"_old"]
|
||||
except KeyError:
|
||||
df[c+"_old"] = df[c]
|
||||
|
||||
df["Bareme"] = new_scale
|
||||
|
||||
TRANFS = {"min": new_scale_min,
|
||||
"prop": new_scale_proportionnal,
|
||||
}
|
||||
try:
|
||||
t = TRANFS[method]
|
||||
except KeyError:
|
||||
df["Mark"] = df.apply(method)
|
||||
else:
|
||||
df["Mark"] = df.apply(t, axis=1)
|
||||
|
||||
df["Mark_barem"] = compute_mark_barem(df)
|
||||
return df
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# -----------------------------
|
||||
# Reglages pour 'vim'
|
||||
# vim:set autoindent expandtab tabstop=4 shiftwidth=4:
|
||||
# cursor: 16 del
|
||||
501
Repytex/tools/df_marks_manip.py
Normal file
501
Repytex/tools/df_marks_manip.py
Normal file
@@ -0,0 +1,501 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from math import ceil, floor
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
NOANSWER = "."
|
||||
NORATED = ""
|
||||
|
||||
# Values manipulations
|
||||
|
||||
def round_half_point(val):
|
||||
try:
|
||||
return 0.5 * ceil(2.0 * val)
|
||||
except ValueError:
|
||||
return val
|
||||
except TypeError:
|
||||
return val
|
||||
|
||||
def num_format(num):
|
||||
""" Tranform a number into an appropriate string """
|
||||
try:
|
||||
if int(num) == num:
|
||||
return str(int(num))
|
||||
except ValueError:
|
||||
pass
|
||||
return f"{num:.1f}".replace(".", ",")
|
||||
|
||||
latex_caract = ["\\NoRep", "\\RepZ", "\\RepU", "\\RepD", "\\RepT"]
|
||||
def note_to_rep(x):
|
||||
r""" Transform a Note to the latex caracter
|
||||
|
||||
:param x: dictionnary with "Niveau" and "Note" keys
|
||||
|
||||
>>> d = {"Eleve":["E1"]*6 + ["E2"]*6,
|
||||
... "Nom": ["N1"]*4+["N2"]*2 + ["N1"]*4+["N2"]*2,
|
||||
... "Exercice":["Ex1"]*2+["Ex2"]*2+["Ex1"]+["Ex2"] + ["Ex1"]*2+["Ex2"]*2+["Ex1"]+["Ex2"],
|
||||
... "Question":["Q1"]+["Q2"]+["Q1"]+["Q2"]+["Q1"]+["Q1"] + ["Q1"]+["Q2"]+["Q1"]+["Q2"]+["Q1"]+["Q1"],
|
||||
... "Date":["16/09/2016"]*4+["01/10/2016"]*2 + ["16/09/2016"]*4+["01/10/2016"]*2,
|
||||
... "Trimestre": ["1"]*12,
|
||||
... "Bareme":[1]*2+[2]*2+[2]*2 + [1]*2+[2]*2+[2]*2,
|
||||
... "Niveau":[0]*4+[1]*2 + [0]*4+[1]*2,
|
||||
... "Note":[1, 0.33, 2, 1.5, 1, 3, 0.67, 1, 1.5, 1, 2, 3],
|
||||
... }
|
||||
>>> df = pd.DataFrame(d)
|
||||
>>> note_to_rep(df.loc[0])
|
||||
1.0
|
||||
>>> note_to_rep(df.loc[4])
|
||||
'\\RepU'
|
||||
"""
|
||||
if x["Niveau"]:
|
||||
if x["Note"] == NOANSWER:
|
||||
return latex_caract[0]
|
||||
elif x["Note"] in range(4):
|
||||
return latex_caract[int(x["Note"])+1]
|
||||
return x["Note"]
|
||||
|
||||
def note_to_mark(x):
|
||||
""" Compute the mark when it is a "Niveau" note
|
||||
|
||||
:param x: dictionnary with "Niveau", "Note" and "Bareme" keys
|
||||
|
||||
>>> d = {"Eleve":["E1"]*6 + ["E2"]*6,
|
||||
... "Nom": ["N1"]*4+["N2"]*2 + ["N1"]*4+["N2"]*2,
|
||||
... "Exercice":["Ex1"]*2+["Ex2"]*2+["Ex1"]+["Ex2"] + ["Ex1"]*2+["Ex2"]*2+["Ex1"]+["Ex2"],
|
||||
... "Question":["Q1"]+["Q2"]+["Q1"]+["Q2"]+["Q1"]+["Q1"] + ["Q1"]+["Q2"]+["Q1"]+["Q2"]+["Q1"]+["Q1"],
|
||||
... "Date":["16/09/2016"]*4+["01/10/2016"]*2 + ["16/09/2016"]*4+["01/10/2016"]*2,
|
||||
... "Trimestre": ["1"]*12,
|
||||
... "Bareme":[1]*2+[2]*2+[2]*2 + [1]*2+[2]*2+[2]*2,
|
||||
... "Niveau":[0]*4+[1]*2 + [0]*4+[1]*2,
|
||||
... "Note":[1, 0.33, 2, 1.5, 1, 3, 0.666, 1, 1.5, 1, 2, 3],
|
||||
... }
|
||||
>>> df = pd.DataFrame(d)
|
||||
>>> note_to_mark(df.loc[0])
|
||||
1.0
|
||||
>>> note_to_mark(df.loc[10])
|
||||
1.3333333333333333
|
||||
"""
|
||||
|
||||
if x["Niveau"]:
|
||||
if x["Note"] == NOANSWER:
|
||||
return 0
|
||||
if x["Note"] not in [0, 1, 2, 3]:
|
||||
raise ValueError(f"The evaluation is out of range: {x['Note']} at {x}")
|
||||
return x["Note"] * x["Bareme"] / 3
|
||||
|
||||
if x["Note"] > x["Bareme"]:
|
||||
logger.warning(f"The note ({x['Note']}) is greated than the rating scale ({x['Bareme']}) at {x}")
|
||||
return x["Note"]
|
||||
|
||||
def note_to_level(x):
|
||||
""" Compute the level ("na",0,1,2,3).
|
||||
|
||||
"na" correspond to "no answer"
|
||||
|
||||
:param x: dictionnary with "Niveau", "Note" and "Bareme" keys
|
||||
|
||||
>>> d = {"Eleve":["E1"]*6 + ["E2"]*6,
|
||||
... "Nom": ["N1"]*4+["N2"]*2 + ["N1"]*4+["N2"]*2,
|
||||
... "Exercice":["Ex1"]*2+["Ex2"]*2+["Ex1"]+["Ex2"] + ["Ex1"]*2+["Ex2"]*2+["Ex1"]+["Ex2"],
|
||||
... "Question":["Q1"]+["Q2"]+["Q1"]+["Q2"]+["Q1"]+["Q1"] + ["Q1"]+["Q2"]+["Q1"]+["Q2"]+["Q1"]+["Q1"],
|
||||
... "Date":["16/09/2016"]*4+["01/10/2016"]*2 + ["16/09/2016"]*4+["01/10/2016"]*2,
|
||||
... "Trimestre": ["1"]*12,
|
||||
... "Bareme":[1]*2+[2]*2+[2]*2 + [1]*2+[2]*2+[2]*2,
|
||||
... "Niveau":[0]*4+[1]*2 + [0]*4+[1]*2,
|
||||
... "Note":[1, 0.33, np.nan, 1.5, 1, 3, 0.666, 1, 1.5, 1, 2, 3],
|
||||
... }
|
||||
>>> df = pd.DataFrame(d)
|
||||
>>> note_to_level(df.loc[0])
|
||||
3
|
||||
>>> note_to_level(df.loc[1])
|
||||
1
|
||||
>>> note_to_level(df.loc[2])
|
||||
'na'
|
||||
>>> note_to_level(df.loc[3])
|
||||
3
|
||||
>>> note_to_level(df.loc[5])
|
||||
3
|
||||
>>> note_to_level(df.loc[10])
|
||||
2
|
||||
"""
|
||||
if x["Note"] == NOANSWER:
|
||||
return "na"
|
||||
|
||||
if pd.isnull(x["Bareme"]) or x["Bareme"] == 0:
|
||||
return "na"
|
||||
|
||||
if x["Niveau"]:
|
||||
return int(x["Note"])
|
||||
else:
|
||||
return int(ceil(x["Note"] / x["Bareme"] * 3))
|
||||
|
||||
def mark_bareme_formater(row):
|
||||
""" Create m/b string """
|
||||
return f"{num_format(row['Mark'])} / {num_format(row['Bareme'])}"
|
||||
|
||||
def question_uniq_formater(row):
|
||||
""" Create a kind of unique description of the question
|
||||
|
||||
>>> d = {"Eleve":["E1"]*6 + ["E2"]*6,
|
||||
... "Nom": ["N1"]*4+["N2"]*2 + ["N1"]*4+["N2"]*2,
|
||||
... "Exercice":["Ex1"]*2+["Ex2"]*2+["Ex1"]+["Ex2"] + ["Ex1"]*2+["Ex2"]*2+["Ex1"]+["Ex2"],
|
||||
... "Question":["Q1"]+["Q2"]+["Q1"]+["Q2"]+["Q1"]+["Q1"] + ["Q1"]+["Q2"]+["Q1"]+["Q2"]+["Q1"]+["Q1"],
|
||||
... "Date":["16/09/2016"]*4+["01/10/2016"]*2 + ["16/09/2016"]*4+["01/10/2016"]*2,
|
||||
... "Trimestre": ["1"]*12,
|
||||
... "Bareme":[1]*2+[2]*2+[2]*2 + [1]*2+[2]*2+[2]*2,
|
||||
... "Niveau":[0]*4+[1]*2 + [0]*4+[1]*2,
|
||||
... "Note":[1, 0.33, 2, 1.5, 1, 3, 0.666, 1, 1.5, 1, 2, 3],
|
||||
... }
|
||||
>>> df = pd.DataFrame(d)
|
||||
>>> question_uniq_formater(df.loc[0])
|
||||
'Ex1 Q1'
|
||||
>>> question_uniq_formater(df.loc[10])
|
||||
'Ex1 Q1'
|
||||
|
||||
"""
|
||||
ans = ""
|
||||
try:
|
||||
int(row['Exercice'])
|
||||
except ValueError:
|
||||
ans += str(row["Exercice"])
|
||||
else:
|
||||
ans += "Exo"+str(row["Exercice"])
|
||||
|
||||
ans += " "
|
||||
|
||||
try:
|
||||
int(row["Question"])
|
||||
except ValueError:
|
||||
if not pd.isnull(row["Question"]):
|
||||
ans += str(row["Question"])
|
||||
else:
|
||||
ans += "Qu"+str(row["Question"])
|
||||
|
||||
try:
|
||||
row["Commentaire"]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
if not pd.isnull(row["Commentaire"]):
|
||||
ans += " ({})".format(row["Commentaire"])
|
||||
return ans
|
||||
|
||||
# DataFrame columns manipulations
|
||||
|
||||
def compute_marks(df):
|
||||
""" Add Mark column to df
|
||||
|
||||
:param df: DataFrame with "Note", "Niveau" and "Bareme" columns.
|
||||
|
||||
>>> d = {"Eleve":["E1"]*6 + ["E2"]*6,
|
||||
... "Nom": ["N1"]*4+["N2"]*2 + ["N1"]*4+["N2"]*2,
|
||||
... "Exercice":["Ex1"]*2+["Ex2"]*2+["Ex1"]+["Ex2"] + ["Ex1"]*2+["Ex2"]*2+["Ex1"]+["Ex2"],
|
||||
... "Question":["Q1"]+["Q2"]+["Q1"]+["Q2"]+["Q1"]+["Q1"] + ["Q1"]+["Q2"]+["Q1"]+["Q2"]+["Q1"]+["Q1"],
|
||||
... "Date":["16/09/2016"]*4+["01/10/2016"]*2 + ["16/09/2016"]*4+["01/10/2016"]*2,
|
||||
... "Trimestre": ["1"]*12,
|
||||
... "Bareme":[1]*2+[2]*2+[2]*2 + [1]*2+[2]*2+[2]*2,
|
||||
... "Niveau":[0]*4+[1]*2 + [0]*4+[1]*2,
|
||||
... "Note":[1, 0.33, 2, 1.5, 1, 3, 0.666, 1, 1.5, 1, 2, 3],
|
||||
... }
|
||||
>>> df = pd.DataFrame(d)
|
||||
>>> compute_marks(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[["Note", "Niveau", "Bareme"]].apply(note_to_mark, axis=1)
|
||||
|
||||
def compute_level(df):
|
||||
""" Add Mark column to df
|
||||
|
||||
:param df: DataFrame with "Note", "Niveau" and "Bareme" columns.
|
||||
|
||||
>>> d = {"Eleve":["E1"]*6 + ["E2"]*6,
|
||||
... "Nom": ["N1"]*4+["N2"]*2 + ["N1"]*4+["N2"]*2,
|
||||
... "Exercice":["Ex1"]*2+["Ex2"]*2+["Ex1"]+["Ex2"] + ["Ex1"]*2+["Ex2"]*2+["Ex1"]+["Ex2"],
|
||||
... "Question":["Q1"]+["Q2"]+["Q1"]+["Q2"]+["Q1"]+["Q1"] + ["Q1"]+["Q2"]+["Q1"]+["Q2"]+["Q1"]+["Q1"],
|
||||
... "Date":["16/09/2016"]*4+["01/10/2016"]*2 + ["16/09/2016"]*4+["01/10/2016"]*2,
|
||||
... "Trimestre": ["1"]*12,
|
||||
... "Bareme":[1]*2+[2]*2+[2]*2 + [1]*2+[2]*2+[2]*2,
|
||||
... "Niveau":[0]*4+[1]*2 + [0]*4+[1]*2,
|
||||
... "Note":[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[["Note", "Niveau", "Bareme"]].apply(note_to_level, axis=1)
|
||||
|
||||
def compute_latex_rep(df):
|
||||
""" Add Latex_rep column to df
|
||||
|
||||
:param df: DataFrame with "Note" and "Niveau" columns.
|
||||
|
||||
>>> d = {"Eleve":["E1"]*6 + ["E2"]*6,
|
||||
... "Nom": ["N1"]*4+["N2"]*2 + ["N1"]*4+["N2"]*2,
|
||||
... "Exercice":["Ex1"]*2+["Ex2"]*2+["Ex1"]+["Ex2"] + ["Ex1"]*2+["Ex2"]*2+["Ex1"]+["Ex2"],
|
||||
... "Question":["Q1"]+["Q2"]+["Q1"]+["Q2"]+["Q1"]+["Q1"] + ["Q1"]+["Q2"]+["Q1"]+["Q2"]+["Q1"]+["Q1"],
|
||||
... "Date":["16/09/2016"]*4+["01/10/2016"]*2 + ["16/09/2016"]*4+["01/10/2016"]*2,
|
||||
... "Trimestre": ["1"]*12,
|
||||
... "Bareme":[1]*2+[2]*2+[2]*2 + [1]*2+[2]*2+[2]*2,
|
||||
... "Niveau":[0]*4+[1]*2 + [0]*4+[1]*2,
|
||||
... "Note":[1, 0.33, 2, 1.5, 1, 3, 0.666, 1, 1.5, 1, 2, 3],
|
||||
... }
|
||||
>>> df = pd.DataFrame(d)
|
||||
>>> compute_latex_rep(df)
|
||||
0 1
|
||||
1 0.33
|
||||
2 2
|
||||
3 1.5
|
||||
4 \RepU
|
||||
5 \RepT
|
||||
6 0.67
|
||||
7 1
|
||||
8 1.5
|
||||
9 1
|
||||
10 \RepD
|
||||
11 \RepT
|
||||
dtype: object
|
||||
"""
|
||||
return df[["Note", "Niveau"]].apply(note_to_rep, axis=1).fillna("??")
|
||||
|
||||
def compute_normalized(df):
|
||||
""" Compute the normalized mark (Mark / Bareme)
|
||||
|
||||
:param df: DataFrame with "Mark" and "Bareme" columns
|
||||
|
||||
>>> d = {"Eleve":["E1"]*6 + ["E2"]*6,
|
||||
... "Nom": ["N1"]*4+["N2"]*2 + ["N1"]*4+["N2"]*2,
|
||||
... "Exercice":["Ex1"]*2+["Ex2"]*2+["Ex1"]+["Ex2"] + ["Ex1"]*2+["Ex2"]*2+["Ex1"]+["Ex2"],
|
||||
... "Question":["Q1"]+["Q2"]+["Q1"]+["Q2"]+["Q1"]+["Q1"] + ["Q1"]+["Q2"]+["Q1"]+["Q2"]+["Q1"]+["Q1"],
|
||||
... "Date":["16/09/2016"]*4+["01/10/2016"]*2 + ["16/09/2016"]*4+["01/10/2016"]*2,
|
||||
... "Trimestre": ["1"]*12,
|
||||
... "Bareme":[1]*2+[2]*2+[2]*2 + [1]*2+[2]*2+[2]*2,
|
||||
... "Niveau":[0]*4+[1]*2 + [0]*4+[1]*2,
|
||||
... "Note":[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["Mark"] / df["Bareme"]
|
||||
|
||||
def compute_mark_barem(df):
|
||||
""" Build the string mark m/b """
|
||||
return df.apply(mark_bareme_formater, axis=1)
|
||||
|
||||
def compute_question_description(df):
|
||||
""" Compute the unique description of a question """
|
||||
return df.apply(question_uniq_formater, axis = 1)
|
||||
|
||||
# Computing custom values
|
||||
|
||||
def compute_exo_marks(df):
|
||||
""" Compute Exercice level marks
|
||||
|
||||
:param df: the original marks
|
||||
:returns: DataFrame with computed marks
|
||||
|
||||
>>> d = {"Eleve":["E1"]*6 + ["E2"]*6,
|
||||
... "Nom": ["N1"]*4+["N2"]*2 + ["N1"]*4+["N2"]*2,
|
||||
... "Exercice":["Ex1"]*2+["Ex2"]*2+["Ex1"]+["Ex2"] + ["Ex1"]*2+["Ex2"]*2+["Ex1"]+["Ex2"],
|
||||
... "Question":["Q1"]+["Q2"]+["Q1"]+["Q2"]+["Q1"]+["Q1"] + ["Q1"]+["Q2"]+["Q1"]+["Q2"]+["Q1"]+["Q1"],
|
||||
... "Date":["16/09/2016"]*4+["01/10/2016"]*2 + ["16/09/2016"]*4+["01/10/2016"]*2,
|
||||
... "Trimestre": ["1"]*12,
|
||||
... "Bareme":[1]*2+[2]*2+[2]*2 + [1]*2+[2]*2+[2]*2,
|
||||
... "Niveau":[0]*4+[1]*2 + [0]*4+[1]*2,
|
||||
... "Note":[1, 0.33, 2, 1.5, 1, 3, 0.67, 1, 1.5, 1, 2, 3],
|
||||
... }
|
||||
>>> df = pd.DataFrame(d)
|
||||
>>> df["Mark"] = compute_marks(df)
|
||||
>>> compute_exo_marks(df)
|
||||
Eleve Nom Exercice Date Trimestre Bareme Mark Question Niveau
|
||||
0 E1 N1 Ex1 16/09/2016 1 2.0 1.5 Total 0
|
||||
1 E1 N1 Ex2 16/09/2016 1 4.0 3.5 Total 0
|
||||
2 E1 N2 Ex1 01/10/2016 1 2.0 1.0 Total 0
|
||||
3 E1 N2 Ex2 01/10/2016 1 2.0 2.0 Total 0
|
||||
4 E2 N1 Ex1 16/09/2016 1 2.0 2.0 Total 0
|
||||
5 E2 N1 Ex2 16/09/2016 1 4.0 2.5 Total 0
|
||||
6 E2 N2 Ex1 01/10/2016 1 2.0 1.5 Total 0
|
||||
7 E2 N2 Ex2 01/10/2016 1 2.0 2.0 Total 0
|
||||
|
||||
|
||||
"""
|
||||
exo_pt = pd.pivot_table(df,
|
||||
index = [ "Eleve", "Nom", "Exercice", "Date", "Trimestre"],
|
||||
values = ["Bareme", "Mark"],
|
||||
aggfunc=np.sum,
|
||||
).applymap(round_half_point)
|
||||
|
||||
exo = exo_pt.reset_index()
|
||||
exo["Question"] = "Total"
|
||||
exo["Niveau"] = 0
|
||||
return exo
|
||||
|
||||
def compute_eval_marks(df):
|
||||
""" Compute Nom level marks from the dataframe using only row with Total in Question
|
||||
|
||||
:param df: DataFrame with value Total in Question column
|
||||
:returns: DataFrame with evaluation marks
|
||||
|
||||
>>> d = {"Eleve":["E1"]*6 + ["E2"]*6,
|
||||
... "Nom": ["N1"]*4+["N2"]*2 + ["N1"]*4+["N2"]*2,
|
||||
... "Exercice":["Ex1"]*2+["Ex2"]*2+["Ex1"]+["Ex2"] + ["Ex1"]*2+["Ex2"]*2+["Ex1"]+["Ex2"],
|
||||
... "Question":["Q1"]+["Q2"]+["Q1"]+["Q2"]+["Q1"]+["Q1"] + ["Q1"]+["Q2"]+["Q1"]+["Q2"]+["Q1"]+["Q1"],
|
||||
... "Date":["16/09/2016"]*4+["01/10/2016"]*2 + ["16/09/2016"]*4+["01/10/2016"]*2,
|
||||
... "Trimestre": ["1"]*12,
|
||||
... "Bareme":[1]*2+[2]*2+[2]*2 + [1]*2+[2]*2+[2]*2,
|
||||
... "Niveau":[0]*4+[1]*2 + [0]*4+[1]*2,
|
||||
... "Note":[1, 0.33, 2, 1.5, 1, 3, 0.67, 1, 1.5, 1, 2, 3],
|
||||
... }
|
||||
>>> df = pd.DataFrame(d)
|
||||
>>> df["Mark"] = compute_marks(df)
|
||||
>>> df_exo = compute_exo_marks(df)
|
||||
>>> compute_eval_marks(df_exo)
|
||||
index Eleve Nom Trimestre Bareme Date Mark
|
||||
0 0 E1 N1 1 6.0 16/09/2016 5.0
|
||||
1 1 E2 N1 1 6.0 16/09/2016 4.5
|
||||
2 0 E1 N2 1 4.0 01/10/2016 3.0
|
||||
3 1 E2 N2 1 4.0 01/10/2016 3.5
|
||||
|
||||
|
||||
"""
|
||||
def date_format(dates):
|
||||
date_l = list(dates.unique())
|
||||
if len(date_l) == 1:
|
||||
return date_l[0]
|
||||
else:
|
||||
return "Trimestre"
|
||||
|
||||
eval_m = pd.DataFrame()
|
||||
for eval_name in df["Nom"].unique():
|
||||
logger.debug(f"Compute marks for {eval_name}")
|
||||
eval_df = df[df["Nom"] == eval_name]
|
||||
dates = eval_df["Date"].unique()
|
||||
logger.debug(f"Find those dates: {dates}")
|
||||
if len(dates) > 1 or dates[0] == "Trimestre":
|
||||
# Les devoirs sur la durée, les NaN ne sont pas pénalisants
|
||||
# On les enlèves
|
||||
eval_df = eval_df.dropna(subset=["Mark"])
|
||||
dates = ["Trimestre"]
|
||||
|
||||
eval_pt = pd.pivot_table(eval_df,
|
||||
index = [ "Eleve", "Nom", "Trimestre"],
|
||||
values = ["Bareme", "Mark", "Date"],
|
||||
aggfunc={"Bareme": np.sum, "Mark": np.sum, "Date":lambda x:dates[0]},
|
||||
)
|
||||
eval_pt = eval_pt.reset_index()
|
||||
eval_m = pd.concat([eval_m, eval_pt])
|
||||
|
||||
eval_m = eval_m.reset_index()
|
||||
|
||||
return eval_m
|
||||
|
||||
def digest_flat_df(flat_df):
|
||||
r""" Compute necessary element to make a flat df usable for analysis.
|
||||
|
||||
>>> from numpy import nan
|
||||
>>> d = {"Eleve":["E1"]*6 + ["E2"]*6,
|
||||
... "Nom": ["N1"]*4+["N2"]*2 + ["N1"]*4+["N2"]*2,
|
||||
... "Exercice":["Ex1"]*2+["Ex2"]*2+["Ex1"]+["Ex2"] + ["Ex1"]*2+["Ex2"]*2+["Ex1"]+["Ex2"],
|
||||
... "Question":["Q1"]+["Q2"]+["Q1"]+["Q2"]+["Q1"]+["Q1"] + ["Q1"]+["Q2"]+["Q1"]+["Q2"]+["Q1"]+["Q1"],
|
||||
... "Date":["16/09/2016"]*4+["01/10/2016"]*2 + ["16/09/2016"]*4+["01/10/2016"]*2,
|
||||
... "Trimestre": ["1"]*12,
|
||||
... "Bareme":[1]*2+[2]*2+[2]*2 + [1]*2+[2]*2+[2]*2,
|
||||
... "Niveau":[0]*4+[1]*2 + [0]*4+[1]*2,
|
||||
... "Note":[1, 0.33, 2, 1.5, 1, 3, np.nan, 0, 0, np.nan, np.nan, np.nan],
|
||||
... }
|
||||
>>> df = pd.DataFrame(d)
|
||||
>>> quest_df, exo_df, eval_df = digest_flat_df(df)
|
||||
>>> quest_df[['Eleve', "Nom", "Mark", "Latex_rep", "Normalized", "Uniq_quest", "Level"]]
|
||||
Eleve Nom Mark Latex_rep Normalized Uniq_quest Level
|
||||
0 E1 N1 1.00 1 1.00 Ex1 Q1 3
|
||||
1 E1 N1 0.33 0.33 0.33 Ex1 Q2 1
|
||||
2 E1 N1 2.00 2 1.00 Ex2 Q1 3
|
||||
3 E1 N1 1.50 1.5 0.75 Ex2 Q2 3
|
||||
4 E1 N2 0.67 \RepU 0.33 Ex1 Q1 1
|
||||
5 E1 N2 2.00 \RepT 1.00 Ex2 Q1 3
|
||||
6 E2 N1 NaN ?? NaN Ex1 Q1 na
|
||||
7 E2 N1 0.00 0 0.00 Ex1 Q2 0
|
||||
8 E2 N1 0.00 0 0.00 Ex2 Q1 0
|
||||
9 E2 N1 NaN ?? NaN Ex2 Q2 na
|
||||
10 E2 N2 NaN \NoRep NaN Ex1 Q1 na
|
||||
11 E2 N2 NaN \NoRep NaN Ex2 Q1 na
|
||||
>>> exo_df[['Eleve', "Nom", "Exercice", "Mark", "Normalized"]]
|
||||
Eleve Nom Exercice Mark Normalized
|
||||
0 E1 N1 Ex1 1.5 0.75
|
||||
1 E1 N1 Ex2 3.5 0.88
|
||||
2 E1 N2 Ex1 1.0 0.50
|
||||
3 E1 N2 Ex2 2.0 1.00
|
||||
4 E2 N1 Ex1 0.0 0.00
|
||||
5 E2 N1 Ex2 0.0 0.00
|
||||
6 E2 N2 Ex1 NaN NaN
|
||||
7 E2 N2 Ex2 NaN NaN
|
||||
>>> eval_df
|
||||
index Eleve Nom Trimestre Bareme Date Mark Normalized
|
||||
0 0 E1 N1 1 6.0 16/09/2016 5.0 0.83
|
||||
1 1 E2 N1 1 6.0 16/09/2016 0.0 0.00
|
||||
2 0 E1 N2 1 4.0 01/10/2016 3.0 0.75
|
||||
3 1 E2 N2 1 4.0 01/10/2016 NaN NaN
|
||||
"""
|
||||
df = flat_df.dropna(subset=["Note"])
|
||||
|
||||
df["Mark"] = compute_marks(df)
|
||||
df["Level"] = compute_level(df)
|
||||
df["Latex_rep"] = compute_latex_rep(df)
|
||||
df["Normalized"] = compute_normalized(df)
|
||||
#df["Uniq_quest"] = compute_question_description(df)
|
||||
|
||||
exo_df = compute_exo_marks(df)
|
||||
exo_df["Normalized"] = compute_normalized(exo_df)
|
||||
exo_df["Mark_barem"] = compute_mark_barem(exo_df)
|
||||
eval_df = compute_eval_marks(exo_df)
|
||||
eval_df["Normalized"] = compute_normalized(eval_df)
|
||||
eval_df["Mark_barem"] = compute_mark_barem(eval_df)
|
||||
|
||||
return df, exo_df, eval_df
|
||||
|
||||
|
||||
# -----------------------------
|
||||
# Reglages pour 'vim'
|
||||
# vim:set autoindent expandtab tabstop=4 shiftwidth=4:
|
||||
# cursor: 16 del
|
||||
282
Repytex/tools/evaluation.py
Normal file
282
Repytex/tools/evaluation.py
Normal file
@@ -0,0 +1,282 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from notes_tools.tools.marks_plottings import (pie_pivot_table,
|
||||
parallel_on,
|
||||
radar_on,
|
||||
hist_boxplot
|
||||
)
|
||||
|
||||
import seaborn as sns
|
||||
|
||||
class Student(object):
|
||||
|
||||
"""
|
||||
Informations on a student which can be use inside template.
|
||||
|
||||
Those informations should not be modify or use for compute analysis otherwise they won't be spread over other POV.
|
||||
"""
|
||||
|
||||
def __init__(self, quest_df, exo_df, eval_df):
|
||||
"""
|
||||
Description of a student from quest, exo and eval
|
||||
|
||||
"""
|
||||
|
||||
name = {*quest_df["Eleve"].unique(),
|
||||
*exo_df["Eleve"].unique(),
|
||||
*eval_df["Eleve"].unique(),
|
||||
}
|
||||
|
||||
if len(name) != 1:
|
||||
raise ValueError("Can't initiate Student: dfs contains different student names")
|
||||
|
||||
self.name = name.pop()
|
||||
|
||||
evalname = {*quest_df["Nom"].unique(),
|
||||
*exo_df["Nom"].unique(),
|
||||
*eval_df["Nom"].unique(),
|
||||
}
|
||||
|
||||
if len(evalname) != 1:
|
||||
raise ValueError(f"Can't initiate Student: dfs contains different evaluation names ({'-'.join(evalname)})")
|
||||
|
||||
self.quest_df = quest_df
|
||||
self.exo_df = exo_df
|
||||
self.eval = eval_df.to_dict('records')[0]
|
||||
|
||||
@property
|
||||
def latex_exo_tabulars(self):
|
||||
""" Return list of latex tabulars. One by exercise of the evaluation """
|
||||
try:
|
||||
self._latex_exo_tabulars
|
||||
except AttributeError:
|
||||
self._latex_exo_tabulars = self.build_latex_exo_tabulars()
|
||||
return self._latex_exo_tabulars
|
||||
|
||||
def build_latex_exo_tabulars(self):
|
||||
tabulars = []
|
||||
for t in self.exo_df["Exercice"]:
|
||||
tabulars.append(self.build_latex_exo_tabular(t))
|
||||
|
||||
return tabulars
|
||||
|
||||
def build_latex_exo_tabular(self, exo_name):
|
||||
exo = self.exo_df[self.exo_df["Exercice"] == exo_name]
|
||||
quest = self.quest_df[self.quest_df["Exercice"] == exo_name]
|
||||
|
||||
tabular = [r"\begin{tabular}{|p{2cm}|p{1cm}|}"]
|
||||
tabular.append(r"\hline")
|
||||
tabular.append(r"\rowcolor{highlightbg}")
|
||||
|
||||
if type(exo_name) == int:
|
||||
l = f"Exercice {exo_name} & {exo['Mark_barem'].all()}"
|
||||
tabular.append(l + r" \\")
|
||||
else:
|
||||
l = f"{exo_name} & {exo['Mark_barem'].all()}"
|
||||
tabular.append(l + r" \\")
|
||||
tabular.append(r"\hline")
|
||||
|
||||
if len(quest) > 1:
|
||||
for _, q in quest.iterrows():
|
||||
line = ""
|
||||
if not pd.isnull(q["Question"]):
|
||||
line += " "+str(q['Question'])
|
||||
if not pd.isnull(q["Commentaire"]):
|
||||
line += " "+str(q['Commentaire'])
|
||||
|
||||
line += " & "
|
||||
if q["Niveau"] == 1:
|
||||
line += q['Latex_rep']
|
||||
else:
|
||||
line += str(q['Mark'])
|
||||
line += r" \\"
|
||||
tabular.append(line)
|
||||
tabular.append(r"\hline")
|
||||
|
||||
tabular.append(r"\end{tabular}")
|
||||
return '\n'.join(tabular)
|
||||
|
||||
@property
|
||||
def pies_on_competence(self):
|
||||
""" Pies chart on competences """
|
||||
return pie_pivot_table(self.quest_df,
|
||||
index = "Level",
|
||||
columns = "Competence",
|
||||
values = "Eleve",
|
||||
aggfunc = len,
|
||||
fill_value = 0,
|
||||
)
|
||||
|
||||
@property
|
||||
def pies_on_domaine(self):
|
||||
""" Pies chart on domaines """
|
||||
return pie_pivot_table(self.quest_df,
|
||||
index = "Level",
|
||||
columns = "Domaine",
|
||||
values = "Eleve",
|
||||
aggfunc = len,
|
||||
fill_value = 0,
|
||||
)
|
||||
|
||||
@property
|
||||
def radar_on_competence(self):
|
||||
""" Radar plot on competence """
|
||||
return radar_on(self.quest_df,
|
||||
"Competence")
|
||||
|
||||
@property
|
||||
def radar_on_domaine(self):
|
||||
""" Radar plot on domaine """
|
||||
return radar_on(self.quest_df,
|
||||
"Domaine")
|
||||
|
||||
@property
|
||||
def heatmap_on_domain(self):
|
||||
""" Heatmap over evals on domains """
|
||||
comp = pd.pivot_table(self.quest_df,
|
||||
index = "Competence",
|
||||
columns = ["Exercice", "Question"],
|
||||
values = ["Normalized"],
|
||||
aggfunc = np.mean,
|
||||
)
|
||||
comp.columns = [f"{i['Exercice']} {i['Question']}" for _,i in self.quest_df[["Exercice", "Question"]].drop_duplicates().iterrows()]
|
||||
return sns.heatmap(comp)
|
||||
|
||||
class Classe(object):
|
||||
|
||||
"""
|
||||
Informations on a class which can be use inside template.
|
||||
|
||||
Those informations should not be modify or use for compute analysis otherwise they won't be spread over other POV.
|
||||
"""
|
||||
def __init__(self, quest_df, exo_df, eval_df):
|
||||
""" Init of a class from quest, exo and eval """
|
||||
names = {*quest_df["Nom"].unique(),
|
||||
*exo_df["Nom"].unique(),
|
||||
*eval_df["Nom"].unique(),
|
||||
}
|
||||
|
||||
if len(names) != 1:
|
||||
raise ValueError("Can't initiate Classe: dfs contains different evaluation names")
|
||||
|
||||
self.name = names.pop()
|
||||
|
||||
self.quest_df = quest_df
|
||||
self.exo_df = exo_df
|
||||
self.eval_df = eval_df
|
||||
|
||||
@property
|
||||
def marks_tabular(self):
|
||||
""" Latex tabular with marks of students"""
|
||||
try:
|
||||
self._marks_tabular
|
||||
except AttributeError:
|
||||
self._marks_tabular = self.eval_df[["Eleve", "Mark_barem"]]
|
||||
self._marks_tabular.columns = ["Élèves", "Note"]
|
||||
return self._marks_tabular.to_latex()
|
||||
|
||||
@property
|
||||
def hist_boxplot(self):
|
||||
""" Marks histogram and associed box plot """
|
||||
return hist_boxplot(self.eval_df)
|
||||
|
||||
@property
|
||||
def level_heatmap(self):
|
||||
""" Heapmap on acheivement level """
|
||||
pv = pd.pivot_table(self.quest_df,
|
||||
index = "Eleve",
|
||||
columns = ["Exercice", "Question", "Commentaire"],
|
||||
values = ["Normalized"],
|
||||
aggfunc = "mean",
|
||||
)
|
||||
|
||||
def lines_4_heatmap(c):
|
||||
lines = []
|
||||
ini = ''
|
||||
for k,v in enumerate(c.labels[1][::-1]):
|
||||
if v != ini:
|
||||
lines.append(k)
|
||||
ini = v
|
||||
return lines[1:]
|
||||
exercice_sep = lines_4_heatmap(pv.columns)
|
||||
|
||||
pv.columns = [f"{i[3]:.15} {i[1]} {i[2]}" for i in pv.columns.get_values()]
|
||||
|
||||
level_heatmap = sns.heatmap(pv.T)
|
||||
|
||||
level_heatmap.hlines(exercice_sep,
|
||||
*level_heatmap.get_xlim(),
|
||||
colors = "orange",
|
||||
)
|
||||
return level_heatmap
|
||||
|
||||
|
||||
@property
|
||||
def pies_eff_pts_on_competence(self):
|
||||
""" Pie charts on competence with repartition of evaluated times and attributed points """
|
||||
return pie_pivot_table(self.quest_df[["Competence", "Bareme", "Exercice", "Question", "Commentaire"]].drop_duplicates(),
|
||||
index = "Competence",
|
||||
#columns = "Level",
|
||||
values = "Bareme",
|
||||
aggfunc=[len,np.sum],
|
||||
fill_value=0)
|
||||
|
||||
@property
|
||||
def pies_eff_pts_on_domaine(self):
|
||||
""" Pie charts on domaine with repartition of evaluated times and attributed points """
|
||||
return pie_pivot_table(self.quest_df[["Domaine", "Bareme", "Exercice", "Question", "Commentaire"]].drop_duplicates(),
|
||||
index = "Domaine",
|
||||
#columns = "Level",
|
||||
values = "Bareme",
|
||||
aggfunc=[len,np.sum],
|
||||
fill_value=0)
|
||||
|
||||
|
||||
|
||||
# TODO: à factoriser Il y a la même dans term.py |jeu. mars 23 19:36:28 EAT 2017
|
||||
def select(quest_df, exo_df, eval_df, index, value):
|
||||
""" Return quest, exo and eval rows which correspond index == value
|
||||
|
||||
:param quest_df: TODO
|
||||
:param exo_df: TODO
|
||||
:param eval_df: TODO
|
||||
|
||||
"""
|
||||
qu = quest_df[quest_df[index] == value]
|
||||
exo = exo_df[exo_df[index] == value]
|
||||
ev = eval_df[eval_df[index] == value]
|
||||
return qu, exo, ev
|
||||
|
||||
def select_contains(quest_df, exo_df, eval_df, index, name_part):
|
||||
""" Return quest, exo and eval rows which contains name_part
|
||||
|
||||
:param quest_df: TODO
|
||||
:param exo_df: TODO
|
||||
:param eval_df: TODO
|
||||
|
||||
"""
|
||||
qu = quest_df[quest_df[index].str.contains(name_part)]
|
||||
exo = exo_df[exo_df[index].str.contains(name_part)]
|
||||
ev = eval_df[eval_df[index].str.contains(name_part)]
|
||||
return qu, exo, ev
|
||||
|
||||
def students_pov(quest_df, exo_df, eval_df):
|
||||
es = []
|
||||
for e in eval_df["Eleve"].unique():
|
||||
d = select(quest_df, exo_df, eval_df, "Eleve", e)
|
||||
eleve = Student(*d)
|
||||
es.append(eleve)
|
||||
return es
|
||||
|
||||
def class_pov(quest_df, exo_df, eval_df):
|
||||
return Classe(quest_df, exo_df, eval_df)
|
||||
|
||||
|
||||
|
||||
# -----------------------------
|
||||
# Reglages pour 'vim'
|
||||
# vim:set autoindent expandtab tabstop=4 shiftwidth=4:
|
||||
# cursor: 16 del
|
||||
149
Repytex/tools/extract.py
Normal file
149
Repytex/tools/extract.py
Normal file
@@ -0,0 +1,149 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import xlrd
|
||||
from path import Path
|
||||
|
||||
|
||||
notes_path = Path("./")
|
||||
|
||||
no_student_columns = ["Trimestre",
|
||||
"Nom",
|
||||
"Date",
|
||||
"Exercice",
|
||||
"Question",
|
||||
"Competence",
|
||||
"Domaine",
|
||||
"Commentaire",
|
||||
"Bareme",
|
||||
"Niveau"]
|
||||
|
||||
pd.set_option("Precision",2)
|
||||
|
||||
def list_classes(path = notes_path):
|
||||
"""
|
||||
List classes available in notes_path
|
||||
|
||||
>>> list_classes()
|
||||
[]
|
||||
>>> p = Path("./samples/")
|
||||
>>> list_classes(p)
|
||||
['503', '312', '308']
|
||||
>>> list_classes("./samples/")
|
||||
['503', '312', '308']
|
||||
"""
|
||||
try:
|
||||
return [n.namebase for n in path.files("*.xlsx")]
|
||||
except AttributeError:
|
||||
p = Path(path)
|
||||
return [n.namebase for n in p.files("*.xlsx")]
|
||||
|
||||
def get_class_ws(classe, path = notes_path):
|
||||
"""
|
||||
From the name of a classe, returns pd.ExcelFile
|
||||
"""
|
||||
p = Path(path)
|
||||
if classe in list_classes(p):
|
||||
return pd.ExcelFile(p/classe+".xlsx")
|
||||
else:
|
||||
raise ValueError("This class is not disponible in {p}. You have to choose in {c}".format(p = p, c = list_classes(p)))
|
||||
|
||||
def extract_students(df, no_student_columns = no_student_columns):
|
||||
""" Extract the list of students from df """
|
||||
students = df.columns.difference(no_student_columns)
|
||||
return students
|
||||
|
||||
def check_students(dfs, no_student_columns = no_student_columns):
|
||||
""" Build students list """
|
||||
dfs_students = [extract_students(df) for df in dfs]
|
||||
|
||||
if not are_equal(dfs_students):
|
||||
raise ValueError("Not same list of students amoung worksheets")
|
||||
|
||||
return dfs_students[0]
|
||||
|
||||
def are_equal(elems):
|
||||
""" Test if item of elems are equal
|
||||
|
||||
>>> L = [[1, 2, 3], [1, 3, 2], [1, 3, 2]]
|
||||
>>> are_equal(L)
|
||||
True
|
||||
>>> L = [[0, 2, 3], [1, 3, 2], [1, 3, 2]]
|
||||
>>> are_equal(L)
|
||||
False
|
||||
|
||||
"""
|
||||
first = sorted(elems[0])
|
||||
others = [sorted(e) for e in elems[1:]]
|
||||
diff = [e == first for e in others]
|
||||
|
||||
if False in diff:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def flat_df_students(df, students):
|
||||
""" Flat the ws for students """
|
||||
flat_df = pd.DataFrame()
|
||||
flat_data = []
|
||||
dfT = df.T
|
||||
for n in dfT:
|
||||
pre_di = dfT[n][no_student_columns].to_dict()
|
||||
for e in students:
|
||||
data = pre_di.copy()
|
||||
data["Eleve"] = e
|
||||
data["Note"] = dfT[n].loc[e]
|
||||
flat_data.append(data)
|
||||
return pd.DataFrame.from_dict(flat_data)
|
||||
|
||||
def parse_sheets(ws,
|
||||
marks_sheetnames = ["Notes", "Connaissances", "Calcul mental"]):
|
||||
""" Parse sheets from marks_sheetnames
|
||||
|
||||
:param ws: the worksheet
|
||||
:param marks_sheetnames: names of sheets for extracting
|
||||
|
||||
"""
|
||||
sheets = []
|
||||
for sheetname in marks_sheetnames:
|
||||
try:
|
||||
sheets.append(ws.parse(sheetname))
|
||||
except xlrd.biffh.XLRDError:
|
||||
pass
|
||||
return sheets
|
||||
|
||||
def extract_flat_marks(ws,
|
||||
marks_sheetnames=["Notes", "Connaissances", "Calcul mental"]):
|
||||
""" Extract, flat and contact marks from the worksheet
|
||||
|
||||
:param ws: the worksheet
|
||||
:param marks_sheetnames: name of worksheets
|
||||
:returns: TODO
|
||||
|
||||
"""
|
||||
sheets = parse_sheets(ws, marks_sheetnames)
|
||||
|
||||
students = check_students(sheets)
|
||||
|
||||
flat_df = pd.DataFrame()
|
||||
for sheet in sheets:
|
||||
flat = flat_df_students(sheet, students)
|
||||
flat_df = pd.concat([flat_df, flat])
|
||||
|
||||
flat_df["Question"].fillna("", inplace = True)
|
||||
flat_df["Exercice"].fillna("", inplace = True)
|
||||
flat_df["Commentaire"].fillna("", inplace = True)
|
||||
flat_df["Competence"].fillna("", inplace = True)
|
||||
|
||||
return flat_df
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# -----------------------------
|
||||
# Reglages pour 'vim'
|
||||
# vim:set autoindent expandtab tabstop=4 shiftwidth=4:
|
||||
# cursor: 16 del
|
||||
116
Repytex/tools/marks_plottings.py
Normal file
116
Repytex/tools/marks_plottings.py
Normal file
@@ -0,0 +1,116 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
from .plottings import radar_graph, pivot_table_to_pie
|
||||
from .skills_tools import count_levels, count_skill_evaluation
|
||||
import matplotlib.pyplot as plt
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
__all__ = ["radar_on",
|
||||
"pie_pivot_table",
|
||||
"marks_hist",
|
||||
"parallel_on",
|
||||
]
|
||||
|
||||
def radar_on(df, index, optimum = None):
|
||||
""" Plot the radar graph concerning index column of the df
|
||||
|
||||
:param df: DataFrame with index and "Normalized" column
|
||||
:returns: exes with radar plot
|
||||
|
||||
"""
|
||||
comp_pt = pd.pivot_table(df,
|
||||
index = [index],
|
||||
values = ["Normalized"],
|
||||
aggfunc=np.mean,
|
||||
)
|
||||
labels = list(comp_pt.index)
|
||||
values = [i[0] for i in comp_pt.values]
|
||||
if optimum is None:
|
||||
optimum = [1]*len(values)
|
||||
fig, ax = radar_graph(labels, values, optimum)
|
||||
return fig, ax
|
||||
|
||||
def pie_pivot_table(df, pies_per_lines = 3, **kwargs):
|
||||
""" Plot a pie plot of the pivot_table of df
|
||||
|
||||
:param df: the dataframe.
|
||||
:param pies_per_lines: Number of pies per line.
|
||||
:param kwargs: arguments to pass to pd.pivot_table.
|
||||
"""
|
||||
logger.debug(f"pie_pivot_table avec les arguments {kwargs}")
|
||||
pv = pd.pivot_table(df, **kwargs)
|
||||
return pivot_table_to_pie(pv, pies_per_lines)
|
||||
|
||||
def marks_hist(df, **kwargs):
|
||||
""" Return axe for the histogramme of the dataframe
|
||||
|
||||
:param df: Dataframe with "Mark" and "Bareme" columns. If it has "Nom" column, it is use in title.
|
||||
:param kwargs: argument to pass to hist
|
||||
"""
|
||||
bareme = df["Bareme"].max()
|
||||
bins = int(bareme*2)
|
||||
|
||||
ax = df["Mark"].hist(bins = bins, range=(0,bareme), **kwargs)
|
||||
|
||||
try:
|
||||
nom = df["Nom"].unique()
|
||||
except KeyError:
|
||||
title="Histogramme"
|
||||
else:
|
||||
title="Histogramme pour {}".format(" ".join(nom))
|
||||
|
||||
ax.set_title(title)
|
||||
|
||||
return ax
|
||||
|
||||
def hist_boxplot(df, kwargs_hist=[], kwargs_box=[]):
|
||||
f, (ax_hist, ax_box) = plt.subplots(2, sharex=True,
|
||||
gridspec_kw={"height_ratios": (.85, .15)})
|
||||
|
||||
marks_hist(df, ax = ax_hist, rwidth=0.9)
|
||||
|
||||
ev_desc = df["Mark"].describe()
|
||||
m = round(ev_desc["mean"], 1)
|
||||
|
||||
ax_hist.plot([m,m], ax_hist.get_ylim())
|
||||
ax_hist.annotate(round(ev_desc["mean"],1),
|
||||
xy=(ev_desc["mean"] + 0.2, ax_hist.get_ylim()[1]-0.2))
|
||||
|
||||
df["Mark"].plot.box(ax = ax_box, vert=False, widths = 0.6)
|
||||
ax_box.set_yticklabels("")
|
||||
for e in ["min", "25%", "50%", "75%", "max"]:
|
||||
ax_box.annotate(round(ev_desc[e], 1),
|
||||
xy=(ev_desc[e] - 0.2, ax_box.get_ylim()[1]))
|
||||
|
||||
return f, (ax_hist, ax_box)
|
||||
|
||||
def parallel_on(df, index, student=None):
|
||||
""" Plot parallel one line by student
|
||||
|
||||
:param df: TODO
|
||||
:param index: TODO
|
||||
:returns: TODO
|
||||
|
||||
"""
|
||||
pt = pd.pivot_table(df,
|
||||
index = [index],
|
||||
values = ["Normalized"],
|
||||
columns = ["Eleve"],
|
||||
aggfunc = np.mean,
|
||||
)["Normalized"]
|
||||
ax = pt.plot(color="b", figsize=(10,5), legend=False)
|
||||
pt.T.describe().T[["min", "25%","50%", "75%", "max"]].plot(ax=ax,
|
||||
kind='area', stacked=False, alpha=0.2)
|
||||
if not student is None:
|
||||
pt.ix[:,student].plot(ax=ax, color="r")
|
||||
return ax
|
||||
|
||||
|
||||
# -----------------------------
|
||||
# Reglages pour 'vim'
|
||||
# vim:set autoindent expandtab tabstop=4 shiftwidth=4:
|
||||
# cursor: 16 del
|
||||
105
Repytex/tools/plottings.py
Normal file
105
Repytex/tools/plottings.py
Normal file
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib.path import Path
|
||||
from matplotlib.spines import Spine
|
||||
from matplotlib.projections.polar import PolarAxes
|
||||
from matplotlib.projections import register_projection
|
||||
plt.style.use('ggplot')
|
||||
|
||||
def _radar_factory(num_vars):
|
||||
theta = 2*np.pi * np.linspace(0, 1-1./num_vars, num_vars)
|
||||
theta += np.pi/2
|
||||
|
||||
def unit_poly_verts(theta):
|
||||
x0, y0, r = [0.5] * 3
|
||||
verts = [(r*np.cos(t) + x0, r*np.sin(t) + y0) for t in theta]
|
||||
return verts
|
||||
|
||||
class RadarAxes(PolarAxes):
|
||||
name = 'radar'
|
||||
RESOLUTION = 1
|
||||
|
||||
def fill(self, *args, **kwargs):
|
||||
closed = kwargs.pop('closed', True)
|
||||
return super(RadarAxes, self).fill(closed=closed, *args, **kwargs)
|
||||
|
||||
def plot(self, *args, **kwargs):
|
||||
lines = super(RadarAxes, self).plot(*args, **kwargs)
|
||||
for line in lines:
|
||||
self._close_line(line)
|
||||
|
||||
def _close_line(self, line):
|
||||
x, y = line.get_data()
|
||||
# FIXME: markers at x[0], y[0] get doubled-up
|
||||
if x[0] != x[-1]:
|
||||
x = np.concatenate((x, [x[0]]))
|
||||
y = np.concatenate((y, [y[0]]))
|
||||
line.set_data(x, y)
|
||||
|
||||
def set_varlabels(self, labels):
|
||||
self.set_thetagrids(theta * 180/np.pi, labels)
|
||||
|
||||
def _gen_axes_patch(self):
|
||||
verts = unit_poly_verts(theta)
|
||||
return plt.Polygon(verts, closed=True, edgecolor='k')
|
||||
|
||||
def _gen_axes_spines(self):
|
||||
spine_type = 'circle'
|
||||
verts = unit_poly_verts(theta)
|
||||
verts.append(verts[0])
|
||||
path = Path(verts)
|
||||
spine = Spine(self, spine_type, path)
|
||||
spine.set_transform(self.transAxes)
|
||||
return {'polar': spine}
|
||||
|
||||
register_projection(RadarAxes)
|
||||
return theta
|
||||
|
||||
def radar_graph(labels = [], values = [], optimum = []):
|
||||
N = len(labels)
|
||||
theta = _radar_factory(N)
|
||||
max_val = max(max(optimum), max(values))
|
||||
|
||||
fig = plt.figure(figsize=(3,3))
|
||||
ax = fig.add_subplot(1, 1, 1, projection='radar')
|
||||
|
||||
ax.plot(theta, values, color='k')
|
||||
ax.plot(theta, optimum, color='r')
|
||||
ax.set_varlabels(labels)
|
||||
return fig, ax
|
||||
|
||||
def my_autopct(values):
|
||||
def my_autopct(pct):
|
||||
total = sum(values)
|
||||
val = int(round(pct*total/100.0))
|
||||
return f'{val}'
|
||||
return my_autopct
|
||||
|
||||
def pivot_table_to_pie(pv, pies_per_lines = 3):
|
||||
nbr_pies = len(pv.columns)
|
||||
nbr_cols = pies_per_lines
|
||||
nbr_rows = nbr_pies // nbr_cols + 1
|
||||
f, axs = plt.subplots(nbr_rows, nbr_cols, figsize = (4*nbr_cols,4*nbr_rows))
|
||||
for (c, ax) in zip(pv, axs.flatten()):
|
||||
datas = pv[c]
|
||||
explode = [0.1]*len(datas)
|
||||
pv[c].plot(kind="pie",
|
||||
ax=ax,
|
||||
use_index = False,
|
||||
title = f"{c} (total={datas.sum()})",
|
||||
legend = False,
|
||||
autopct=my_autopct(datas),
|
||||
explode = explode,
|
||||
)
|
||||
ax.set_ylabel("")
|
||||
for i in range(nbr_pies//nbr_cols, nbr_cols*nbr_rows):
|
||||
axs.flat[i].axis("off")
|
||||
return (f, axs)
|
||||
|
||||
# -----------------------------
|
||||
# Reglages pour 'vim'
|
||||
# vim:set autoindent expandtab tabstop=4 shiftwidth=4:
|
||||
# cursor: 16 del
|
||||
41
Repytex/tools/skills_tools.py
Normal file
41
Repytex/tools/skills_tools.py
Normal file
@@ -0,0 +1,41 @@
|
||||
#! /usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim:fenc=utf-8
|
||||
#
|
||||
# Copyright © 2017 lafrite <lafrite@Poivre>
|
||||
#
|
||||
# Distributed under terms of the MIT license.
|
||||
|
||||
"""
|
||||
Skills are "competence" and "domaine" (which is program elements)!
|
||||
|
||||
Thoses tools are made to ease their manipulation
|
||||
"""
|
||||
|
||||
__all__ = []
|
||||
|
||||
def count_levels(df, skill):
|
||||
""" Counts Levels of skill
|
||||
|
||||
:param df: dataframe with skill, Level and Trimestre columns
|
||||
:param skill: "Competence" or "Domaine"
|
||||
:returns: Datafram (lines -> skills and columns -> levels)
|
||||
|
||||
"""
|
||||
# TODO: Trimestre est arbitraire |mar. mars 7 17:55:16 EAT 2017
|
||||
return df.groupby([skill, "Level"]).count()["Trimestre"].unstack()
|
||||
|
||||
def count_skill_evaluation(df, skill):
|
||||
""" Count how many times the skill has been evaluated
|
||||
|
||||
:param df: dataframe with skill, Level and Trimestre columns
|
||||
:param skill: "Competence" or "Domaine"
|
||||
|
||||
"""
|
||||
return count_levels(df, skill).T.sum()
|
||||
|
||||
|
||||
# -----------------------------
|
||||
# Reglages pour 'vim'
|
||||
# vim:set autoindent expandtab tabstop=4 shiftwidth=4:
|
||||
# cursor: 16 del
|
||||
196
Repytex/tools/term.py
Normal file
196
Repytex/tools/term.py
Normal file
@@ -0,0 +1,196 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from notes_tools.tools.marks_plottings import (pie_pivot_table,
|
||||
parallel_on,
|
||||
radar_on,
|
||||
)
|
||||
|
||||
import seaborn as sns
|
||||
|
||||
__all__ = ["students_pov", "class_pov"]
|
||||
|
||||
class Student(object):
|
||||
|
||||
"""
|
||||
Informations on a student which can be use inside template.
|
||||
|
||||
Those informations should not be modify or use for compute analysis otherwise they won't be spread over other POV.
|
||||
"""
|
||||
|
||||
def __init__(self, quest_df, exo_df, eval_df):
|
||||
"""
|
||||
Description of a student from quest, exo and eval
|
||||
|
||||
"""
|
||||
|
||||
name = {*quest_df["Eleve"].unique(),
|
||||
*exo_df["Eleve"].unique(),
|
||||
*eval_df["Eleve"].unique(),
|
||||
}
|
||||
|
||||
if len(name) != 1:
|
||||
raise ValueError("Can't initiate Student: dfs contains different student names")
|
||||
|
||||
self.name = name.pop()
|
||||
|
||||
self.quest_df = quest_df
|
||||
self.exo_df = exo_df
|
||||
self.eval_df = eval_df
|
||||
|
||||
@property
|
||||
def marks_tabular(self):
|
||||
""" Latex tabular with all of his marks of the term """
|
||||
try:
|
||||
self._marks_tabular
|
||||
except AttributeError:
|
||||
self._marks_tabular = self.eval_df[["Nom", "Mark_barem"]]
|
||||
self._marks_tabular.columns = ["Devoir", "Note"]
|
||||
return self._marks_tabular.to_latex()
|
||||
|
||||
@property
|
||||
def pies_on_competence(self):
|
||||
""" Pies chart on competences """
|
||||
return pie_pivot_table(self.quest_df,
|
||||
index = "Level",
|
||||
columns = "Competence",
|
||||
values = "Eleve",
|
||||
aggfunc = len,
|
||||
fill_value = 0,
|
||||
)
|
||||
|
||||
@property
|
||||
def pies_on_domaine(self):
|
||||
""" Pies chart on domaines """
|
||||
return pie_pivot_table(self.quest_df,
|
||||
index = "Level",
|
||||
columns = "Domaine",
|
||||
values = "Eleve",
|
||||
aggfunc = len,
|
||||
fill_value = 0,
|
||||
)
|
||||
|
||||
@property
|
||||
def radar_on_competence(self):
|
||||
""" Radar plot on competence """
|
||||
return radar_on(self.quest_df,
|
||||
"Competence")
|
||||
|
||||
@property
|
||||
def radar_on_domaine(self):
|
||||
""" Radar plot on domaine """
|
||||
return radar_on(self.quest_df,
|
||||
"Domaine")
|
||||
|
||||
@property
|
||||
def heatmap_on_domain(self):
|
||||
""" Heatmap over evals on domains """
|
||||
comp = pd.pivot_table(self.quest_df,
|
||||
index = "Domaine",
|
||||
columns = ["Date","Nom"],
|
||||
values = ["Normalized"],
|
||||
aggfunc = np.mean,
|
||||
)
|
||||
comp.columns = [i[1].strftime("%Y-%m-%d") + "\n" + i[2] for i in comp.columns]
|
||||
return sns.heatmap(comp)
|
||||
|
||||
@property
|
||||
def heatmap_on_competence(self):
|
||||
""" Heatmap over evals on competences """
|
||||
comp = pd.pivot_table(self.quest_df,
|
||||
index = "Competence",
|
||||
columns = ["Date","Nom"],
|
||||
values = ["Normalized"],
|
||||
aggfunc = np.mean,
|
||||
)
|
||||
comp.columns = [i[1].strftime("%Y-%m-%d") + "\n" + i[2] for i in comp.columns]
|
||||
return sns.heatmap(comp)
|
||||
|
||||
def parallel_on_evals(self, classe_evals):
|
||||
""" Parallel coordinate plot of the class with student line highlight """
|
||||
return parallel_on(classe_evals, "Nom", self.name)
|
||||
|
||||
class Classe(object):
|
||||
|
||||
"""
|
||||
Informations on a class which can be use inside template.
|
||||
|
||||
Those informations should not be modify or use for compute analysis otherwise they won't be spread over other POV.
|
||||
"""
|
||||
|
||||
def __init__(self, quest_df, exo_df, eval_df):
|
||||
""" Init of a class from quest, exo and eval """
|
||||
self.quest_df = quest_df
|
||||
self.exo_df = exo_df
|
||||
self.eval_df = eval_df
|
||||
|
||||
@property
|
||||
def evals_tabular(self):
|
||||
""" Summary of all evaluations for all students """
|
||||
try:
|
||||
self._evals_tabular
|
||||
except AttributeError:
|
||||
self._evals_tabular = pd.pivot_table(self.eval_df,
|
||||
index = "Eleve",
|
||||
columns = "Nom",
|
||||
values = "Mark_barem",
|
||||
aggfunc = lambda x: " ".join(x)).to_latex()
|
||||
return self._evals_tabular
|
||||
|
||||
|
||||
@property
|
||||
def parallel_on_evals(self):
|
||||
""" Parallel coordinate plot of the class """
|
||||
return parallel_on(self.eval_df, "Nom")
|
||||
|
||||
@property
|
||||
def pies_eff_pts_on_competence(self):
|
||||
""" Pie charts on competence with repartition of evaluated times and attributed points """
|
||||
return pie_pivot_table(self.quest_df[["Competence", "Bareme", "Exercice", "Question", "Commentaire"]].drop_duplicates(),
|
||||
index = "Competence",
|
||||
#columns = "Level",
|
||||
values = "Bareme",
|
||||
aggfunc=[len,np.sum],
|
||||
fill_value=0)
|
||||
|
||||
@property
|
||||
def pies_eff_pts_on_domaine(self):
|
||||
""" Pie charts on domaine with repartition of evaluated times and attributed points """
|
||||
return pie_pivot_table(self.quest_df[["Domaine", "Bareme", "Exercice", "Question", "Commentaire"]].drop_duplicates(),
|
||||
index = "Domaine",
|
||||
#columns = "Level",
|
||||
values = "Bareme",
|
||||
aggfunc=[len,np.sum],
|
||||
fill_value=0)
|
||||
|
||||
def select(quest_df, exo_df, eval_df, index, value):
|
||||
""" Return quest, exo and eval rows which correspond index == value
|
||||
|
||||
:param quest_df: TODO
|
||||
:param exo_df: TODO
|
||||
:param eval_df: TODO
|
||||
|
||||
"""
|
||||
qu = quest_df[quest_df[index] == value]
|
||||
exo = exo_df[exo_df[index] == value]
|
||||
ev = eval_df[eval_df[index] == value]
|
||||
return qu, exo, ev
|
||||
|
||||
def students_pov(quest_df, exo_df, eval_df):
|
||||
es = []
|
||||
for e in eval_df["Eleve"].unique():
|
||||
d = select(quest_df, exo_df, eval_df, "Eleve", e)
|
||||
eleve = Student(*d)
|
||||
es.append(eleve)
|
||||
return es
|
||||
|
||||
def class_pov(quest_df, exo_df, eval_df):
|
||||
return Classe(quest_df, exo_df, eval_df)
|
||||
|
||||
|
||||
# -----------------------------
|
||||
# Reglages pour 'vim'
|
||||
# vim:set autoindent expandtab tabstop=4 shiftwidth=4:
|
||||
# cursor: 16 del
|
||||
62
Repytex/tools/test/test_df_marks_manip.py
Normal file
62
Repytex/tools/test/test_df_marks_manip.py
Normal file
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
|
||||
from notes_tools.tools import df_marks_manip
|
||||
import pandas
|
||||
|
||||
|
||||
def test_round_half_point():
|
||||
assert df_marks_manip.round_half_point(2) == 2
|
||||
assert df_marks_manip.round_half_point(2.1) == 2.5
|
||||
assert df_marks_manip.round_half_point(2.4) == 2.5
|
||||
assert df_marks_manip.round_half_point(2.6) == 3
|
||||
assert df_marks_manip.round_half_point(2.9) == 3
|
||||
assert df_marks_manip.round_half_point(pandas.np.nan)
|
||||
|
||||
def test_note_to_rep():
|
||||
d = {"Niveau": 1, "Note": 0}
|
||||
assert df_marks_manip.note_to_rep(d) == "\\RepZ"
|
||||
|
||||
d = {"Niveau": 1, "Note": 1}
|
||||
assert df_marks_manip.note_to_rep(d) == "\\RepU"
|
||||
|
||||
d = {"Niveau": 1, "Note": 2}
|
||||
assert df_marks_manip.note_to_rep(d) == "\\RepD"
|
||||
|
||||
d = {"Niveau": 1, "Note": 3}
|
||||
assert df_marks_manip.note_to_rep(d) == "\\RepT"
|
||||
|
||||
d = {"Niveau": 1, "Note": None}
|
||||
assert df_marks_manip.note_to_rep(d) == "\\NoRep"
|
||||
|
||||
d = {"Niveau": 1, "Note": pandas.np.nan}
|
||||
assert df_marks_manip.note_to_rep(d) == "\\NoRep"
|
||||
|
||||
d = {"Niveau": 0, "Note": "plop"}
|
||||
assert df_marks_manip.note_to_rep(d) == "plop"
|
||||
|
||||
d = {"Niveau": 0, "Note": 1}
|
||||
assert df_marks_manip.note_to_rep(d) == 1
|
||||
|
||||
|
||||
def test_note_to_mark():
|
||||
d = {"Niveau": 1, "Note": 0, "Bareme": 6}
|
||||
assert df_marks_manip.note_to_mark(d) == 6/3*0
|
||||
|
||||
d = {"Niveau": 1, "Note": 1, "Bareme": 6}
|
||||
assert df_marks_manip.note_to_mark(d) == 6/3*1
|
||||
|
||||
d = {"Niveau": 1, "Note": 2, "Bareme": 6}
|
||||
assert df_marks_manip.note_to_mark(d) == 6/3*2
|
||||
|
||||
d = {"Niveau": 1, "Note": 3, "Bareme": 6}
|
||||
assert df_marks_manip.note_to_mark(d) == 6/3*3
|
||||
|
||||
d = {"Niveau": 0, "Note": 3, "Bareme": 6}
|
||||
assert df_marks_manip.note_to_mark(d) == 3
|
||||
|
||||
# -----------------------------
|
||||
# Reglages pour 'vim'
|
||||
# vim:set autoindent expandtab tabstop=4 shiftwidth=4:
|
||||
# cursor: 16 del
|
||||
59
Repytex/tools/test/test_extract.py
Normal file
59
Repytex/tools/test/test_extract.py
Normal file
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
|
||||
from notes_tools.tools import extract
|
||||
import pandas
|
||||
import pytest
|
||||
|
||||
|
||||
sample_path = "./samples/"
|
||||
|
||||
def test_list_classes():
|
||||
clss = extract.list_classes(sample_path)
|
||||
assert clss == ["503", "312", "308"]
|
||||
|
||||
def test_get_class_ws_raise():
|
||||
with pytest.raises(Exception) as e_info:
|
||||
extract.get_class_ws("312")
|
||||
|
||||
def test_parse_sheets():
|
||||
ws = extract.get_class_ws("312", sample_path)
|
||||
sheets = extract.parse_sheets(ws)
|
||||
assert len(sheets) == 2
|
||||
assert type(sheets[0]) == pandas.core.frame.DataFrame
|
||||
|
||||
def test_extract_students():
|
||||
ws = extract.get_class_ws("312", sample_path)
|
||||
sheets = extract.parse_sheets(ws)
|
||||
students = extract.extract_students(sheets[0])
|
||||
_students = pandas.Index(['Eleve 1', 'Eleve 10', 'Eleve 2', 'Eleve 3', 'Eleve 4', 'Eleve 5', 'Eleve 6', 'Eleve 7', 'Eleve 8', 'Eleve 9'], dtype='object')
|
||||
assert list(students) == list(_students)
|
||||
|
||||
def test_check_students():
|
||||
ws = extract.get_class_ws("312", sample_path)
|
||||
sheets = extract.parse_sheets(ws)
|
||||
students = extract.check_students(sheets)
|
||||
_students = pandas.Index(['Eleve 1', 'Eleve 10', 'Eleve 2', 'Eleve 3', 'Eleve 4', 'Eleve 5', 'Eleve 6', 'Eleve 7', 'Eleve 8', 'Eleve 9'], dtype='object')
|
||||
assert list(students) == list(_students)
|
||||
|
||||
|
||||
ws = extract.get_class_ws("308", sample_path)
|
||||
sheets = extract.parse_sheets(ws)
|
||||
with pytest.raises(Exception) as e_info:
|
||||
students = extract.check_students(sheets)
|
||||
|
||||
def test_flat_df_students():
|
||||
ws = extract.get_class_ws("312", sample_path)
|
||||
sheets = extract.parse_sheets(ws)
|
||||
students = extract.check_students(sheets)
|
||||
|
||||
# Sheets[1] is the sheet Connaissances
|
||||
flat_df = extract.flat_df_students(sheets[1], students)
|
||||
assert len(flat_df) == 80
|
||||
|
||||
|
||||
# -----------------------------
|
||||
# Reglages pour 'vim'
|
||||
# vim:set autoindent expandtab tabstop=4 shiftwidth=4:
|
||||
# cursor: 16 del
|
||||
Reference in New Issue
Block a user