Некоторые советы и рекомендации при создании развивающегося приложения

Streamlit - отличный инструмент для создания интерфейса для вашей работы в области науки о данных. Я использовал его в качестве облегченного инструмента панели инструментов для отображения простых визуализаций, обертывания пакетов Python в пользовательском интерфейсе и изучения оценок моделей для моделей НЛП (живые примеры 1 и 2). Это позволило мне лучше понять мои данные и создать интерфейс, который может помочь в переводе между кодом / данными / анализом и общением с заинтересованными сторонами и экспертами в предметной области.

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

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

(В качестве функционального примечания: каждый раз, когда вы видите суть, вы должны иметь возможность запустить ее как оптимизированное приложение и изучить себя. Если вы хотите сделать pip install streamlit pandas altair vega_datasets, вы можете запустить любую из приведенных ниже сущностей с помощью streamlit run <gist_url>).

Отображение чистых имен переменных

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

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

Используйте кеширование (но сначала проверьте его)

Может возникнуть соблазн применить этот удобный @st.cache декоратор ко всему и надеяться на лучшее. Однако бездумное применение кеширования означает, что мы упускаем прекрасную возможность получить мета и использовать streamlit, чтобы понять, где кэширование помогает больше всего.

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

В приведенном ниже примере мы моделируем загрузку большого набора данных путем объединения 100 копий airports набора данных, затем динамически выбираем первые n строк и описываем их.

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

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

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

Создать динамические виджеты

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

Связывание двух раскрывающихся списков является обычным вариантом использования. В приведенном ниже примере строится диаграмма рассеяния с набором данных cars. Здесь нам нужен динамический раскрывающийся список, потому что переменная, которую мы выбираем для оси x, не обязательно должна быть доступна для выбора на оси y.

Мы также можем выйти за рамки этой базовой динамической функции: что, если мы отсортируем доступные параметры оси Y по их корреляции с выбранной переменной x? Мы можем вычислить корреляции и объединить их с format_func виджета, чтобы отобразить переменные и их корреляции в отсортированном порядке.

Активно используйте f-струны и уценку

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

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

mean = df["values"].mean()
n_rows = len(df)
md_results = f"The mean is **{mean:.2f}** and there are **{n_rows:,}**."
st.markdown(md_results)

Здесь мы использовали два формата: .2f для округления числа с плавающей запятой до двух десятичных знаков и , для использования запятой в качестве разделителя тысяч. Мы также использовали синтаксис уценки, чтобы выделить значения жирным шрифтом, чтобы они были визуально заметны в тексте.

Рассмотрите возможность перехода на Альтаир для визуализаций

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

  1. Альтаир, вероятно, быстрее (если мы не строим много данных)
  2. Он работает непосредственно с пандами DataFrames
  3. Интерактивные визуализации легко создавать

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

Работа напрямую с DataFrames дает еще одно преимущество. Это может упростить процесс отладки: если есть проблема с входными данными, мы можем использовать st.write(df) для отображения DataFrame в приложении Streamlit и его проверки. Это значительно сокращает цикл обратной связи для устранения проблем с данными. Второе преимущество заключается в том, что он сокращает объем кода трансформации, который иногда требуется для создания конкретных визуализаций. Для базовых графиков мы могли бы использовать методы построения DataFrame, но более сложные визуализации могут потребовать от нас реструктуризации нашего набора данных таким образом, чтобы это имело смысл с помощью API визуализации. Этот дополнительный код между набором данных и визуализацией может быть источником дополнительной сложности и может стать проблемой по мере роста приложения. Поскольку Альтаир использует грамматику визуализации Vega-Lite, функции, доступные в API преобразований, могут использоваться для выполнения любых преобразований, подходящих для визуализации.

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

Не пренебрегайте рефакторингом, написанием модульного кода и тестированием

Легко провести несколько часов с streamlit и получить файл из 500 строк app.py, который никто, кроме вас, не поймет. Если вы передаете свой код, развертываете приложение или добавляете какие-то новые функции, теперь возможно, что вы потратите значительное количество времени, пытаясь вспомнить, как работает ваш код, потому что вы пренебрегали хорошей гигиеной кода.

Если приложение превышает 100 строк кода, ему, вероятно, следует провести рефакторинг. Хороший первый шаг - создать функции из кода и поместить их в отдельный helpers.py файл. Это также упрощает тестирование и тестирование кеширования для этих функций.

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

Упражнение по рефакторингу

В app.py попробуйте:

  • импортировать только функции streamlit и вспомогательные функции (не забудьте протестировать @st.cache на этих вспомогательных функциях)
  • никогда не создавайте переменную, которая не вводится в объект с подсветкой, например, визуализацию или виджет, в следующей строке кода (за исключением функции загрузки данных)

Это не жесткие и быстрые правила, которые нужно всегда соблюдать: вы можете строго следовать им и иметь плохо организованное приложение, потому что у вас есть большие и сложные функции, которые делают слишком много. Однако это хорошие цели для начала при переходе от всего, что есть в app.py, к более модульной структуре.

В приведенном ниже примере показано приложение до и после выполнения этого упражнения.

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

Заворачивать

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