Каждый исследователь, студент или преподаватель рано или поздно сталкивается с необходимостью создавать интерактивные и красивые отчеты, которые позже следует приложить к статье, сдать на проверку или поделиться со студентами. Все чаще в топовых ВУЗах приевшиеся методички заменяют на Jupiter ноутбуки, а возможность сварганить красивый отчет "на коленке" прельщает огромное количество студентов.

Выделим основные недостатки отчетов в Jupiter Notebook:

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

  • Сложность в интерактивной демонстрации. Для полноценной работы Jupiter Notebook необходимо иметь приложение и интерпретатор питон (или онлайн сервис).

Streamlit

Это low-code фреймворк, созданные для исследователей, которые используют python. Благодаря встроенному набору элементов можно быстро накидать свой отчет или веб приложение, а главное поделиться им с другими, развернув его в облаке всего за пять минут!

Установка

Устанавливается streamlit через pip:

pip install streamlit

После установки можно запустить пример, введя в терминале команду:

streamlit

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

Открывшееся окно приложения
Открывшееся окно приложения

На страничке мы видим ссылку на сайт и много других полезных ссылок, которые пригодятся во время создания своего приложения.

Создаем свой отчет

Все начитается с того, что нужно установить и импортировать streamlit

import streamlit as st

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

page = st.sidebar.selectbox("Выбрать страницу", 
                            ["Тяжелые хвосты распределений", 
                             "Iris Dataset"])

Здесь первый параметр является заголовком виджета, а список содержит варианты выбора. Когда пользователь меняет свой выбор, пременной page присваивается соответствующее значение из списка.

Чтобы переключать страницы, нужно лишь использовать условный оператор if:

    if page == "Тяжелые хвосты распределений":
        st.header("""Демонстрация Fisher's Iris датасета""")
    elif page == "Iris Dataset":
      st.header("""Сгенерировать N случайных событий из распределения Фреше с функцией распределения:""")

Для формирования заголовка используется виджет st.header

Так же есть возможность использовать latex:

st.latex(r'''    
            F(x) = exp(-(\gamma x)^{-1/\gamma}1\{x>0\})
          ''')

Итак, код для отображения страницы "Тяжелые хвосты распределений":

def main():
    page = st.sidebar.selectbox("Выбрать страницу", ["Тяжелые хвосты распределений", "Iris Dataset"])

    if page == "Тяжелые хвосты распределений":
        st.header("""Сгенерировать N случайных событий из распределения Фреше с функцией распределения:""")
        st.latex(r'''    
            F(x) = exp(-(\gamma x)^{-1/\gamma}1\{x>0\})
            ''')
        st.text("Для получения результата:")
        st.markdown("* Сгенерируем N нормально распределенных случайных величин $U_i$ [0,1] (нулевое среднее и единичная диспресия).")
        st.markdown("* Вычислим N cлучайных величин с распределением Фреше по формуле:")
        st.latex(r'''    
                    X_i=\dfrac{1}{\gamma}\left(-lnU_i)^{-\gamma}\right)
                ''')
        mu, sigma = 0, 1  # mean and standard deviation
        gamma = st.slider('Желаемая гамма', 0.25, 2.25, 0.5, 0.25)
        N = st.number_input("Желаемое N", 100, 10000, 10000)
        U = np.abs(np.random.normal(mu, sigma, N))
        X = 1 / gamma * (-np.log(U)) ** (-gamma)
        X2 = X[X < 20]
        fig, ax = plt.subplots()
        count, bins, ignored = plt.hist(X2, 100, density=True)
        plt.plot(bins,
                 np.exp(- (gamma * bins) ** (-1 / gamma)) * (1 / gamma) * (gamma * bins) ** (-1 / gamma - 1) * gamma,
                 linewidth=2, color='r')
        st.pyplot(fig)

Кэширование данных

Каждый раз, когда пользователь нажимает что-то в интерфейсе весь скрипт выполняется заново. Это не очень хорошо, если мы скачиваем какие-то датасеты и работаем с ML моделями. Специально для этого есть две аннотации:

  • st.cache_data - используется для кеширования загружаемых датасетов.

  • st.cache_resource - используется для ML моделей.

Так, чтобы загрузить известный датасет ирисов, напишем функцию с аннотацией st.cache_data:

@st.cache_data
def load_data():
        if not os.path.isfile("data/iris.csv"):
        # Download the Iris Fisher dataset
        url = "https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data"
        response = requests.get(url)
        data = response.text
        # Save the dataset to a local file
        with open("data/iris.csv", "w") as file:
            file.write(data)
    # Load the dataset into a pandas DataFrame
    df = pd.read_csv("data/iris.csv", header=None,
                     names=["sepal_length", "sepal_width", "petal_length", "petal_width", "class"])
    return df

Я поместил датсет в папку data не спроста, она нам потом понадобиться.

Отобразим его на соответствующей странице, используя st.pyplot:

    elif page == "Iris Dataset":
        st.header("""Демонстрация Fisher's Iris датасета""")
        df = load_data()

        # Plotting the dataset
        fig, ax = plt.subplots()
        plt.scatter(df['sepal_length'], df['sepal_width'], c='blue', label='Iris-setosa')
        plt.scatter(df['sepal_length'], df['petal_width'], c='red', label='Iris-versicolor')
        plt.scatter(df['sepal_length'], df['petal_length'], c='green', label='Iris-virginica')

        plt.xlabel('sepal_length')
        plt.ylabel('sepal_width')

        plt.title('Iris Fisher Dataset')

        plt.legend()
        st.pyplot(fig)

Запуск приложения

Для запуска проекта можно рассмотреть два варианта:

  1. Запустить командой streamlit run app.py, через параметр--server.port=8057 можно указать, какой порт будет слушать приложение.

    Не забываем в нашем файле указать точку входа:

    if __name__ == "__main__":
        main()

  2. Для любителей запускать всего через python main.py:

    Создадим отдельный файл main.py и обозначим точку входа, где пропишем, что нужно запустить и с каким портом:

    from streamlit.web.cli import main
    import sys
    
    if __name__ == "__main__":
        # Set prog_name so that the Streamlit server sees the same command line
        # string whether streamlit is called directly or via `python -m streamlit`.
        sys.argv = ["streamlit", "run", "app.py", ""]
        sys.exit(main(prog_name="streamlit"))

В итоге получаем красивый отчет:

Полученный отчет
Полученный отчет

Разворачиваем в облаке

Нам осталось самое интересное. То, ради чего мы вообще все это затеяли: сделать наш отчет общедоступным. Есть несколько вариантов, как это сделать:

  • Воспользоваться Streamlit Community Cloud - облако от создателей данного фреймворка.

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

  • Разворачивание в облаке через пуш в GIT. Это такие сервисы, как Heroku и Amvera.

Amvera Cloud

  1. Переходим на страничку входа и создаем аккаунт, если его ещё нет.

  2. Нажимаем на кнопку "Создать" и вводим название проекта и тариф. Для большинтства проектов подойдет "Начальный"

    Создание проекта
    Создание проекта
  3. На ПК переходим в папку с нашим проектом. Создаем там GIT репозиторий командой git init

  4. На странице проекта находим инструкцию, как подключиться к удаленному репозиторию. Для этого нужно выполнить git remote add amvera <адрес удаленного репозитория>

  5. Генерируем инструкцию для облака, как развернуть наше приложение:

    1. Переходим на страницу генератора инструкций, которая, кстати, тоже написана с помощью streamlit.

    2. Указываем, что мы используем python, обязательно иметь файл requirements.txt, где указаны все используемые библиотеки (в том числе и streamlit).

    3. Если локально запускали все через команду streamlit run app.py, то ставим галку на "укажу свою" и прописываем эту команду.

    4. Тут ещё есть поле "Введите mountpoint", где указывается, где будут храниться данные, которые не стираются после перезапуска или остановки проекта. Так, если вы собираете какую-то статистику или собираетесь хранить данные, то их слудует класть в отдельную папку, путь до которой указывается в этом поле. Я не хочу аждый раз при перезапуске проекта скачивать заново весь датесет, поэтому положил его в папку data и указал этот путь в конфигурации.

    5. Нажимаем на кнопку Generate YAML. Добавляем полученный файл amvera.yml в корень нашего проекта.

  6. Моя папка с проектом выглядит следующим образом (venv у вас может отсутствовать)

    Папка с проектом
    Папка с проектом
  7. Заносим наши изменения в репозиторий, выполнив:

git add .
git commit -m "Initial commit"
git push amvera master
  1. Ждем статуса "Успешно развернуто на странице проекта

    Успешно развернутый проект
    Успешно развернутый проект
  2. Переходи по ссылке, указанной на этой же странице, у меня это https://streamlit-amvera-services.amvera.io и наслаждаемся нашим отчетом.

  3. (Опционально) В разделе "Настройки" можно привязать свое доменное имя.

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

Комментарии (1)


  1. Data4
    06.07.2023 06:46

    А можно ли выводить видеозаписи и загружать/выгружать файлы с компьютера?