Быстрые дашборды на Python с помощью DashExpress

Сегодня дашборды используются повсеместно: от быстрых отчетов «на лету» до демонстрации возможностей 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 крупных проекта:

  1. Plotly Dash — для web-части

  2. Pandas — для хранения данных

  3. Dash Mantine — для симпатичного интерфейса

  4. 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='Owerview',            # Название старницы
    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

Jupiter

Jupiter

Теперь расскажу подробнее о некоторых шагах и начну с получения данных.

Получение таблицы с данными

В параметр 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 вопроса:

  1. Где находится график

  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 заботится о повышении производительности вместо вас, вот способы, встроенные по умолчанию:

  1. Использование обратных вызовов на стороне клиента — Большинство обратных вызовов реализовано на стороне клиента, а не на сервере в Python.

  2. Частичные обновления свойств — Функции создания графиков автоматически преобразуются в Patch объекты, обновляя только те части свойства, которые вы хотите изменить.

  3. Кэширование — Dash Express использует библиотеку Flask-Caching, которая сохраняет результаты в базе данных с общей памятью, такой как Redis, или в виде файла в вашей файловой системе.

  4. Сериализация данных с помощью orson — Dash Express использует or json для ускорения сериализации в JSON и, в свою очередь, повышения производительности обратного вызова

Спасибо за прочтение!

© Habrahabr.ru