Сегодня дашборды используются повсеместно: от быстрых отчетов "на лету" до демонстрации возможностей AI.
Существует множество замечательных продуктов для создания дашбордов: PowerBI, Tableu, Apache SuperSet и много других со своими плюсами и минусами. Однако все они являются отдельными приложениями в которые нужно дополнительно загружать данные, что часто бывает неудобно, если нужно быстро проанализировать датасет. И, если мы говорим про аналитиков, как правило все используют Python для анализа и ad-hoc исследований.
Для Python существует open source фреймворк построения дашбордов: PLotly Dash. Но те, кто его использовал, знают, насколько сложным может быть создание симпатичного интерфейса, и как скучно писать функции обратного вызова для каждого графика и фильтра. С этим еще можно мириться, если вы делаете дашборд для постоянного длительного использования, но с ad-hoc аналитикой этот вариант не годится.
Осознав проблему, я решил написать обертку над Dash, позволяющую создавать полноценные дашборды в 10-15 строк и запускать их прямо в jupiter-notebook и при этом отказаться от необходимости писать callback-функции. Так родился DashExpress. Документация
Концепция
Библиотека DashExpress в основном предназначена для ускорения разработки приложений Dash, но так же содержит ряд оптимизаций для более быстрой работы.
DashExpress опирается на 4 крупных проекта:
Plotly Dash - для web-части
Pandas - для хранения данных
Dash Mantine - для симпатичного интерфейса
Dash Leaflet - для построения карт
Установка
Библиотека устанавливается стандартно, через pip:
pip install dash-express
Это также установит компоненты Plotly Dash, Pandas, Dash Mantine Components и Dash Leaflet.
Создание DashExpress-приложения
Вот код минимального приложения с одним графиком и тремя фильтрами:
import pandas as pd
import plotly.graph_objects as go
import dash_mantine_components as dmc
from dash_express import DashExpress, Page
# Получаем данные
get_df = lambda: pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/gapminder2007.csv')
# Инициализируем приложение
app = DashExpress(logo='DashExpress')
# Создаем страницу дашборда
page = Page(
app=app, # DashExpress app
url_path='/', # Путь страницы
name='Overview', # Название страницы
get_df=get_df, # Функция получения дашборда
)
# Функция построения графика
def bar_func(df):
pv = pd.pivot_table(df, index='continent', values='lifeExp').reset_index()
fig = go.Figure([go.Bar(x=pv['continent'], y=pv['lifeExp'])])
return fig
# Размечаем макет
page.layout = dmc.SimpleGrid(
page.add_graph(h='calc(100vh - 138px)',render_func=bar_func)
)
# По каким колонкам фильтруем
page.add_autofilter('continent', multi=True)
page.add_autofilter('country', multi=True)
page.add_autofilter('lifeExp', multi=True)
app.run()
Запущенное приложение будет выглядеть так:
Приложение так же можно запустить прямо в jupiter-notebook, только перед этим нужно установить дополнительный пакет:
pip install jupyter-dash
Теперь расскажу подробнее о некоторых шагах и начну с получения данных.
Получение таблицы с данными
В параметр get_df нужно передать функцию получения таблицы. По умолчанию результат функции DashExpress кэширует на 1 час.
Если вы исследуете данные в jupiter notebook и у вас уже есть таблица, которую не нужно обновлять, просто передайте ее через функцию:
get_df = lambda: your_df
Если у вас большой датасет используйте оптимизации на стороне pandas:
def get_df():
# Загружаем только нужные колонки
df = pd.read_csv('titanic.csv', usecols=['survived', 'age', 'class', 'who', 'alone'])
# Преобразуем к оптимальным форматам
df.age = df.age.fillna(0)
df = df.astype(
{
'survived': 'int8',
'age': 'int8',
'class': 'category',
'who': 'category',
'alone': 'bool'
}
)
return df
Разметка страницы
Разметьте страницу с помощью grid сетки, и вставьте графики, карты и KPI c помощью методов .add_graph, .add_map и .add_kpi. Рекомендую использовать компоненты dash mantine. (dmc.Grid & dmc.SimpleGrid)
page.layout = dmc.SimpleGrid(
...
)
KPI cards
KPI карточки являются основной частью мониторинга эффективности бизнеса и отслеживания актуальной информации. Любая карта состоит из постоянной части (контейнера) и переменной части (показателя KPI).
Система рендеринга KPI основана на использовании класса KPI, который содержит контейнерное представление и логику расчета показателя. Простейшая реализация KPI с автоматической генерацией функции расчета представлена в классе FastKPI:
app.add_kpi(FastKPI('survived', agg_func=np.mean)
Графики Plotly
Библиотека построения графиков Plotly содержит более 50 типов диаграмм на выбор. Чтобы встроить их в приложение Dash Express, вам нужно ответить на 2 вопроса:
Где находится график
Как построить график
Ответ на первый вопрос закладывается при разработке макета, путем вызова метода page.add_graph(...) в расположении графика, простой пример:
dmc.SimpleGrid(
[
page.add_graph(render_func=bar_chart),
page.add_graph(render_func=line_chart),
],
cols=2
)
На второй вопрос отвечает параметр render_func, представляющий собой функцию принимающую DataFrame, и возвращающую график plotly.
Карты Leaflet
Если вы используете GeoPandas, вы можете добавлять карты на свою панель мониторинга, это так же просто, как добавить график:
dmc.SimpleGrid(
[
page.add_map(geojson_func=None),
],
cols=1
)
geojson_func - должен возвращать geojson для построения карты. Если вам не нужны какие-либо дополнительные преобразования, не указывайте этот параметр, DashExpress все сделает за вас.
def geojson_func(gdf):
gdf = gdf[gdf.geometry.geom_type == 'Polygon']
return gdf.__geo_interface__
Фильтрация
Последним действием является добавление фильтров, которое выполняется простым вызовом метода page.add_filter и указанием столбца фильтрации.
page.add_autofilter('continent', multi=True)
page.add_autofilter('country', multi=True)
page.add_autofilter('lifeExp', multi=True)
Заключение
DashExpress не только быстрое решение, но и гибкое: Вы можете полностью изменить внешний вид интерфейса переопределив класс BaseAppShell. Подробнее об этом расскажу в одной из следующих статей.
Помимо этого Dash Express заботится о повышении производительности вместо вас, вот способы, встроенные по умолчанию:
Использование обратных вызовов на стороне клиента - Большинство обратных вызовов реализовано на стороне клиента, а не на сервере в Python.
Частичные обновления свойств - Функции создания графиков автоматически преобразуются в Patch объекты, обновляя только те части свойства, которые вы хотите изменить.
Кэширование - Dash Express использует библиотеку Flask-Caching, которая сохраняет результаты в базе данных с общей памятью, такой как Redis, или в виде файла в вашей файловой системе.
Сериализация данных с помощью orson - Dash Express использует or json для ускорения сериализации в JSON и, в свою очередь, повышения производительности обратного вызова
Спасибо за прочтение!
Комментарии (13)
sixxio
02.08.2023 17:12Вспоминая опыт работы с Dash, выглядит супер!
Но возник вопрос, что думаете насчет Streamlit?stpnvkirill Автор
02.08.2023 17:12Лично я streamlit не понял, какой-то он сильно деревянный. Dash имеет большое community и возможность делать пользовательские компоненты на основе react-библиотек. DashExpress, например, использует dash mantine и dash leafle. Эти компоненты разработало сообщество, а не plotly.
VPryadchenko
02.08.2023 17:12Супер! Скажите, а если нужно что-то посчитать налёту, можно это отобразить? Например, гистограмму распределения по какому-то срезу. Или только для статичных датафреймов годится?
stpnvkirill Автор
02.08.2023 17:12При применении фильтрации DashExpress вызывает функции построения графиков (которые были переданы через .add_graph) и просто передает им отфильтрованный dataframe. Поэтому, если гистограмму, которую вы хотите отобразить, впринципе можно построить по исходным данным, то и при фильтрации всё будет работать. Уточните, что вы подразумеваете под статичным датафреймом?
VPryadchenko
02.08.2023 17:12Константный, наверное, было бы правильнее сказать - загруженный единожды.
Поэтому, если гистограмму, которую вы хотите отобразить, впринципе можно построить по исходным данным, то и при фильтрации всё будет работать.
Понял. Не уверен, что сходу соображу, как, но, уверен, что можно.
А если, допустим, мне нужно при определенных действиях пользователя (скажем, выборе строки таблицы), подтягивать какие-то доп. данные и уже по ним что-то строить (в dash как есть это можно, но рутинно) - Ваш фреймворк позволит?stpnvkirill Автор
02.08.2023 17:12На данный момент я реализовал взаимодействие с отчетом только через фильтры. Однако вы можете использовать базовый синтаксис dash для кодирования всего, что вы хотите.
A-V-tor
02.08.2023 17:12@stpnvkirill Подскажи а с Flask дружит также как и Dash?
from flask import Flask
from dash import Dashserver = Flask(name)
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = Dash(
name,
server=server,
url_base_pathname='/admin/analytics/',
external_stylesheets=external_stylesheets,
)stpnvkirill Автор
02.08.2023 17:12Да, конечно! В DashExpress можно передать все те же параметры, что и в Dash.
Если вы хотите встроить DashExpress в приложение Flask, рекомендую, помимо сервера Flask, передать еще экземпляр flask_caching.Cache, чтобы dashexpres не создавал отдельный экземпляр, а использовал глобальный кэш для всего приложения.
Abobcum
Выглядит круто! У меня есть два предложения:
Упростить конструктор до пайплайнов.
Добавить много разных примеров в документацию.
stpnvkirill Автор
Спасибо за предложения! Над конструктором подумаю, а документация в работе. Сейчас в плане еще примеры с полной кастомизацией интерфейса и разработкой собственных KPI карт.