基于Dash制作的疫情資料可視化APP
在此之前,我寫過一篇博客《基于Streamlit制作的時間序列資料分析APP》,Streamlit的優點是上手簡單、能在短時間內快速構建具有一定互動性的WebAPP,缺點也顯而易見:樣式較為固定、運行速度慢,
本篇博客,我將利用一種新的方式——Dash,來構建一個疫情資料可視化APP,Dash與Streamlit相比又有何優缺點呢?讓我們一起來看看吧!
1.Dash介紹
官網介紹鏈接:https://dash.plotly.com/introduction
1.1 Dash簡介(Introduction)
Dash是一個用于構建Web分析應用程式的高效Python框架,Dash應用程式由兩部分組成:
- 應用程式的“布局”,它描述了應用程式的外觀,
- 應用程式的互動性,
Dash基于Flask,Plotly.js和React.js,可高度自定義用戶界面,如果你是純Python用戶,那么Dash一定是構建資料可視化應用程式的理想選擇,
通過幾個簡單的模式,Dash提取了構建基于Web的互動式應用程式所需的所有技術和協議,Dash應用程式在Web瀏覽器中呈現,
我們可以將應用程式部署到服務器,然后通過URL共享它們,由于Dash應用程式是在Web瀏覽器中查看的,因此Dash本質上是跨平臺且可移動的,
1.2 安裝(Installation)
pip install dash
import dash
import dash_core_components as dcc
import dash_html_components as html
1.3 布局(Layout)
dash的布局由html.Div和dcc.Graph等“組件”共同構成,
dash_html_components庫對于每個HTML標簽都有一個組件,比如,html.H1(children=‘Hello Dash’)相當于<h1> Hello Dash </h1> ,
dash_core_components則描述了更高級的互動式組件,這些組件是通過React.js庫使用JavaScript、HTML和CSS生成的,
Dash是宣告性的,每個組件都是通過關鍵字屬性來描述,children屬性比較特別,它始終是第一個屬性,一般情況下我們可以忽略它,html.H1(children=‘Hello Dash’)與html.H1(‘Hello Dash’)相同,而且,它可以包含字串,數字,單個組件或組件串列,
1.4 回呼函式(Basic Callbacks)
回呼函式:每當輸入組件的屬性發生更改時,Dash會自動呼叫的函式,
我們將應用程式介面的inputs和outputs宣告為@app.callback裝飾器的引數,在Dash中,我們應用程式的輸入和輸出只是特定組件的屬性,只要輸入屬性發生變化,回呼函式就會自動呼叫, Dash為函式提供輸入屬性的新值作為輸入引數,Dash使用函式回傳的值更新輸出組件的屬性,
Dash應用程式啟動時,它將自動使用輸入組件的初始值呼叫所有回呼,以填充輸出組件的初始狀態,借助Dash的互動性,我們可以通過回呼函式動態更新任何屬性,比如,我們將更新組件的子模塊以顯示新文本,或者更新dcc.Graph組件的圖形以顯示新資料,
1.5 互動可視化(Interactive Visualizations)
dash_core_components庫包含一個名為Graph的組件,Graph使用開源plotly.js圖形庫呈現互動式資料可視化,dcc.Graph組件中的圖形引數與Plotly的圖形庫使用的圖形引數相同,
我們通過一組屬性宣告性地描述dash組件,所有的這些屬性都可以通過回呼函式進行更新,但是這些屬性的子集只能通過用戶互動來更新,例如,當我們單擊dcc.Dropdown組件中的一個選項時,該組件的value屬性將發生更改,
dcc.Graph組件具有四個可以通過用戶互動更改的屬性:hoverData,clickData,selectedData,relayoutData,當我們將滑鼠懸停在點上、單擊點或選擇圖形中的點區域時,這些屬性將會更新,
2.Dash實戰:疫情資料可視化
下面將結合一個“疫情資料可視化”實體來熟悉dash及其各種組件,
本文所有代碼均已上傳至我的Github,需要的朋友可以自行下載,
最后的呈現效果如下圖所示,

2.1 匯入依賴包
import pandas as pd
pd.set_option('max_rows',20)
import plotly.express as px
import plotly.io as pio
pio.renderers.default = "browser"
dash_core_components和dash_html_components是Dash中非常重要的兩個組件,
dash-bootstrap-components是用于Plotly Dash的Bootstrap組件庫,
import dash
from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html
import dash_bootstrap_components as dbc
external_stylesheets = [dbc.themes.BOOTSTRAP]
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
app.title = 'Covid-19 Dashboard'
2.2 獲取資料
全球疫情資料是從20年1月22日至今(就是您看博客的這一天),由于統計方法的不同,可能有部分資料不準確,
# 確診資料
CONF_URL = 'https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_confirmed_global.csv'
# 死亡資料
DEAD_URL = 'https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_deaths_global.csv'
# 治愈資料
RECV_URL = 'https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_recovered_global.csv'
covid_conf_ts = pd.read_csv(CONF_URL)
covid_dead_ts = pd.read_csv(DEAD_URL)
covid_recv_ts = pd.read_csv(RECV_URL)
2.3 資料處理
# 以規整的時間序列格式獲取某個國家或地區的疫情資料
def process_data(data,cntry='China',window=3):
conf_ts = data
conf_ts_cntry = conf_ts[conf_ts['Country/Region']==cntry]
final_dataset = conf_ts_cntry.T[4:].sum(axis='columns').diff().rolling(window=window).mean()[40:]
df = pd.DataFrame(final_dataset,columns=['Total'])
return df
# 獲取全球確診、治愈和死亡的總資料
def get_overall_total(df):
return df.iloc[:,-1].sum()
conf_overall_total = get_overall_total(covid_conf_ts)
dead_overall_total = get_overall_total(covid_dead_ts)
recv_overall_total = get_overall_total(covid_recv_ts)
# 獲取某個國家或地區的確診、治愈和死亡的總資料
def get_cntry_total(df,cntry='China'):
return df[df['Country/Region']==cntry].iloc[:,-1].sum()
cntry = 'China'
conf_cntry_total = get_cntry_total(covid_conf_ts,cntry)
dead_cntry_total = get_cntry_total(covid_dead_ts,cntry)
recv_cntry_total = get_cntry_total(covid_recv_ts,cntry)
2.4 利用Plotly繪圖
# 繪制確診病例資料折線圖
def fig_world_trend(cntry='China',window=3):
df = process_data(data=covid_conf_ts,cntry=cntry,window=window)
df.head(10)
if window==1:
yaxis_title = "Daily Cases"
else:
yaxis_title = "Daily Cases ({}-day MA)".format(window)
fig = px.line(df, y='Total', x=df.index, title='Daily confirmed cases trend for {}'.format(cntry),height=600,color_discrete_sequence =['maroon'])
fig.update_layout(title_x=0.5,plot_bgcolor='#F2DFCE',paper_bgcolor='#F2DFCE',xaxis_title="Date",yaxis_title=yaxis_title)
return fig
2.5 網頁頂部標題設計
colors = {
'background': '#111111',
'bodyColor':'#F2DFCE',
'text': '#7FDBFF'
}
# 網頁頭部設計
def get_page_heading_style():
return {'backgroundColor': colors['background']}
# 網頁標題設計
def get_page_heading_title():
return html.H1(children='COVID-19 Dashboard',
style={
'textAlign': 'center',
'color': colors['text']
})
# 網頁副標題設計
def get_page_heading_subtitle():
return html.Div(children='Visualize Covid-19 data generated from sources all over the world.',
style={
'textAlign':'center',
'color':colors['text']
})
利用dbc.Row和dbc.Col組合標題和副標題,
# 合并標題與副標題
def generate_page_header():
main_header = dbc.Row(
[
dbc.Col(get_page_heading_title(),md=12)
],
align="center",
style=get_page_heading_style()
)
subtitle_header = dbc.Row(
[
dbc.Col(get_page_heading_subtitle(),md=12)
],
align="center",
style=get_page_heading_style()
)
header = (main_header,subtitle_header)
return header
2.6 下拉選擇框設計
# 獲取所有國家或地區的名稱串列
def get_country_list():
return covid_conf_ts['Country/Region'].unique()
# 生成一個排好序的國家或地區名稱串列
def create_dropdown_list(cntry_list):
dropdown_list = []
for cntry in sorted(cntry_list):
tmp_dict = {'label':cntry,'value':cntry}
dropdown_list.append(tmp_dict)
return dropdown_list
利用dcc.Dropdown生成下拉串列,
def get_country_dropdown(id):
return html.Div([
html.Label('Select Country'),
dcc.Dropdown(id='my-id'+str(id),
options=create_dropdown_list(get_country_list()),
value='China'
),
html.Div(id='my-div'+str(id))
])
2.7 圖形容器
將fig_world_trend()生成的圖形傳入dcc.Graph生成的圖形容器中,
def graph1():
return dcc.Graph(id='graph1',figure=fig_world_trend('China'))
2.8 卡片設計
我們將以卡片的形式呈現確診、治愈、死亡資料,
def generate_card_content(card_header,card_value,overall_value):
card_head_style = {'textAlign':'center','fontSize':'150%'}
card_body_style = {'textAlign':'center','fontSize':'200%'}
card_header = dbc.CardHeader(card_header,style=card_head_style)
card_body = dbc.CardBody(
[
html.H5(f"{int(card_value):,}", className="card-title",style=card_body_style),
html.P(
"Worlwide: {:,}".format(overall_value),
className="card-text",style={'textAlign':'center'}
),
]
)
card = [card_header,card_body]
return card
利用dbc.Card生成卡片,利用dbc.Row和dbc.Col排列卡片,
def generate_cards(cntry='China'):
conf_cntry_total = get_cntry_total(covid_conf_ts,cntry)
dead_cntry_total = get_cntry_total(covid_dead_ts,cntry)
recv_cntry_total = get_cntry_total(covid_recv_ts,cntry)
cards = html.Div(
[
dbc.Row(
[
dbc.Col(dbc.Card(generate_card_content("Recovered",recv_cntry_total,recv_overall_total), color="success", inverse=True),md=dict(size=2,offset=3)),
dbc.Col(dbc.Card(generate_card_content("Confirmed",conf_cntry_total,conf_overall_total), color="warning", inverse=True),md=dict(size=2)),
dbc.Col(dbc.Card(generate_card_content("Dead",dead_cntry_total,dead_overall_total),color="dark", inverse=True),md=dict(size=2)),
],
className="mb-4",
),
],id='card1'
)
return cards
2.9 滑動條設計
利用dcc.Slider生成滑動條,用來選擇移動平均視窗大小,
def get_slider():
return html.Div([
dcc.Slider(
id='my-slider',
min=1,
max=15,
step=None,
marks={
1: '1',
3: '3',
5: '5',
7: '1-Week',
14: 'Fortnight'
},
value=3,
),
html.Div([html.Label('Select Moving Average Window')],id='my-div'+str(id),style={'textAlign':'center'})
])
2.10 頁面布局
將前面設計完成的所有組件進行頁面布局,
def generate_layout():
page_header = generate_page_header()
layout = dbc.Container(
[
page_header[0],
page_header[1],
html.Hr(),
generate_cards(),
html.Hr(),
dbc.Row(
[
dbc.Col(get_country_dropdown(id=1),md=dict(size=4,offset=4))
]
),
dbc.Row(
[
dbc.Col(graph1(),md=dict(size=6,offset=3))
],
align="center",
),
dbc.Row(
[
dbc.Col(get_slider(),md=dict(size=4,offset=4))
]
),
],fluid=True,style={'backgroundColor': colors['bodyColor']}
)
return layout
app.layout = generate_layout()
2.11 回呼函式(★★★)
本實體中有兩個輸入:
- 下拉串列選擇的國家或地區,
- 滑動條選擇的移動平均視窗大小,
同時也有兩個輸出:
- 卡片上的資料將得到更新,
- 圖形將會得到更新,
@app.callback(
[Output(component_id='graph1',component_property='figure'), #line chart
Output(component_id='card1',component_property='children')], #overall card numbers
[Input(component_id='my-id1',component_property='value'), #dropdown
Input(component_id='my-slider',component_property='value')] #slider
)
def update_output_div(input_value1,input_value2):
return fig_world_trend(input_value1,input_value2),generate_cards(input_value1)
2.12 運行
if __name__ == '__main__':
app.run_server(debug=True)
呈現效果如下所示:

3.總結
| 優點 | 缺點 | |
|---|---|---|
| Streamlit | 簡單易上手,快速搭建APP | 樣式固定,運行速度慢 |
| Dash | 樣式多變,組件豐富 | 稍復雜,需要一定的HTML、CSS基礎知識 |
本文所有代碼均已上傳至我的Github,需要的朋友可以自行下載,
https://github.com/Beracle/06-COVID-19-DashBoard
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/262049.html
標籤:python
上一篇:試驗資料的篩選和質量可視化
下一篇:資料結構與演算法(5)
