【呆鳥譯Py】Python交互式數(shù)據(jù)分析報(bào)告框架~Dash介紹
【呆鳥譯Py】Dash用戶指南01-02_安裝與應(yīng)用布局
【呆鳥譯Py】Dash用戶指南03_交互性簡(jiǎn)介
【呆鳥譯Py】Dash用戶指南04_交互式數(shù)據(jù)圖
【呆鳥譯Py】Dash用戶指南05_使用State進(jìn)行回調(diào)
3. 交互性簡(jiǎn)介
本教程的第一部分介紹了Dash應(yīng)用布局 勃救,第二部分將介紹如何實(shí)現(xiàn)Dash應(yīng)用的交互瑟枫。
下面先看一個(gè)例子骡送。
Dash應(yīng)用的交互式布局
import dash
from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html
app = dash.Dash()
app.css.append_css(
{"external_url": "https://codepen.io/chriddyp/pen/bWLwgP.css"})
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)
if __name__ == '__main__':
app.run_server()
在文本框中輸入文字娶视,輸出組件的子項(xiàng)會(huì)立即更新慌盯。下面說(shuō)明這個(gè)例子后臺(tái)的每個(gè)操作步驟:
-
app.callback
裝飾器通過(guò)聲明描述應(yīng)用界面的“輸入”與“輸出”項(xiàng)。 - Dash應(yīng)用的輸入肠套、輸出項(xiàng)是指定組件的特性嗽冒。本例中,輸入項(xiàng)是ID名為
my-id
組件的value
特性知举。 輸出項(xiàng)是ID名為my-div
組件的children
特性瞬沦。 - Dash提供了輸入項(xiàng)特性改變時(shí),能夠自動(dòng)調(diào)用
callback
裝飾器打包的函數(shù)雇锡。輸入項(xiàng)特性的值更新后逛钻,可以作為輸入項(xiàng)參數(shù),然后返回該函數(shù)的輸入內(nèi)容锰提。 -
component_id
與component_property
關(guān)鍵字是可選的曙痘,這些對(duì)象只有兩個(gè)參數(shù)。本例中為了便于理解立肘,列出了這兩個(gè)關(guān)鍵字边坤,正常情況下,為了讓代碼簡(jiǎn)明谅年、易讀惩嘉,可以省略這兩個(gè)關(guān)鍵字。 - 不要混淆
dash.dependencies.Input
與dash_core_components.Input
對(duì)象踢故。前者只在回調(diào)函數(shù)中使用文黎,后者才是真正的組件。 - 不要在
layout
中設(shè)置my-div
組件的children
特性殿较。Dash應(yīng)用啟動(dòng)時(shí)會(huì)自動(dòng)調(diào)用所有回調(diào)函數(shù)耸峭,獲取輸入組件中的初始值,使之轉(zhuǎn)化為輸出組件的初始狀態(tài)淋纲。本例中劳闹,如果指定了html.Div(id='my-div', children='Hello world')
等內(nèi)容,應(yīng)用啟動(dòng)時(shí)會(huì)被覆蓋洽瞬。
這種方式類似于用Excel編程本涕,單元格的內(nèi)容發(fā)生變化時(shí),所有與該單元格相關(guān)的單元格都會(huì)自動(dòng)更新伙窃,這就是所謂的“響應(yīng)式編程”菩颖。
請(qǐng)記住如何用關(guān)鍵字參數(shù)描述組件,這點(diǎn)非常重要为障。通過(guò)Dash的交互性晦闰,可以使用回調(diào)函數(shù)動(dòng)態(tài)更新這些特性。Dash可以更新組件的children
特性從而顯示更新的文本鳍怨,也可以通過(guò)dcc.Graph
組件的figure
特性展示更新的數(shù)據(jù)呻右,還可以更新組件的style
,甚至是dcc.Dropdown
組件的options
鞋喇。
下面這個(gè)例子通過(guò)dcc.Slider
更新dcc.Gragh
:
import dash
import dash_core_components as dcc
import dash_html_components as html
import plotly.graph_objs as go
import pandas as pd
df = pd.read_csv(
'https://raw.githubusercontent.com/plotly/'
'datasets/master/gapminderDataFiveYear.csv')
app = dash.Dash()
app.css.append_css(
{"external_url": "https://codepen.io/chriddyp/pen/bWLwgP.css"})
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(),
step=None,
marks={str(year): str(year) for year in df['year'].unique()}
)
])
@app.callback(
dash.dependencies.Output('graph-with-slider', 'figure'),
[dash.dependencies.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'},
yaxis={'title': '平均壽命', '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()
本例中声滥,Slider
的value
特性是Dash應(yīng)用的輸入項(xiàng),輸出項(xiàng)是Graph
的figure
特性侦香。Slider
的value
變更時(shí)落塑,Dash調(diào)用update_figure
回調(diào)函數(shù)獲取更新值。這個(gè)函數(shù)會(huì)篩選DataFrame生成新的值鄙皇,創(chuàng)建figure
對(duì)象芜赌,并將其返回至Dash應(yīng)用。
以下是本例的核心內(nèi)容:
- 使用Pandas導(dǎo)入并篩選內(nèi)存中的數(shù)據(jù)集伴逸;
- 應(yīng)用啟動(dòng)時(shí)缠沈,加載DataFrame:
df = pd.read_csv('...')
。在這個(gè)Dash應(yīng)用里错蝴,DataFramedf
是全局的洲愤,可以被回調(diào)函數(shù)讀取。 - 將數(shù)據(jù)加載至內(nèi)存并進(jìn)行計(jì)算的代價(jià)很高顷锰,所以要在應(yīng)用啟動(dòng)時(shí)載入數(shù)據(jù)柬赐,避免在回調(diào)函數(shù)中加載數(shù)據(jù),確保用戶訪問(wèn)或與應(yīng)用交互時(shí)官紫,數(shù)據(jù)(即
df
)已經(jīng)載入至內(nèi)存肛宋。盡量在應(yīng)用的全局范圍內(nèi)下載或查詢數(shù)據(jù)等大規(guī)模數(shù)據(jù)初始化操作州藕,避免在回調(diào)函數(shù)里進(jìn)行這類操作。 - 回調(diào)函數(shù)不會(huì)修改原始數(shù)據(jù)酝陈,只是通過(guò)Pandas的過(guò)濾器來(lái)篩選數(shù)據(jù)床玻,并創(chuàng)建DataFrame的副本。這點(diǎn)非常重要:不要在回調(diào)函數(shù)范圍之外更改變量沉帮。如果在全局狀態(tài)下調(diào)整回調(diào)函數(shù)锈死,某一用戶的會(huì)話就可能影響下一用戶的會(huì)話,特別是應(yīng)用部署在多進(jìn)程或多線程的環(huán)境時(shí)穆壕,這些修改將導(dǎo)致跨會(huì)話數(shù)據(jù)分享出現(xiàn)問(wèn)題待牵。
多重輸入
任一Dash**輸出項(xiàng) 都可對(duì)應(yīng)多個(gè)輸入項(xiàng) **。下面這個(gè)例子為某個(gè)輸出組件(Graph
組件的figure
特性)綁定了5個(gè)輸入項(xiàng)(2個(gè)下拉菜單Dropdown
組件喇勋,兩個(gè)單選按鈕RadioItems
組件缨该,還有1個(gè)滑動(dòng)條Slider
組件)。注意?在第2個(gè)參數(shù)里茄蚯,app.callback
是如何在一個(gè)列表中列出5個(gè)dash.dependenceies.Input
輸入項(xiàng)的压彭。
import dash
import dash_core_components as dcc
import dash_html_components as html
import plotly.graph_objs as go
import pandas as pd
app = dash.Dash()
app.css.append_css(
{"external_url": "https://codepen.io/chriddyp/pen/bWLwgP.css"})
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(),
step=None,
marks={str(year): str(year) for year in df['Year'].unique()}
)
])
@app.callback(
dash.dependencies.Output('indicator-graphic', 'figure'),
[dash.dependencies.Input('xaxis-column', 'value'),
dash.dependencies.Input('yaxis-column', 'value'),
dash.dependencies.Input('xaxis-type', 'value'),
dash.dependencies.Input('yaxis-type', 'value'),
dash.dependencies.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()
本例中,Dropdown
渗常、Slider
壮不、RadioItems
這些組件的value
特性變化時(shí),就會(huì)調(diào)用update_graph
函數(shù)皱碘。
update_graph
的輸入?yún)?shù)就是這些組件Input
特性的當(dāng)前值或更新值询一,其優(yōu)先級(jí)為它們的指定順序。
雖然同一時(shí)間內(nèi)癌椿,只能修改一個(gè)Input
特性(比如用戶一次只能修改一個(gè)下拉菜單的值)健蕊,但時(shí),Dash會(huì)采集所有綁定組件Input
特性的當(dāng)前值踢俄,并通過(guò)函數(shù)傳遞給回調(diào)函數(shù)缩功,確保總能獲得該應(yīng)用當(dāng)前狀態(tài)的值都办。
下面在這個(gè)例子的基礎(chǔ)上加入多重輸出嫡锌。
多重輸出
一個(gè)Dash回調(diào)函數(shù)僅能更新一個(gè)輸出屬性。要想實(shí)現(xiàn)多重輸出琳钉,需要編寫多個(gè)函數(shù)势木。
import dash
import dash_core_components as dcc
import dash_html_components as html
app = dash.Dash('')
app.css.append_css(
{"external_url": "https://codepen.io/chriddyp/pen/bWLwgP.css"})
app.layout = html.Div([
dcc.RadioItems(
id='dropdown-a',
options=[{'label': i, 'value': i} for i in ['北京', '天津', '上海']],
value='北京'
),
html.Div(id='output-a'),
dcc.RadioItems(
id='dropdown-b',
options=[{'label': i, 'value': i} for i in ['東城區(qū)', '西城區(qū)', '朝陽(yáng)區(qū)']],
value='朝陽(yáng)區(qū)'
),
html.Div(id='output-b')
])
@app.callback(
dash.dependencies.Output('output-a', 'children'),
[dash.dependencies.Input('dropdown-a', 'value')])
def callback_a(dropdown_value):
return '已選中"{}"'.format(dropdown_value)
@app.callback(
dash.dependencies.Output('output-b', 'children'),
[dash.dependencies.Input('dropdown-b', 'value')])
def callback_b(dropdown_value):
return '已選中"{}"'.format(dropdown_value)
if __name__ == '__main__':
app.run_server()
可以將輸入與輸出項(xiàng)鏈在一起:一個(gè)回調(diào)函數(shù)的輸出項(xiàng)可以是另一個(gè)回調(diào)函數(shù)的輸入項(xiàng)。
這個(gè)模式可以用來(lái)創(chuàng)建動(dòng)態(tài)UI歌懒,一個(gè)輸入組件可以更新另一個(gè)輸入組件的可用選項(xiàng)啦桌,請(qǐng)看下面的例子。
# -*- coding: utf-8 -*-
import dash
from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html
app = dash.Dash(__name__)
app.css.append_css(
{"external_url": "https://codepen.io/chriddyp/pen/bWLwgP.css"})
all_options = {
'北京': ['東城區(qū)', '西城區(qū)', '朝陽(yáng)區(qū)'],
'上海': ['黃浦區(qū)', '靜安區(qū)', '普陀區(qū)']
}
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(
dash.dependencies.Output('cities-dropdown', 'options'),
[dash.dependencies.Input('countries-dropdown', 'value')])
def set_cities_options(selected_country):
return [{'label': i, 'value': i} for i in all_options[selected_country]]
@app.callback(
dash.dependencies.Output('cities-dropdown', 'value'),
[dash.dependencies.Input('cities-dropdown', 'options')])
def set_cities_value(available_options):
return available_options[0]['value']
@app.callback(
dash.dependencies.Output('display-selected-values', 'children'),
[dash.dependencies.Input('countries-dropdown', 'value'),
dash.dependencies.Input('cities-dropdown', 'value')])
def set_display_children(selected_country, selected_city):
return '{}是{}的轄區(qū)及皂。'.format(
selected_city, selected_country,
)
if __name__ == '__main__':
app.run_server(debug=True)
第二個(gè)單選按鈕RadioItems
的選項(xiàng)基于第一個(gè)回調(diào)函數(shù)傳遞的單選按鈕RadioItems
中選擇的值甫男。
第二個(gè)回調(diào)函數(shù)設(shè)置了options
特性改變時(shí)的初始值:它將自身設(shè)置為options
數(shù)組中的第一個(gè)值且改。
最后的回調(diào)函數(shù)顯示了每個(gè)組件中的可選值value
。如果改變城市單選按鈕RadioItems
的值value
查剖,Dash會(huì)等城區(qū)單選按鈕RadioItems
的值value
更新后钾虐,再調(diào)用最終的回調(diào)函數(shù)。
小結(jié)
本節(jié)介紹了Dash回調(diào)函數(shù)的基本概念笋庄。Dash應(yīng)用是基于下述簡(jiǎn)單但強(qiáng)大的原則進(jìn)行構(gòu)建的:可以通過(guò)響應(yīng)式與函數(shù)式的Python回調(diào)函數(shù)自定義聲明式的UI。聲明式組件中的每個(gè)元素屬性都可以通過(guò)回調(diào)函數(shù)和屬性子集進(jìn)行更新倔监,比如dcc.Dropdown
的value
特性直砂,這樣用戶就可以在交互界面中進(jìn)行編輯。
下一章將闡述如何使用上述規(guī)則浩习,通過(guò)dash_core-componets.Graph
組件讓Dash應(yīng)用響應(yīng)頁(yè)面上的圖形交互功能静暂。
【呆鳥譯Py】Python交互式數(shù)據(jù)分析報(bào)告框架~Dash介紹
【呆鳥譯Py】Dash用戶指南01-02_安裝與應(yīng)用布局
【呆鳥譯Py】Dash用戶指南03_交互性簡(jiǎn)介
【呆鳥譯Py】Dash用戶指南04_交互式數(shù)據(jù)圖
【呆鳥譯Py】Dash用戶指南05_使用State進(jìn)行回調(diào)