使用Plotly來簡化Python中的數(shù)據(jù)可視化
Plotly 是一個(gè)數(shù)據(jù)繪圖庫,具有整潔的接口,它旨在允許你構(gòu)建自己的 API。
Plotly 是一個(gè)繪圖生態(tài)系統(tǒng),可以讓你在 Python 以及 JavaScript 和 R 中進(jìn)行繪圖。在本文中,我將重點(diǎn)介紹使用 Python 庫進(jìn)行繪圖。
Plotly 有三種不同的 Python API,你可以選擇不同的方法來使用它:
- 類似于 Matplotlib 的面向?qū)ο蟮?API
- 數(shù)據(jù)驅(qū)動的 API,通過構(gòu)造類似 JSON 的數(shù)據(jù)結(jié)構(gòu)來定義繪圖
- 類似于 Seaborn 的高級繪圖接口,稱為 “Plotly Express” API
我將通過使用每個(gè) API 來繪制相同的圖來探索它們:英國大選結(jié)果的分組柱狀圖。
在我們進(jìn)一步探討之前,請注意,你可能需要調(diào)整你的 Python 環(huán)境來讓這段代碼運(yùn)行,包括以下內(nèi)容:
數(shù)據(jù)可在線獲得,可以用 Pandas 導(dǎo)入。
import pandas as pd
df = pd.read_csv('https://anvil.works/blog/img/plotting-in-python/uk-election-results.csv')
現(xiàn)在我們可以繼續(xù)進(jìn)行了。
使用圖對象來繪制圖
Plotly 面向?qū)ο蟮?API 被稱為 graph_objects
,它有點(diǎn)類似于 Matplotlib 的面向?qū)ο?API。
要?jiǎng)?chuàng)建一個(gè)柱狀圖,你可以構(gòu)造一個(gè)包含四個(gè)柱狀圖的對象:
# 導(dǎo)入 Plotly 和數(shù)據(jù)
import plotly.graph_objects as go
from votes import wide as df
# 得到 x 列表
years = df['year']
x = list(range(len(years)))
# 定義繪圖
bar_plots = [
go.Bar(x=x, y=df['conservative'], name='Conservative', marker=go.bar.Marker(color='#0343df')),
go.Bar(x=x, y=df['labour'], name='Labour', marker=go.bar.Marker(color='#e50000')),
go.Bar(x=x, y=df['liberal'], name='Liberal', marker=go.bar.Marker(color='#ffff14')),
go.Bar(x=x, y=df['others'], name='Others', marker=go.bar.Marker(color='#929591')),
]
# 指定樣式
layout = go.Layout(
title=go.layout.Title(text="Election results", x=0.5),
yaxis_title="Seats",
xaxis_tickmode="array",
xaxis_tickvals=list(range(27)),
xaxis_ticktext=tuple(df['year'].values),
)
# 繪制柱狀圖
fig = go.Figure(data=bar_plots, layout=layout)
# 告訴 Plotly 去渲染
fig.show()
與 Matplotlib 不同的是,你無需手動計(jì)算柱狀圖的 x
軸位置,Plotly 會幫你適配。
最終結(jié)果圖:
A multi-bar plot made using Graph Objects (© 2019 Anvil)
使用 Python 數(shù)據(jù)結(jié)構(gòu)來繪圖
你還可以使用 Python 基本數(shù)據(jù)結(jié)構(gòu)來定義繪圖,它與面對對象 API 具有相同的結(jié)構(gòu)。這直接對應(yīng)于 Plotly 的 JavaScript 實(shí)現(xiàn)的 JSON API。
# 定義繪圖數(shù)據(jù)
fig = {
'data': [
{'type': 'bar', 'x': x, 'y': df['conservative'], 'name': 'Conservative', 'marker': {'color': '#0343df'}},
{'type': 'bar', 'x': x, 'y': df['labour'], 'name': 'Labour', 'marker': {'color': '#e50000'}},
{'type': 'bar', 'x': x, 'y': df['liberal'], 'name': 'Liberal', 'marker': {'color': '#ffff14'}},
{'type': 'bar', 'x': x, 'y': df['others'], 'name': 'Others', 'marker': {'color': '#929591'}},
],
'layout': {
'title': {'text': 'Election results', 'x': 0.5},
'yaxis': {'title': 'Seats'},
'xaxis': {
'tickmode': 'array',
'tickvals': list(range(27)),
'ticktext': tuple(df['year'].values),
}
}
}
# 告訴 Plotly 去渲染它
pio.show(fig)
最終結(jié)果與上次完全相同:
A multi-bar plot made using JSON-like data structures (© 2019 Anvil)
使用 Plotly Express 進(jìn)行繪圖
Plotly Express 是對圖對象進(jìn)行封裝的高級 API。
你可以使用一行代碼來繪制柱狀圖:
# 導(dǎo)入 Plotly 和數(shù)據(jù)
import plotly.express as px
from votes import long as df
# 定義顏色字典獲得自定義欄顏色
cmap = {
'Conservative': '#0343df',
'Labour': '#e50000',
'Liberal': '#ffff14',
'Others': '#929591',
}
# 生成圖
fig = px.bar(df, x="year", y="seats", color="party", barmode="group", color_discrete_map=cmap)
這里使用了長表 數(shù)據(jù),也稱為“整潔數(shù)據(jù)”。這些列代表年份、政黨和席位,而不是按政黨劃分。這與在 Seaborn 中制作柱狀圖非常相似。
>> print(long)
year party seats
0 1922 Conservative 344
1 1923 Conservative 258
2 1924 Conservative 412
3 1929 Conservative 260
4 1931 Conservative 470
.. ... ... ...
103 2005 Others 30
104 2010 Others 29
105 2015 Others 80
106 2017 Others 59
107 2019 Others 72
[108 rows x 3 columns]
你可以訪問底層的圖對象 API 進(jìn)行詳細(xì)調(diào)整。如添加標(biāo)題和 y
軸標(biāo)簽:
# 使用圖對象 API 來調(diào)整繪圖
import plotly.graph_objects as go
fig.layout = go.Layout(
title=go.layout.Title(text="Election results", x=0.5),
yaxis_title="Seats",
)
最后,讓 Plotly 渲染:
fig.show()
這將在未使用的端口上運(yùn)行一個(gè)臨時(shí) Web 服務(wù)器,并打開默認(rèn)的 Web 瀏覽器來查看圖像(Web 服務(wù)器將會馬上被關(guān)閉)。
不幸的是,結(jié)果并不完美。x
軸被視為整數(shù),因此兩組之間的距離很遠(yuǎn)且很小,這使得我們很難看到趨勢。
A multi-bar plot made using Plotly Express (© 2019 Anvil)
你可能會嘗試通過將 x
值轉(zhuǎn)換為字符串來使 Plotly Express 將其視為字符串,這樣它就會以均勻的間隔和詞法順序來繪制。不幸的是,它們的間隔還是很大,像在 graph_objects
中那樣設(shè)置 xaxis_tickvals
也不行。
與 Seaborn 中的類似示例不同,在這種情況下,抽象似乎沒有提供足夠的應(yīng)急方案來提供你想要的東西,但是也許你可以編寫自己的 API?
構(gòu)建自己的 Plotly API
對 Plotly 的操作方式不滿意?那就構(gòu)建自己的 Plotly API!
Plotly 的核心是一個(gè) JavaScript 庫,它使用 D3 和 stack.gl 進(jìn)行繪圖。JavaScript 庫的接口使用指定的 JSON 結(jié)構(gòu)來繪圖。因此,你只需要輸出 JavaScript 庫喜歡使用的 JSON 結(jié)構(gòu)就好了。
Anvil 這樣做是為了創(chuàng)建一個(gè)完全在瀏覽器中工作的 Python Plotly API。
Plotly uses a JavaScript library to create plots, driven by libraries in other languages via JSON (© 2019 Anvil)
在 Anvil 版本中,你可以同時(shí)使用圖對象 API 和上面介紹的 Python 數(shù)據(jù)結(jié)構(gòu)方法。運(yùn)行完全相同的命令,將數(shù)據(jù)和布局分配給 Anvil 應(yīng)用程序中的 Plot 組件。
這是用 Anvil 的客戶端 Python API 繪制的多列柱狀圖:
# 導(dǎo)入 Anvil 庫
from ._anvil_designer import EntrypointTemplate
from anvil import *
import anvil.server
# 導(dǎo)入客戶端 Plotly
import plotly.graph_objs as go
# 這是一個(gè) Anvil 表單
class Entrypoint(EntrypointTemplate):
def __init__(self, **properties):
# Set Form properties and Data Bindings.
self.init_components(**properties)
# 從服務(wù)器獲取數(shù)據(jù)
data = anvil.server.call('get_election_data')
# 獲取一個(gè)方便的 x 值列表
years = data['year']
x = list(range(len(years)))
# 定義繪圖
bar_plots = [
go.Bar(x=x, y=data['conservative'], name='Conservative', marker=go.Marker(color='#0343df')),
go.Bar(x=x, y=data['labour'], name='Labour', marker=go.Marker(color='#e50000')),
go.Bar(x=x, y=data['liberal'], name='Liberal', marker=go.Marker(color='#ffff14')),
go.Bar(x=x, y=data['others'], name='Others', marker=go.Marker(color='#929591')),
]
# 規(guī)定布局
layout = {
'title': 'Election results',
'yaxis': {'title': 'Seats'},
'xaxis': {
'tickmode': 'array',
'tickvals': list(range(27)),
'ticktext': data['year'],
},
}
# 生成多列柱狀圖
self.plot_1.data = bar_plots
self.plot_1.layout = layout
繪圖邏輯與上面相同,但是它完全在 Web 瀏覽器中運(yùn)行,繪圖是由用戶計(jì)算機(jī)上的 Plotly JavaScript 庫完成的!與本系列的所有其它 Python 繪圖庫相比,這是一個(gè)很大的優(yōu)勢。因?yàn)槠渌?Python 庫都需要在服務(wù)器上運(yùn)行。
這是在 Anvil 應(yīng)用中運(yùn)行的交互式 Plotly 圖:
The election plot on the web using Anvil's client-side-Python Plotly library (© 2019 Anvil)
你可以復(fù)制此示例作為一個(gè) Anvil 應(yīng)用程序(注意:Anvil 需要注冊才能使用)。
在前端運(yùn)行 Plotly 還有另一個(gè)優(yōu)勢:它為自定義交互行為提供了更多選項(xiàng)。
在 Plotly 中自定義交互
Plotly 繪圖不僅是動態(tài)的,你可以自定義它們的互動行為。例如,你可以在每個(gè)柱狀圖中使用 hovertemplate
自定義工具提示的格式:
go.Bar(
x=x,
y=df['others'],
name='others',
marker=go.bar.Marker(color='#929591'),
hovertemplate='Seats: <b>%{y}</b>',
),
當(dāng)你把這個(gè)應(yīng)用到每個(gè)柱狀圖時(shí),你會看到以下結(jié)果:
A multi-bar plot with custom tool-tips (© 2019 Anvil)
這很有用,當(dāng)你想要在某些事件發(fā)生時(shí)執(zhí)行任何你想要的代碼就更好了(例如,當(dāng)用戶將鼠標(biāo)懸停在欄上,你想要顯示一個(gè)相關(guān)選舉的信息框)。在 Anvil 的 Plotly 庫中,你可以將事件處理程序綁定到諸如懸停之類的事件,這使得復(fù)雜的交互成為可能。
A multi-bar plot with a hover event handler (© 2019 Anvil)
你可以通過將方法綁定到繪圖的懸停事件來實(shí)現(xiàn):
def plot_1_hover(self, points, **event_args):
"""This method is called when a data point is hovered."""
i = points[0]['point_number']
self.label_year.text = self.data['year'][i]
self.label_con.text = self.data['conservative'][i]
self.label_lab.text = self.data['labour'][i]
self.label_lib.text = self.data['liberal'][i]
self.label_oth.text = self.data['others'][i]
url = f"https://en.wikipedia.org/wiki/{self.data['year'][i]}_United_Kingdom_general_election"
self.link_more_info.text = url
self.link_more_info.url = url
這是一種相當(dāng)極端的交互性,從開發(fā)人員的角度來看,也是一種極端的可定制性。這都要?dú)w功于 Plotly 的架構(gòu) —— 它有一個(gè)簡潔的接口,明確的設(shè)計(jì)是為了讓你建立自己的API。如果到處都能看到這種偉大的設(shè)計(jì),那將會很有幫助!
使用 Bokeh 進(jìn)行自定義交互
現(xiàn)在你已經(jīng)了解了 Plotly 如何使用 JavaScript 來創(chuàng)建動態(tài)圖,并且可以使用 Anvil 的客戶端編寫 Python 代碼在瀏覽器中實(shí)時(shí)編輯它們。
Bokeh 是另一個(gè) Python 繪圖庫,它可以輸出可嵌入 Web 應(yīng)用程序的 HTML 文檔,并獲得與 Plotly 提供的功能類似的動態(tài)功能(如果你想知道如何發(fā)音,那就是 “BOE-kay”)。