keefereuther commited on
Commit
227a12f
·
verified ·
1 Parent(s): fe70623

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +123 -75
app.py CHANGED
@@ -1,77 +1,125 @@
 
 
 
 
1
  import streamlit as st
2
- import text2qti
3
- import traceback
4
- import subprocess
5
- import os
6
-
7
- def convert_txt_to_qti(file_path):
8
- # Use subprocess to run the text2qti command
9
- output_file = file_path.replace('.txt', '.zip')
10
- try:
11
- subprocess.run(["text2qti", file_path], check=True, capture_output=True, text=True)
12
- return output_file, None
13
- except subprocess.CalledProcessError as e:
14
- # Capture the stderr output
15
- error_message = e.stderr.strip() if e.stderr else "An error occurred without any specific error message."
16
- return None, error_message
17
-
18
- def main():
19
- st.title("text2qti Converter")
20
-
21
- st.markdown("""
22
- For help or more information about how to generate/format .txt files for conversion and upload to Canvas, please visit the [following walk-through guide.](https://reutherlab.biosci.ucsd.edu/)
23
- """)
 
 
 
 
 
 
 
24
 
25
- uploaded_file = st.file_uploader("Upload a .txt file", type=["txt"])
26
-
27
- if uploaded_file:
28
- # Extract the filename without the extension
29
- file_name = os.path.splitext(uploaded_file.name)[0]
30
-
31
- # Save the uploaded file to a location with the same name
32
- temp_file_path = f"{file_name}.txt"
33
- with open(temp_file_path, "wb") as f:
34
- f.write(uploaded_file.getbuffer())
35
-
36
- st.write("File uploaded successfully!")
37
-
38
- # Button to initiate the conversion
39
- if st.button('Convert to QTI'):
40
- try:
41
- qti_zip_file, error = convert_txt_to_qti(temp_file_path)
42
- if qti_zip_file and os.path.exists(qti_zip_file):
43
- with open(qti_zip_file, "rb") as f:
44
- zip_data = f.read()
45
- st.download_button(f'Download {file_name}.zip', zip_data, file_name=f'{file_name}.zip', mime='application/zip')
46
- else:
47
- # If the conversion failed, display the error message
48
- error_message = (f"""Failed to convert the file. You will see a long, confusing error. **Focus on the part that provides a line number and text from the quiz itself. it will often be near the end of the error message.**
49
- ---
50
- **Here is an made-up example of what you should look for:**
51
- In test_quiz_error.txt on line 23: Syntax error; unexpected text, or incorrect indentation for a wrapped paragraph: #a) Polar heads face outward, nonpolar tails face inward
52
- This would indicate that there is an error on line 23 of the quiz. You need to change #a) to \*a) to fix the error.
53
- Please visit the walkthrough linked above for more information.
54
- ---
55
- **Your error:**
56
- {error}")
57
- """)
58
- st.markdown(error_message)
59
- except Exception as e:
60
- # Catch any other exception that might occur and display it using traceback
61
- error_traceback = traceback.format_exc()
62
- st.error(f"An unexpected error occurred: {error_traceback}")
63
-
64
-
65
- # Footer
66
- st.markdown("---")
67
- st.markdown("""
68
- The text2qti python library is Copyright (c) 2020 by Geoffrey M. Poore.
69
- It can be found at [https://github.com/gpoore/text2qti](https://github.com/gpoore/text2qti)
70
- and is distributed under the BSD 3-Clause License.
71
- """)
72
- st.markdown("""
73
- This app is managed by Keefe Reuther - [https://reutherlab.biosci.ucsd.edu/](https://reutherlab.biosci.ucsd.edu/)
74
- """)
75
-
76
- if __name__ == "__main__":
77
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Import necessary libraries
2
+ import pandas as pd
3
+ import numpy as np
4
+ import matplotlib.pyplot as plt
5
  import streamlit as st
6
+ from scipy.stats import ttest_ind
7
+ from scipy.stats import median_abs_deviation
8
+
9
+ # Load the gradebook data
10
+ gradebook = pd.read_csv('FAKE_EXAMPLE_DATA.csv')
11
+
12
+ # Define the assignment groups
13
+ assignment_groups = {
14
+ "attendance": [column for column in gradebook.columns if 'Attendance' in column],
15
+ "study_activities": [column for column in gradebook.columns if 'Study' in column],
16
+ "quizzes": [column for column in gradebook.columns if 'Quiz' in column],
17
+ "midterms": [column for column in gradebook.columns if 'Midterm' in column],
18
+ "final_exam": [column for column in gradebook.columns if 'Final_Exam' in column]
19
+ }
20
+
21
+ # Add a title to the app
22
+ st.title("Minimizing Inequity in Grading")
23
+
24
+ # Create sidebar for weightings
25
+ weightings = {group: st.sidebar.slider(f'{group} weighting', min_value=0.000, max_value=1.000, value=0.2, step=0.01) for group in assignment_groups.keys()}
26
+
27
+ # Create sidebar for dropped scores
28
+ dropped_scores = {group: st.sidebar.slider(f'{group} dropped scores', min_value=0, max_value=6, value=0) for group in assignment_groups.keys()}
29
+
30
+ # Create sidebar for minimum scores
31
+ minimum_scores = {group: st.sidebar.slider(f'{group} minimum score', min_value=0.0, max_value=1.0, value=0.0, step=0.01) for group in assignment_groups.keys()}
32
+
33
+ # Define the function for calculating final grades
34
+ def calculate_final_grade(gradebook, assignment_groups, weightings, dropped_scores, minimum_scores):
35
 
36
+ # Initialize a DataFrame to store the final grades
37
+ final_grades = pd.DataFrame()
38
+ final_grades["ID"] = gradebook["ID"]
39
+ final_grades["demographic_group"] = gradebook["demographic_group"]
40
+
41
+ # Initialize a Series to store the total weights (for normalization)
42
+ total_weights = pd.Series(0, index=gradebook.index)
43
+
44
+ # Loop over each assignment group
45
+ for group, columns in assignment_groups.items():
46
+ # Calculate the total score for the group, replacing missing or below-minimum scores with the minimum
47
+ group_scores = gradebook[columns].apply(pd.to_numeric, errors='coerce')
48
+ group_scores = group_scores.fillna(minimum_scores[group]).clip(lower=minimum_scores[group])
49
+
50
+ # Drop the lowest scores
51
+ if dropped_scores[group] > 0:
52
+ group_scores = group_scores.apply(lambda row: row.nlargest(len(row) - dropped_scores[group]), axis=1)
53
+
54
+ # Calculate the average score for the group
55
+ group_averages = group_scores.mean(axis=1)
56
+
57
+ # Multiply by the weight and add to the final grades
58
+ final_grades[group] = group_averages * weightings[group]
59
+ total_weights += weightings[group]
60
+
61
+ # Normalize the final grades by the total weights
62
+ final_grades["Final_Grade"] = final_grades.drop(columns=["ID", "demographic_group"]).sum(axis=1) / total_weights
63
+
64
+ return final_grades
65
+
66
+ # Calculate final grades
67
+ final_grades = calculate_final_grade(gradebook, assignment_groups, weightings, dropped_scores, minimum_scores)
68
+
69
+ # Display the final grades
70
+ # st.write(final_grades.head())
71
+
72
+ # Calculate summary statistics for each group
73
+ summary_statistics = final_grades.groupby("demographic_group")["Final_Grade"].describe()
74
+
75
+ # Calculate the median for each group
76
+ demo1_median = final_grades[final_grades["demographic_group"] == True]["Final_Grade"].median()
77
+ non_demo1_median = final_grades[final_grades["demographic_group"] == False]["Final_Grade"].median()
78
+
79
+ # Calculate the difference between the medians
80
+ median_difference = demo1_median - non_demo1_median
81
+
82
+ # Convert to percentage and round to 2 decimal places
83
+ demo1_median = round(demo1_median * 100, 2)
84
+ non_demo1_median = round(non_demo1_median * 100, 2)
85
+ median_difference = round(median_difference * 100, 2)
86
+
87
+ # Display the medians
88
+ st.markdown(f"<p style='font-size:28px;'>This data is FAKE and only for the purpose of demonstrating the app.</p>", unsafe_allow_html=True)
89
+ st.markdown(f"<p style='font-size:20px;'>Demographic Group 1 Median Grade: {demo1_median}%</p>", unsafe_allow_html=True)
90
+ st.markdown(f"<p style='font-size:20px;'>Demographic Group 2 Median Grade: {non_demo1_median}%</p>", unsafe_allow_html=True)
91
+ st.markdown(f"<p style='font-size:20px;'>Median Difference: {median_difference}%</p>", unsafe_allow_html=True)
92
+
93
+ # Plot histograms of final grades for each group
94
+ plt.hist(final_grades.loc[final_grades["demographic_group"] == False, "Final_Grade"], bins=50, histtype='step', label='Demographic Group 2', color='blue')
95
+ plt.hist(final_grades.loc[final_grades["demographic_group"] == True, "Final_Grade"], bins=50, histtype='step', label='Demographic Group 1', color='orange')
96
+
97
+ # Add labels and legend
98
+ plt.xlabel('Final Grade')
99
+ plt.ylabel('Frequency')
100
+ plt.legend(loc='upper left')
101
+
102
+ # Display the plot
103
+ st.pyplot(plt)
104
+
105
+
106
+ # Calculate MAD for each group
107
+ demo1_mad = median_abs_deviation(final_grades.loc[final_grades["demographic_group"] == True, "Final_Grade"])
108
+ non_demo1_mad = median_abs_deviation(final_grades.loc[final_grades["demographic_group"] == False, "Final_Grade"])
109
+
110
+ st.write('Demographic Group 1 MAD: ', demo1_mad)
111
+ st.write('Demographic Group 2 MAD: ', non_demo1_mad)
112
+
113
+ # Display the summary statistics
114
+ st.write(summary_statistics)
115
+
116
+ # Conduct t-test
117
+ t_stat, p_value = ttest_ind(final_grades[final_grades["demographic_group"] == True]["Final_Grade"],
118
+ final_grades[final_grades["demographic_group"] == False]["Final_Grade"],
119
+ equal_var=False, nan_policy='omit')
120
+ st.write(f"P-value (t-test): {p_value}")
121
+
122
+ # Calculate Glass's Delta
123
+ non_demo1_std = final_grades.loc[final_grades["demographic_group"] == False, "Final_Grade"].std()
124
+ glass_delta = (final_grades.loc[final_grades["demographic_group"] == True, "Final_Grade"].mean() - final_grades.loc[final_grades["demographic_group"] == False, "Final_Grade"].mean()) / non_demo1_std
125
+ st.write('Glass Delta: ', glass_delta)