Py.Cafe

iisakkirotko/

Dynamic Grade Dashboard-21

Grade Dashboard

DocsPricing
  • data/
  • app.py
  • requirements.txt
app.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# -*- coding: utf-8 -*-
"""
Created on Thu Oct 10 17:13:50 2024

@author: thoma
"""

# Libraries for the data 
from collections import defaultdict #Make it is easier to create data strcuture for bar graph
import os #Talk to the operating system
import glob #Searches directories and files
import json #Reads javascript files
import numpy as np #Mathematical library
import pandas as pd #Dataframes -- python's Excel

# Dashboard Library
import solara # Key dashboard
import ipywidgets as widgets #Makes the dropdowns and sliders
# Plotting library
import plotly.express as px
import plotly.graph_objects as go

######################################################

#                      HELPER FUNCTIONS

###############################################

with open('data/drop_down_small.json', 'r') as json_file:
   drop_downs = json.load(json_file)
   
def score_to_grade(score):
    if score >= 93:
        return 'A'
    elif score >= 90:
        return 'A-'
    elif score >= 83:
        return 'B'
    elif score >= 80:
        return 'B-'
    elif score >= 78:
        return 'C+'
    elif score >= 70:
        return 'C'
    else:
        return 'F'
    
def final_grades(section, assignment):
    grades = {}
    all_section = pd.read_csv(f"data/course_grades/{section}/{assignment}")
    if "final" in assignment: 
        grade_list = all_section['Final Grade'].apply(score_to_grade)
    else: 
        grade_list = all_section['Grades'].apply(score_to_grade)
    for g in grade_list: 
        if g not in grades.keys(): 
            grades[g] = 1
        else: 
            grades[g] += 1
    df = pd.DataFrame([grades])
    return df

def make_overall(df, title): 
    # Get rid of unanmed 
    df.columns = [col if 'Unnamed' not in col else 'unknown' for col in df.columns]
    
    # Ensure all expected columns (A, B, C, D, F) are present, add missing ones with default value (e.g., None)
    expected_columns = ['A', 'A-','B','B-','C+', 'C','F', 'Unknown']
    for col in expected_columns:
        if col not in df.columns:
            df[col] = 0  
    
    # Reorder the columns in grade order
    df = df[expected_columns] 
    
    fig = go.Figure(data=[
    go.Bar(x=list(df.columns), y=df.iloc[0].tolist())  # x for categories, y for values
    ])
    # Customize the layout (optional)
    fig.update_layout(
        title=title,
        xaxis_title="Grades",
        yaxis_title="Frequency"
    )

    # Set the figure height to display all rows
    fig.update_layout(height=500)
    return solara.FigurePlotly(fig)  

def plot_grades(program, course,section,assignment):
    dfs = []
    folders=[]
    file=f"data/course_grades/{program}/{course}/{section}/{assignment}.csv" 
    df = pd.read_csv(file)
    df = df.replace({pd.NA: None, np.nan: None})   
    df["Student ID"] = df['Student ID'].astype(str)
    fig = px.bar(df, 
             x='Grades', 
             y='Student ID', 
             orientation='h', 
             hover_data=["Feedback"],
             title=assignment)

    # Set the figure height to display all rows
    fig.update_layout(height=1000)
    
    # Set the x-axis range to start from the lowest grade minus 10
    min_grade = df['Grades'].min()
    max_grade = df["Grades"].max()
    fig.update_xaxes(range=[min_grade - 5, max_grade])  # Lower bound set, upper bound auto

    return solara.FigurePlotly(fig)
     
def grade_distribution(level, target, details):
    if level=="all":
        return make_overall(pd.read_csv("data/course_grades/overall_grades.csv"), "Overall Grade Distribution")
    if level=="program":
        return make_overall(pd.read_csv(f"data/course_grades/{target}/overall_grades.csv"), f"{target} Grade Distribution")
    if level=="course":
        try: #account for faculty who dont use blackboard
            return make_overall(pd.read_csv(f"data/course_grades/{target}/overall_grades.csv"), f"{target} Grade Distribution")
        except: 
            return solara.Markdown("## This course did not input grades in blackboard.")
    if level=="section": 
        section_grades = final_grades(target, "final_grade.csv")
        return make_overall(section_grades, f"{target} Grade Distribution")
    if level == "assignment":
        section_grades = final_grades(target,details)
        return make_overall(section_grades, "Assignment Grade Distribution")
    
############################################################

###################  DASHBOARD

###########################################################

# Reactive state management for dropdowns
selected_program = solara.reactive(None)
selected_course = solara.reactive(None)
selected_section = solara.reactive(None)
selected_assignments = solara.reactive(None)
graph_grades = solara.reactive(False) 
reset_selection =solara.reactive(False)
    
def reset_controls():
    selected_program.value = None
    selected_course.value = None
    selected_section.value = None
    selected_assignments.value = None
    reset_selection.value=False
            

def program_selected(program):
    if program != None: 
        courses = list(drop_downs[program].keys())
        courses.sort()
        return courses

def course_selected(course):
    if course != None: 
        if course not in list(drop_downs[selected_program.value].keys()):
            return None
        else: 
            sections = list(drop_downs[selected_program.value][course]["Sections"].keys())
            sections.sort()
            #plot
            return sections
    else: 
        return None

def section_selected(section):
    if section != None: 
        if section not in list(drop_downs[selected_program.value][selected_course.value]["Sections"].keys()):
            return None
        else: 
            assignments = drop_downs[selected_program.value][selected_course.value]["Sections"][section]
            assignments.sort()
            return assignments
    else: 
        return None

def plot_grade_status(): 
    graph_grades.set(not graph_grades.value)
    
    
@solara.component
def View():    
   
    try:
        if selected_program.value == None:
            grade_distribution("all", None, None)
        elif selected_program.value != None and selected_course.value == None:
            grade_distribution("program", selected_program.value, drop_downs[selected_program.value])
        elif selected_course.value != None and selected_section.value == None:
            target =selected_program.value+"/"+selected_course.value
            grade_distribution("course", target, drop_downs[selected_program.value][selected_course.value])
        elif selected_section.value != None and selected_assignments.value == None:
            target =selected_program.value+"/"+selected_course.value+"/"+selected_section.value
            grade_distribution("section", target, None)
        elif selected_assignments != None:
            target = selected_program.value+"/"+selected_course.value+"/"+selected_section.value
            assignment = selected_assignments.value +".csv"
            grade_distribution("assignment", target,assignment)
            solara.Button(label="Show Grades by Student ID", on_click=plot_grade_status)
            if graph_grades.value== True:
                plot_grades(selected_program.value, selected_course.value, selected_section.value, selected_assignments.value)
       
    except: 
        solara.Markdown("## Selections do not compute, please update selections")
        reset_controls()

@solara.component
def Controls():     
    solara.Button(label="Reset Selection", on_click=reset_controls)
    try: 
        solara.Select(label="Program", value=selected_program, values=list(drop_downs.keys()))
        solara.Select(label="Course", value=selected_course, values = program_selected(selected_program.value))
        #TODO change to SelectMultiple 
        solara.Select(label="Section", value=selected_section, values = course_selected(selected_course.value))
        #TODO change to SelectMultiple
        solara.Select(label="Assignments", value = selected_assignments, values = section_selected(selected_section.value))
    except: 
        solara.Markdown("## Selections do not compute, please update selections")
        
@solara.component
def Title():
        solara.Markdown("## Assignment Grade Dashboard")


 
@solara.component
def Page():
    Title()
    with solara.Sidebar():
        Controls()
    View()
     

Page()