With Python and Streamlit I’m build apps to assist teachers grading essays. In this segment of the app the user is provided with all the student submissions as .txt files. These files are displayed on the main screen, and users scroll down to display additional texts. In a sidebar there are input fields for entering in comments and grades for each individual txt. The layout for the app is below:
So far so good. However, I’m at a loss to figure out how to capture the output from the sidebar radio buttons and number inputs into a csv with pandas. Here’s my working code below:
import os import streamlit as st import pandas as pd # for establishing length of loops below path, dirs, files = next(os.walk("TXTs")) float_file_count = ((len(files)) / 3) file_count = int(float_file_count) txts = [f"TXTs\feedback_{n}.txt" for n in range(file_count)] #streamlit layout st.set_page_config(layout="wide") st.sidebar.header("Grading Interface") button1 = st.sidebar.button("Record comments and grades.") st.header("Demo - Level 3.6.2 (Prototype)") st.subheader("In this level...") # loop to display txt files in main screen def display_txts(): for txt in txts: with open(txt, 'r', encoding="utf8") as infile: contents = infile.read() st.markdown(":point_right: " + "**" + txt + "**" + " :point_left:" + "nn" + contents) # loop to display `engagement_comment` and `engagement_grade` fields in sidebar def display_engagement(): for txt in txts: engagement_comment = st.sidebar.radio("Engagement Comment: " + txt, ["Substantial engagement with text - good work.", "Satisfactory engagement with text.", "Little engagement with text - needs improvement."], key = txt) engagement_grade = st.sidebar.number_input("Engagement Grade: " + txt) display_txts() display_engagement() if button1: df = pd.read_csv("Control2.csv") df['engagement_grade'] = df.apply(display_engagement()) # <<<< Here's the crux of my difficulty. How do I write the outputs to be written to csv? df df.to_csv("Control2.csv", index=False) st.write("Grades and comments recorded. This process can be repeated as needed.")
However, when I click on button1
to write the output I receive the following error:
DuplicateWidgetID: There are multiple identical st.radio widgets with key='TXTsfeedback_0.txt'. To fix this, please make sure that the key argument is unique for each st.radio you create.
Traceback: File "C:UsersdanieDesktopPythonStreamlitExperiment_3_6_2.py", line 50, in <module> df['engagement_grade'] = df.apply(display_engagement()) File "C:UsersdanieDesktopPythonStreamlitExperiment_3_6_2.py", line 37, in display_engagement engagement_comment = st.sidebar.radio("Engagement Comment: " + txt, ["Substantial engagement with text - good work.", "Satisfactory engagement with text
I thought I had set up individual keys within the display_engagement
function, but it appears something is amiss. Any ideas or suggestion to resolve this, or to structure this differently? All advice and assistance appreciated.
Advertisement
Answer
I modified your code a bit, but here’s the general solution. There were a few problems.
Problem 1: This made it so that only one item in your list was being passed to the unique key (print off the txts in streamlit and you will see what I mean)
float_file_count = ((len(files)) / 3)
Problem 2: You need to allow the user to generate the data dynamically as a form so that it is all passed at once.
Problem 3: You need to access the unique key in streamlit’s session state.
Problem 4: You need to append to the existing DataFrame
The code below works and it should give you the framework to finish up your app. By the way, super cool idea! I tried to keep a lot of your syntax the sam
import os import streamlit as st import pandas as pd import glob st.set_page_config(layout="wide") st.sidebar.header("Grading Interface") st.header("Demo - Level 3.6.2 (Prototype)") st.subheader("In this level...") txts = glob.glob("TXTs/*.txt") def display_txts(): with open(txt, 'r', encoding="utf8") as infile: contents = infile.read() st.markdown(":point_right: " + "**" + txt + "**" + " :point_left:" + "nn" + contents) form = st.sidebar.form(key='grading') submit_button = form.form_submit_button(label='Submit') for txt in txts: display_txts() txt = txt.replace("TXTs\", "").replace(".txt", "").replace("_", "") rb = form.radio("Engagement Comment: " + txt, ["Substantial engagement with text - good work.", "Satisfactory engagement with text.", "Little engagement with text - needs improvement."], key = txt) if submit_button: # df = pd.read_csv("Control2.csv") df = pd.DataFrame(columns=['data']) st.write(st.session_state.feedback0) #Find out which items in the session state have feedback in the name for item in st.session_state: if "feedback" in item: df = df.append({'data': st.session_state[item]}, ignore_index=True) df.to_csv("Control2.csv", index=False)