【数据可视化】基于 Dash 制作的疫情数据可视化 APP(Dash 入门必读,附可运行源码)

在此之前,我写过一篇博客《基于Streamlit制作的时间序列数据分析APP》。Streamlit的优点是上手简单、能在短时间内快速构建具有一定交互性的WebAPP,缺点也显而易见:样式较为固定、运行速度慢。

本篇博客,我将利用一种新的方式——Dash,来构建一个疫情数据可视化APP。Dash与Streamlit相比又有何优缺点呢?让我们一起来看看吧!

1.Dash介绍

官网介绍链接:https://dash.plotly.com/introduction

1.1 Dash简介(Introduction)

Dash是一个用于构建Web分析应用程序的高效Python框架。Dash应用程序由两部分组成:

  1. 应用程序的“布局”,它描述了应用程序的外观。
  2. 应用程序的交互性。

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.Divdcc.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会自动调用的函数。

我们将应用程序接口的inputsoutputs声明为@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组件具有四个可以通过用户交互更改的属性:hoverDataclickDataselectedDatarelayoutData。当我们将鼠标悬停在点上、单击点或选择图形中的点区域时,这些属性将会更新。

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_componentsdash_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.Rowdbc.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.Rowdbc.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 回调函数(★★★)

本实例中有两个输入:

  1. 下拉列表选择的国家或地区。
  2. 滑动条选择的移动平均窗口大小。

同时也有两个输出:

  1. 卡片上的数据将得到更新。
  2. 图形将会得到更新。
@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

  • 22
    点赞
  • 119
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 35
    评论
评论 35
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

G皮T

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值