【数据科学系列】基于Python的Web应用框架Dash-回调函数

基本的Dash回调

回调:Dash应用程序具有交互性

Dash应用布局

import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

app.layout = html.Div([
    dcc.Input(id='my-id', value=u'初始值', type='text'),
    html.Div(id='my-div')
])


@app.callback(
    Output(component_id='my-div', component_property='children'),
    [Input(component_id='my-id', component_property='value')]
)
def update_output_div(input_value):
    return 'You\'ve entered "{}"'.format(input_value)


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

在文本框中输入内容,输出组件的子项随之立即更新。让我们分解这里发生的事情:

  1. 应用程序接口的“输入”和“输出”通过app.callback装饰器以声明方式描述。
  2. 在Dash中,我们的应用程序的输入和输出只是特定组件的属性。在此示例中,我们的输入是具有ID“ my-id” 的组件的“value”属性。我们的输出是具有ID“ my-div” 的组件的“children”属性。
  3. 每当输入属性发生更改时,将自动调用回调装饰器包装的函数。Dash为函数提供输入属性的新值作为输入参数,Dash使用函数返回的内容更新输出组件的属性。
  1. 不要将dash.dependencies.Input对象与 dash_core_components.Input对象混淆。前者仅用于这些回调,后者是实际组件。

Dash设置组件属性值的过程
当Dash应用程序启动时,它会自动使用输入组件的初始值调用所有回调,以填充输出组件的初始状态。在这个例子中,如果你指定了类似的东西html.Div(id=‘my-div’, children=‘Hello world’) ,它会在应用程序启动时被覆盖。

类似使用Microsoft Excel进行编程:每当输入单元格发生更改时,依赖于该单元格的所有单元格都将自动更新。这称为“反应式编程”(Reactive Programming)。

还记得每个组件是如何通过其关键字参数集完全描述的吗?那些属性现在很重要。通过Dash交互,我们可以通过回调函数动态更新任何这些属性。我们经常会更新该组件的children属性来显示新文本或dcc.Graph组成部分figure,以显示新的数据,但我们也可以更新style的组件,甚至可用一个的dcc.Dropdown的分量options!

dcc.Slider 更新 dcc.Graph

import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output

import pandas as pd
import plotly.graph_objs as go

df = pd.read_csv(
    'https://raw.githubusercontent.com/plotly/'
    'datasets/master/gapminderDataFiveYear.csv')

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

app.layout = html.Div([
    dcc.Graph(id='graph-with-slider'),
    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
    )
])


@app.callback(
    Output('graph-with-slider', 'figure'),
    [Input('year-slider', 'value')])
def update_figure(selected_year):
    filtered_df = df[df.year == selected_year]
    traces = []
    for i in filtered_df.continent.unique():
        df_by_continent = filtered_df[filtered_df['continent'] == i]
        traces.append(go.Scatter(
            x=df_by_continent['gdpPercap'],
            y=df_by_continent['lifeExp'],
            text=df_by_continent['country'],
            mode='markers',
            opacity=0.7,
            marker={
                'size': 15,
                'line': {'width': 0.5, 'color': 'white'}
            },
            name=i
        ))

    return {
        'data': traces,
        'layout': go.Layout(
            xaxis={'type': 'log', 'title': 'GDP Per Capita'},
            yaxis={'title': 'Life Expectancy', 'range': [20, 90]},
            margin={'l': 40, 'b': 40, 't': 10, 'r': 10},
            legend={'x': 0, 'y': 1},
            hovermode='closest'
        )
    }


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

在这里插入图片描述

在此示例中,"value"属性Slider是app的输入,app的输出是Graph的"figure"属性。每当Slider的value变化,Dash用新值调用回调函数update_figure。该函数使用此新值过滤数据框,构造figure对象,并将其返回到Dash应用程序。

这个例子中有一些很好的模式:

  1. 用户使用Pandas库导入和过滤内存中的数据集。
  2. 用户在应用程序的开头加载数据框: df = pd.read_csv(’…’)。此数据框df处于应用程序的全局状态,可以在回调函数内部读取。
    3.将数据加载到内存中可能很昂贵。通过在应用程序开始时而不是在回调函数内部加载查询数据,我们确保仅在应用程序服务器启动时执行此操作。当用户访问应用程序或与应用程序交互时,该数据(df)已在内存中。如果可能,昂贵的初始化(如下载或查询数据)应该在应用程序的全局范围内而不是在回调函数中完成。
    回调不会修改原始数据,它只是通过pandas过滤器过滤来创建数据帧的副本。这很重要:你的回调不应该改变其范围之外的变量。如果您的回调修改了全局状态,那么一个用户的会话可能会影响下一个用户的会话,并且当应用程序部署在多个进程或线程上时,这些修改将不会跨会话共享

多个输入

在Dash中,任何“ Output”都可以有多个“ Input”组件。这是一个简单的示例,它将五个输入(2个Dropdown组件,2个RadioItems组件和1个Slider组件的value属性)绑定到1个输出组件(组件Graph的figure属性)。
注意:app.callback列举了第二个参数中列表的所有五个dash.dependencies.Input。

import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output

import pandas as pd
import plotly.graph_objs as go

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

df = pd.read_csv(
    'https://gist.githubusercontent.com/chriddyp/'
    'cb5392c35661370d95f300086accea51/raw/'
    '8e0768211f6b747c0db42a9ce9a0937dafcbd8b2/'
    'indicators.csv')

available_indicators = df['Indicator Name'].unique()

app.layout = html.Div([
    html.Div([

        html.Div([
            dcc.Dropdown(
                id='xaxis-column',
                options=[{'label': i, 'value': i} for i in available_indicators],
                value='Fertility rate, total (births per woman)'
            ),
            dcc.RadioItems(
                id='xaxis-type',
                options=[{'label': i, 'value': i} for i in ['Linear', 'Log']],
                value='Linear',
                labelStyle={'display': 'inline-block'}
            )
        ],
        style={'width': '48%', 'display': 'inline-block'}),

        html.Div([
            dcc.Dropdown(
                id='yaxis-column',
                options=[{'label': i, 'value': i} for i in available_indicators],
                value='Life expectancy at birth, total (years)'
            ),
            dcc.RadioItems(
                id='yaxis-type',
                options=[{'label': i, 'value': i} for i in ['Linear', 'Log']],
                value='Linear',
                labelStyle={'display': 'inline-block'}
            )
        ],style={'width': '48%', 'float': 'right', 'display': 'inline-block'})
    ]),

    dcc.Graph(id='indicator-graphic'),

    dcc.Slider(
        id='year--slider',
        min=df['Year'].min(),
        max=df['Year'].max(),
        value=df['Year'].max(),
        marks={str(year): str(year) for year in df['Year'].unique()},
        step=None
    )
])

@app.callback(
    Output('indicator-graphic', 'figure'),
    [Input('xaxis-column', 'value'),
     Input('yaxis-column', 'value'),
     Input('xaxis-type', 'value'),
     Input('yaxis-type', 'value'),
     Input('year--slider', 'value')])
def update_graph(xaxis_column_name, yaxis_column_name,
                 xaxis_type, yaxis_type,
                 year_value):
    dff = df[df['Year'] == year_value]

    return {
        'data': [go.Scatter(
            x=dff[dff['Indicator Name'] == xaxis_column_name]['Value'],
            y=dff[dff['Indicator Name'] == yaxis_column_name]['Value'],
            text=dff[dff['Indicator Name'] == yaxis_column_name]['Country Name'],
            mode='markers',
            marker={
                'size': 15,
                'opacity': 0.5,
                'line': {'width': 0.5, 'color': 'white'}
            }
        )],
        'layout': go.Layout(
            xaxis={
                'title': xaxis_column_name,
                'type': 'linear' if xaxis_type == 'Linear' else 'log'
            },
            yaxis={
                'title': yaxis_column_name,
                'type': 'linear' if yaxis_type == 'Linear' else 'log'
            },
            margin={'l': 40, 'b': 40, 't': 10, 'r': 0},
            hovermode='closest'
        )
    }


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

在这里插入图片描述

在此示例中,只要元素或组件Dropdown、Slider或者RadioItems的value属性发生更改,就会调用该函数update_graph 。

update_graph函数的输入参数是每个Input属性的新值或当前值,按其指定的顺序排列。

即使一次只Input更改一个(用户只能在给定时刻更改单个Dropdown的值),Dash会收集所有指定Input属性的当前状态,并将它们传递给您的函数。您的回调函数始终保证会传递给应用程序的代表状态。

让我们扩展我们的例子,包括多个输出。

多个输出

Dash 0.39.0版本中的新功能

到目前为止,我们编写的所有回调只更新了一个Output属性。我们还可以一次更新几个:将要更新的所有属性作为列表添加到装饰器中,并从回调中返回那么多项。如果两个输出依赖于相同的计算密集型的中间结果,例如慢速数据库查询,则这尤其好。

import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

app.layout = html.Div([
    dcc.Input(
        id='num',
        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', 'value')])
def callback_a(x):
    return x**2, x**3, 2**x, 3**x, x**x


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

在这里插入图片描述

注意:组合输出并不总是一个好主意,即使你可以:

  1. 如果输出依赖于某些但不是所有相同的输入,则将它们分开可以避免不必要的更新。
  2. 如果它们具有相同的输入但使用这些输入进行独立计算,则将回调分开可以允许它们并行运行。

链式回调

用户还可以将输出和输入链接在一起:一个回调函数的输出作为另一个回调函数的输入。

此模式可用于创建动态UI,其中一个输入组件更新下一个输入组件的可用选项。

# -*- coding: utf-8 -*-
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

all_options = {
    u'上海': [u'漕河泾', u'张江', u'创智'],
    u'北京': [u'中关村', u'西二旗']
}
app.layout = html.Div([
    dcc.RadioItems(
        id='countries-dropdown',
        options=[{'label': k, 'value': k} for k in all_options.keys()],
        value=u'上海'
    ),

    html.Hr(),

    dcc.RadioItems(id='cities-dropdown'),

    html.Hr(),

    html.Div(id='display-selected-values')
])


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


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


@app.callback(
    Output('display-selected-values', 'children'),
    [Input('countries-dropdown', 'value'),
     Input('cities-dropdown', 'value')])
def set_display_children(selected_country, selected_city):
    return u'{} is a TechPark in {}'.format(
        selected_city, selected_country,
    )


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

在这里插入图片描述
第一个回调根据第一个RadioItems 组件中的选定值更新第二个组件中的RadioItems 可用选项。

第二个回调在options属性更改时设置初始值:它将其设置为该options数组中的第一个值。

更新逻辑:先更新画面再调用回调
最终回调显示value每个组件的选定内容。如果更改了RadioItems组件的value属性,则Dash将等待,直到value更新状态组件,然后再调用最终回调。这可以防止使用与"北京"和不一致的状态调用回调"张江"。

Dash应用程序基于一系列简单但功能强大的原则构建:声明性UI可通过反应式和功能性Python回调进行自定义。声明性组件的每个元素属性都可以通过回调进行更新,属性的子集(如value 属性)dcc.Dropdown可由用户在界面中编辑。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值