Compare commits

...

4 Commits

6 changed files with 100 additions and 9 deletions

View File

@ -72,6 +72,14 @@ layout = html.Div(
],
id="fig_exam_histo_container",
),
html.Div(
children=[
dcc.Graph(
id="fig_questions_bar",
)
],
id="fig_questions_bar_container",
),
],
id="analysis",
),

View File

@ -18,7 +18,10 @@ from .models import (
get_unstack_scores,
get_students_from_exam,
get_score_colors,
get_level_color_bar,
score_to_final_mark,
stack_scores,
pivot_score_on,
)
@ -90,8 +93,8 @@ def update_scores_store(exam):
)
def update_finale_score_table(scores):
scores_df = pd.DataFrame.from_records(scores)
# print(scores_df)
return score_to_final_mark(scores_df)
stacked_scores = stack_scores(scores_df)
return score_to_final_mark(stacked_scores)
@app.callback(
@ -106,7 +109,6 @@ def update_finale_score_table(scores):
def update_statictics_table(finale_score):
df = pd.DataFrame.from_records(finale_score)
statistics = df["mark"].describe().to_frame().T
print(statistics)
return [
[{"id": c, "name": c} for c in statistics.columns],
statistics.to_dict("records"),
@ -155,3 +157,53 @@ def update_exam_histo(finale_scores):
margin=dict(l=5, r=5, b=5, t=5),
)
return [fig]
@app.callback(
[
Output("fig_questions_bar", "figure"),
],
[
Input("scores_table", "data"),
],
)
def update_questions_bar(finale_scores):
scores = pd.DataFrame.from_records(finale_scores)
scores = stack_scores(scores)
if scores.empty:
return [go.Figure(data=[go.Scatter(x=[], y=[])])]
pt = pivot_score_on(scores, ["exercise", "question", "comment"], "score")
# separation between exercises
for i in {i for i in pt.index.get_level_values(0)}:
pt.loc[(str(i), "", ""), :] = ""
pt.sort_index(inplace=True)
# Bar label
index = (
pt.index.get_level_values(0).map(str)
+ ":"
+ pt.index.get_level_values(1).map(str)
+ " "
+ pt.index.get_level_values(2).map(str)
)
fig = go.Figure()
bars = get_level_color_bar()
for b in bars:
try:
fig.add_bar(
x=index, y=pt[b["score"]], name=b["name"], marker_color=b["color"]
)
except KeyError:
pass
fig.update_layout(barmode="relative")
fig.update_layout(
height=500,
margin=dict(l=5, r=5, b=5, t=5),
)
return [fig]

View File

@ -70,6 +70,13 @@ def get_score_colors():
return score_color
def get_level_color_bar():
return [
{"score": s["value"], "name": s["comment"], "color": s["color"]}
for s in SCORES_CONFIG.values()
]
is_none_score = lambda x: on_column.is_none_score(x, SCORES_CONFIG)
score_to_numeric_score = lambda x: on_column.score_to_numeric_score(x, SCORES_CONFIG)
score_to_mark = lambda x: on_column.score_to_mark(
@ -80,8 +87,7 @@ score_to_mark = lambda x: on_column.score_to_mark(
def score_to_final_mark(scores):
""" Compute marks then reduce to final mark per student """
melted_scores = stack_scores(scores)
filtered_scores = melted_scores[~melted_scores.apply(is_none_score, axis=1)]
filtered_scores = scores[~scores.apply(is_none_score, axis=1)]
filtered_scores = filtered_scores.assign(
score=filtered_scores.apply(score_to_numeric_score, axis=1)
)
@ -93,3 +99,20 @@ def score_to_final_mark(scores):
].sum()
return [final_score.reset_index().to_dict("records")]
def pivot_score_on(scores, index, columns, aggfunc="size"):
"""Pivot scores on index, columns with aggfunc
It assumes thant scores are levels
"""
filtered_scores = scores[~scores.apply(is_none_score, axis=1)]
pt = pd.pivot_table(
scores,
index=index,
columns=columns,
aggfunc=aggfunc,
fill_value=0,
)
return pt

View File

@ -26,30 +26,37 @@ scores: #
value: 0
numeric_value: 0
color: "#E7472B"
comment: Faux
FEW: # Few good things
value: 1
numeric_value: 1
color: "#FF712B"
comment: Peu juste
NEARLY: # Nearly good but things are missing
value: 2
numeric_value: 2
color: "#F2EC4C"
comment: Presque juste
GOOD: # Everything is good
value: 3
numeric_value: 3
color: "#68D42F"
comment: Juste
NOTFILLED: # The item is not scored yet
value: ""
numeric_value: None
color: white
comment: En attente
NOANSWER: # Student gives no answer (count as 0)
value: "."
numeric_value: 0
color: black
comment: Pas de réponse
ABS: # Student has absent (this score won't be impact the final mark)
value: a
numeric_value: None
color: lightgray
comment: Non noté
csv_fields: # dataframe_field: csv_field
term: Trimestre

View File

@ -31,10 +31,11 @@ class CSVLoader(Loader):
:example:
>>> loader = CSVLoader()
>>> loader.get_config()
{'source': './', 'competences': {'Chercher': {'name': 'Chercher', 'abrv': 'Cher'}, 'Représenter': {'name': 'Représenter', 'abrv': 'Rep'}, 'Modéliser': {'name': 'Modéliser', 'abrv': 'Mod'}, 'Raisonner': {'name': 'Raisonner', 'abrv': 'Rai'}, 'Calculer': {'name': 'Calculer', 'abrv': 'Cal'}, 'Communiquer': {'name': 'Communiquer', 'abrv': 'Com'}}, 'scores': {'BAD': {'value': 0, 'numeric_value': 0, 'color': '#E7472B'}, 'FEW': {'value': 1, 'numeric_value': 1, 'color': '#FF712B'}, 'NEARLY': {'value': 2, 'numeric_value': 2, 'color': '#F2EC4C'}, 'GOOD': {'value': 3, 'numeric_value': 3, 'color': '#68D42F'}, 'NOTFILLED': {'value': '', 'numeric_value': 'None', 'color': 'white'}, 'NOANSWER': {'value': '.', 'numeric_value': 0, 'color': 'black'}, 'ABS': {'value': 'a', 'numeric_value': 'None', 'color': 'lightgray'}}, 'csv_fields': {'term': 'Trimestre', 'exam': 'Nom', 'date': 'Date', 'exercise': 'Exercice', 'question': 'Question', 'competence': 'Competence', 'theme': 'Domaine', 'comment': 'Commentaire', 'score_rate': 'Bareme', 'is_leveled': 'Est_nivele'}, 'id_templates': {'exam': '{name}_{tribe}', 'question': '{exam_id}_{exercise}_{question}_{comment}'}}
{'source': './', 'competences': {'Chercher': {'name': 'Chercher', 'abrv': 'Cher'}, 'Représenter': {'name': 'Représenter', 'abrv': 'Rep'}, 'Modéliser': {'name': 'Modéliser', 'abrv': 'Mod'}, 'Raisonner': {'name': 'Raisonner', 'abrv': 'Rai'}, 'Calculer': {'name': 'Calculer', 'abrv': 'Cal'}, 'Communiquer': {'name': 'Communiquer', 'abrv': 'Com'}}, 'scores': {'BAD': {'value': 0, 'numeric_value': 0, 'color': '#E7472B', 'comment': 'Faux'}, 'FEW': {'value': 1, 'numeric_value': 1, 'color': '#FF712B', 'comment': 'Peu juste'}, 'NEARLY': {'value': 2, 'numeric_value': 2, 'color': '#F2EC4C', 'comment': 'Presque juste'}, 'GOOD': {'value': 3, 'numeric_value': 3, 'color': '#68D42F', 'comment': 'Juste'}, 'NOTFILLED': {'value': '', 'numeric_value': 'None', 'color': 'white', 'comment': 'En attente'}, 'NOANSWER': {'value': '.', 'numeric_value': 0, 'color': 'black', 'comment': 'Pas de réponse'}, 'ABS': {'value': 'a', 'numeric_value': 'None', 'color': 'lightgray', 'comment': 'Non noté'}}, 'csv_fields': {'term': 'Trimestre', 'exam': 'Nom', 'date': 'Date', 'exercise': 'Exercice', 'question': 'Question', 'competence': 'Competence', 'theme': 'Domaine', 'comment': 'Commentaire', 'score_rate': 'Bareme', 'is_leveled': 'Est_nivele'}, 'id_templates': {'exam': '{name}_{tribe}', 'question': '{exam_id}_{exercise}_{question}_{comment}'}}
>>> loader = CSVLoader("./test_config.yml")
>>> loader.get_config()
{'source': './example', 'competences': {'Chercher': {'name': 'Chercher', 'abrv': 'Cher'}, 'Représenter': {'name': 'Représenter', 'abrv': 'Rep'}, 'Modéliser': {'name': 'Modéliser', 'abrv': 'Mod'}, 'Raisonner': {'name': 'Raisonner', 'abrv': 'Rai'}, 'Calculer': {'name': 'Calculer', 'abrv': 'Cal'}, 'Communiquer': {'name': 'Communiquer', 'abrv': 'Com'}}, 'scores': {'BAD': {'value': 0, 'numeric_value': 0, 'color': '#E7472B'}, 'FEW': {'value': 1, 'numeric_value': 1, 'color': '#FF712B'}, 'NEARLY': {'value': 2, 'numeric_value': 2, 'color': '#F2EC4C'}, 'GOOD': {'value': 3, 'numeric_value': 3, 'color': '#68D42F'}, 'NOTFILLED': {'value': '', 'numeric_value': 'None', 'color': 'white'}, 'NOANSWER': {'value': '.', 'numeric_value': 0, 'color': 'black'}, 'ABS': {'value': 'a', 'numeric_value': 'None', 'color': 'lightgray'}}, 'csv_fields': {'term': 'Trimestre', 'exam': 'Nom', 'date': 'Date', 'exercise': 'Exercice', 'question': 'Question', 'competence': 'Competence', 'theme': 'Domaine', 'comment': 'Commentaire', 'score_rate': 'Bareme', 'is_leveled': 'Est_nivele'}, 'id_templates': {'exam': '{name}_{tribe}', 'question': '{exam_id}_{exercise}_{question}_{comment}'}, 'output': './output', 'templates': 'templates/', 'tribes': {'Tribe1': {'name': 'Tribe1', 'type': 'Type1', 'students': 'tribe1.csv'}, 'Tribe2': {'name': 'Tribe2', 'students': 'tribe2.csv'}}}
{'source': './example', 'competences': {'Chercher': {'name': 'Chercher', 'abrv': 'Cher'}, 'Représenter': {'name': 'Représenter', 'abrv': 'Rep'}, 'Modéliser': {'name': 'Modéliser', 'abrv': 'Mod'}, 'Raisonner': {'name': 'Raisonner', 'abrv': 'Rai'}, 'Calculer': {'name': 'Calculer', 'abrv': 'Cal'}, 'Communiquer': {'name': 'Communiquer', 'abrv': 'Com'}}, 'scores': {'BAD': {'value': 0, 'numeric_value': 0, 'color': '#E7472B', 'comment': 'Faux'}, 'FEW': {'value': 1, 'numeric_value': 1, 'color': '#FF712B', 'comment': 'Peu juste'}, 'NEARLY': {'value': 2, 'numeric_value': 2, 'color': '#F2EC4C', 'comment': 'Presque juste'}, 'GOOD': {'value': 3, 'numeric_value': 3, 'color': '#68D42F', 'comment': 'Juste'}, 'NOTFILLED': {'value': '', 'numeric_value': 'None', 'color': 'white', 'comment': 'En attente'}, 'NOANSWER': {'value': '.', 'numeric_value': 0, 'color': 'black', 'comment': 'Pas de réponse'}, 'ABS': {'value': 'a', 'numeric_value': 'None', 'color': 'lightgray', 'comment': 'Non noté'}}, 'csv_fields': {'term': 'Trimestre', 'exam': 'Nom', 'date': 'Date', 'exercise': 'Exercice', 'question': 'Question', 'competence': 'Competence', 'theme': 'Domaine', 'comment': 'Commentaire', 'score_rate': 'Bareme', 'is_leveled': 'Est_nivele'}, 'id_templates': {'exam': '{name}_{tribe}', 'question': '{exam_id}_{exercise}_{question}_{comment}'}, 'output': './output', 'templates': 'templates/', 'tribes': {'Tribe1': {'name': 'Tribe1', 'type': 'Type1', 'students': 'tribe1.csv'}, 'Tribe2': {'name': 'Tribe2', 'students': 'tribe2.csv'}}}
"""
CONFIG = DEFAULT_CONFIG

View File

@ -2,7 +2,7 @@
# encoding: utf-8
from math import ceil
import numpy as np
import pandas as pd
def is_none_score(x, score_config):
@ -40,7 +40,7 @@ def is_none_score(x, score_config):
for v in score_config.values()
if str(v["numeric_value"]).lower() == "none"
]
return x["score"] in none_values or x["score"] is None or np.isnan(x["score"])
return x["score"] in none_values or pd.isnull(x["score"])
def score_to_numeric_score(x, score_config):