(數據科學學習手札102)Python+Dash快速web應用開發——基礎概念篇

本文示例程式碼與數據已上傳至我的Github倉庫//github.com/CNFeffery/DataScienceStudyNotes

1 簡介

   這是我的新系列教程Python+Dash快速web應用開發的第一期,我們都清楚學習一個新工具需要一定的動力,那麼為什麼我要專門為Dash製作一個系列教程呢?

圖1

  Dash是一個高效簡潔的Python框架,建立在FlaskPoltly.js以及React.js的基礎上,設計之初是為了幫助前端知識匱乏的數據分析人員,以純Python編程的方式快速開發出互動式的數據可視化web應用。

  Dash已經過數年的迭代發展,早期的Dash我也體驗過,但當時還比較簡陋,很多問題亟待解決,因此並沒有引起我的多大注意。

  但隨著近一兩年的高速發展和積極更新迭代,現階段的Dash已經是一個相當成熟的框架,且其功能已經豐富到不僅僅可以用來開發在線數據可視化作品,即使是輕量級的數據儀錶盤BI應用,甚至是搭建文檔說明部落格或常規的網站,都駕馭得住,配合豐富的第三方拓展,只會Python的你可以開發出相當精美正式的web應用。

圖2

  而關於Dash的像樣的中文教程幾乎沒有(其實英文教程也沒多少😅),有的也大多只是在照搬官方文檔,因此類似之前寫作完成反響不錯的geopandas教程那樣,我來寫一個看得過去的系列教程吧~下面開始我們的Dash之旅吧!

2 Dash中的基礎概念

  在學習Dash的一開始,我們需要對Dash的若干基礎概念進行了解,首先我們來從頭開始搭建Dash環境,因為主要是面向數據分析處理人員,所以我推薦使用conda進行環境管理,參考下列命令即可完成環境的初始化:

conda create -n dash-dev python=3.7 -y
conda activate dash-dev
pip install dash -U

  上述程式碼執行完成後,你就已經創建好最基本的Dash運行所需環境了,你可以創建程式碼如下的py腳本並執行(推薦使用pycharmvscode等工具進行Dash開發):

app1.py

import dash
import dash_html_components as html

app = dash.Dash(__name__)

app.layout = html.H1('第一個Dash應用!')

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

  運行上述腳本之後,一切正常的話,按照提示點擊進對應網址會看到如下內容:

圖3

  至此我們就完成了Dash環境的搭建,下面我們來了解Dash應用中的一些基礎概念:

2.1 用layout設計頁面內容

  一個web應用的關鍵之一在於其前端所呈現的頁面內容,在Dash中我們通過對其layout屬性進行定義,從而自由設計頁面內容。

  在前面的app1.py中,我們設置了app.layout = html.H1('第一個Dash應用!'),這裡的html即開頭導入的dash_html_components,它是dash的自帶依賴庫,用於在Dash應用中定義常見的html元素,就像前面用到的H1對應一級標題,即<h1></h1>標籤。

  而每個html.XX對象,其接收的第一個位置上的參數都是children,它用於表示對應html標籤所包裹的內容,譬如上文的'第一個Dash應用!',也可以通過傳入多元素列表或進行多層嵌套,從而構建結構更複雜的頁面內容,就像下面的例子一樣:

app2.py

import dash
import dash_html_components as html

app = dash.Dash(__name__)

app.layout = html.Div(
    [
        html.H1('標題1'),
        html.H1('標題2'),
        html.P(['測試', html.Br(), '測試']),
        html.Table(
            html.Tr(
                [
                    html.Td('第一列'),
                    html.Td('第二列')
                ]
            )
        )
    ]
)

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

圖4

  而除了常見的html元素之外,Dash還在其官方依賴庫dash_core_components中內置了眾多常見網頁小部件,是我們實現互動式所依託的重要元素,就像下面的例子一樣我們利用其Dropdown部件創建出一個下拉選擇部件:

app3.py

import dash
import dash_html_components as html
import dash_core_components as dcc

app = dash.Dash(__name__)

app.layout = html.Div(
    [
        html.H1('下拉選擇'),
        html.Br(),
        dcc.Dropdown(
            options=[
                {'label': '選項一', 'value': 1},
                {'label': '選項二', 'value': 2},
                {'label': '選項三', 'value': 3}
            ]
        )
    ]
)

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

圖5

  Dashplotly既然「師出同門」,自然已經相互打通,我們同樣可以非常輕鬆的在網頁中插入數據可視化的內容,這裡我們使用到plotly.express,它簡化了諸多plotly圖表的創建過程,將創建好的圖表對象作為figure參數傳入dcc.Graph()中即可:

app4.py

import dash
import dash_html_components as html
import dash_core_components as dcc
import plotly.express as px

app = dash.Dash(__name__)

fig = px.scatter(x=range(10), y=range(10))

app.layout = html.Div(
    [
        html.H1('嵌入plotly圖表'),
        dcc.Graph(figure=fig)
    ]
)

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

圖6

  除了上述的幾個官方Dash依賴庫以外,還有很多優秀的第三方庫都可以幫助我們快速創建出效果驚人的前端內容,關於這部分的詳細內容我將會在本系列之後的文章中分主題詳細介紹,敬請期待。

2.2 用callback實現交互

  Dash最大的優點之一就是其高度封裝了React.js,使得我們無需編寫js程式碼即可實現前端與後端之間的非同步交互,為了實現這一步,我們需要使用到dash.dependencies中的InputOutput,再配合自定義回調函數來實現所需交互功能。

  舉一個非常簡單的例子:我們設計一個web頁面,其中有一個下拉選項部件,當我們下拉選取到某個選項值對應的省份時,其下方列印出對應的省會城市:

app5.py

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

app = dash.Dash(__name__)

app.layout = html.Div(
    [
        html.H1('根據省名查詢省會城市:'),
        html.Br(),
        dcc.Dropdown(
            id='province',
            options=[
                {'label': '四川省', 'value': '四川省'},
                {'label': '陝西省', 'value': '陝西省'},
                {'label': '廣東省', 'value': '廣東省'}
            ],
            value='四川省'
        ),
        html.P(id='city')
    ]
)

province2city_dict = {
    '四川省': '成都市',
    '陝西省': '西安市',
    '廣東省': '廣州市'
}

@app.callback(Output('city', 'children'),
              Input('province', 'value'))
def province2city(province):

    return province2city_dict[province]

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

圖7

  在交互操作的時候查看後台可以看到,每一次點選都在進行與後台的非同步通訊,我們整個應用的頁面並沒有刷新,如果不用Dash,你就得書寫相應的js語句,較為繁瑣:

圖8

  而Dash目前已經支援多輸入多輸出的回調函數書寫方式,以及阻止初次回調基於表單提交狀態的回調等諸多特性,理論上你可以創建出任何形式的頁面交互行為,這些內容我們都會在之後的系列文章中詳細教授給大家。

2.3 監聽圖表互動式選擇行為

  Dashplotly的高度耦合,還體現在其可以監聽針對plotly圖表的懸浮、選擇、框選等行為,廣泛適用於plotly中的大量常規圖表與地圖,這一點懂的朋友應該都明白,藉助這個特性,我們可以創建出交互能力強大的web應用,就像我下面的這個典型的例子:

app6.py

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

app = dash.Dash(__name__)

fig = px.scatter(x=range(10), y=range(10), height=400)
fig.update_layout(clickmode='event+select')  # 設置點擊模式

app.layout = html.Div(
    [
        dcc.Graph(figure=fig, id='scatter'),
        html.Hr(),
        html.Div([
            '懸浮事件:',
            html.P(id='hover')
        ]),
        html.Hr(),
        html.Div([
            '點擊事件:',
            html.P(id='click')
        ]),
        html.Hr(),
        html.Div([
            '選擇事件:',
            html.P(id='select')
        ]),
        html.Hr(),
        html.Div([
            '框選事件:',
            html.P(id='zoom')
        ])
    ]
)


# 多對多的回調函數
@app.callback([Output('hover', 'children'),
               Output('click', 'children'),
               Output('select', 'children'),
               Output('zoom', 'children'),],
              [Input('scatter', 'hoverData'),
               Input('scatter', 'clickData'),
               Input('scatter', 'selectedData'),
               Input('scatter', 'relayoutData')])
def listen_to_hover(hoverData, clickData, selectedData, relayoutData):
    return str(hoverData), str(clickData), str(selectedData), str(relayoutData)


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

  可以看到,我們監聽到的懸浮、點擊、選擇以及框選四種行為對應傳回的特徵數據:

圖9

  而這方面內容,我也會在之後的系列文章中進行非常詳實的介紹😇~

  我們接下來的系列文章就會圍繞上述基礎概念,以及多頁面應用外部css、js的引入Dash應用的部署發布等還未提及的重要內容進行詳細介紹,以幫助廣大使用Python的讀者朋友使用最少的前端知識,創建出優秀的web應用,方便日常的工作學習生產生活,敬請期待!


  以上就是本文的全部內容,歡迎在評論區與我進行討論~

Tags: