Skip to content
Advertisement

Streamlit, Python, and Pandas: Duplicate keys and writing

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:

enter image description here

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)
User contributions licensed under: CC BY-SA
7 People found this is helpful
Advertisement