From f6bfac4144f0323048bbd8d02b100035b2da7ec1 Mon Sep 17 00:00:00 2001 From: Bertrand Benjamin Date: Tue, 12 Jan 2021 22:32:26 +0100 Subject: [PATCH] Feat: Hist graph and describe --- recopytex/scripts/exam_dash.py | 260 ++++++++++++++++++++++++--------- 1 file changed, 188 insertions(+), 72 deletions(-) diff --git a/recopytex/scripts/exam_dash.py b/recopytex/scripts/exam_dash.py index 3a06436..1c3e6cd 100644 --- a/recopytex/scripts/exam_dash.py +++ b/recopytex/scripts/exam_dash.py @@ -6,9 +6,11 @@ import dash_html_components as html import dash_core_components as dcc import dash_table from dash.exceptions import PreventUpdate +import plotly.graph_objects as go from pathlib import Path from datetime import datetime import pandas as pd +import numpy as np from .. import flat_df_students, pp_q_scores @@ -16,62 +18,101 @@ from ..config import NO_ST_COLUMNS from .getconfig import config, CONFIGPATH COLORS = { - ".": "black", - 0: "#E7472B", - 1: "#FF712B", - 2: "#F2EC4C", - 3: "#68D42F", + ".": "black", + 0: "#E7472B", + 1: "#FF712B", + 2: "#F2EC4C", + 3: "#68D42F", } -app = dash.Dash(__name__) +external_stylesheets = ["https://codepen.io/chriddyp/pen/bWLwgP.css"] +app = dash.Dash(__name__, external_stylesheets=external_stylesheets) +# app = dash.Dash(__name__) -app.layout = html.Div([ - html.H1("Analyse des notes"), - html.Div(["Classe: ", dcc.Dropdown( - id='tribe', - options=[{"label": t["name"], "value": t["name"]} for t in config["tribes"]], - value=config["tribes"][0]["name"], - )]), - html.Div(["Evaluation: ", dcc.Dropdown(id='csv')]), - html.Div([dash_table.DataTable( - id="final_score_table", - columns = [{"id": "Élève", "name": "Élève"}, {"id": "Note", "name": "Note"},{"id": "Barème", "name": "Bareme"}], - data=[], - style_data_conditional=[ - { - 'if': {'row_index': 'odd'}, - 'backgroundColor': 'rgb(248, 248, 248)' - } +app.layout = html.Div( + [ + html.H1("Analyse des notes"), + html.Div( + [ + "Classe: ", + dcc.Dropdown( + id="tribe", + options=[ + {"label": t["name"], "value": t["name"]} + for t in config["tribes"] + ], + value=config["tribes"][0]["name"], + ), + "Evaluation: ", + dcc.Dropdown(id="csv"), ], - style_header={ - 'backgroundColor': 'rgb(230, 230, 230)', - 'fontWeight': 'bold' - }, - style_data={ - 'width': '100px', - 'maxWidth': '100px', - 'minWidth': '100px', - }, + style={"columnCount": 2}, ), - ]), - html.Br(), - html.Div([dash_table.DataTable( - id="scores_table", - columns = [{"id": c, "name":c} for c in NO_ST_COLUMNS.values()], - style_cell={ - 'whiteSpace': 'normal', - 'height': 'auto', - }, - style_data_conditional=[], - editable=True, - )]), - html.P(id="lastsave"), - ]) + html.Div( + [ + dash_table.DataTable( + id="final_score_table", + columns=[ + {"id": "Élève", "name": "Élève"}, + {"id": "Note", "name": "Note"}, + {"id": "Barème", "name": "Bareme"}, + ], + data=[], + style_data_conditional=[ + { + "if": {"row_index": "odd"}, + "backgroundColor": "rgb(248, 248, 248)", + } + ], + style_header={ + "backgroundColor": "rgb(230, 230, 230)", + "fontWeight": "bold", + }, + style_data={ + "width": "100px", + "maxWidth": "100px", + "minWidth": "100px", + }, + ), + html.Div( + [ + dash_table.DataTable( + id="final_score_describe", + ), + dcc.Graph(id="fig_assessment_hist"), + ] + ), + ], + style={"columnCount": 2}, + ), + html.Br(), + html.Div( + [ + dash_table.DataTable( + id="scores_table", + columns=[{"id": c, "name": c} for c in NO_ST_COLUMNS.values()], + style_cell={ + "whiteSpace": "normal", + "height": "auto", + }, + style_data_conditional=[], + editable=True, + ) + ] + ), + html.P(id="lastsave"), + dcc.Store(id="final_score"), + ] +) + @app.callback( - [dash.dependencies.Output("csv", "options"), dash.dependencies.Output("csv", "value")], - [dash.dependencies.Input("tribe", "value")], - ) + [ + dash.dependencies.Output("csv", "options"), + dash.dependencies.Output("csv", "value"), + ], + [dash.dependencies.Input("tribe", "value")], +) def update_csvs(value): if not value: raise PreventUpdate @@ -82,26 +123,93 @@ def update_csvs(value): except IndexError: return [] + @app.callback( - [dash.dependencies.Output("final_score_table", "columns"), dash.dependencies.Output("final_score_table", "data")], - [dash.dependencies.Input("scores_table", "data")], - ) -def update_final_scores_table(data): + [ + dash.dependencies.Output("final_score", "data"), + ], + [dash.dependencies.Input("scores_table", "data")], +) +def update_final_scores(data): if not data: raise PreventUpdate try: scores = pd.DataFrame.from_records(data) scores = flat_df_students(scores).dropna(subset=["Score"]) scores = pp_q_scores(scores) - assessment_scores = scores.groupby(["Eleve"]).agg({"Note": "sum", "Bareme": "sum"}) - return [{"id": c, "name": c} for c in assessment_scores.reset_index().columns], assessment_scores.reset_index().to_dict('records') + assessment_scores = scores.groupby(["Eleve"]).agg( + {"Note": "sum", "Bareme": "sum"} + ) + return [assessment_scores.reset_index().to_dict("records")] except KeyError: raise PreventUpdate + @app.callback( - [dash.dependencies.Output("lastsave", "children")], - [dash.dependencies.Input("scores_table", "data"), dash.dependencies.State("csv", "value")], - ) + [ + dash.dependencies.Output("final_score_table", "columns"), + dash.dependencies.Output("final_score_table", "data"), + ], + [dash.dependencies.Input("final_score", "data")], +) +def update_final_scores_table(data): + assessment_scores = pd.DataFrame.from_records(data) + return [ + {"id": c, "name": c} for c in assessment_scores.columns + ], assessment_scores.to_dict("records") + + +@app.callback( + [ + dash.dependencies.Output("final_score_describe", "columns"), + dash.dependencies.Output("final_score_describe", "data"), + ], + [dash.dependencies.Input("final_score", "data")], +) +def update_final_scores_descr(data): + desc = pd.DataFrame.from_records(data)["Note"].describe() + print(desc.keys()) + return [{"id": c, "name": c} for c in desc.keys()], [desc.to_dict()] + + +@app.callback( + [ + dash.dependencies.Output("fig_assessment_hist", "figure"), + ], + [dash.dependencies.Input("final_score", "data")], +) +def update_final_scores_hist(data): + assessment_scores = pd.DataFrame.from_records(data) + ranges = np.linspace( + 0, assessment_scores.Bareme.max(), int(assessment_scores.Bareme.max() * 2 + 1) + ) + bins = pd.cut(assessment_scores["Note"], ranges) + assessment_scores["Bin"] = bins + assessment_grouped = ( + assessment_scores.reset_index() + .groupby("Bin") + .agg({"Bareme": "count", "Eleve": lambda x: "\n".join(x)}) + ) + assessment_grouped.index = assessment_grouped.index.map(lambda i: i.right) + fig = go.Figure() + fig.add_bar( + x=assessment_grouped.index, + y=assessment_grouped.Bareme, + text=assessment_grouped.Eleve, + textposition="auto", + hovertemplate="", + marker_color="#4E89DE", + ) + return [fig] + + +@app.callback( + [dash.dependencies.Output("lastsave", "children")], + [ + dash.dependencies.Input("scores_table", "data"), + dash.dependencies.State("csv", "value"), + ], +) def save_scores(data, csv): scores = pd.DataFrame.from_records(data) print(f"save at {csv} ({datetime.today()})") @@ -113,22 +221,26 @@ def highlight_value(df): """ Cells style """ hight = [] for v, color in COLORS.items(): - hight +=[ - { - 'if': { - 'filter_query': '{{{}}} = {}'.format(col, v), - 'column_id': col - }, - 'backgroundColor': color, - 'color': 'white' - } for col in df.columns if col not in NO_ST_COLUMNS.values() - ] + hight += [ + { + "if": {"filter_query": "{{{}}} = {}".format(col, v), "column_id": col}, + "backgroundColor": color, + "color": "white", + } + for col in df.columns + if col not in NO_ST_COLUMNS.values() + ] return hight + @app.callback( - [dash.dependencies.Output("scores_table", "columns"), dash.dependencies.Output("scores_table", "data"), dash.dependencies.Output("scores_table", "style_data_conditional"), ], - [dash.dependencies.Input("csv", "value")], - ) + [ + dash.dependencies.Output("scores_table", "columns"), + dash.dependencies.Output("scores_table", "data"), + dash.dependencies.Output("scores_table", "style_data_conditional"), + ], + [dash.dependencies.Input("csv", "value")], +) def update_scores_table(value): if not value: raise PreventUpdate @@ -137,4 +249,8 @@ def update_scores_table(value): # stack = stack.drop(columns=["Nom", "Trimestre", "Date", "Competence", "Domaine", "Est_nivele", "Bareme"]) # except KeyError: # stack = stack - return [{"id": c, "name": c} for c in stack.columns], stack.to_dict('records'), highlight_value(stack) + return ( + [{"id": c, "name": c} for c in stack.columns], + stack.to_dict("records"), + highlight_value(stack), + )