初學Pandas+Ploty+Dash大禮包

張凱喬
22 min readMar 4, 2022
Photo by Josh Calabrese on Unsplash

關於#資料視覺化,其實除了老牌的Tableau,以及近年崛起的Google Data Studio、Power BI,這幾款應用其實都相當的好用。而過往的data pipeline的設計,以Data Studio為例,從資料源(例如銷售POS、CRM管理或SAP資料庫)做ETL、然後簡單處理之後丟Big Query(搭配Data Studio可以加速),最後由Data Studio串接Dataset,在雲端上設計出高可互動且高效能的資料報表。

近期則是在一些比較封閉、且資料敏感的區域做事,許多報表都是直接仰賴Python、Pandas做一些邏輯運算、初步的分析之後,把資料透過excel再整理出最後的乾淨資料、做圖表,寄給內部的同事。

假設要在內部架一個地端的Tableau 或 Power BI 的服務(Google Data Studio 沒有on-Premise),雖然圖表功能較完整、服務的架構也較健全,但學習門檻會比較高,也比較需要時間處理資料與圖表。所以我想用一條龍的服務(都是以Python為基礎),從Pandas資料處理開始,可以很快速的在整理資料ETL時,『同時建立好圖表』,並且『很快地完成部署』,最後這條路,Pandas -> Plotly -> Dash,則是我目前覺得最棒的組合。

這次不談ETL部分,我們的目標比較簡單如下三項:

  1. 『透過Python Pandas 取得資料集,並簡單解析結構』
  2. 『Plotly 承接Pandas DataFrame資料集建立簡單的圖表』
  3. 『Dash 再承接Plotly的圖形,轉成可部署的簡易Web APP』

那開始之前我們就來介紹幾個必須先準備好的套件

  • Python,這次會使用Python 3.8,並搭配jupyter lab開發,最後會將開發好的notebook 轉成py檔給dash 執行。
  • Pandas,讀取資料及做行列運算的利器,Pandas提供了大量能快速便捷地處理資料的函式和方法,主要的資料架構包含一維陣列(Series),能儲存不同種資料型別,字串、boolean值、數字等。多維陣列(DataFrame)則像是我們在使用的excel表格一樣,可以擁有多欄及多列的內容。
  • Plotly、Plotly Express,Plotly作為Python重要的數據視覺化工具之一,其餘的像是Matplotlib、Seaborn,而Plotly是Python的一個開源的數據可視化框架,基於D3.js框架,生成的互動圖以web的形式呈現於瀏覽器上,可以直接在Jupyert lab上面呈現互動圖表也是優勢之一。

目前Plotly比較常見的兩種繪圖流程,分別為『graph_objects』與『Plotly Express』。前者是Plotly最原始的作圖方式,需要先定義好圖形內容(trace),例如座軸及數值,再放進去圖形物件(Figure Object)。後者則是高級版本,直接封裝多樣複雜操作,可以直接套用於pandas Dataframe之上。

以下的這串程式碼,設定pd的繪圖引擎成為Plotly,就可以直接使用Pandas 的.plot() 方法來從Dataframe 取得 Plotly圖形:

import pandas as pd
pd.options.plotting.backend = "plotly"
  • 最後則是Dash,Dash 是建構於 Plotly.js、React.js 與 Flask 之上的 Python 網頁應用程式框架,主要應用目的是數據分析儀表板。而Dash的強項是 100% 兼容 Plotly Express 產生的對象,只需將它們直接傳遞到dash_core_components.Graph就可以在網頁上顯示。

準備好上述幾個套件之後,我們要先取得一個可以作為範例的資料集。而Plotly Express裡面就有預設幾個Sample Dataset可以做測試使用。

那我們這次就娶一個gapminder來試試,如下圖,先開一個新的Jupyer Notebook,import兩樣套件,分別是plotly express 跟 pandas。然後透過px.data 來拿gapminder。

可以透過df.info(),看到這個px.data.gapminder()本身就是一個dataframe類型的物件,並且有1704列,資料組合型態則是,同樣的國家、有不同年份的資料(包含平均壽命、人口數量、GDP)。

承上述所提到的,我們可以直接透過Pandas Plot Function來使Dataframe轉成圖形,在沒有任何指定圖形、單純只是給x跟y軸的內容,就會如下圖這樣呈現出曲線圖。同時,也可以看到這個圖形本身已經有簡單的互動功能,譬如我把游標放在圖上,他就可以告訴我該點位的詳細數據。

但是對於初學者如我而言,要用Pandas內建的Plot Function、再搭配Plotly 的設定,有點小複雜。所以往下我們的目標是直接把資料塞進Plotly做圖表,這樣可以直接略過pd.plot的功能做圖。

Plotly Express(px) 是Plotly 庫的高級封裝,對於一般圖形建立的需求,可以提高及高效率的開發,每個Plotly Express 函數會返回plotly.graph_objects.Figure 物件,據稱可以節省5 到10 倍的程式碼。

接著我們就來嘗試,透過建立台灣與日本人口成長比較圖,直接透過px. 可以引用不同的圖形類型,譬如 bar , hist, scatter, line等等,比較常見的如下

  • 基礎圖形: scatter, line, area, bar, funnel, timeline
  • 比例圖形: pie, sunburst, treemap, icicle, funnel_area
  • 一維分佈: histogram, box, violin, strip, ecdf
  • 二維分佈: density_heatmap, density_contour
  • 三維: scatter_3d, line_3d
  • 多維: scatter_matrix, parallel_coordinates, parallel_categories

我們先挑最基本的長條圖來看,基本上就是輸入dataframe、然後列好x、y軸,圖形就會跑出來。

px.bar(df[df.country.isin(['Taiwan','Japan'])],x='year',y='pop',color='country',barmode='group',height=600)

可以在這個案例裡面看到了我們用,barmode 來設定群組長條圖,另外常見的還有barmode=overlay、barmode=relative,都是可以嘗試的喔

接下來我們開始做簡單的分析,譬如說用點圖來看人口與GDP的關係。簡單來說,如果人口成長的過程中,GDP也同步成長,那就代表該國的經濟是正向的發展。

px.scatter(df[df.country.isin(['Taiwan','Japan'])],x="pop", y="gdpPercap", color="country" ,height=600)

上圖可以看到台灣的GDP上升的很快、但人口有趨緩,日本則是較為均衡一點,人口跟GDP都有往上。

下圖,我先將範圍放大到全部亞洲國家,會看到中國(橘色)跟印度(桃紅色)的線幾乎都躺在地上,人口成長的太快,但沒有對應的經濟成長效果。

另外,也可以換一個方式來加入時間軸,就可以看到不同的資訊。從下圖就可以看到日本從2000年之後有一個GDP下滑的樣子。

px.scatter(df[df.country.isin(['Taiwan','Japan'])],x="year", y="gdpPercap", color="country",size='pop' ,height=600)

另外一個更酷的功能,可以自動搭配多種dimension,將不同dimension的組合的多種scatter放在一起看。

px.scatter_matrix(df[df.country.isin(['Taiwan','Japan'])],dimensions=["year", "gdpPercap",'pop'], color="country" ,height=600)

接下來我們要來嘗試,把圖形丟進Dash裡面。Dash能夠將常見的使用者介面元件包含像是下拉式選單、滑桿或圖形與 資料分析應用快速地連結起來。

在安裝Dash時,其中一些比較常用的Dash子模組一起安裝好,其餘的必須單獨安裝,而幾乎每個應用程式中都會看到兩組組件:

1、Dash HTML Components:一般會import as html,為HTML元素提供Python包裝器。簡單來說,就是透過它來用簡單的python語言產生html的元素,例如段落,標題或列表。

2、Dash Core Components:一般會import as dcc,提供了用於創建交互式用戶界面的模組,包含圖形本身,以及常用的篩選氣,例如滑桿或下拉選單等

所以一般dash應用,就會先用html來架一個網頁框架,開始可以放入標題、文字、設定css之類的,而dcc就是一個封裝了graph或互動圖形套件的內容,一樣就是把dcc的模組當作是內容丟進去html就好。認識這兩個模組之後,就可以開始來搭dash應用了。

在dash 1.x的時候,會看到範例都會像下面這樣寫

import dash 
import dash_html_components as html
import dash_core_components as dcc

在2021年的時候dash 來到了2.x版本,會把這些常用的模組直接整合進dash裡面,所以更新了dash之後的同鞋們請改成下列方式開dash

import dash
from dash import dcc
from dash import html

以我們這次的範例,我們的目標是:
『Dash 再承接Plotly的圖形,轉成可部署的簡易Web APP』

所以直接把圖丟進去dcc、再丟進html,就可以打開dash,後面的工作就會讓python直接幫你完成。

#part_1 先導入模組
import dash
from dash import dcc
from dash import html
import plotly_express as px
#part_2 資料準備好、包含px圖形 fig1,fig2df = px.data.gapminder()fig1 = px.bar(df[df.country.isin(['Taiwan','Japan'])],x='year',y='gdpPercap',color='country',barmode='group',height=600)
fig2 = px.scatter_matrix(df[df.country.isin(['Taiwan','Japan'])],dimensions=["year", "gdpPercap",'pop'], color="country" ,height=600)
#part_3 開一個dash app,然後裡面開一個html.Div 把fig1,fig2丟進去app = dash.Dash(__name__)app.layout = html.Div(
children = [
dcc.Graph(
figure = fig1),
dcc.Graph(
figure = fig2)
])#part_4 直接運行這個py檔的設定if __name__ == "__main__":
app.run_server(debug=True)

存成一個app.py,再用python app.py的方式執行這個檔案,就可以看到他在local host開了一個web app,可以透過127.0.0.1:8050看到你的網頁

以上,我們大致上完成了我們在前文所定的目標

  1. 『透過Python Pandas 取得資料集,並簡單解析結構』
  2. 『Plotly 承接Pandas DataFrame資料集建立簡單的圖表』
  3. 『Dash 再承接Plotly的圖形,轉成可部署的簡易Web APP』

最後,再補充簡單的篩選功能

篩選功能就是可以透過選項(下拉式選單或者是多選方塊)、或者是日期、滑桿等方式,來讓網頁使用者可以自由選擇需要的資料內容。這一塊在Dash裡面整合於Callback功能內,也就是當dcc的某一個篩選器被觸發之後(input),會回饋回dash app,然後再次更新資料的output。

我們在前面,已經認識了dcc,也嘗試透過dcc建立plotly graph物件,接下來,資料篩選器也同樣要靠dcc來完成。舉例來說,我們現在想要透過『滑桿』來操作資料的年份區間,這時候我們就可以找到『RangeSlider』這個方法,而另外一個容易搞混的是『Slider』,前者是可以選擇『起點』與『迄點』、後者則是固定起點,只能選擇區間的『迄點』,端看資料的型態及使用者體驗設計的角度來選擇。

而這個RangeSlider可以參考dash文件,裡面必須要先表明資料的範圍、預設的資料等等。而我們這次選的gapminder資料集,裡面的時間資料欄位名稱是『year』,我們就可以很輕鬆的透過pandas方法,來找到時間最大最小值

dcc.RangeSlider(
id='year-slider',
min=df['year'].min(),
max=df['year'].max(),
value=[df['year'].max(),df['year'].min()],
step=None,
marks={str(year): str(year) for year in df['year'].unique()}
)

而把他加入html.Div就會像這樣

重新開啟伺服器,就可以看到畫面上方新增了一條bar,可以自由地移動時間軸,只是現在我們還沒有辦法讓這個時間軸對圖形產生影響。

Dash 的 callback 部分是以一個 Python Decorator 將一個你所命名的函數包裝起來,在這個Decorator 裡指定 『Input』(選擇資料篩選器之後所提供給你的條件)、以及指定『Output』(根據這個篩選器你要更改的component)。

舉例來說,我們想要一個時間軸滑桿,當這個滑軸的年份改變時,來觸發callback 以及函數,完成更新圖表的功能。可以看到Output 就是要指定 dcc 物件的 id 以及他的類型,然後Input 一樣是指定物件 id 以及類型。

#往下放一個call back 函數,把input, output塞進去
@app
.callback(
dash.dependencies.Output("fig1", "figure"),
[dash.dependencies.Input("year-slider", "value")])
#往下放一個函數,當call back發生的時候,會更新output
def data_filter(year):
fig = px.bar(df[(df.country.isin(['Taiwan','Japan']))& \
(df.year>=min(year))&(df.year<=max(year))] \
,x='year',y='gdpPercap',color='country',barmode='group',height=600)
return fig

函數裡面拋入的參數,也就是input所產生的篩選器資訊,以此案例,我們是用”RangeSlider”,會傳遞一個陣列,所以我們要去限制『範圍』,就可以搭配pandas 的條件功能,寫入 df.year ≥ min(year) 等條件,來更新圖表資料。

如果是多個input及output,使用的情境包含,你想要有一個以上的篩選器,而且他們能同時影響多張圖表。

只要在callback 裡面,把input及output用陣列的形式群組起來,並且依序加入function 傳遞的參數,以及回拋的物件。

@app.callback(
[dash.dependencies.Output("fig1", "figure"),
dash.dependencies.Output("fig2", "figure")],
[dash.dependencies.Input("year-slider", "value")])
def data_filter(year):
fig = px.bar(df[(df.country.isin(['Taiwan','Japan']))&
(df.year>=min(year))&(df.year<=max(year))]
,x='year',y='gdpPercap',color='country',barmode='group',height=600)

fig2 = px.scatter_matrix(df[df.country.isin(['Taiwan','Japan'])&
(df.year>=min(year))&(df.year<=max(year))]
,dimensions=["year", "gdpPercap",'pop'], color="country" ,height=600)
return fig,fig2

OK,這篇就先到這邊

ㄅㄅ

https://www.bianalyst-gt.com/post/python-dash-%E5%AF%A6%E8%B8%90%EF%BC%88%E4%B8%8A%EF%BC%89-%E8%8D%89%E5%9C%96%E8%A8%AD%E8%A8%88%E8%88%87css-%E6%95%99%E5%AD%B8

--

--