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

Сам Dash это некий коллаб  HTML, React.Js, Flask  и CSS и предоставляет python классы для всех своих визуальных компонентов.

В качестве демонстративного датасета я возьму датасет diamonds с сайта kaggle (https://www.kaggle.com/shivam2503/diamonds)

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

Привожу пример сэмпла датасета

import dash
import dash_core_components as dcc
import dash_html_components as html
import plotly.express as px
import pandas as pd
from dash.dependencies import Input, Output

Для начала отметим что работу в dash  можно разделить на две части внешнюю(layout) то есть создание визуальной составляющей дашборда, и внутреннюю(callbacks) в которых мы описываем логику взаимодействия с объектами на дашборде.

В начале инициализируем класс dash, а также подгружаем dataset

app = dash.Dash(__name__)
df  = pd.read_csv('C://Users//User//Desktop//Учеба//diam.csv',sep = ';')

Далее создадим заголовок.

Для более красивого отображения, я использовал эти стили (https://github.com/matyushkin/lessons/blob/master/visualization/dash/avocado_analytics/apps/app_2/assets/style.css)

Определяем компонент html.Div а также вносим в него заголовок(html.h1) и абзац(html.P)

app.layout = html.Div(children=[

    html.Div(
        children=[
            html.H1(children='Analysis diamonds dataset', className= 'header-title'),


            html.P(children='maybe this demo will be useful to someone (:', className= 'header-description')и 
                ], className= 'header')])

ClassName является ссылкой на css файл

Который можно подгрузить внутрь проекта создав для него папку assets.

Вид структуры должен быть подобный

И запустим наше приложение

if __name__ == '__main__':
    app.run_server(debug=True)

Заголовок готов, теперь можно приступить к созданию объекта dropdown

Это будет меню для выбора необходимой гистограммы.

 

Данный элемент в коде будет выглядеть вот так

html.Div([
    dcc.Dropdown(
        id='demo_drop',
        options=[
            {'label': 'Огранка', 'value': 'cut'},
            {'label': 'Ясность(чистота)', 'value': 'clarity'},
            {'label': 'Цвет', 'value': 'color'}
        ],
        value='cut', className="dropdown"
    )])

В данном случае id означает уникальный идентификатор объекта он нам понадобится, когда будем описывать логику. Как можно понять из кода функционал данного объекта можно описать через словарь.  Атрибут value означает дефолтное выбранное значение.

Далее добавляем графический объект

dcc.Graph(id='output_graph')],className="card")

Пока что он пуст, но это только пока.

Далее нам нужно сделать так что бы наше приложение реагировало на запрос пользователя и для этого нужно использовать функции обратного вызова.  В них и будет описана связь между объектами Dropdown  и graph.

@app.callback(
 	   Output(component_id='output_graph', component_property='figure'),
    [	Input(component_id='demo_drop', component_property='value')]
)

В данном случае в input у нас заносится уникальный идентификатор dropdown объекта и его значения, которые мы описывали в словаре атрибута options.

А output возвращает гистограмму и ее значения.

Остается только прописать логику для обработки информации.

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

Для pandas это будет выглядеть вот так

h = df.groupby(['cut'], as_index=False, sort=False)['carat'].count()

Теперь вплетаем данное вычисление в функцию

def update_output(value):
    	if value == 'cut':
        		h = df.groupby(['cut'], as_index=False, sort=False)['carat'].count()
    	elif value == 'clarity':
        		h = df.groupby(['clarity'], as_index=False, sort=False)['carat'].count()
    	elif value == 'color':
        		h = df.groupby(['color'], as_index=False, sort=False)['carat'].count()
    	fig = px.bar(h, x=value, y="carat", labels = {"carat" : "Count"} )
    	 return fig

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

А переменная fig создает гистограмму

Таким образом у нас выходит такого вида интерфейс с выбором параметра группировки

Итоговый код

import dash
import dash_core_components as dcc
import dash_html_components as html
import plotly.express as px
import pandas as pd
from dash.dependencies import Input, Output

 
app = dash.Dash(__name__)
df  = pd.read_csv('C://Users//User//Desktop//Учеба//diam.csv',sep = ';')
 
 
app.layout = html.Div(children=[


 
    html.Div(
        children=[
            html.H1(children='Analysis diamonds dataset', className= 'header-title'),
 

            html.P(children='maybe this demo will be useful to someone (:', className= 'header-description')
                ], className= 'header'),
 
    #html.Label('Количество'),
    html.Div([
        dcc.Dropdown(
            id='demo_drop',
            options=[
                {'label': 'Огранка', 'value': 'cut'},
                {'label': 'Ясность(чистота)', 'value': 'clarity'},
                {'label': 'Цвет', 'value': 'color'}
            ],
            value='cut', className="dropdown"
        ),  dcc.Graph(id='output_graph')],className="card")
 
])
@app.callback(
    Output(component_id='output_graph', component_property='figure'),
    [Input(component_id='demo_drop', component_property='value')]
)
def update_output(value):
    if value == 'cut':
        h = df.groupby(['cut'], as_index=False, sort=False)['carat'].count()
    elif value == 'clarity':
        h = df.groupby(['clarity'], as_index=False, sort=False)['carat'].count()
    elif value == 'color':
        h = df.groupby(['color'], as_index=False, sort=False)['carat'].count()
    fig = px.bar(h, x=value, y="carat", labels = {"carat" : "Count"} )
    return fig
 
if __name__ == '__main__':
    app.run_server(debug=True)

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

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


  1. YAKOROLEVAZAMKA
    11.11.2021 18:42

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

    Код примерно такой:

    ### DASH START
    app = dash.Dash(external_stylesheets=[dbc.themes.BOOTSTRAP])
    
    # the style arguments for the sidebar. We use position:fixed and a fixed width
    SIDEBAR_STYLE = {
        "position": "fixed",
        "top": 0,
        "left": 0,
        "bottom": 0,
        "width": "16rem",
        "padding": "2rem 1rem",
        "background-color": "#f8f9fa",
    }
    
    # the styles for the main content position it to the right of the sidebar and
    # add some padding.
    CONTENT_STYLE = {
        "margin-left": "18rem",
        "margin-right": "2rem",
        "padding": "2rem 1rem",
    }
    
    sidebar = html.Div(
        [
            html.H2("Навигация", className="display-6"), # SIZE OF STRING
            html.Hr(),
            #html.P(
            #    "A simple sidebar layout with navigation links", className="lead"
            #),
            dbc.Nav(
                [
                    dbc.NavLink("Main Page", href="/", active="exact"),
                    dbc.NavLink("Page 1", href="/page1", active="exact"),
                    dbc.NavLink("Pgae 2", href="/page2", active="exact"),
                ],
                vertical=True,
                pills=True,
            ),
        ],
        style=SIDEBAR_STYLE,
    )
    
    def main_page(df):
        
        return html.Div([
        # filter1   
        # data-table1
    ])
      
    def page1(df):
      
        return html.Div([
        # filter2
        # data-table2
    ]) 
    
    def page2(df1, df2):
        return html.Div([
        # data-table3 filtered by df1 & df2
    ]) 
    content = html.Div(id="page-content", style=CONTENT_STYLE)
    
    app.layout = html.Div([dcc.Location(id="url", refresh=False), sidebar, content])
    
    
    @app.callback(
                  Output("page-content", "children"),
                 [
                  Input("url", "pathname"),
                 ]
                 )
    def render_page_content(pathname):
    
        global VAR1
        global VAR2
    
        if pathname == "/":        
            return main_page(VAR1)
    
        elif pathname == "/page1":
            return page1(VAR1)
    
        elif pathname == "/page2":
            return page3(VAR1, VAR2)
    
        # If the user tries to reach a different page, return a 404 message
        return dbc.Jumbotron(
            [
                html.H1("404: Not found", className="text-danger"),
                html.Hr(),
                html.P(f"The pathname {pathname} was not recognised..."),
            ]
        )
    


    ПС интересуюсь для самообразования, вижу Dash очень мощным инструментом (и в то же время простым и удобным).


    1. george3
      13.11.2021 10:54
      +2

      Если dash кажется простым и удобным, то возможно большое удивление на фоне https://github.com/Claus1/unigui


      1. YAKOROLEVAZAMKA
        15.11.2021 12:11

        Выглядит действительно интересно, спасибо! Покопаюсь на досуге (хотя я уже перенес все свои "недодаши" в метабазу :))


    1. twistfire92
      15.11.2021 14:26

      Попробуйте использовать dcc.Store. Позволяет сохранять данные на стороне клиента. Хотя не факт, что получится это провернуть с многостраничным приложением.


      1. YAKOROLEVAZAMKA
        15.11.2021 17:07

        пробовал - даже нашел как сохранять некие данные в кэше браузера (как раз с помощью dcc.Store если не ошибаюсь), но с мультистраницой это не сработало :(


        1. twistfire92
          15.11.2021 17:33

          А какой storage_type выбирали? Все пробовали? И memory и local и session? И куда вы его добавляли? В sidebar или в content


  1. mtop
    11.11.2021 20:06

    Как за интегрировать dash с django?


  1. QtRoS
    12.11.2021 00:48
    -2

    Как-то уныло выглядит API dash'а после streamlit'а...


  1. mattroskin
    15.11.2021 14:26

    Полезно было бы почитать про ограничения бесплатной версии. Столкнулись вы с какими-то важными возможностями, которые только в платной работают?


    1. NewTechAudit Автор
      03.12.2021 13:01

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