From 18f855ab83b24cc37cab81881b2e99fb1033b1da Mon Sep 17 00:00:00 2001 From: Bertrand Benjamin Date: Wed, 21 Apr 2021 07:03:45 +0200 Subject: [PATCH] Feat: question levels bar figure --- recopytex/dashboard/pages/exams_scores/app.py | 8 +++ .../dashboard/pages/exams_scores/callbacks.py | 59 ++++++++++++++++++- .../dashboard/pages/exams_scores/models.py | 27 ++++++++- 3 files changed, 89 insertions(+), 5 deletions(-) diff --git a/recopytex/dashboard/pages/exams_scores/app.py b/recopytex/dashboard/pages/exams_scores/app.py index 3f9139f..ac2e781 100644 --- a/recopytex/dashboard/pages/exams_scores/app.py +++ b/recopytex/dashboard/pages/exams_scores/app.py @@ -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", ), diff --git a/recopytex/dashboard/pages/exams_scores/callbacks.py b/recopytex/dashboard/pages/exams_scores/callbacks.py index 938ea14..3a66eb8 100644 --- a/recopytex/dashboard/pages/exams_scores/callbacks.py +++ b/recopytex/dashboard/pages/exams_scores/callbacks.py @@ -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,54 @@ 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") + print(pt) + + # 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] diff --git a/recopytex/dashboard/pages/exams_scores/models.py b/recopytex/dashboard/pages/exams_scores/models.py index ddc051a..bd02816 100644 --- a/recopytex/dashboard/pages/exams_scores/models.py +++ b/recopytex/dashboard/pages/exams_scores/models.py @@ -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 +