美文网首页呆鸟的Python数据分析
Python的Web可视化框架Dash(4)---基本回调

Python的Web可视化框架Dash(4)---基本回调

作者: 惑也 | 来源:发表于2019-08-03 16:26 被阅读0次

    【Dash系列】Python的Web可视化框架Dash(1)---简介
    【Dash系列】Python的Web可视化框架Dash(2)---安装
    【Dash系列】Python的Web可视化框架Dash(3)---布局设置
    【Dash系列】Python的Web可视化框架Dash(4)---基本回调
    【Dash系列】Python的Web可视化框架Dash(5)---状态和预更新
    【Dash系列】Python的Web可视化框架Dash(6)---交互和过滤
    【Dash系列】Python的Web可视化框架Dash(7)---回调共享
    【Dash系列】Python的Web可视化框架Dash(8)---核心组件

    本节介绍如何实现Dash应用的回调先导入本节用到的所有包
    import pandas as pd
    import plotly.graph_objs as go
    import dash
    import dash_core_components as dcc                  # 交互式组件
    import dash_html_components as html                 # 代码转html
    from dash.dependencies import Input, Output         # 回调
    from jupyter_plotly_dash import JupyterDash         # Jupyter中的Dash
    

    一、交互式布局

    (一) 代码

    app = JupyterDash('Dash Layout')
    
    app.layout = html.Div([
        dcc.Input(id = 'my-id', value = '初始值', 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 '你输入了"{}"'.format(input_value)
    
    app
    

    (二)效果图

    (三)说明

    1. 在文本框中输入文字,输出组件的子项会立即更新。效果图显示,第一个设置的默认值,后两个,分布输入了数值100和字符串Dash;

    2. app.callback 装饰器通过声明,描述应用程序界面的“输入”与“输出”项;

    3. Dash中应用程序的【输入】和【输出】只是特定组件的属性。本例中,输入项是ID名为my-id 组件的value特性。 输出项是ID名为my-div 组件的children特性;

    4. 当输入项组件的属性值,发生更改时,将自动调用callback装饰器打包的函数,将更新的内容,作为输入项参数,返回函数的输入内容,更新输出项组件的属性值;

    5. 【输入项】和【输出项】对象的关键字参数 component_idcomponent_property,都是可选的。本例中,为了便于理解,列出了这两个关键字,通常情况下,为了让代码简明、易读,可以省略这两个关键字;

    6. 不要混淆 dash.dependencies.Inputdash_core_components.Input对象。前者只在回调函数中使用,后者才是真正的组件;

    7. Dash应用程序启动时,会自动使用输入组件的初始值,调用所有的回调函数,以填充输出组件的初始值。所以,不要在layout中设置 my-div组件的children特性,本例中,如果指定了 html.Div(id='my-div', children='Hello world') 的内容,应用启动时会被覆盖。这种方式类似于Microsoft Excel编程:当单元格的内容发生变化时,依赖于该单元格的所有单元格的内容,都将自动更新。这称为 “反应式编程” (Reactive Programming)

    二、滑动条

    (一) 代码

    # 数据源
    df = pd.read_csv(
        'https://raw.githubusercontent.com/plotly/'
        'datasets/master/gapminderDataFiveYear.csv')
    
    # 设置Dash应用程序
    app = JupyterDash('Slider Update Gragh')
    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 val in filtered_df.continent.unique():
            df_by_continent = filtered_df[filtered_df.continent == val]
            
            traces.append(go.Scatter(
                x = df_by_continent['gdpPercap'],
                y = df_by_continent['lifeExp'],
                text = df_by_continent['country'],
                name = val,
                mode = 'markers',
                opacity = 0.8,
                marker = dict(size = 15, line = dict(width = 0.5, color = 'white'))
            ))
        
        fig = dict(
            data = traces,
            layout = go.Layout(
                xaxis = dict(type = 'log', title = '人均GDP'),
                yaxis = dict(title = '平均寿命', range = [20, 90]),
                margin = dict(l = 40, b = 40, t = 10, r = 10),
                hovermode = 'closest'
            )
        )
        
        return fig
    
    app
    

    (二) 效果图

    (三)说明

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

    2. 使用关键字参数进行组件描述,很重要。通过Dash交互性,使用回调函数,可以动态地更新这些特性。如:更新组件的 children 属性从而更新文本内容、更新 dcc.Graph 组件的 figure 属性从而更新数据、更新组件的 style 属性从而更新画布样式、更新 dcc.Dropdown 组件的 options 从而更新下拉菜单;

    3. 将数据加载至内存并进行计算的代价很高,所以尽量在应用的全局范围内下载或查询数据,避免在回调函数里进行这类操作,确保用户访问或与应用交互时,数据(df)已经载入至内存。本例中 df 获取的数据是全局的,可以被回调函数读取;

    4. 回调函数不会修改原始数据,只是通过Pandas的过滤器来筛选数据,并创建DataFrame的副本。这点非常重要:不要在回调函数范围之外更改变量。如果在全局状态下调整回调函数,某一用户的会话就可能影响下一用户的会话,特别是应用部署在多进程或多线程的环境时,这些修改可能会导致跨会话数据分享出现问题;

    三、多重输入

    (一) 代码

    # 数据
    df = pd.read_csv(
        'https://gist.githubusercontent.com/chriddyp/'
        'cb5392c35661370d95f300086accea51/raw/'
        '8e0768211f6b747c0db42a9ce9a0937dafcbd8b2/'
        'indicators.csv')
    
    # 设置Dash
    app = JupyterDash('many input')
    app.layout = html.Div([
        html.Div([
            html.Div([
                dcc.Dropdown(
                    id = 'xaxis-column',
                    options = [{'label': i, 'value': i} for i in df['Indicator Name'].unique()],
                    value = 'Fertility rate, total (births per woman)'),
                dcc.RadioItems(
                    id = 'xaxis-type',
                    options = [{'label': i, 'value': i} for i in ['线性', '日志']],
                    value = '线性',
                    labelStyle = dict(display = 'inline-block'))],
                style = dict(width = '48%', display = 'inline-block')
            ),
            html.Div([
                dcc.Dropdown(
                    id = 'yaxis-column',
                    options = [{'label': i, 'value': i} for i in df['Indicator Name'].unique()],
                    value = 'Life expectancy at birth, total (years)'),
                dcc.RadioItems(
                    id = 'yaxis-type',
                    options = [{'label': i, 'value': i} for i in ['线性', '日志']],
                    value = '线性',
                    labelStyle = dict(display = 'inline-block'))],
                style = dict(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]
        result = dict(
            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 = dict(title = xaxis_column_name, type = 'linear' if xaxis_type == '线性' else '日志'),
                yaxis = dict(title = yaxis_column_name, type = 'linear' if yaxis_type == '线性' else '日志'),
                margin = {'l': 40, 'b': 40, 't': 10, 'r': 0},
                hovermode = 'closest'
            )
        )
        return result
    
    app
    

    (二) 效果图

    (三)说明

    1. 在Dash中,任何“ Output”都可以有多个“ Input”组件;

    2. 本例中,将五个输入组件:2个下拉菜单(Dropdown)组件、2个单选按钮(RadioItems)组件、1个滑动条(Slider)组件,绑定到1个输出组件(Graph 组件的figure特性);

    3. 回调函数的第二个参数,列表中列举了所有的五个输入项dash.dependencies.Input

    4. 示例中,Dropdown、Slider、RadioItems这些组件的value特性变化时,就会调用update_graph函数;

    5. update_graph函数的输入参数,就是这些组件Input特性的当前值或更新值,按其指定的顺序排列;

    6. 即使每次只修改一个Input特性,比如用户一次只能修改一个下拉菜单的值,但Dash会采集所有绑定组件Input 特性的当前值,并传递给回调函数,确保总能获得该应用当前状态的值。

    四、多重输出

    (一) 代码

    app = JupyterDash('many output')
    app.layout = html.Div([
        dcc.RadioItems(
            id = 'button-a',
            options = [{'label': i, 'value': i} for i in ['北京', '天津', '上海']],
            value = '北京'),
        html.Div(id = 'output-a'),
        
        dcc.RadioItems(
            id = 'button-b',
            options = [{'label': i, 'value': i} for i in ['东城区', '西城区', '朝阳区']],
            value = '朝阳区'),
        html.Div(id = 'output-b')
    ])
    
    @app.callback(
        Output('output-a', 'children'),
        [Input('button-a', 'value')]
    )
    def callback_a(button_value):
        return f"已选中{button_value}"
    
    @app.callback(
        Output('output-b', 'children'),
        [Input('button-b', 'value')]
    )
    def callback_a(button_value):
        return f"已选中{button_value}"
    
    app
    

    (二) 效果图

    (三)说明

    1. 一个Dash回调函数只能更新一个输出属性。要想实现多重输出,需要编写多个函数;

    2. 具体方法:将需要更新的所有属性,作为列表添加到装饰器中,并从回调中返回多个输出项。如果两个输出依赖于相同的计算密集型中间结果,例如慢速数据库查询,推荐使用该方法;

    3. 组合输出并不总是一个好主意:1)如果输出依赖于某些但不是所有相同的输入,则将它们分开可以避免不必要的更新;2)如果它们具有相同的输入,但使用这些输入进行独立计算,则将回调分开,可以实现并行运行它们;

    五、链式回调

    (一) 代码

    app = JupyterDash('Chained Callbacks')
    all_options = {
        '北京': ['东城区', '西城区', '朝阳区'],
        '上海': ['黄浦区', '静安区', '普陀区']
    }
    
    app.layout = html.Div([
        dcc.RadioItems(
            id = 'countries-dropdown',
            options = [{'label': k, 'value': k} for k in all_options.keys()],
            value = '北京'),
        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(select_country):
        return [{'label': i, 'value': i} for i in all_options[select_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(select_country, select_city):
        return f"{select_city}是{select_country}的辖区。"
    
    app
    

    (二) 效果图

    (三)说明

    1. 链式回调:将输出和输入链接在一起,即一个回调函数的输出是另一个回调函数的输入;
    2. 此模式用于创建动态UI,其中一个输入组件更新下一个输入组件的可用选项;
    3. 第二个单选按钮RadioItems的选项,基于第一个回调函数传递的单选按钮RadioItems中选择的值;
    4. 第二个回调函数设置了options特性改变时的初始值:将自身设置为options数组中的第一个值;
    5. 最后的回调函数,显示了每个组件中的可选内容。如果更改了城市单选按钮RadioItems组件的value属性,则Dash将等待,直到value更新状态组件后,再调用最后的回调函数。

    六、小结

    1. Dash应用是基于下述简单但强大的原则进行构建的:通过响应式与函数式的Python回调函数,自定义声明式的UI;
    2. 声明式组件中的每个元素属性,都可以通过回调函数和属性子集进行更新,比如dcc.Dropdown的value特性,这样用户就可以在交互界面中进行编辑。

    相关文章

      网友评论

        本文标题:Python的Web可视化框架Dash(4)---基本回调

        本文链接:https://www.haomeiwen.com/subject/ussjrctx.html