Компоненты пользовательского интерфейса Streamlit упростили создание простых опросов.

  • Что вы думаете о будущем ИИ, следует ли его регулировать, создаст ли он новые рабочие места или уничтожит их?
  • Как, по вашему мнению, изменение климата повлияет на ваш образ жизни?
  • Верите ли вы, что во Вселенной есть инопланетная жизнь?
  • Какой язык программирования для обработки данных вы предпочитаете?

Иногда мы используем данные других людей для создания истории — в других случаях нам нужно создать свои собственные данные, и поэтому мы должны их собрать. Это может быть опрос или журнал экспериментальных результатов, но нам нужно задавать вопросы и записывать полученные данные.

Конечно, есть сервисы, которые сделают это за вас (иногда за плату, но часто бывают и бесплатные варианты). Или вы можете придерживаться проверенного метода буфера обмена и карандаша.

Но создать простой опрос довольно просто, если вы являетесь пользователем Streamlit.

Хранение данных

Тем не менее, есть небольшой камень преткновения. Компоненты пользовательского интерфейса Streamlit великолепны и просты в использовании, но встроенных методов хранения данных нет. Вы можете просто хранить данные в текстовом файле или базе данных SQLite, и это будет хорошо работать, но только для локального приложения.

Если вы попытаетесь развернуть это приложение в облаке Streamlit, вы обнаружите, что все созданные вами данные исчезают.

Это очевидно, когда вы думаете об этом.

Когда вы запускаете приложение Streamlit Cloud, оно копирует исходные файлы из Github, включая любые файлы данных или базы данных, но когда вы выходите из приложения, обратно ничего не записывается. Поэтому, когда вы снова запускаете приложение, вы начинаете с нуля. Любые данные, которые вы собрали и сохранили, сохраняются только до тех пор, пока работает приложение. Когда вы выходите из приложения, эти данные теряются.

Не лучшее поведение для приложения для опросов.

Разработчики Streamlit, конечно же, подумали об этом и предложили решения в своей документации (см. раздел туториалов в Базе знаний). В основном они связаны с подключением к серверам баз данных, на которых работают различные базы данных, такие как MySQL, Microsoft SQL Server и т. д., но также показывают, как использовать Streamlit с облачными сервисами, такими как Amazon S3, MongoDB и Google Cloud Storage.

Существует также Databutton, который представляет собой комплексную онлайн-среду разработки для Streamlit, которая включает, среди прочего, развертывание одним щелчком мыши, кодирование с поддержкой ИИ и, что удобно, хранение данных как часть среды разработки и развертывания. В конце статьи есть раздел о переносе на Databutton.

(Возможно, некоторые из вас видели эту статью опубликованной с кнопкой Databutton в заголовке — это было ошибкой. Я включил раздел Databutton, но в ближайшем будущем напишу более подробную статью о Databutton.)

На данный момент мы собираемся сконцентрироваться на части обзора и отдельно заняться хранилищем. В этом приложении мы просто будем использовать локальный файл для хранения данных, но чтобы упростить нашу будущую жизнь, мы поместим все файловые операции в библиотеку. Таким образом, если мы хотим портировать на другую платформу, нам просто нужно переписать библиотеку. Итак, просто имейте в виду, что наше исходное приложение предназначено не для развертывания, а для работы на локальном компьютере.

Создание опроса в Streamlit

Streamlit предоставляет хороший набор компонентов пользовательского интерфейса, которые можно использовать для создания, представления и анализа данных опроса. В частности, мы будем использовать группы переключателей для реализации вопросов с несколькими вариантами ответов и редактируемый фрейм данных для отображения и редактирования самой анкеты.

Мы можем подумать о более сложных презентациях или других типах вопросов позже, а пока оставим их простыми.

Приложение состоит из трех компонентов: редактор анкеты; презентация опроса; и анализатор/визуализатор результатов. Я реализовал их как страницы в многостраничном приложении. (Все это означает, что они находятся в папке с именем pages.)

Редактор

В основном мы будем использовать словари Python для представления наших данных — как анкеты, так и результатов — и в этой локальной версии приложения мы будем хранить их в виде файлов JSON.

Вопросы будут храниться в двух полях: text, строка, содержащая текст вопроса, и responses, представляющая собой строку ответов с несколькими вариантами ответов, разделенных запятыми.

Вы можете просмотреть данные вопроса, отображаемые в виде компонента Streamlit data_editor на снимке экрана ниже. А с помощью этого компонента вы можете редактировать анкету напрямую, если хотите.

Над редактируемым фреймом данных находится пара полей: первое — для вопроса, а второе — для списка возможных ответов. Заполните это и нажмите кнопку Добавить вопрос в опрос, и вы увидите, что новый вопрос появится во фрейме данных.

Как я уже сказал, вы также можете напрямую редактировать фрейм данных: щелкните в соответствующем поле, чтобы изменить существующие данные; щелкните слева от строки, чтобы выбрать ее, и используйте клавишу удаления, чтобы удалить ее; или щелкните слева под последней строкой, чтобы добавить новую строку.

В любом случае вам нужно нажать Сохранить изменения, чтобы сохранить данные.

Вы можете увидеть реализацию ниже.

Программы Streamlit перезапускаются каждый раз, когда происходит взаимодействие с пользователем, поэтому мы используем средства сеанса Streamlit для сохранения вопросника, чтобы его значение сохранялось должным образом. В остальном это довольно простая программа Streamlit; он представляет два компонента st.text_input() (добавьте строку ответа по умолчанию ко второму), за которыми следует st.data_editor(), который одновременно отображает вопросник и позволяет его изменять.

Последняя часть программы — это место, где хранятся данные. Здесь используются подпрограммы, которые я написал в библиотеке DButils. По сути, это оболочка для базовых функций хранения файлов — как я уже говорил ранее, я реализовал подобные хранилища, чтобы программы можно было использовать с альтернативными вариантами хранения на другой платформе.

DButils.get_survey() извлекает сохраненную анкету, а DButils.save_survey() сохраняет весь фрейм данных в файл.

import streamlit as st
import DButils

st.set_page_config(layout="wide")

if 'survey' not in st.session_state:
    st.session_state['survey'] = DButils.get_survey()

st.title("Questionaire editor")
st.write("""Type in the question text in the field below and then add
            a list of possible responses (or you can leave, or edit, 
            the default responses)."""
)

# Set a default response
default_response = (
    "Strongly agree,Agree,Neither agree not disagree,Disagree,Strongly disagree"
)

st.header("Question")
q_text = st.text_input("Question text")
q_responses = st.text_input(
    "A comma separated list of responses", value=default_response
)

submitted = st.button("Add question to survey")

if submitted:
    st.session_state['survey'].append(
        {
            "text": q_text,
            "responses": q_responses,
        }
    )

st.write("You can also edit the questions and response directly in the table.")

edited_df = st.data_editor(st.session_state['survey'], num_rows="dynamic")

save = st.button("Save changes")
if save:
    DButils.save_survey(edited_df)
    st.success(f"Changes saved")

Представьте опрос

Каждый вопрос представлен в виде группы переключателей.

Как вы можете видеть ниже, мы перебираем анкету, извлекая поле text для подсказки и разбивая поле responses на отдельные ответы, чтобы отобразить группу кнопок.

import pandas as pd
import streamlit as st

import DButils

st.info("## Select the answer to each question and then click on 'Submit'")
questions = DButils.get_survey()

responses = {}

for q in questions:
    response = st.radio(label=q['text'], options=q['responses'].split(","))
    responses[q['text']] = response.strip()

if st.button("Submit"):
    entry = responses
    DButils.append_results(entry)
    st.write("Updated")

Затем полный набор записанных данных добавляется к сохраненным ответам с помощью DButils.update().

Представьте результаты

Страница результатов разделена на 3 раздела: первый показывает результаты в виде таблицы данных, которую можно загрузить в виде файла CSV.

Вторая часть представляет собой графический обзор всего обследования. Гистограмма создается с помощью Plotly Express.

И последняя часть позволяет пользователю выбирать результаты для каждого вопроса, которые отображаются в виде гистограммы (также Plotly).

Код для этого ниже. Мы используем DButils.get_results() для загрузки фрейма данных результатов, а затем отображаем его как st.dataframe() (на этот раз, конечно, не редактируемый!) И добавляем кнопку загрузки, которая сохранит данные на вашем локальном компьютере в виде CSV-файла.

После этого есть столбчатая диаграмма всех данных ответов (цветные для каждого вопроса). И так как это не обязательно легче читать, за ней следует группа переключателей, которая позволяет вам выбрать конкретный вопрос, на котором нужно сосредоточиться. Гистограммы для каждого вопроса рисуются заранее, в цикле, и соответствующая диаграмма отображается для выбранного переключателя.

import streamlit as st
import plotly.express as px
import pandas as pd
import DButils

st.set_page_config(layout="wide")

st.info("## Here are the results:")

st.write("The results are presented as a dataframe.")

# Read data from Databutton's datastore
results = DButils.get_results()
st.dataframe(results, use_container_width=True)

df = pd.DataFrame(results)

st.download_button(
    label="Download data as CSV",
    data=df.to_csv().encode("utf-8"),
    file_name="survey_results.csv",
    mime="text/csv",
)

# Plot a summary bar graph

fig = px.bar(results, title="Survey responses - overview")
fig.update_xaxes(title_text="Response")
fig.update_yaxes(title_text="Count")
st.plotly_chart(fig)

# Create an array of bar graph figures
# one for each question 

figures = []

for q in df.columns:
    fig = px.bar(df[q], title=q)
    fig.update_layout(showlegend=False)
    fig.update_xaxes(title_text="Response")
    fig.update_yaxes(title_text="Count")
    figures.append(fig)

# Choose which graph to display with a set of radio buttons

st.info("### Choose the graph for a specific question")
f = st.radio("Choose a graph", options=df.columns)
column_index = df.columns.get_loc(f)
st.plotly_chart(figures[column_index])

DButils библиотека

Как вы можете видеть ниже, библиотека DButils имеет ряд функций для чтения, записи и обновления CSV-файлов. Он также определяет константы для двух файлов, которые мы использовали выше.

Библиотека написана специально для локального приложения, использующего файлы JSON для хранения данных, но если вы хотите перенести ее на другую платформу, нужно переписать только четыре простые функции и определить две константы.

import os
import json

SURVEY_KEY = "survey.json"
RESULTS_KEY = "results.json"

# Save data
def save_dict(value, key=SURVEY_KEY):
    print(f"Saving: {value}")
    #return None
    out_file = open(key, "w")
    json.dump(value,out_file)
    out_file.close()

def save_results(value):
    save_dict(value,RESULTS_KEY)

def save_survey(value):
    save_dict(value, SURVEY_KEY)

# Retrieve data
def retrieve(key):
    # file exists read it and return dict array
    if os.path.isfile(key):
        in_file = open(key, "r")
        result = json.load(in_file)
        in_file.close()
        return result
    else:
        # File does not exist return an empty dict array
        return []

def get_survey(key=SURVEY_KEY):
    return retrieve(key)

def get_results(key=RESULTS_KEY):
    return retrieve(key)

# Update results
# This may not be efficient but it is simple
def append_results(value):
    results = get_results()
    results.append(value)
    save_results(results)

Кнопка данных

Чтобы продемонстрировать, как легко перенести это на другую платформу и, в частности, как легко перенести на Databutton, вот новая версия библиотеки DButils.

import databutton as db

def get_survey():
    survey = db.storage.json.get("survey", default=[])
    return survey

def save_survey(survey):
    db.storage.json.put("survey", survey)

def append_results(entry):
    # Retrieve the existing survey results from the JSON file in Databutton
    survey_results = db.storage.json.get("survey_results", default=[])

    # Append the new entry to the survey results
    survey_results.append(entry)

    # Save the updated survey results back to the JSON file in Databutton
    db.storage.json.put("survey_results", survey_results)

def get_results():
    # Retrieve the results data from Databutton's datastore
    results = db.storage.json.get("survey_results", default={})
    return results

Чтобы перенести все это на Databutton, просто скопируйте приведенные выше страницы на страницы Databutton и скопируйте приведенный выше код в библиотеку Databutton.

Что делает это таким простым, так это то, что мне почти не пришлось писать код — Databutler сделал это за меня. Я просто попросил помощника ИИ сгенерировать код библиотеки для каждой страницы, а затем вставил его в файл библиотеки.

И это просто сработало?

Не совсем. В коде, сгенерированном Databutler для отдельных страниц, использовались несколько разные имена для хранилищ данных, например, survey_results для одной страницы и просто survey для другой. Это было исправлено за несколько секунд. Тогда это просто сработало!

Поразмыслив, я мог бы быть более точным в своих подсказках и сообщить Databulter имена, которые он должен использовать.

Пожалуйста, попробуйте инструмент опроса здесь, на кнопке данных.

В реальном мире

Я надеюсь, вы согласитесь, что эти простые подпрограммы создают достаточно привлекательное приложение и показывают, как можно использовать Streamlit для создания простых опросов.

Но есть несколько вещей, о которых вам нужно подумать, если вы хотите развернуть такую ​​​​вещь в реальном мире.

Вот несколько вещей, которые вы, возможно, захотите рассмотреть:

  • Несмотря на то, что опросы проводятся анонимно, вы можете захотеть установить личность респондента, чтобы избежать дублирования записей.
  • Возможно, вы захотите включить разные типы вопросов или представить их по-разному (например, st.select_slider()).
  • Случайный способ представления ответов иногда может не привести респондента к конкретному ответу.
  • Вы почти наверняка захотите добавить в опрос демографические вопросы. Они также могут быть реализованы как вопросы с несколькими вариантами ответов, но при проведении анализа результаты должны рассматриваться иначе, чем другие.

Но это не руководство по разработке опроса, поэтому я оставлю его там.

Спасибо за прочтение, я надеюсь, вы нашли это полезным руководством о том, как вы можете подойти к разработке опросов в Streamlit. Это приложение преднамеренно очень простое, а хранилище данных работает только тогда, когда приложение развернуто локально — я надеюсь рассмотреть эти вопросы в следующей статье.

Если вы хотите увидеть больше моих работ, загляните на мою веб-страницу.