Compare commits
4 Commits
8cdeecfc53
...
97b97af2de
Author | SHA1 | Date | |
---|---|---|---|
97b97af2de | |||
d8d84690c6 | |||
18f855ab83 | |||
36425e587e |
@ -72,6 +72,14 @@ layout = html.Div(
|
|||||||
],
|
],
|
||||||
id="fig_exam_histo_container",
|
id="fig_exam_histo_container",
|
||||||
),
|
),
|
||||||
|
html.Div(
|
||||||
|
children=[
|
||||||
|
dcc.Graph(
|
||||||
|
id="fig_questions_bar",
|
||||||
|
)
|
||||||
|
],
|
||||||
|
id="fig_questions_bar_container",
|
||||||
|
),
|
||||||
],
|
],
|
||||||
id="analysis",
|
id="analysis",
|
||||||
),
|
),
|
||||||
|
@ -18,7 +18,10 @@ from .models import (
|
|||||||
get_unstack_scores,
|
get_unstack_scores,
|
||||||
get_students_from_exam,
|
get_students_from_exam,
|
||||||
get_score_colors,
|
get_score_colors,
|
||||||
|
get_level_color_bar,
|
||||||
score_to_final_mark,
|
score_to_final_mark,
|
||||||
|
stack_scores,
|
||||||
|
pivot_score_on,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -90,8 +93,8 @@ def update_scores_store(exam):
|
|||||||
)
|
)
|
||||||
def update_finale_score_table(scores):
|
def update_finale_score_table(scores):
|
||||||
scores_df = pd.DataFrame.from_records(scores)
|
scores_df = pd.DataFrame.from_records(scores)
|
||||||
# print(scores_df)
|
stacked_scores = stack_scores(scores_df)
|
||||||
return score_to_final_mark(scores_df)
|
return score_to_final_mark(stacked_scores)
|
||||||
|
|
||||||
|
|
||||||
@app.callback(
|
@app.callback(
|
||||||
@ -106,7 +109,6 @@ def update_finale_score_table(scores):
|
|||||||
def update_statictics_table(finale_score):
|
def update_statictics_table(finale_score):
|
||||||
df = pd.DataFrame.from_records(finale_score)
|
df = pd.DataFrame.from_records(finale_score)
|
||||||
statistics = df["mark"].describe().to_frame().T
|
statistics = df["mark"].describe().to_frame().T
|
||||||
print(statistics)
|
|
||||||
return [
|
return [
|
||||||
[{"id": c, "name": c} for c in statistics.columns],
|
[{"id": c, "name": c} for c in statistics.columns],
|
||||||
statistics.to_dict("records"),
|
statistics.to_dict("records"),
|
||||||
@ -155,3 +157,53 @@ def update_exam_histo(finale_scores):
|
|||||||
margin=dict(l=5, r=5, b=5, t=5),
|
margin=dict(l=5, r=5, b=5, t=5),
|
||||||
)
|
)
|
||||||
return [fig]
|
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]
|
||||||
|
@ -70,6 +70,13 @@ def get_score_colors():
|
|||||||
return score_color
|
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)
|
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_numeric_score = lambda x: on_column.score_to_numeric_score(x, SCORES_CONFIG)
|
||||||
score_to_mark = lambda x: on_column.score_to_mark(
|
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):
|
def score_to_final_mark(scores):
|
||||||
""" Compute marks then reduce to final mark per student """
|
""" Compute marks then reduce to final mark per student """
|
||||||
|
|
||||||
melted_scores = stack_scores(scores)
|
filtered_scores = scores[~scores.apply(is_none_score, axis=1)]
|
||||||
filtered_scores = melted_scores[~melted_scores.apply(is_none_score, axis=1)]
|
|
||||||
filtered_scores = filtered_scores.assign(
|
filtered_scores = filtered_scores.assign(
|
||||||
score=filtered_scores.apply(score_to_numeric_score, axis=1)
|
score=filtered_scores.apply(score_to_numeric_score, axis=1)
|
||||||
)
|
)
|
||||||
@ -93,3 +99,20 @@ def score_to_final_mark(scores):
|
|||||||
].sum()
|
].sum()
|
||||||
return [final_score.reset_index().to_dict("records")]
|
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
|
||||||
|
|
||||||
|
@ -26,30 +26,37 @@ scores: #
|
|||||||
value: 0
|
value: 0
|
||||||
numeric_value: 0
|
numeric_value: 0
|
||||||
color: "#E7472B"
|
color: "#E7472B"
|
||||||
|
comment: Faux
|
||||||
FEW: # Few good things
|
FEW: # Few good things
|
||||||
value: 1
|
value: 1
|
||||||
numeric_value: 1
|
numeric_value: 1
|
||||||
color: "#FF712B"
|
color: "#FF712B"
|
||||||
|
comment: Peu juste
|
||||||
NEARLY: # Nearly good but things are missing
|
NEARLY: # Nearly good but things are missing
|
||||||
value: 2
|
value: 2
|
||||||
numeric_value: 2
|
numeric_value: 2
|
||||||
color: "#F2EC4C"
|
color: "#F2EC4C"
|
||||||
|
comment: Presque juste
|
||||||
GOOD: # Everything is good
|
GOOD: # Everything is good
|
||||||
value: 3
|
value: 3
|
||||||
numeric_value: 3
|
numeric_value: 3
|
||||||
color: "#68D42F"
|
color: "#68D42F"
|
||||||
|
comment: Juste
|
||||||
NOTFILLED: # The item is not scored yet
|
NOTFILLED: # The item is not scored yet
|
||||||
value: ""
|
value: ""
|
||||||
numeric_value: None
|
numeric_value: None
|
||||||
color: white
|
color: white
|
||||||
|
comment: En attente
|
||||||
NOANSWER: # Student gives no answer (count as 0)
|
NOANSWER: # Student gives no answer (count as 0)
|
||||||
value: "."
|
value: "."
|
||||||
numeric_value: 0
|
numeric_value: 0
|
||||||
color: black
|
color: black
|
||||||
|
comment: Pas de réponse
|
||||||
ABS: # Student has absent (this score won't be impact the final mark)
|
ABS: # Student has absent (this score won't be impact the final mark)
|
||||||
value: a
|
value: a
|
||||||
numeric_value: None
|
numeric_value: None
|
||||||
color: lightgray
|
color: lightgray
|
||||||
|
comment: Non noté
|
||||||
|
|
||||||
csv_fields: # dataframe_field: csv_field
|
csv_fields: # dataframe_field: csv_field
|
||||||
term: Trimestre
|
term: Trimestre
|
||||||
|
@ -31,10 +31,11 @@ class CSVLoader(Loader):
|
|||||||
:example:
|
:example:
|
||||||
>>> loader = CSVLoader()
|
>>> loader = CSVLoader()
|
||||||
>>> loader.get_config()
|
>>> 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 = CSVLoader("./test_config.yml")
|
||||||
>>> loader.get_config()
|
>>> 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
|
CONFIG = DEFAULT_CONFIG
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
# encoding: utf-8
|
# encoding: utf-8
|
||||||
|
|
||||||
from math import ceil
|
from math import ceil
|
||||||
import numpy as np
|
import pandas as pd
|
||||||
|
|
||||||
|
|
||||||
def is_none_score(x, score_config):
|
def is_none_score(x, score_config):
|
||||||
@ -40,7 +40,7 @@ def is_none_score(x, score_config):
|
|||||||
for v in score_config.values()
|
for v in score_config.values()
|
||||||
if str(v["numeric_value"]).lower() == "none"
|
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):
|
def score_to_numeric_score(x, score_config):
|
||||||
|
Loading…
Reference in New Issue
Block a user