Использование обратных вызовов (callback) в Dash на максимум

В предыдущей статье мы говорили о Python-фреймворке визуализации данных — Dash. Но со временем Data Science проекты могут усложняться, и уже простой визуализацией не отделаться. Поэтому сегодня продолжим углубляться в тему обратных вызовов (callback) в Dash и рассмотрим множество Input и Output, а также сохранение состояния с помощью State.

Множество Input

В предыдущий раз мы реализовали обратный вызов (callback) Dash — отобразили на странице график ожидаемой продолжительности жизни в разные периоды. Также на странице есть слайдер, на котором можно выбрать год. От выбранного года изменяется и вид графика. Это и есть основа реактивного программирования, реализуемое через обратные вызовы, — с изменением входных данных изменяются выходные.
До этого у нас был всего один Input, но их на веб-странице может быть сколько угодно. Так, например, мы можем добавить переключатель для выбора между логарифмической или линейной шкалой.
Добавить такой переключатель можно с помощью RadioItems — одного из компонентов Dash. В options указываем возможные опции: Linear и Log. Код на Python выглядит следующим образом:

import dash
import dash_html_components as html
import dash_core_components as dcc
import pandas as pd


df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/gapminderDataFiveYear.csv')
app = dash.Dash('python-school')

app.layout = html.Div([
    dcc.RadioItems(
        id='scale-button',
        options=[{'label': i, 'value': i} for i in ['Linear', 'Log']],
        value='Linear',
    ),
    dcc.Graph(id='graph'),
    dcc.Slider(
        id='year-slider',
        min=df['year'].min(),
        max=df['year'].max(),
        value=df['year'].min(),
        marks={str(year): str(year) for year in df['year'].unique()},
        step=None
    )
])

Поскольку теперь два Input, то и декорируемая функция тоже должна иметь два аргумента. Второй Input должен принимать id с переключателем и его значением. Для инициализации линейной или логарифмической шкалы передается соответствующий аргумент log_x. Код на Python:

@app.callback(
    Output('graph', 'figure'),
    [Input('year-slider', 'value'),
     Input('scale-button', 'value')])
def update_figure(selected_year, scale_value):
    filtered_df = df[df.year == selected_year]

    if scale_value == 'Linear':
        log_x = False
    else:
        log_x = True
    fig = px.scatter(filtered_df, x="gdpPercap", y="lifeExp",
                     size="pop", color="continent", hover_name="country",
                     log_x=log_x, size_max=55)
    return fig


app.run_server(debug=True)
График с кнопкой выбора Dash callback
Dash-приложение с несколькими Input

Множество Output

Dash-приложение может иметь также множество Output, т.е. отображать на веб-странице множество обновляемых компонент. Нужно лишь в декораторе callback поместить список Output’ов, которые будут обновляться в зависимости от Input’ов. Декорируемая функция должна возвращать столько значений Output, сколько указано в декораторе.
Например, ниже показано простое приложение, которое показывает результаты возведения в степень в виде таблицы.

app.layout = html.Div([
    dcc.Input(
        id='num-multi',
        type='number',
        value=5
    ),
    html.Table([
        html.Tr([html.Td(['x', html.Sup(2)]), html.Td(id='square')]),
        html.Tr([html.Td(['x', html.Sup(3)]), html.Td(id='cube')]),
        html.Tr([html.Td([2, html.Sup('x')]), html.Td(id='twos')]),
        html.Tr([html.Td([3, html.Sup('x')]), html.Td(id='threes')]),
        html.Tr([html.Td(['x', html.Sup('x')]), html.Td(id='x^x')]),
    ]),
])


@app.callback(
    [Output('square', 'children'),
     Output('cube', 'children'),
     Output('twos', 'children'),
     Output('threes', 'children'),
     Output('x^x', 'children')],
    [Input('num-multi', 'value')])
def callback_a(x):
    return x**2, x**3, 2**x, 3**x, x**x
Dash-приложение, которое имеет 5 выходных компонент
Таблица со множеством Output

Взаимосвязанные обратные вызовы

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

all_options = {
    'США': ['Нью-Йорк', 'Сан-Франциско', 'Вашингтон'],
    'Россия': ['Москва', 'Новосибирск', 'Питер']
}
app.layout = html.Div([
    dcc.RadioItems(
        id='countries-radio',
        options=[{'label': k, 'value': k} for k in all_options.keys()],
        value='США'
    ),
    html.Hr(),
    dcc.RadioItems(id='cities-radio'),
    html.Hr(),
    html.Div(id='display-selected-values')
])


@app.callback(
    Output('cities-radio', 'options'),
    [Input('countries-radio', 'value')])
def set_cities_options(selected_country):
    return [{'label': i, 'value': i} for i in all_options[selected_country]]


@app.callback(
    Output('cities-radio', 'value'),
    [Input('cities-radio', 'options')])
def set_cities_value(available_options):
    return available_options[0]['value']


@app.callback(
    Output('display-selected-values', 'children'),
    [Input('countries-radio', 'value'),
     Input('cities-radio', 'value')])
def set_display_children(selected_country, selected_city):
    return '{} - это город {}'.format(
        selected_city, selected_country,
    )
3 связанных callback в Dash-приложении
Dash-приложение со связанными callback

Отметим основные положения:

  • Первый callback обновляет опции выбора города в зависимости от страны, которая указана в RadioItmes;
  • Второй callback устанавливает начальное значение города (в списке выбирается 0-й элемент, т.е. первый);
  • Третий callback отображает выбранную страну и город. Изменяя значение страны, Dash будет ждать обновления города во втором callback. Благодаря такому поведению мы не получим результат в виде «Нью Йорк — это город России».

Фиксируем состояние

В предыдущем примере отображение «Нью Йорк — это город США» происходит сразу после соответствующего выбора. А что если требуется, чтобы результат появился, например, после нажатия на кнопку, а не сразу? Тогда нужно сохранить состояния выбранных значений. Для этого используется зависимость State, которая позволяет передавать значения, не вызывая callback. Ниже представлен пример на Python.

from dash.dependencies import Input, Output, State

app = dash.Dash('my-app')
app.layout = html.Div([
    dcc.Input(id='input-1-state', type='text', value='Питер'),
    dcc.Input(id='input-2-state', type='text', value='Москва'),
    html.Button(id='submit-button-state', n_clicks=0, children='Submit'),
    html.Div(id='output-state')
])

@app.callback(Output('output-state', 'children'),
              [Input('submit-button-state', 'n_clicks')],
              [State('input-1-state', 'value'),
               State('input-2-state', 'value')])
def update_output(n_clicks, input1, input2):
    return u'''
        Кнопка была нажата {} раз,
        Input 1 - это "{}",
        и Input 2 - это "{}"
    '''.format(n_clicks, input1, input2)

app.run_server(debug=True)
Сохранение состояния State Dash
Поля в ожидании нажатия кнопки Dash

В этом примере изменение текста в dcc.Input не вызывает callback. Он вызывается только по значению n_clicks, т.е. после нажатия на кнопку Submit. Количество кликов также сохраняется.

 

 

В следующей статье поговорим об отладке callback’ов в Dash. А о том, как визуализировать данные в Plotly и Dash для решения реальных задач Data Science, вы узнаете на специализированном курсе «VIP: Визуализация данных на языке Python» в лицензированном учебном центре обучения и повышения квалификации IT-специалистов в Москве.

Источники

Добавить комментарий

Поиск по сайту